Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b325261
feat(prepack): gate installed dependencies (AB7014/AB7015) and make e…
ScriptedAlchemy Sep 4, 2026
f89a4ff
chore: changeset PR number
ScriptedAlchemy Sep 4, 2026
0027685
Merge remote-tracking branch 'origin/main' into feat/prepack-dependen…
ScriptedAlchemy Sep 4, 2026
48165af
refactor(prepack): pure dependency evidence, shared digest cache, cla…
ScriptedAlchemy Sep 4, 2026
9db0511
fix(prepack): dependency gate honours tilde ranges, declaration impor…
ScriptedAlchemy Sep 4, 2026
76cb637
refactor(prepack): packer policy at the process edge, pure specifier …
ScriptedAlchemy Sep 4, 2026
99e53f8
fix(prepack): resolution calls, bundleDependencies, optional override…
ScriptedAlchemy Sep 4, 2026
7a2be52
fix(prepack): peers are never bundled; npm: alias targets are classified
ScriptedAlchemy Sep 4, 2026
5e3554d
fix(prepack): type directives map to @types, computed import() withho…
ScriptedAlchemy Sep 4, 2026
6ca7920
fix(prepack): imports-map targets, computed require(), and install-sc…
ScriptedAlchemy Sep 4, 2026
346da1b
fix(prepack): computed resolve/createRequire withhold AB7014; unresol…
ScriptedAlchemy Sep 4, 2026
a1ab106
fix(prepack): warning-only diagnostics pass prepack; literal-prefixed…
ScriptedAlchemy Sep 4, 2026
fd7f4cf
fix(prepack): unknown schemes are non-registry, optional peers keep p…
ScriptedAlchemy Sep 4, 2026
9391777
fix(prepack): bundled exemption needs a packed node_modules entry; al…
ScriptedAlchemy Sep 4, 2026
1513cc9
fix(prepack): namespace-qualified createRequire factories; reference …
ScriptedAlchemy Sep 4, 2026
331dcd9
fix(prepack): validate scheme-less selectors; unparseable optional sp…
ScriptedAlchemy Sep 4, 2026
3b9b78b
fix(prepack): empty known-scheme specifiers unparseable; follow npm r…
ScriptedAlchemy Sep 4, 2026
44a8c01
fix(prepack): URL-form specifiers must parse; chained require('node:m…
ScriptedAlchemy Sep 4, 2026
f945afd
fix(prepack): bare tarball filenames are file sources; invalid depend…
ScriptedAlchemy Sep 4, 2026
3649715
fix(prepack): reserved package names, strict semver identifiers, bare…
ScriptedAlchemy Sep 4, 2026
f0b2b29
fix(prepack): alias names validated, inline createRequire literal, np…
ScriptedAlchemy Sep 4, 2026
65a2ad5
docs(diagnostics): alias name validity, git transports, empty fetch s…
ScriptedAlchemy Sep 4, 2026
28da0f4
fix(prepack): alias targets must be registry specifiers, NPM: prefix …
ScriptedAlchemy Sep 4, 2026
1876a86
refactor(prepack): classify dependency entries with npm-package-arg
ScriptedAlchemy Sep 4, 2026
16524b5
Merge origin/main into feat/prepack-dependency-gate
ScriptedAlchemy Sep 4, 2026
9c5fa96
fix(prepack): executables run by packed code, valued npm run options,…
ScriptedAlchemy Sep 4, 2026
61daea9
fix(prepack): read dependency values exactly as npm does; follow npm …
ScriptedAlchemy Sep 4, 2026
76b2de6
fix(prepack): nested createRequire arguments, quoted delegated script…
ScriptedAlchemy Sep 4, 2026
a0351b9
fix(prepack): file: sources shipped in the tarball install; install-s…
ScriptedAlchemy Sep 4, 2026
eb2f5fc
fix(prepack): resolve extensionless/quoted install-script files, npm …
ScriptedAlchemy Sep 4, 2026
f1711da
fix(prepack): parse packaged tarball manifests, split shell operators…
ScriptedAlchemy Sep 4, 2026
6b5441c
fix(prepack): install-script command positions, directory mains, impo…
ScriptedAlchemy Sep 4, 2026
d6a5d21
fix(prepack): node . root main, --require/--import preloads, newline …
ScriptedAlchemy Sep 4, 2026
a093992
fix(prepack): npm run first-positional delegation, Node option gramma…
ScriptedAlchemy Sep 4, 2026
9ce8c93
fix(prepack): npm restart fallback, NODE_OPTIONS preloads, bare requi…
ScriptedAlchemy Sep 4, 2026
1e6e5cf
fix(prepack): qualified createRequire factories, import() in inline p…
ScriptedAlchemy Sep 4, 2026
76056b7
fix(prepack): treat lexer failure as incomplete evidence; read instal…
ScriptedAlchemy Sep 4, 2026
dd51de5
Merge branch 'main' into feat/prepack-dependency-gate
ScriptedAlchemy Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/prepack-dependency-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'agent-bundle': patch
'create-agent-bundle': patch
---

Gate `agent-bundle prepack` on the installed-dependency fields of `package.json` so a published plugin installs only what its packed files need: `AB7014` reports a `dependencies`, `optionalDependencies`, or `peerDependencies` entry that no packed JavaScript imports, requires, resolves, or runs as an executable (a computed `import`/`require`, a packed file the ESM lexer rejects, or a `require` passed on as a value such as `const load = require`, withholds `AB7014` for the whole package; an installed manifest's `bin` is read as npm reads it, the last of duplicate keys winning), no packed declaration file references, and no `imports` mapping or consumer install script (including scripts it delegates to with `npm run` or `npm test`/`start`/`stop`/`restart`, `npm restart` without a `restart` script running `stop` and `start`) reaches — a warning rather than an error for a `peerDependencies` entry, which may be a deliberate host-compatibility contract — (the build inlines every dependency into `dist/bin` and the host packs, so a runtime external must be reached one of those ways; optional peers are skipped), and `AB7015` reports an entry a consumer's npm cannot resolve through a registry, judged by npm's own parser (`npm-package-arg`, now a dependency of `agent-bundle`): a git, GitHub-shorthand, remote-tarball, or path source, which npm 12 refuses to fetch by default (`allow-git`, `allow-remote`); a name or specifier npm cannot parse (`EINVALIDPACKAGENAME`, `EUNSUPPORTEDPROTOCOL` for `link:`, `portal:`, or a typo, `EINVALIDTAGNAME`, an alias of a non-registry target — reported even on an optional peer, since the manifest read itself fails); and `workspace:`/`catalog:` unless pnpm, Yarn, or Bun is running the pack and will rewrite them; a fetchable-but-unfetched `optionalDependencies` entry warns rather than fails, since npm continues without it (an unparseable one, or one a consumer install script runs, loads from an inline `node -e` program by `require`, `createRequire`, or `import()`, preloads with `node -r`/`--require`/`--import`/`--loader`, or loads from a packed file it executes — `node install.js`, `node .` through the root `main` — stays an error; each command after `&&`, `;`, or a newline counts on its own, shell quotes and backslash escapes are resolved, `node`'s options end at the program so `node install.js --require x` preloads nothing while a `NODE_OPTIONS=--require=x` assignment on the same command does, and `npm run <script>` delegates to the first positional alone — `npm run setup -- dormant` runs `setup`); an entry the tarball itself carries — a bundled dependency npm packed, or a `file:` path whose packed source is an installable package directory or tarball — is not reported — `agent-bundle prepack` prints such warnings and exits 0, and `prepack()` returns them on `PrepackResult.diagnostics`. Emitted `INSTALL.md` files now state that the bundle is self-contained, use the host's own `claude plugin` / `codex plugin` commands for uninstall, and mark every `agent-bundle install`/`uninstall`/`doctor` mention as optional automation. The `create-agent-bundle` `mcp-server` and `cli-tool` templates declare `@agent-bundle/runtime`, `react`, and `zod` under `devDependencies`. (#547)
47 changes: 45 additions & 2 deletions docs/diagnostics.md

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions examples/audiobook-curator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@
"typecheck": "tsc -p tsconfig.json --noEmit",
"validate": "agent-bundle validate"
},
"dependencies": {
"@agent-bundle/runtime": "workspace:*",
"react": "19.2.8",
"zod": "4.5.4"
},
"devDependencies": {
"@agent-bundle/runtime": "workspace:*",
"@rstest/core": "0.11.10",
"@types/react": "19.2.18",
"agent-bundle": "workspace:*"
"agent-bundle": "workspace:*",
"react": "19.2.8",
"zod": "4.5.4"
}
}
10 changes: 4 additions & 6 deletions examples/host-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@
"typecheck": "tsc -p tsconfig.json --noEmit",
"validate": "agent-bundle validate"
},
"dependencies": {
"devDependencies": {
"@agent-bundle/runtime": "workspace:*",
"@modelcontextprotocol/server": "2.0.0",
"react": "19.2.8",
"zod": "4.5.4"
},
"devDependencies": {
"@rstest/core": "0.11.10",
"@types/react": "19.2.18",
"agent-bundle": "workspace:*"
"agent-bundle": "workspace:*",
"react": "19.2.8",
"zod": "4.5.4"
}
}
10 changes: 4 additions & 6 deletions examples/worktree-proximity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
"typecheck": "tsc -p tsconfig.json --noEmit",
"validate": "agent-bundle validate"
},
"dependencies": {
"@agent-bundle/runtime": "workspace:*",
"react": "19.2.8",
"zod": "4.5.4"
},
"devDependencies": {
"@agent-bundle/runtime": "workspace:*",
"@rstest/core": "0.11.10",
"@types/react": "19.2.18",
"agent-bundle": "workspace:*"
"agent-bundle": "workspace:*",
"react": "19.2.8",
"zod": "4.5.4"
}
}
4 changes: 3 additions & 1 deletion packages/agent-bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@
}
},
"dependencies": {
"@effect/platform-node-shared": "4.0.0-rc.112",
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"@rsbuild/core": "2.2.1",
"@rsbuild/plugin-react": "2.1.0",
"@rslib/core": "0.23.2",
"@rslint/core": "0.8.2",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@rstackjs/load-config": "0.1.2",
"acorn": "8.18.0",
"ajv": "8.20.0",
Expand All @@ -113,12 +113,14 @@
"fast-glob": "3.3.3",
"ignore": "7.0.7",
"jiti": "2.7.0",
"npm-package-arg": "13.0.2",
"open": "11.0.2",
"ws": "8.21.3",
"yaml": "2.9.0"
},
"devDependencies": {
"@modelcontextprotocol/server": "2.0.0",
"@types/npm-package-arg": "6.1.4",
"@types/react": "19.2.18",
"@types/ws": "8.18.1",
"effect-rstest": "https://pkg.pr.new/ScriptedAlchemy/effect-rstest@e5f8d5f",
Expand Down
8 changes: 6 additions & 2 deletions packages/agent-bundle/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { TargetArtifactEntry, TargetHookEntry } from './adapters/types.ts';
import { build as buildArtifact, type BuildResult } from './build/build.ts';
import { routedCliBins, targetHostsCliBin } from './build/cli-bins.ts';
import { buildPackageOutputs, type PackageBuildResult } from './build/package-build.ts';
import { rewritesWorkspaceProtocols } from './build/pack-dependencies.ts';
import {
packInventoryDiagnostics,
packOutputFromJson,
Expand Down Expand Up @@ -582,6 +583,8 @@ export interface BuildProjectResult {

export interface PrepackResult {
readonly build: BuildProjectResult;
/** Non-error pack-inventory diagnostics; errors throw `DiagnosticError` instead. */
readonly diagnostics: readonly Diagnostic[];
readonly pack: PackOutput;
}

Expand Down Expand Up @@ -1292,10 +1295,11 @@ export const prepack = async (options: BuildOptions): Promise<PrepackResult> =>
model: result.model,
packageBuild: result.packageBuild,
packOutput: pack,
packerRewritesWorkspaceProtocols: rewritesWorkspaceProtocols(process.env.npm_config_user_agent),
Comment thread
ScriptedAlchemy marked this conversation as resolved.
projectRoot: options.root,
});
if (diagnostics.length > 0) throw new DiagnosticError(diagnostics);
return deepFreeze({ build: result, pack });
if (hasErrors(diagnostics)) throw new DiagnosticError(diagnostics);
return deepFreeze({ build: result, diagnostics, pack });
};

/** Every eval refusal reaches a caller as one actionable diagnostic, never a raw service error. */
Expand Down
14 changes: 7 additions & 7 deletions packages/agent-bundle/src/build/module-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ const remember = (key: string, imports: readonly ModuleImport[]): void => {
importsByDigest.set(key, imports);
};

/** Imports previously read (this process) from bytes with this digest at this check level. */
export const rememberedModuleImports = (
check: ModuleSyntaxCheck,
sha256: string,
): readonly ModuleImport[] | undefined => importsByDigest.get(`${check}:${sha256}`);

/**
* Reads the imports of one ES module source, throwing on invalid syntax
* (the lexer's or, for `parsed`, acorn's). When the source's SHA-256 is
* known the result is remembered for the next pass over the same bytes.
* known, a result remembered for those bytes at this check level is
* returned as is, and a fresh read is remembered for the next pass over the
* same bytes.
*/
export const readModuleImports = async (
source: string,
options: { readonly check: ModuleSyntaxCheck; readonly sha256?: string },
): Promise<readonly ModuleImport[]> => {
if (options.sha256 !== undefined) {
const known = importsByDigest.get(`${options.check}:${options.sha256}`);
if (known !== undefined) return known;
}
await init;
if (options.check === 'parsed') parseJavaScript(source, { ecmaVersion: 'latest', sourceType: 'module' });
const [records] = parse(source);
Expand Down
Loading
Loading