From 1e536ed19112c7122761d62a27a7c18fbbee9a50 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 4 Jun 2026 15:56:34 -0400 Subject: [PATCH] fix: make release tarball install wp-codebox binary --- README.md | 79 ++++++++++++++++++----- examples/recipes/cookbook/README.md | 21 ++++++ package-lock.json | 3 + package.json | 4 ++ packages/cli/README.md | 17 +++-- scripts/package-distribution-smoke.ts | 7 ++ scripts/package-installed-binary-smoke.ts | 45 +++++++++++++ 7 files changed, 154 insertions(+), 22 deletions(-) create mode 100644 scripts/package-installed-binary-smoke.ts diff --git a/README.md b/README.md index 7fe016b0..d302b760 100644 --- a/README.md +++ b/README.md @@ -266,12 +266,40 @@ npm run check ## Distribution Artifacts -The CLI package is prepared as `@automattic/wp-codebox-cli` and exposes the -`wp-codebox` binary from `packages/cli/dist/index.js`. +The GitHub Release workspace tarball exposes the stable `wp-codebox` binary from +`packages/cli/dist/index.js`. The scoped npm packages are prepared under +`@automattic/wp-codebox-*`, but they are not published yet; do not document +`npm install -g @automattic/wp-codebox-cli` as an available install path until +the package exists in the registry. ```bash npm run build npm pack --workspace @automattic/wp-codebox-cli --dry-run --json +npm pack --json +``` + +Install a GitHub Release tarball built from a release that includes the root +`bin` mapping when a downstream control plane needs a stable binary path without +pointing at a local feature worktree: + +```bash +npm install -g https://github.com/Automattic/wp-codebox/releases/download/v/wp-codebox-workspace-.tgz +wp-codebox commands --json +wp-codebox recipe validate --recipe ./examples/recipes/cookbook/codex-agent-smoke.json --json +``` + +`v0.4.0` includes the fresh sandbox session fix and Codex recipe example, but its +release asset predates the root `bin` mapping. Release managers need to cut a new +release from a commit containing this section before relying on the GitHub +Release tarball as the stable installed binary path. + +For a future npm release, publish all three scoped packages from the same clean +release commit after approval: + +```bash +npm publish --workspace @automattic/wp-codebox-core --access public +npm publish --workspace @automattic/wp-codebox-playground --access public +npm publish --workspace @automattic/wp-codebox-cli --access public ``` The WordPress plugin zip is built from `packages/wordpress-plugin` with only the @@ -284,10 +312,13 @@ unzip -Z1 packages/wordpress-plugin/dist/wp-codebox.zip `npm run package-distribution-smoke` validates both artifact shapes. It checks that the CLI pack includes `package.json`, `README.md`, and compiled `dist/` -files without TypeScript source, then builds the WordPress plugin zip and checks -that it contains the plugin bootstrap, README, PHP sources, checked-in browser -runtime asset, and vendored CLI runtime without package metadata or generated -artifacts. +files without TypeScript source, checks that the root release tarball installs a +`wp-codebox` binary, then builds the WordPress plugin zip and checks that it +contains the plugin bootstrap, README, PHP sources, checked-in browser runtime +asset, and vendored CLI runtime without package metadata or generated artifacts. +`npm run package-installed-binary-smoke` packs the root release tarball, installs +it into a temporary global prefix, and verifies the installed `wp-codebox` +binary can emit the command catalog. Versioning and release policy: @@ -296,18 +327,21 @@ Versioning and release policy: `@automattic/wp-codebox-playground` stay on the same version. 2. Keep `packages/wordpress-plugin/wp-codebox.php` `Version:` aligned with the package version used for the matching plugin zip. -3. Treat the npm package and plugin zip as one release unit: publish the CLI, - build the plugin zip from the same commit, and attach the zip to the release. +3. Treat the release tarball, npm packages, and plugin zip as one release unit: + attach the root `wp-codebox-workspace-.tgz` tarball, publish the + scoped npm packages when approved, build the plugin zip from the same commit, + and attach the zip to the release. 4. Use conventional semver: patch for fixes and docs-only distribution updates, minor for new commands or artifact fields, major for runtime contract breaks. Install notes by environment: 1. Self-hosted WordPress control planes should install the CLI on the same host - that runs PHP, install `packages/wordpress-plugin/dist/wp-codebox.zip` as the - parent-site plugin, then set `wp_codebox_bin` to the resolved `wp-codebox` - binary path. Component paths can be supplied through - `wp_codebox_component_paths` or the matching filter. + that runs PHP from the GitHub Release workspace tarball, install + `packages/wordpress-plugin/dist/wp-codebox.zip` as the parent-site plugin, + then set `wp_codebox_bin` to the resolved `wp-codebox` binary path. Component + paths can be supplied through `wp_codebox_component_paths` or the matching + filter. 2. Studio or local development environments can run from a checkout with `npm install`, `npm run build`, and `npm run wp-codebox -- ...`; install the plugin zip into the local parent site and point `wp_codebox_bin` at either @@ -325,11 +359,22 @@ options because the executable path is host-level configuration. Release checklist: 1. Run `npm run check` from a clean checkout. -2. Review `npm pack --workspace @automattic/wp-codebox-cli --dry-run --json` before publishing the CLI package. -3. Build `packages/wordpress-plugin/dist/wp-codebox.zip` with `npm run package:wordpress-plugin` and inspect `unzip -Z1 packages/wordpress-plugin/dist/wp-codebox.zip`. -4. Confirm package and plugin versions are aligned on the release commit. -5. Install the CLI in the target environment and configure the WordPress plugin `wp_codebox_bin` option or filter to the resolved `wp-codebox` binary path. -6. Install the plugin zip on the parent site and run the WordPress plugin smoke or equivalent ability registration check in that environment. +2. Run `npm run package-installed-binary-smoke` to verify the root release + tarball installs a working `wp-codebox` binary. +3. Review `npm pack --workspace @automattic/wp-codebox-cli --dry-run --json` before publishing the CLI package. +4. Build `packages/wordpress-plugin/dist/wp-codebox.zip` with + `npm run package:wordpress-plugin` and inspect + `unzip -Z1 packages/wordpress-plugin/dist/wp-codebox.zip`. +5. Confirm package and plugin versions are aligned on the release commit. +6. Build the root release tarball and attach it to the matching GitHub Release: + `npm pack --json`. The expected asset name is + `wp-codebox-workspace-.tgz`. +7. If npm publishing is approved, publish the scoped packages with the exact + `npm publish --workspace ... --access public` commands above. +8. Install the CLI in the target environment and configure the WordPress plugin + `wp_codebox_bin` option or filter to the resolved `wp-codebox` binary path. +9. Install the plugin zip on the parent site and run the WordPress plugin smoke + or equivalent ability registration check in that environment. ## Quick Start diff --git a/examples/recipes/cookbook/README.md b/examples/recipes/cookbook/README.md index 6dcc231c..dd4a057e 100644 --- a/examples/recipes/cookbook/README.md +++ b/examples/recipes/cookbook/README.md @@ -56,6 +56,27 @@ npm run wp-codebox -- recipe-run \ --json ``` +When running from an installed release binary, replace `npm run wp-codebox --` +with `wp-codebox`: + +```bash +wp-codebox recipe-run \ + --recipe ./examples/recipes/cookbook/codex-agent-smoke.json \ + --json +``` + +The stable installed path is the GitHub Release workspace tarball from a release +that includes the root `bin` mapping, not the unpublished npm package: + +```bash +npm install -g https://github.com/Automattic/wp-codebox/releases/download/v/wp-codebox-workspace-.tgz +``` + +`v0.4.0` includes the fresh sandbox session fix used by the Codex smoke path, but +its release asset predates the root `bin` mapping. Release managers should cut a +new release, attach a new root workspace tarball, and publish the scoped npm +packages from the same commit once npm publishing is approved. + The expected successful response is a JSON recipe run whose agent runtime reports the Playground site title and active theme. Fleet runners such as Homeboy should generate one recipe/run per task and own queueing, diff --git a/package-lock.json b/package-lock.json index 86bd0d09..3522179c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -196,6 +196,9 @@ "yargs": "^17.7.2", "yargs-parser": "^21.1.1" }, + "bin": { + "wp-codebox": "packages/cli/dist/index.js" + }, "devDependencies": { "@types/node": "^24.0.0", "tsx": "^4.20.0" diff --git a/package.json b/package.json index 5a6445b3..ffea87e4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "import": "./packages/cli/dist/index.js" } }, + "bin": { + "wp-codebox": "packages/cli/dist/index.js" + }, "files": [ "packages/runtime-core/dist", "packages/runtime-core/package.json", @@ -34,6 +37,7 @@ "postinstall": "node scripts/normalize-playground-sqlite-package.mjs", "package:wordpress-plugin": "tsx scripts/build-wordpress-plugin-zip.ts", "package-distribution-smoke": "tsx scripts/package-distribution-smoke.ts", + "package-installed-binary-smoke": "tsx scripts/package-installed-binary-smoke.ts", "release:package": "tsx scripts/package-release-artifact.ts", "artifact-bundle-verifier-smoke": "tsx scripts/artifact-bundle-verifier-smoke.ts", "artifact-redaction-smoke": "tsx scripts/artifact-redaction-smoke.ts", diff --git a/packages/cli/README.md b/packages/cli/README.md index 3e1fe0cf..4aa0378b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -5,25 +5,32 @@ reviewable artifact bundles. **Secure coding environments inside WordPress** — ## Install +The public `@automattic/wp-codebox-cli` npm package is not published yet. Until +that package exists, install a GitHub Release workspace tarball built from a +release that includes the root `bin` mapping: + ```bash -npm install -g @automattic/wp-codebox-cli +npm install -g https://github.com/Automattic/wp-codebox/releases/download/v/wp-codebox-workspace-.tgz wp-codebox --help ``` -The package exposes the `wp-codebox` binary and includes the compiled -`dist/` entrypoint only. Build from source with `npm run build` before running -local package validation. +Release tarball installs expose the `wp-codebox` binary from the compiled +`packages/cli/dist/` entrypoint. Build from source with `npm run build` before +running local package validation. ## Smoke ```bash npm run build npm run package-distribution-smoke +npm run package-installed-binary-smoke ``` The distribution smoke runs `npm pack --dry-run --json` and verifies the package contains `package.json`, `README.md`, and the compiled CLI entrypoint used by the -published binary. +published binary. The installed binary smoke packs the root release tarball, +installs it into a temporary global prefix, and verifies `wp-codebox commands +--json` works from that installed path. ## Discovery diff --git a/scripts/package-distribution-smoke.ts b/scripts/package-distribution-smoke.ts index 4d55b36e..76288e76 100644 --- a/scripts/package-distribution-smoke.ts +++ b/scripts/package-distribution-smoke.ts @@ -8,6 +8,7 @@ const execFileAsync = promisify(execFile) const repoRoot = resolve(import.meta.dirname, "..") const rootPackage = JSON.parse(await readFile(resolve(repoRoot, "package.json"), "utf8")) as { + bin?: Record dependencies?: Record devDependencies?: Record } @@ -47,6 +48,12 @@ assert.ok( rootPackedFiles.has("scripts/normalize-playground-sqlite-package.mjs"), "Root package should ship the Playground SQLite package normalizer for clean installs", ) +assert.equal( + rootPackage.bin?.["wp-codebox"], + "packages/cli/dist/index.js", + "Root release tarball should install the stable wp-codebox binary", +) +assert.ok(rootPackedFiles.has("packages/cli/dist/index.js"), "Root package should ship the compiled CLI binary target") await execFileAsync("npm", ["run", "package:wordpress-plugin"], { cwd: repoRoot, maxBuffer: 1024 * 1024 * 10 }) const pluginZip = resolve(repoRoot, "packages", "wordpress-plugin", "dist", "wp-codebox.zip") diff --git a/scripts/package-installed-binary-smoke.ts b/scripts/package-installed-binary-smoke.ts new file mode 100644 index 00000000..e4c8fbe0 --- /dev/null +++ b/scripts/package-installed-binary-smoke.ts @@ -0,0 +1,45 @@ +import assert from "node:assert/strict" +import { execFile } from "node:child_process" +import { mkdir, mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join, resolve } from "node:path" +import { promisify } from "node:util" + +const execFileAsync = promisify(execFile) +const repoRoot = resolve(import.meta.dirname, "..") +const tempRoot = await mkdtemp(join(tmpdir(), "wp-codebox-installed-binary-smoke-")) +const packRoot = join(tempRoot, "pack") +const installRoot = join(tempRoot, "install") + +try { + await execFileAsync("npm", ["run", "build"], { cwd: repoRoot, maxBuffer: 1024 * 1024 * 10 }) + await mkdir(packRoot, { recursive: true }) + + const { stdout: packStdout } = await execFileAsync("npm", ["pack", "--json", "--pack-destination", packRoot], { + cwd: repoRoot, + maxBuffer: 1024 * 1024 * 10, + }) + const [pack] = JSON.parse(packStdout) as Array<{ filename: string }> + const tarballPath = join(packRoot, pack.filename) + + await execFileAsync("npm", ["install", "--global", tarballPath, "--prefix", installRoot, "--no-audit", "--no-fund"], { + cwd: repoRoot, + maxBuffer: 1024 * 1024 * 40, + }) + + const binaryPath = join(installRoot, "bin", "wp-codebox") + const { stdout } = await execFileAsync(binaryPath, ["commands", "--json"], { + cwd: repoRoot, + maxBuffer: 1024 * 1024 * 10, + }) + const catalog = JSON.parse(stdout) as { schema?: string; commands?: Array<{ id?: string }> } + assert.equal(catalog.schema, "wp-codebox/command-catalog/v1", "Installed binary should emit the command catalog") + assert.ok( + catalog.commands?.some((command) => command.id === "wp-codebox.agent-sandbox-run"), + "Installed binary should expose the agent sandbox recipe helper", + ) + + console.log("Package installed binary smoke passed") +} finally { + await rm(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) +}