feat(package): package-relative host installer bins and the agent-bundle prepack inventory gate (#252) - #281
Conversation
…dle prepack inventory gate Generated package builds now emit one self-contained installer bin that resolves the shipped artifact directory from import.meta.url and delegates to installBundle; agent-bundle prepack runs the release build plus an npm pack --dry-run inventory gate (AB7010-AB7013) sharing one packOutputFromJson with the test harness.
🦋 Changeset detectedLatest commit: 835b241 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 835b241a2a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const execFile = promisify(executeFile); | ||
|
|
||
| export const prepack = async (options: BuildOptions): Promise<PrepackResult> => { | ||
| const result = await build({ ...options, packageOutputs: true }); |
There was a problem hiding this comment.
Choose a non-overlapping default for prepack
When agent-bundle prepack is invoked without --output, this forwards no output override, so build() selects dist; every project eligible for prepack also has a package build whose output is dist, causing the existing overlap check to throw AB4706 before packing. This makes the documented bare command and typical "prepack": "agent-bundle prepack" lifecycle unusable unless users discover and supply another output path, so prepack should provide a non-overlapping artifact default or make the option required.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
| const name = packageBuild.bins.some((bin) => bin.name === model.metadata.name) | ||
| ? `${model.metadata.name}-install` | ||
| : model.metadata.name; |
There was a problem hiding this comment.
Reject a second installer-name collision
When plugin foo already declares bins named both foo and foo-install, this fallback still chooses foo-install, producing the same entry name and dist/bin/foo-install.js output as the authored bin. The package build can therefore fail on duplicate entries or replace the configured executable instead of adding an installer; check both candidate names and report a diagnostic or choose a guaranteed-free name.
Useful? React with 👍 / 👎.
| ...manifest.targets.flatMap((target) => | ||
| installSurfaceRequirements(target.name).map((path) => `${artifactPrefix}/${target.name}/${path}`)), | ||
| ]); | ||
| if (await exists(join(projectRoot, 'README.md'))) expected.add('README.md'); |
There was a problem hiding this comment.
Require the README even when it is absent
If a publishable project has no README.md, this conditional omits it from the expected inventory, allowing prepack to succeed without the README that AB7010 and the documented inventory gate say is required. Add README.md unconditionally so a missing source README is diagnosed just like one omitted from the packed tarball.
Useful? React with 👍 / 👎.
Summary
First slice of #252 — the npm distribution contract for generated host packs:
plugintarget implies all three; portable-only emits nothing), the package build synthesizes one additional self-contained executable,dist/bin/<plugin-name>.jswhen that name is free, otherwisedist/bin/<plugin-name>-install.js. Grammar:install <host> [--scope <scope>] [--json]; help lists only built hosts. The bin resolves the shipped artifact directory fromimport.meta.url(a build-time-baked relative URL — neverprocess.cwd()) and delegates to the exactinstallBundleimplementation behindagent-bundle install, bundled in via a newinstall-entryruntime shell aliased like the routed-CLI shell. npm lifecycle never mutates hosts; installation is the explicit documented command (npx <plugin> install <host>when the plugin name is the bin).agent-bundle prepack— the packed-tarball inventory gate: runs the release build (with feat(agent-bundle): optional plugin.version with fail-closed releases and an agent-bundle/meta build-time constant #270's fail-closed version semantics), thennpm pack --dry-run --json --ignore-scripts(no prepack recursion), parsed by the one sharedpackOutputFromJson(promoted fromtests/support/shared-pack.tsintosrc/build/pack-inventory.ts; the test harness now imports it from src). Diagnostics: AB7010 missing packed files (dist outputs, artifact manifest + every manifest file, per-target INSTALL.md/install.mjs surfaces, README), AB7011 artifact files stale against manifest SHA-256s, AB7012 package.json bins escaping the packed dist output (e.g.src/paths), AB7013 package.json / normalized plugin / host-manifest / artifact-provenance version disagreement.installBundleadditionally resolves theplugintarget directory (all three host manifests in one bundle dir), matching the pack gate's inventory.Proof
prepack.test.tsends with the real consumer journey:npm packthe fixture,npm installthe exact tarball into an empty consumer, delete the source project, run the installednode_modules/.bin/<plugin>installer for Cursor into an isolated HOME, and observe registration from package-local assets only.installer-entry.test.tspins naming fallback, portable exclusion, built-host argv validation, missing-artifact errors, and executes emitted bins for cursor/plugin targets.The full empty-dir proof matrix at the
host-installlevel (realclaude/codexbinaries, binary-gated) plus scaffold adoption and release wiring land in the follow-up slices of #252.Test plan
pnpm build,pnpm typecheck,pnpm lint(all green on the tree rebased over9a43616a5)pnpm lint:package(publint) greenorigin/main(9a43616a5) in this environment —dist/api.jsstatically imports@agent-bundle/runtime/statewhile the runtime is an optional peer the packed consumer never installs. Reproduced before and after this diff; needs its own fix in the routed-CLI/state lane.