Repository navigation
feat(vfs-bundle): add @exodus/stasis/vfs-bundle — static bundles from a pnpm or yarn 1 lockfile through an in-memory node_modules - #189
Merged
Conversation
exo-nikita
force-pushed
the
claude/stasis-pnpm-lib
branch
from
October 1, 2026 11:39
0da81c3 to
00503be
Compare
@exodus/stasis/pnpm — static bundles from pnpm-lock.yaml through an in-memory node_modules@exodus/stasis/deptree — static bundles from a pnpm or yarn 1 lockfile through an in-memory node_modules
exo-nikita
force-pushed
the
claude/stasis-pnpm-lib
branch
from
October 1, 2026 11:50
00503be to
80baeec
Compare
@exodus/stasis/deptree — static bundles from a pnpm or yarn 1 lockfile through an in-memory node_modules@exodus/stasis/vfs-bundle — static bundles from a pnpm or yarn 1 lockfile through an in-memory node_modules
exo-nikita
force-pushed
the
claude/stasis-pnpm-lib
branch
2 times, most recently
from
October 1, 2026 13:13
9ac0a15 to
fb0c320
Compare
…rom a pnpm or yarn 1 lockfile through an in-memory node_modules
`@exodus/stasis/vfs-bundle` builds a JS/TS bundle and its companion lockfile from a project's
lockfile alone, over the project held in a @preventive/vfs Vfs, which is only read: no install, no
package script run, and nothing read from disk or written there but tarballs. `packageManager` says
whose: 'pnpm' lays out the tree `pnpm install --frozen-lockfile --ignore-scripts` makes from
pnpm-lock.yaml, with pnpm 10, 11 or 12; 'yarn1' the one `yarn install --frozen-lockfile
--ignore-scripts` makes from yarn.lock, with yarn 1.22. The version is `packageManagerVersion` if
given, which has to be the one the root package.json's packageManager pins where it pins one, else
that one, else pnpm 10.33.4 or yarn 1.22.22.
@preventive/deptree lays the tree out into a Vfs of its own, reading through a read-only view of
the project what the package manager reads: the lockfile, the package.json of every project it
finds (pnpm-workspace.yaml's packages, or the root's workspaces), and its settings files (the
patches and local directories pnpm's name, pnpm-workspace.yaml and .npmrc; .yarnrc and .npmrc).
Every tarball is fetched from registry.npmjs.org and held to the lockfile's integrity, cached only
where setCacheDir says. The scan, Node's resolution algorithm and the State that materializes the
bundle then read the project through a host with the tree in front of it: each project's
node_modules served from the tree alone, none above the root read. The result is byte-identical
to plain `stasis bundle` over a real install of the same lockfile.
buildVfsBundle takes buildBundle's JS options with `vfs`, `packageManager`, `cwd` and
`packageManagerVersion`, checks its entries before any tarball is fetched, reads no EXODUS_STASIS_*
setting, and returns the Bundle and its Lockfile, the Bundle carrying `repo`, the informational `{
github, directory, commit }`, where one is given; loadNodeModules returns the tree, deptree's
counts, the package manager reproduced and the host reading the project through it; createVfsHost is
a host over a Vfs alone. A refusal of the lockfile, or of pnpm-workspace.yaml, names the file.
src/vfs-bundle.js is the export and src/vfs-bundle/tree.js the tree and the host over it, loaded
only there and by buildVfsBundle, so plain `stasis bundle` never loads deptree.
@exodus/stasis-core/host is the disk host and src/resolve-node.js the host-based Node resolver;
State (its root discovery included), the core's bundle-util.js/util.js readers and scan.js,
resolve-fields.js, resolve-typescript.js (tsconfig `extends` included) and the JS builders take a
`host`, the disk host by default, with the disk behaviour unchanged; Config takes the `env` it
reads, process.env by default, and canonicalizes its write targets through the host; a State joins
the live registry only as the preload or to claim writes, and one built with `claim: false`, as a
static build's is, claims none, so a bundleFile a project's stasis.config.json names is no write
target of `stasis bundle` or buildBundle; and detectRepo reads through a host too, a State's its
own, so a build over a Vfs detects its repo in the Vfs as `stasis bundle` does on disk.
@preventive/deptree 1.0.0-alpha.3 and @preventive/vfs 1.0.0-beta.2 are pinned exactly while they are
prereleases. CI installs the yarn fixture too, and its job timeout goes to 3 minutes for the
fixtures and the tests over them.
Fails closed on what deptree refuses -- for pnpm: pnpm 9 and pnpm 12 before 12.8.1, registries other
than registry.npmjs.org and `file:` tarballs, git dependencies and `file:` ones no override names,
injected dependencies, pnpmfiles, package extensions, node-linker=hoisted/pnp, lockfiles older than
v9 or not up to date with a project's package.json, an importer pnpm-workspace.yaml's packages don't
take, a project found through a symlink, an integrity that is not a sha512, a patch that does not
apply; for yarn: yarn 2 and later, a package from anywhere but npm's registry (or yarn's mirror of
it), bundled dependencies, nohoist, Plug'n'Play, settings that change what yarn installs, a lockfile
not up to date with a package.json; for both: a packageManager other than the one reproduced,
Windows -- on a package cwd is in (the nearest package.json with a name) that is none of the
lockfile's projects, and on metroResolver.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KPSLumcb6Rdo4xXL5s5CpE
exo-nikita
force-pushed
the
claude/stasis-pnpm-lib
branch
from
October 1, 2026 18:58
fb0c320 to
ab195e0
Compare
This was referenced Oct 1, 2026
Merged
feat(vfs-bundle): Solidity bundles from soldeer.lock; post-merge fixes to the vfs-bundle export
#194
Merged
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.
What
@exodus/stasis/vfs-bundlebuilds a JS/TS bundle and its companion lockfile from a project's lockfile alone:@preventive/vfsVfs, which is only read. Nothing is read from disk or written there but tarballs.packageManagersays which install to reproduce:packageManager'pnpm'pnpm-lock.yamlpnpm install --frozen-lockfile --ignore-scripts, pnpm's isolated layout'yarn1'yarn.lockyarn install --frozen-lockfile --ignore-scripts, yarn's hoisted layoutThe version is
packageManagerVersionif given, which has to matchpackageManagerin the rootpackage.jsonwhere that pins one. Otherwise it's the pinned one, else pnpm 10.33.4 or yarn 1.22.22.This is the library half of #172, without the
stasis bundle --pnpmCLI flag and withoutdoc/pnpm.md. Plainstasis bundleand the disk-based JS API in@exodus/stasis/cmd/bundle(buildBundle,bundleCommand) produce the same output as onmain.buildVfsBundlebuildBundle's JS options (entries,scope,conditions,mainFields,metro/platforms,jsx,flow,typescript,tsconfig,resources,packageJSON), plusvfs,packageManager,cwd(a path in the Vfs,/by default) andpackageManagerVersion.repoof Bundle: add informationalrepo: { github, directory, commit }field #190/Detect and record the informational bundlerepo(github, directory, commit) #174 on theBundle:stasis bundledetects it on disk:.git, else apackage.jsonrepository;repopassed as{ github, directory, commit }overrides that. It's checked as theBundleconstructor checks it, before anything is fetched;Bundleand itsLockfile, whoseserialize()output is whatstasis bundlewrites over a real install.EXODUS_STASIS_*setting, whateverenva caller passes.metroResolveris refused, because the project's metro-resolver readsnode_moduleson disk.loadNodeModulesreturns:/stands forroot, the lockfile's directory;hostreading the project through the tree. It's a snapshot: a change to the project's Vfs after the call isn't seen.createVfsHost(vfs)is a host over a Vfs alone. It reads the Vfs as it is at each call, and resolves relative paths from its/.setCacheDiris@preventive/upstream's. Tarballs are also looked up in npm's cache and~/.auditbefore the registry, read-only and integrity-checked.How
@preventive/deptreelays it out into a Vfs of its own.package.jsonof every project it finds (pnpm-workspace.yaml'spackages, or the root'sworkspaces);pnpm-workspace.yamland.npmrc; for yarn,.yarnrcand.npmrc.registry.npmjs.org(yarn's mirror is taken for it) and are held to the lockfile's integrity.extends, theStatethat materializes the bundle, and repo detection all read the project through ahostwith the tree in front of it:node_modules(yarn's workspaces' included) comes from the tree alone;node_moduleselsewhere under the root is read as it is;node_modulesabove the root is read, except the directories on the way to the root, so a project can sit under one.cwdnamed through a symlink works.pnpm-workspace.yaml, names the file, whichever@preventive/lockfilecopy raised it: stasis's alpha.3, or deptree's alpha.2.Files
@exodus/stasis-core:src/host.js(@exodus/stasis-core/host): the disk host;Statereads through ahostoption (root discovery,stasis.*probes, recorded files, anddetectRepo), the disk host by default.readFileSyncMaybeanddetectRepotake that host.Statejoins the live registry only as the preload or to claim writes;Statebuilt withclaim: false, as the static build's is, claims no write target. So abundleFilethat the project'sstasis.config.jsonnames is no write target ofstasis bundle,buildBundleorbuildVfsBundle, and building twice in one process no longer fails as already claimed;Configtakes theenvit reads (process.envby default), and canonicalizes its write targets through the host rather than the real disk;bundle-util.js/util.jsreaders take ahost.@exodus/stasis:src/vfs-bundle.js(@exodus/stasis/vfs-bundle): the export alone;src/vfs-bundle/tree.js: the trees per package manager, and the host over a Vfs, alone or in front of the project. It's loaded only by the export andbuildVfsBundle, so plainstasis bundlenever loads deptree. One project host serves a whole build;src/resolve-node.js: Node's CJS resolution over a host;hostthreaded throughscan.js,resolve-fields.js,resolve-typescript.jsandcmd/bundle.js's JS builders, the disk host by default;buildVfsBundleincmd/bundle.js.@preventive/deptree1.0.0-alpha.3 and@preventive/vfs1.0.0-beta.2.@preventive/lockfile(alpha.3) and@preventive/upstream(alpha.2) stay as onmain. deptree still pins@preventive/lockfilealpha.2, so that one is installed beside it.tests/fixtures/pnpm-bundleand the newtests/fixtures/yarn1-bundleare one package set (86 registry packages) under each manager;yarn@1.22.22throughnpx;Verification
buildVfsBundle's bundle and lockfile equal plainstasis bundleover a realpnpm install/yarn install. This holds for the default options,packageJSON,mainFields,metrowith platforms, andnode_modulesscope. yarn's tree covers its nested copies, such assend/node_modules/debug/node_modules/ms.stasis run --bundle=load, and its lockfile verifies under--lock=frozen.node:fsread API before loading stasis. During a build, the only paths it reads are cached tarballs: repo detection included, it reads the Vfs, not the disk.stasis.config.jsonnames abundleFile;node_modulesin the project Vfs doesn't reach the bundle (both);EXODUS_STASIS_BUNDLE=addin the environment is ignored, including withenv: undefinedorenv: process.envpassed;Stateregistry doesn't grow;cwdnamed through a symlink builds;node_modulesdirectory builds, with that directory's other packages masked;repois detected from the Vfs'spackage.jsonrepository, with or withoutmainFields, and overridden by one passed. It's never on the lockfile, and an invalid one is refused before the tree is laid out.createVfsHostsees a file written after it was made, and reads relative paths from the Vfs's/;stasis bundleis byte-identical tomain's:--mainFields, and with--metro --platforms=ios,android --package-json;repo.require.resolveon every parity case, hits and error codes.link:/file:overrides, lockfile v6 and the env-only document;.yarnrcrefusals;packageManagervalidation; the Vfs host (store links,ELOOP, modes, conditions, trailing slashes); tsconfigextendsthrough the tree.Not supported (fails closed)
registry.npmjs.org, andfile:tarballsfile:directory dependency no override namespackageExtensionsnode-linker=hoisted/pnppackagesdoesn't take, and a workspace project found through a symlinkfile:/link:nohoist, Plug'n'Play.yarnrc/.npmrcsettings that change what yarn installspackage.jsonpackageManagerpin other than the one reproducedmetroResolver🤖 Generated with Claude Code
https://claude.ai/code/session_01KPSLumcb6Rdo4xXL5s5CpE