fix(upload): vendor form-data so bundlers can ship it - #681
Merged
Merged
Conversation
John-David Dalton (jdalton)
force-pushed
the
jdalton/ce-356-vendor-form-data
branch
from
August 4, 2026 19:15
f3f1a57 to
00f5933
Compare
socket-cli 1.1.151/1.1.152 crashed every fresh install's first
multipart upload with "Cannot find module 'form-data'" (customer CI
down 24h+). The CLI's rollup inlines the SDK, and the
SDK reached form-data through createRequire('form-data') — a
dynamic call no bundler can follow — so the bare specifier shipped
with form-data neither bundled nor declared. The 4.1.3 sibling
chunk (dist/form-data.js) did not survive consumer bundling either:
rollup inlines dist/index.js without carrying the chunk across, and
both specifiers resolve MODULE_NOT_FOUND from the built output.
Vendor form-data through src/external/ the way socket-lib handles
its externals: getFormData() requires './external/form-data.js' by
static relative literal, a scoped external() emits it verbatim, and
a separate externals build ships the self-contained
dist/external/form-data.js it resolves — laziness (the deferred
node:http eval that keeps SDK importers snapshot-safe) preserved.
Consumer bundlers resolve the relative require against the shipped
file and inline it. The external() match is by resolved path under
THIS repo's src/external only, never a blanket external/ segment
test — a segment match also externalizes the nested dist/external
internals of the inlined @socketsecurity/lib, emitting requires
into a package consumers never install. dist/external ships via
files but is never an exports subpath.
@types/node is now explicit in both tsconfigs: it was previously
loaded only through form-data's typings, which vanish with the old
chunk entry — without it the whole program loses node globals.
Verified end-to-end: socket-cli rebuilt against this SDK contains
zero form-data requires (bytes inlined into vendor.js), and a
dist-mode uploadManifestFiles put a real multipart body on the
wire. Regression tests pin the shim, the packaging, and the
no-bare-specifier bundle shape.
John-David Dalton (jdalton)
force-pushed
the
jdalton/ce-356-vendor-form-data
branch
from
August 4, 2026 19:25
00f5933 to
20cb07e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
socket@latest(1.1.151/1.1.152) throwsCannot find module 'form-data'on every fresh install's first multipart upload and a customer's CI has been down for over a day. The break is in how this SDK reaches form-data, so the fix lands here.Root cause.
getFormData()usedcreateRequire(...)('form-data')— a dynamic call no bundler can follow. socket-cli's rollup inlines the SDK intodist/vendor.js, so the bare specifier shipped with form-data neither bundled nor declared, and the first upload threw on any clean install. The 4.1.3 sibling chunk does not survive consumer bundling either: rollup inlinesdist/index.jswithout carrying the chunk across, and both./form-data.jsandform-dataresolve MODULE_NOT_FOUND from the built CLI (reproduced withcreateRequireagainst the real artifact).Fix. Vendor form-data through
src/external/the way socket-lib handles its externals:src/external/form-data.js— staticmodule.exports = require('form-data')shim, with a hand-authored structural.d.tsso published types never import a package consumers don't install.getFormData()requires the shim by static relative literal, and a scopedexternal()in the rolldown config emits that require verbatim intodist/index.js, so the deferrednode:httpeval that keeps SDK importers--build-snapshot-safe is preserved.dist/external/form-data.js(socket-lib'sbuild-externalsstep, SDK-sized), which the verbatim require resolves at runtime — and which a consumer bundler resolves and inlines. Ships viafiles, never an exports subpath.external()match is by resolved path under this repo'ssrc/external/only — never a blanketexternal/-segment test, which would also externalize the nesteddist/external/*internals of the inlined@socketsecurity/liband emit requires into a package consumers never install.@types/nodeis now explicit in both tsconfigs — it was previously loaded only through form-data's typings, which vanish with the old chunk entry; without it the whole program silently loses node globals (45 errors).Verified end-to-end with socket-cli rebuilt against this SDK: zero form-data requires anywhere in the CLI dist (bytes inlined into vendor.js), the undeclared-runtime-require scan is back to the 1.1.147 baseline, and a dist-mode
uploadManifestFilesagainst a live local server put a real 6,984-byte multipart body on the wire (verified for both the plain-install path and the bundled path).Actions needed
socket@1.1.147.Why not the simpler fixes, and what deliberately did not change
./form-datapackage export with self-reference resolution — tried first; preferring the export from a source run loads the bundled copy while callers hold the node_modules copy, putting two FormData classes in one process and breakinginstanceof(6 test failures). Reverted.external/paths in the main bundle (socket-lib's exact predicate) — breaks this build: the SDK inlines@socketsecurity/lib, whose internals require lib's owndist/external/*; externalizing those emits relative requires into a package consumers never install. The scoped-by-resolved-path predicate is the fix; a matching scoping PR for socket-lib's own config is coming separately.files+ exports ignore), and the no-bare-specifier bundle shape; the mirror-named split isfile-upload.test.mts+package-exports.config.test.mts.utils.test.mtsresolveBasePathfailures (fail on pristine main in a/tmpworktree — macOS/privatesymlink) and thedispatch-table-is-currentcheck (fails on pristine main).