fix(bundle): read package.json as Node does; remapping errors never quote; green main after #178 - #201
Merged
Conversation
buildGitHubBundle and suggestedEntries check their options against an empty Vfs, before anything is fetched: "what the tree is never decides them" (#197). #178's directoryEntryError then found every extensionless entry missing from that empty tree. So `src` with pnpm was refused as "no such file or directory" rather than "only JS bundles are built with pnpm", and main's "buildGitHubBundle reads nothing from disk" test fails. The pre-fetch checks now pass `fetched: false`, which skips the missing-entry errors. buildVfsBundle still checks entries against the project's own tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA
…, as Node refuses it host.stat returns null for any failure, as Node's module lookup does. So every package.json reader took a link loop, or a file behind a directory that may not be searched, for no package.json and walked past it: - the strict Solidity reader, which bucketed a forge lib's files as the project's own; - the resolver the Vfs host uses, and the Vfs host's findPackageJSON; - State's walk up to a bucket's name, and its root discovery; - readModuleManifest. Node refuses such a package.json with ERR_INVALID_PACKAGE_CONFIG, and counts only "nothing there" as none, a link to nothing included. Real stat tells the two apart and host.stat doesn't. That's right for the module and lib probes, which treat any failure as absent as Node and Rust do, so stat stays as it is. When a package.json reader's stat fails, a read now says why: - statStrict, for configs and the strict package.json read, names the file from the project root (`lib/dep/package.json: can't be read (ELOOP)`). readRegularFileOrNull uses it too, instead of rethrowing the raw fs error with its absolute path. - packageJSONStat throws Node's ERR_INVALID_PACKAGE_CONFIG, for the resolver, the Vfs host's findPackageJSON and State. Lenient lookups (Bash, Rust, `stasis add`, --package-json) still walk past a malformed or unreadable one. A differential against Node's require.resolve and findPackageJSON now agrees on every case: missing, dangling, a directory, malformed, a self-loop, a loop through directories, and (unprivileged) a link into an unsearchable directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA
An invalid remapping's error quoted it. The text is normally a remapping the project wrote, but a file named as a mapping by mistake can hold anything: `--mapping=token.txt` printed `token.txt:1: invalid remapping "ghp_…"` to stderr, and into CI logs with it. A token pasted into FOUNDRY_REMAPPINGS did the same. Errors now give the location and the form the entry should have: `remappings.txt:2: invalid remapping, expected [context:]prefix=target`. A foundry.toml entry is named by its position (`` `remappings` entry 2 ``, `` `remappings` entry 1 is not a string ``). This is the last error of stasis's own that quoted file content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA
This was referenced Oct 2, 2026
ChALkeR
pushed a commit
that referenced
this pull request
Oct 2, 2026
…4.21+ and 26.8+ alone (#202) The differential against Node's own resolver (#201) expected Node to refuse a package.json it can't read (a link loop, a loop through directories) with ERR_INVALID_PACKAGE_CONFIG. Node does so since 24.21.0 and 26.8.0 (nodejs/node#65223, "module: report unreadable package.json"); before, it took one for none and resolved past it. CI runs the engines floor, 24.14.0, and 26.0.0 too, where the test failed. The test now checks Node's answer against its two known behaviors by version, keeps the differential where every Node agrees (a link to nothing is none), and asserts ours refuses on every Node, as before. Claude-Session: https://claude.ai/code/session_01GGr7zsXxPPC6Mrmtn7qyJG Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.
Follow-up to #178. It makes two decided changes and fixes a test that fails on
mainbecause of how #178 and #197 combine.A
package.jsonthat's there but can't be read is refused, as Node refuses ithost.statreturnsnullfor any failure. That's right for its ~30 callers, which are existence checks during module resolution, forge's lib discovery and the Solidity walk. Node's resolver and Rust'sPath::existsalso treat any failure as "not there", sostatstays as it is.Every
package.jsonreader, though, also took a link loop, or a file behind a directory that may not be searched, for nopackage.json, and walked past it:proj@1.0.0);findPackageJSON;readModuleManifest.Node counts only "nothing there" as no
package.json, a link to nothing included. Anything else that can't be read it refuses withERR_INVALID_PACKAGE_CONFIG. Realstatdraws the same line.So when a reader's stat fails, a read now says why:
statStrictnames the file from the project root:lib/dep/package.json: can't be read (ELOOP). It serves configs and the strictpackage.jsonread.readRegularFileOrNulluses it too, instead of rethrowing the rawfserror with its absolute path.packageJSONStatthrows Node'sERR_INVALID_PACKAGE_CONFIG. It serves the resolver, the Vfs host'sfindPackageJSONand State.Lenient lookups (Bash, Rust,
stasis add,--package-json) still walk past a malformed or unreadable one, as before.Invalid-remapping errors never quote the remapping
An invalid remapping's error quoted it. The text is normally a remapping the project wrote. But a file named as a mapping by mistake can hold anything:
--mapping=token.txtprintedtoken.txt:1: invalid remapping "ghp_…"to stderr, and so into CI logs. A token pasted intoFOUNDRY_REMAPPINGSdid the same.Errors now give the location and the expected form:
remappings.txt:2: invalid remapping, expected [context:]prefix=targetfoundry.toml: `remappings` entry 2: invalid remapping, expected [context:]prefix=target`remappings` entry 1 is not a stringThis was the last error of stasis's own that quoted file content.
mainwas red after #178buildGitHubBundleandsuggestedEntriescheck their options against an empty Vfs before anything is fetched (#197). #178'sdirectoryEntryErrorthen found every extensionless entry missing from that empty tree. Sosrcwith pnpm was refused as "no such file or directory" rather than "only JS bundles are built with pnpm", andbuildGitHubBundle reads nothing from diskfails onmain.The pre-fetch checks now pass
fetched: false, which skips the missing-entry errors.buildVfsBundlestill checks entries against the project's own tree.Checks
require.resolveandfindPackageJSONagrees on every case: missing, a link to nothing, a directory namedpackage.json, malformed, a self-loop, a loop through directories, and (run unprivileged) a link into an unsearchable directory.package.jsontests fail onmain. The remapping test also checks that a secret named as a mapping isn't echoed.node --run test(Node 24): 2191 pass, 0 fail, 3 skipped, all 82 suites.node --run lint: clean.extendsand test-case suites all match, as do 400 new fuzzed projects.🤖 Generated with Claude Code
https://claude.ai/code/session_01C6oBS5QX4oqZcd2d3STiGA
Generated by Claude Code