Commit cbda5ab
authored
feat(prepack): gate installed dependencies (AB7014/AB7015); host-native INSTALL.md (#547)
* feat(prepack): gate installed dependencies (AB7014/AB7015) and make emitted INSTALL.md host-native
A published plugin installs nothing beyond its own files: the build inlines
every dependency into dist/bin and the host packs, so package.json
dependencies/optionalDependencies/peerDependencies entries only make every
consumer's npm install fetch build-time packages — and fail outright under
npm 12's default allow-git=none / allow-remote=none when one is a git or
remote specifier (cargo-hauler 0.4.7 is uninstallable for exactly this).
- AB7014: an installed-dependency field names packages no packed JavaScript
imports or requires (evidence read from the packed bytes, one diagnostic
per field).
- AB7015: an installed-dependency entry resolves through git, a GitHub
shorthand, a remote tarball, or a path.
- Emitted INSTALL.md states the bundle is self-contained, uses the host's
own claude plugin / codex plugin commands for uninstall, and marks every
agent-bundle install/uninstall/doctor mention as optional automation.
- create-agent-bundle mcp-server and cli-tool templates, and the examples,
declare @agent-bundle/runtime, react, and zod under devDependencies.
* chore: changeset PR number
* refactor(prepack): pure dependency evidence, shared digest cache, classifier fixes
- readModuleImports consults the digest cache itself (a parsed result answers
a lexed request); rememberedModuleImports and its three call-site lookups
are gone.
- pack-dependencies.ts is evidence only (declared entries, imported names,
classifiers); AB7014/AB7015 are emitted in pack-inventory.ts beside
AB7010-AB7013 through its diagnostic helper and a shared quoteAll.
- isRegistrySpecifier catches scp-style git@host:path and Windows drive
paths; packageNameOf drops its dead @-guard for one positive shape.
- readFile errors other than ENOENT propagate; packed files are read in
parallel; AB7015 says optionalDependencies fail to fetch, not install.
- Table tests for the classifiers; prepack fixture tests share
withPackageDocument and assert codes/names rather than sentences.
- Claude/Codex INSTALL.md share the optional-CLI paragraphs.
* fix(prepack): dependency gate honours tilde ranges, declaration imports, optional peers, and the packer
- ~1.2.3 is a semver range, not a home path (AB7015 false positive).
- Packed .d.ts/.d.mts/.d.cts files count as dependency usage: a consumer
needs the package that provides referenced types even without a runtime
import (AB7014 false positive).
- peerDependenciesMeta optional peers are never installed by npm and are
not inspected.
- workspace:/catalog: are registry specifiers only when pnpm, Yarn, or Bun
runs the pack (npm_config_user_agent) and rewrites them; npm publishes
them verbatim and consumers fail with EUNSUPPORTEDPROTOCOL.
* refactor(prepack): packer policy at the process edge, pure specifier classifiers, table-tested scanners
- prepack() turns npm_config_user_agent into packerRewritesWorkspaceProtocols;
isRegistrySpecifier classifies the string as written and isWorkspaceProtocol
lets the emitter apply the policy. RegistrySpecifierOptions is gone.
- optionalPeers is one set computed once; declarationSpecifiers is exported
and table-tested; requireCall and declarationSpecifier share quotedLiteral.
- The JS path keeps the lexer on purpose: bundled library docblocks contain
literal 'from "effect"' lines a text scan would count as usage.
- pack-inventory uses isErrno and sha256Hex like its sibling; module-imports
caches per check level only; AB7015 wording per partition; docs/changeset
name declaration references and workspace protocols.
* fix(prepack): resolution calls, bundleDependencies, optional overrides; INSTALL.md gates marketplace removal
- require.resolve / createRequire(...).resolve / import.meta.resolve with a
literal argument count as usage for AB7014.
- bundleDependencies (name list or true, either spelling) are embedded in the
tarball and never reported by AB7015.
- A name under both dependencies and optionalDependencies is judged by its
optional entry, as npm does.
- Emitted INSTALL.md no longer lists 'plugin marketplace remove' in the
uninstall block: it is a separate step gated on 'plugin list' showing no
other plugin from the marketplace (any scope or project, for Claude).
* fix(prepack): peers are never bundled; npm: alias targets are classified
- bundleDependencies never covers peerDependencies (npm packs no
node_modules entry for a peer-only name) and 'true' covers dependencies
only, so AB7015 still reports a peer's file:/git/workspace specifier.
- An npm: alias is a registry specifier only when its target is: npm:bar@file:../bar,
npm:bar@workspace:*, and nested npm: are reported.
* fix(prepack): type directives map to @types, computed import() withholds AB7014, Claude marketplace inventory
- /// <reference types="x" /> counts for x and @types/x (@types/scope__name
when scoped): the declaration cannot say which one the consumer needs.
- A computed import(expression) in packed JavaScript may load any declared
package, so importedPackageNames reports complete=false and AB7014 is
withheld; the recovery text and docs say so.
- Claude INSTALL.md: marketplace removal is gated on the cross-project
registry plugins/installed_plugins.json, not just 'claude plugin list',
and points at the optional uninstaller that performs that inventory.
- Docs: externalized runtime packages belong under dependencies; AB7014
permits them.
* fix(prepack): imports-map targets, computed require(), and install-script bins count as usage
- A packed '#subpath' import counts for every package the manifest's
imports map targets (conditional targets included).
- require(expression) withholds AB7014 like import(expression); bundler
runtimes (__webpack_require__) never match.
- A dependency a consumer-side preinstall/install/postinstall/prepare script
names, or whose bin command it runs (read from node_modules/<name>), is
used.
* fix(prepack): computed resolve/createRequire withhold AB7014; unresolvable optional deps warn
- require.resolve(x), import.meta.resolve(x), and a direct
createRequire(...)(x) with a non-literal argument mark the evidence
incomplete like require(x); path.resolve/Promise.resolve never match.
- AB7015 on optionalDependencies is a warning: npm continues an install
without an optional dependency it cannot fetch.
* fix(prepack): warning-only diagnostics pass prepack; literal-prefixed computed loads withhold AB7014
- prepack() throws only on error-severity diagnostics and returns the
surviving warnings on PrepackResult.diagnostics; the CLI prints them.
- require("driver/" + variant) and the template-literal form count as
computed loads and mark the evidence incomplete.
* fix(prepack): unknown schemes are non-registry, optional peers keep protocol checks, createRequire bindings are loaders
- A specifier scheme npm cannot parse (link:, portal:, jsr:, a typo) is
non-registry: consumers fail with EUNSUPPORTEDPROTOCOL before any fetch.
- Optional peers stay in the declared list as not-installed: AB7014 and
the fetch rules skip them, but an unparseable scheme is still reported.
- const load = createRequire(...) makes load(...) a loader: literal calls
record the package, computed calls withhold AB7014.
* fix(prepack): bundled exemption needs a packed node_modules entry; aliased createRequire; uninstalled bin fallback
- bundleDependencies exempts an entry from AB7015 only when the pack
inventory contains node_modules/<name>/package.json.
- import { createRequire as x } / { createRequire: x } makes x a factory.
- An install-script dependency with no readable node_modules manifest is
matched by its unscoped name, npm's default bin.
* fix(prepack): namespace-qualified createRequire factories; reference summary names every evidence source
- const load = Module.createRequire(...) binds a loader like the bare form.
- targets-artifacts (en/zh) and the changeset describe what AB7014 accepts
(require/resolve, declarations, imports map, install scripts) instead of
'imports' alone, and drop the 'installs nothing beyond its own files' claim.
* fix(prepack): validate scheme-less selectors; unparseable optional specifiers stay fatal
- A scheme-less specifier must be a semver range (npm's range grammar) or
a URL-safe dist-tag; 'not a valid spec' is EINVALIDTAGNAME for consumers.
- AB7015 warns only for an optionalDependencies entry npm parses but cannot
fetch; an unsupported scheme, invalid selector, or unrewritten workspace
protocol on an optional entry remains an error.
* fix(prepack): empty known-scheme specifiers unparseable; follow npm run delegation; unused peers warn
- 'npm:', 'file:', 'github:' with nothing after the colon are unparseable.
- Install-script evidence follows npm/pnpm/yarn/bun run <name> transitively,
including pre<name>/post<name> hooks.
- AB7014 on peerDependencies is a warning: a required peer nothing imports
may be a deliberate host-compatibility contract.
* fix(prepack): URL-form specifiers must parse; chained require('node:module').createRequire; AB7014 summary rows list every evidence source
* fix(prepack): bare tarball filenames are file sources; invalid dependency names fail AB7015
* fix(prepack): reserved package names, strict semver identifiers, bare directory specs
* fix(prepack): alias names validated, inline createRequire literal, npm git transports, leading run flags, declare module, empty fetch sources parse
* docs(diagnostics): alias name validity, git transports, empty fetch sources, declare module, run flags
* fix(prepack): alias targets must be registry specifiers, NPM: prefix case-insensitive, git+ftp/rsync transports
* refactor(prepack): classify dependency entries with npm-package-arg
Replace the hand-rolled specifier and name grammar with npm's own parser, so
AB7015 agrees with the consumer's install by construction: registry, fetched
(git/remote/path), or unparseable. Covers hosted-shorthand validation,
scp-style hosts, scoped-name components, loose semver ranges, alias targets,
and every git transport in one step. String-form bin names come from the
installed manifest's name; literal .resolve() evidence is limited to
require/createRequire/import.meta resolvers.
* fix(prepack): executables run by packed code, valued npm run options, optional fetches install scripts need
A dependency packed JavaScript only shells out to (a string literal that is
one of its bin commands) counts as used. Delegated npm run detection no
longer assumes valueless options: every token after run that names a
script is visited. An optional non-registry dependency a consumer install
script runs stays a fatal AB7015, since npm skips the failed fetch and the
script then fails on the missing command.
* fix(prepack): read dependency values exactly as npm does; follow npm run aliases
Pass the manifest value untrimmed to npm-package-arg (a leading space makes
an alias an invalid dist-tag, as it does for consumers) and stop trimming in
isWorkspaceProtocol, since no packer rewrites such a value either. Delegated
run detection also follows npm's rum/urn aliases.
* fix(prepack): nested createRequire arguments, quoted delegated script names
createRequire(new URL("./entry.js", import.meta.url))("driver") is a
load: the factory argument may nest calls two deep. A delegated npm run
"setup" names the script the shell unquotes.
* fix(prepack): file: sources shipped in the tarball install; install-script files load optional deps
A file: or bare path inside the package whose source directory manifest or
tarball file is packed is installable from the consumer's copy, so AB7015
exempts it like a packed bundleDependencies entry. An install script that
runs a packed file (node install.cjs) needs every package that file loads,
following relative imports through the tarball, so a fetched optional
dependency reached that way stays fatal.
* fix(prepack): resolve extensionless/quoted install-script files, npm test/start delegation, validate packed file: sources
installScriptFiles tokenizes the shell command keeping quoted words whole and resolves an extensionless path the way Node does (scripts/install -> scripts/install.js; .cjs/.mjs are never tried). Delegated-run traversal follows npm's direct script commands (test/t/tst/start/stop/restart) with their pre/post hooks. A file: source shipped in the tarball is exempt from AB7015 only when the packed copy is installable: a directory whose package.json parses to an object, or a (gzipped or plain) tar holding <dir>/package.json.
* fix(prepack): parse packaged tarball manifests, split shell operators, follow own-file imports targets, allow call-site comments
tarHoldsPackage validates each ustar header checksum and payload bounds and JSON-parses the <dir>/package.json payload before a packed file: tarball exempts AB7015 (npm: TAR_BAD_ARCHIVE / EJSONPARSE). shellWords splits &&, ||, ;, |, & without surrounding whitespace so 'node install.js&&echo done' still follows install.js. Install-script module traversal enqueues relative imports-map targets ('#setup' -> ./setup.js) instead of dropping them. Comments between a CommonJS loader and its parentheses, or around the literal, are trivia for literalLoad and never start a computed argument.
* fix(prepack): install-script command positions, directory mains, imports wildcards, shadowed peers
AB7015 escalation of an optional dependency now needs the install script to run it: a bin in command position (after env assignments, options, npx/bunx/cross-env/env wrappers and pnpm/npm/yarn/bun exec|dlx|x), a node_modules/<name>/ file, or a require in an inline node -e program; a bare mention (echo foo) stays keep-only AB7014 evidence. packedModule consults each packed directory manifest's main before index.js, as Node does. Imports-map specifiers resolve to the exact or best wildcard key with * substituted, for install traversal and for AB7014 reachability. declaredDependencies drops a peer that dependencies or optionalDependencies also names, whose selector npm never reads.
* fix(prepack): node . root main, --require/--import preloads, newline command separators
* fix(prepack): npm run first-positional delegation, Node option grammar, shell backslash escapes
* fix(prepack): npm restart fallback, NODE_OPTIONS preloads, bare require aliases mark evidence incomplete
* fix(prepack): qualified createRequire factories, import() in inline programs, unreadable dependency manifests
* fix(prepack): treat lexer failure as incomplete evidence; read installed manifests as npm does
Second self-review pass on 1e6e5cf (gpt-5.6-sol-medium):
- A packed file or inline `node -e` program es-module-lexer rejects now
marks its evidence incomplete: its `import()` calls cannot be reported,
so AB7014 is withheld for the package and a skipped optional dependency
an install script needs escalates to an AB7015 error instead of hiding
behind an empty import list.
- `node_modules/<dep>/package.json` is parsed with plain `JSON.parse`,
as npm parses it, so the last of duplicate keys decides a string-form
`bin` name; the strict duplicate-key parser is for our own config, not
a third party's manifest (comment says why it is not strict-json.ts).
Tests: lexer-rejected packed file withholds AB7014; lexer-rejected inline
program escalates AB7015; duplicate-`name` manifest resolves the effective
unscoped bin. All three fail on 1e6e5cf. Docs (diagnostics.md, en/zh
validation.mdx) and the changeset gain one clause each.1 parent 2e91ea1 commit cbda5ab
29 files changed
Lines changed: 2570 additions & 161 deletions
File tree
- .changeset
- docs
- examples
- audiobook-curator
- host-test
- worktree-proximity
- packages
- agent-bundle
- src
- build
- install
- tests
- create-agent-bundle
- templates
- cli-tool
- mcp-server
- tests
- website/docs
- en
- guide/distribution
- reference
- zh
- guide/distribution
- reference
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | 33 | | |
| 34 | + | |
39 | 35 | | |
40 | 36 | | |
41 | | - | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
42 | 40 | | |
43 | 41 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | 33 | | |
38 | 34 | | |
39 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
40 | 38 | | |
41 | 39 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | 24 | | |
| 25 | + | |
30 | 26 | | |
31 | 27 | | |
32 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
33 | 31 | | |
34 | 32 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
97 | 98 | | |
98 | 99 | | |
99 | 100 | | |
100 | 101 | | |
101 | 102 | | |
102 | 103 | | |
103 | 104 | | |
104 | | - | |
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
116 | 117 | | |
117 | 118 | | |
118 | 119 | | |
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
| 123 | + | |
122 | 124 | | |
123 | 125 | | |
124 | 126 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
582 | 583 | | |
583 | 584 | | |
584 | 585 | | |
| 586 | + | |
| 587 | + | |
585 | 588 | | |
586 | 589 | | |
587 | 590 | | |
| |||
1292 | 1295 | | |
1293 | 1296 | | |
1294 | 1297 | | |
| 1298 | + | |
1295 | 1299 | | |
1296 | 1300 | | |
1297 | | - | |
1298 | | - | |
| 1301 | + | |
| 1302 | + | |
1299 | 1303 | | |
1300 | 1304 | | |
1301 | 1305 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | 51 | | |
58 | 52 | | |
59 | 53 | | |
60 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
61 | 57 | | |
62 | 58 | | |
63 | 59 | | |
64 | 60 | | |
65 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| |||
0 commit comments