From 9c52974492c12317d92bff7d78ca1376c6dafce2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 16 Sep 2026 20:51:45 +0000 Subject: [PATCH] ci(release): verify pkg.pr.new artifacts --- .changeset/README.md | 33 +++--- .github/workflows/package-preview.yml | 7 +- .github/workflows/release.yml | 74 ++++-------- README.md | 8 +- docs/local-ci.md | 5 +- docs/preview-packages.md | 56 ++++----- package.json | 1 - packages/agent-bundle/README.md | 8 +- .../tests/release-outcome-summary.test.ts | 106 +++--------------- .../agent-bundle/tests/support/shared-pack.ts | 4 +- .../tests/verify-preview-artifacts.test.ts | 89 +++++++++++++++ .../tests/verify-registry-versions.test.ts | 86 -------------- packages/create-agent-bundle/README.md | 28 +---- .../templates/minimal/README.md | 6 +- packages/rsc-runtime/README.md | 8 +- scripts/local-ci.mjs | 2 +- scripts/pnpm-pack.mjs | 4 +- scripts/release-outcome-summary.sh | 46 +++----- scripts/run-packed-tests.mjs | 4 +- scripts/verify-preview-artifacts.sh | 45 ++++++++ scripts/verify-registry-versions.sh | 40 ------- website/docs/en/contributing/index.mdx | 17 ++- .../guide/distribution/preview-packages.mdx | 52 ++++----- website/docs/en/guide/start/installation.mdx | 37 +++--- website/docs/zh/contributing/index.mdx | 16 +-- .../guide/distribution/preview-packages.mdx | 46 ++++---- website/docs/zh/guide/start/installation.mdx | 29 ++--- 27 files changed, 337 insertions(+), 520 deletions(-) create mode 100644 packages/agent-bundle/tests/verify-preview-artifacts.test.ts delete mode 100644 packages/agent-bundle/tests/verify-registry-versions.test.ts create mode 100755 scripts/verify-preview-artifacts.sh delete mode 100755 scripts/verify-registry-versions.sh diff --git a/.changeset/README.md b/.changeset/README.md index 035b8f61f..fc4ef8d2d 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -1,11 +1,11 @@ # Changesets -This repository versions its published packages with +This repository versions its distributed packages with [Changesets](https://github.com/changesets/changesets). A changeset is a small Markdown file in this directory that names the packages a pull request changes, the bump each one needs, and a user-facing summary. `changeset version` folds pending changesets into `CHANGELOG.md` and `package.json` -versions; `changeset publish` (when enabled) publishes the result. +versions. pkg.pr.new publishes commit-addressed previews of the result. ## Which packages get changesets @@ -108,10 +108,9 @@ because they change no publishable package. `rsc-markdown-stream: workspace:^` edge republishes it with the renderer's new caret. The scaffolder's two optional `workspace:*` peers patch-bump and republish it whenever either member of its exact release pair moves. -- `access` stays `"restricted"` at the repository level until the release - owner decides the npm package names and access policy - (`docs/preview-packages.md`). `@agent-bundle/runtime` and - `create-agent-bundle` already override it with `publishConfig.access`. +- `access` stays `"restricted"` at the repository level. It is harmless while + Changesets only versions packages and pkg.pr.new is the distribution + channel; no npm publish command consumes it. ## Release flow @@ -138,16 +137,12 @@ because they change no publishable package. directly. 3. Merging Version Packages pushes a `Version Packages` commit to `main` - with no pending changesets. The workflow runs the release gates - (`pnpm check:release`) and then `scripts/verify-registry-versions.sh`, - which fails the job unless every publishable `package.json` version - resolves on npm. With publishing disabled that step is red by design: - green means published, never "versioned but not shipped". -4. Publishing is opt-in: set the repository variable - `AGENT_BUNDLE_NPM_PUBLISH=true` and the `NPM_TOKEN` secret. The action - then runs `pnpm release` (`pnpm check:release && changeset publish`) with - npm provenance (`NPM_CONFIG_PROVENANCE=true`, `id-token: write`) and - creates GitHub releases and tags. - -Until publishing is enabled, installable previews come from pkg.pr.new -(`pnpm preview:publish`, `docs/preview-packages.md`). + with no pending changesets. The `Release packages` workflow first calls + the reusable `Package preview` workflow, which runs `pnpm preview:publish` + and verifies that all four package URLs resolve for the merge SHA. The + release job depends on that proof and runs `pnpm check:release`; a green + Version Packages run therefore means both the packed gates and the exact + commit-addressed previews passed. +4. Consumers pin the resulting artifacts as + `https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@`. No npm + registry credential is needed or expected. diff --git a/.github/workflows/package-preview.yml b/.github/workflows/package-preview.yml index 467c6d624..22e774130 100644 --- a/.github/workflows/package-preview.yml +++ b/.github/workflows/package-preview.yml @@ -2,8 +2,7 @@ name: Package preview on: pull_request: - push: - branches: [main] + workflow_call: permissions: {} @@ -26,3 +25,7 @@ jobs: node-version: 22.19.0 - run: pnpm build - run: pnpm preview:publish + - name: Verify pkg.pr.new preview artifacts + run: >- + bash scripts/verify-preview-artifacts.sh + "${{ github.event.pull_request.head.sha || github.sha }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2d8ae8ed..fd7579d80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,39 +10,36 @@ on: # actions: write lets the built-in token dispatch CI for that generated branch # when no external Changesets token is configured. workflow_dispatch is one of # the explicit recursion exceptions for GITHUB_TOKEN-created events. -# id-token: write is required for npm package provenance once publishing is -# enabled; it is inert while PUBLISH_ENABLED below is false. The repository -# setting "Allow GitHub Actions to create and approve pull requests" must -# also be on, or the PR step fails after the branch is pushed. +# The repository setting "Allow GitHub Actions to create and approve pull +# requests" must also be on, or the PR step fails after the branch is pushed. permissions: actions: write contents: write - id-token: write pull-requests: write concurrency: group: package-release jobs: + preview: + name: Publish and verify pkg.pr.new + permissions: {} + uses: ./.github/workflows/package-preview.yml + release: - name: Version Packages / release + name: Version Packages / verify release + needs: preview runs-on: ubuntu-latest timeout-minutes: 60 env: - # npm publishing is opt-in and off by default: previews ship through - # pkg.pr.new (docs/preview-packages.md) until the package-name and - # access decisions are made. Set the repository variable - # AGENT_BUNDLE_NPM_PUBLISH=true *and* the NPM_TOKEN secret to let the - # action run `pnpm release` (release gates + `changeset publish`) when - # the Version Packages PR merges. While off, the action still opens and - # updates the Version Packages PR, and merging that PR only runs the - # release gates (`pnpm check:release`) so the tree stays publishable. - PUBLISH_ENABLED: ${{ vars.AGENT_BUNDLE_NPM_PUBLISH == 'true' && secrets.NPM_TOKEN != '' }} + # pkg.pr.new is the distribution channel. The reusable preview job + # publishes and resolves all four commit-addressed package URLs before + # this job maintains or verifies a Version Packages commit. CHANGESETS_TOKEN_CONFIGURED: ${{ secrets.CHANGESETS_GITHUB_TOKEN != '' }} AGENT_BUNDLE_PLAYWRIGHT_CHANNEL: chromium # Packed qualification writes package/digest evidence here when - # `pnpm check:release` / `pnpm release` runs. `github.workspace` is - # valid at job env; `runner.temp` is not (no runner assigned yet). + # `pnpm check:release` runs. `github.workspace` is valid at job env; + # `runner.temp` is not (no runner assigned yet). AGENT_BUNDLE_RELEASE_EVIDENCE: ${{ github.workspace }}/.release-qualification.json steps: # The action pushes and opens the PR through the GitHub API with the @@ -58,19 +55,12 @@ jobs: with: node-version: 22.19.0 playwright-browser: chromium - # changesets/action v2 no longer writes .npmrc from an NPM_TOKEN env - # variable; expose the token via npm config so `changeset publish` - # (pnpm publish) can authenticate. The ${NPM_TOKEN} placeholder is kept - # literal here and expanded by npm/pnpm from the step environment. - - if: env.PUBLISH_ENABLED == 'true' - run: echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> "$HOME/.npmrc" - id: changesets uses: changesets/action@v2 with: version-script: pnpm version-packages - # Empty publish-script means "version only": the action opens or - # refreshes the Version Packages PR and never publishes. - publish-script: ${{ env.PUBLISH_ENABLED == 'true' && 'pnpm release' || '' }} + # No publish-script: Changesets only opens or refreshes the Version + # Packages PR. Distribution is the commit-addressed preview above. commit-message: Version Packages pr-title: Version Packages # An external PAT/App token makes the generated PR's native @@ -78,9 +68,6 @@ jobs: # step below dispatches CI explicitly so the candidate still gets # pre-merge qualification without a manual close/reopen. github-token: ${{ secrets.CHANGESETS_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_CONFIG_PROVENANCE: true # GITHUB_TOKEN-created pushes do not recursively start workflows, but # workflow_dispatch is an explicit GitHub exception. ci.yml documents # manual dispatch as the full main-push matrix on any ref, including the @@ -103,41 +90,22 @@ jobs: gh workflow run ci.yml --ref changeset-release/main gh workflow run release-candidate.yml --ref changeset-release/main - # Publishing disabled: when the Version Packages PR lands (no pending - # changesets, "Version Packages" merge commit), prove the versioned - # tree still passes the pre-publish gates that `pnpm release` would run. - - name: Release gates (publish disabled) + # When the Version Packages PR lands, prove the versioned tree still + # passes the packed release gates. `needs: preview` separately proves + # that every package resolves from pkg.pr.new at this exact commit. + - name: Release gates for Version Packages merge id: qualify if: >- - env.PUBLISH_ENABLED != 'true' && steps.changesets.outputs.has-changesets == 'false' && startsWith(github.event.head_commit.message, 'Version Packages') run: pnpm check:release - # Green must mean published. Every publishable package.json version on - # main must resolve on npm — whether this run published it or an earlier - # one did — so this runs on every main push while publishing is enabled - # (versions only move through Version Packages merges, which publish), - # and always for a Version Packages merge so a disabled publish cannot - # pass one silently. Not gated on the action's `published` output: a - # publish script that prints no "New tag" lines leaves it false, and - # that silence is exactly what must turn red. - - name: Verify published registry artifacts - id: registry - if: >- - env.PUBLISH_ENABLED == 'true' || - (steps.changesets.outputs.has-changesets == 'false' && - startsWith(github.event.head_commit.message, 'Version Packages')) - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: bash scripts/verify-registry-versions.sh - name: Release outcome summary if: always() env: HAS_CHANGESETS: ${{ steps.changesets.outputs.has-changesets }} - PUBLISHED: ${{ steps.changesets.outputs.published }} QUALIFY_OUTCOME: ${{ steps.qualify.outcome }} CHANGESETS_OUTCOME: ${{ steps.changesets.outcome }} - REGISTRY_OUTCOME: ${{ steps.registry.outcome }} + PREVIEW_OUTCOME: ${{ needs.preview.result }} JOB_STATUS: ${{ job.status }} CANDIDATE_SHA: ${{ github.sha }} EVIDENCE_FILE: ${{ env.AGENT_BUNDLE_RELEASE_EVIDENCE }} diff --git a/README.md b/README.md index cfb59f6f0..11a090651 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ Requires Node.js 22.19 or later. ## Install -Nothing is published to npm yet (the `agent-bundle` name on npm currently belongs to an unrelated project). Until the first release, install the preview tarballs CI publishes for every commit and pull request: +Packages are distributed through pkg.pr.new, not the npm registry. Install the +preview tarballs CI publishes for every commit and pull request: ```sh npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ @@ -26,9 +27,8 @@ project that already passes its own `check`: npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin ``` -(`npm create agent-bundle` once npm releases exist. See the -[create-agent-bundle README](packages/create-agent-bundle/README.md) for -templates and flags.) +See the [create-agent-bundle README](packages/create-agent-bundle/README.md) +for templates and flags. Or describe the plugin by hand in `agent-bundle.config.ts` at the project root: diff --git a/docs/local-ci.md b/docs/local-ci.md index 33b7200ed..977edfe47 100644 --- a/docs/local-ci.md +++ b/docs/local-ci.md @@ -268,8 +268,9 @@ then treat a repeat as a real signal. - **dependency-review** runs as a GitHub-side action against the GitHub advisory database on the PR diff; it has no local equivalent and stays a hosted-only, PR-time check. -- **package-preview** (pkg.pr.new) and the **release publish** workflow are - publish-side effects, not checks; nothing about them gates a merge. +- **package-preview** (pkg.pr.new) and the **Release packages** workflow have + hosted publish-side effects. The latter also verifies the four remote + commit URLs, which a local gate cannot reproduce. - **host-install-proofs** needs the pinned `claude` and `codex` CLIs on PATH (see [Real-host install proofs](#real-host-install-proofs)). The local gate does not install host CLIs into its legs, so run those proofs by hand with diff --git a/docs/preview-packages.md b/docs/preview-packages.md index 79efa6b44..31ba75eb4 100644 --- a/docs/preview-packages.md +++ b/docs/preview-packages.md @@ -1,21 +1,13 @@ # Preview packages (pkg.pr.new) -Nothing is published to npm yet, deliberately: the current package names are -placeholders, and npm publishing is deferred until the final name is chosen -(it will then use [npm package provenance](https://docs.npmjs.com/generating-provenance-statements); -the publish step exports `NPM_CONFIG_PROVENANCE=true` and runs the packed -release gates before `changeset publish`, and only runs at all when the -`AGENT_BUNDLE_NPM_PUBLISH` repository variable is `true` — see "How an npm -release will flow" below). Before enabling that path, the -release owner must resolve the repository-wide `"access": "restricted"` -policy for `agent-bundle`, which does not currently override it with -`publishConfig.access`. Until then -pkg.pr.new is the release channel. Every CI package-preview run publishes real, -installable tarballs of all four publishable workspace packages (`agent-bundle`, +pkg.pr.new is the only package distribution channel. Every CI package-preview +run publishes real, installable tarballs of all four publishable workspace +packages (`agent-bundle`, `@agent-bundle/runtime`, `rsc-markdown-stream`, `create-agent-bundle`) to [pkg.pr.new](https://pkg.pr.new) — a free continuous-release registry keyed by commit SHA and pull request. -These are the packages to install until a first npm release is cut. +Consumers pin these previews by commit SHA; no npm registry credential is +needed or expected. ## Install the latest preview @@ -87,25 +79,20 @@ URL, so `pnpm add` of a preview `@agent-bundle/runtime` fails with `blockExoticSubdeps: false` in the consuming project's `pnpm-workspace.yaml`, or install previews with npm. -## How an npm release will flow +## How a Version Packages merge flows Versioning is driven by Changesets (`.changeset/README.md`). Every PR that changes a publishable package carries a `.changeset/*.md`; on each push to `main`, `.github/workflows/release.yml` runs `changesets/action`, which keeps a machine-owned **Version Packages** pull request up to date with the pending -bumps and `CHANGELOG.md` entries. Merging that PR versions the packages and -must leave them on npm: the workflow runs the release gates -(`pnpm check:release`) against that exact versioned candidate SHA and then -`scripts/verify-registry-versions.sh`, which fails the job unless every -publishable `package.json` version resolves with `npm view`. Publishing turns -on when the repository variable `AGENT_BUNDLE_NPM_PUBLISH` is `true` *and* -the `NPM_TOKEN` secret exists; the action then runs `pnpm release` -(`pnpm check:release && changeset publish`) with npm provenance, and the -registry check runs on every later `main` push too, so a silently failed -publish is red at the next merge. With publishing disabled, a Version -Packages merge fails the registry check (`outcome: failed`, **NOT -PUBLISHED**) instead of reporting green; a push that only refreshes the -Version Packages PR records `version-maintenance-only`. +bumps and `CHANGELOG.md` entries. Merging that PR versions the packages. The +`Release packages` workflow calls the reusable `Package preview` workflow, +which publishes and resolves all four +`https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@` URLs. The +release job depends on that proof and runs `pnpm check:release` against the +same Version Packages commit. A green run records `preview-release`; a push +that only refreshes the Version Packages PR records +`version-maintenance-only`. ## Where previews come from @@ -113,11 +100,10 @@ Version Packages PR records `version-maintenance-only`. `pnpm preview:publish` (`pkg-pr-new publish --previewVersion --peerDeps --no-compact --no-template './packages/agent-bundle' './packages/rsc-runtime' './packages/rsc-markdown-stream' './packages/create-agent-bundle'`) -after a full build, on every pull request and on every push to `main`. Runs are -grouped per PR and per branch with `cancel-in-progress`, so a newer push -cancels the superseded build: only the latest preview of a PR or of `main` -matters, and a `main` commit overtaken before its preview published has no -installable snapshot (pin the tip instead). The "Publish pkg.pr.new preview" -check on a PR or commit links to the exact URLs for that build. Previews are -built from the same `pnpm build` output the release gates verify; they are -not npm releases and carry preview version strings. +after a full build. Pull requests invoke the workflow directly and cancel a +superseded run for the same PR. On `main`, the serialized `Release packages` +workflow calls it as a reusable workflow and waits for all four URLs to +resolve before continuing. The "Publish pkg.pr.new preview" check on a PR or +commit links to the exact URLs for that build. Previews are built from the same +`pnpm build` output the release gates verify; they are not npm releases and +carry preview version strings. diff --git a/package.json b/package.json index 4d062040c..b9576eae0 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "test:host-install:session:claude": "pnpm build && AGENT_BUNDLE_HOST_INSTALL_CLAUDE_SESSION=1 pnpm test:host-install:session", "changeset": "changeset", "version-packages": "changeset version", - "release": "pnpm check:release && changeset publish", "preview:publish": "pkg-pr-new publish --previewVersion --peerDeps --no-compact --no-template './packages/agent-bundle' './packages/rsc-runtime' './packages/rsc-markdown-stream' './packages/create-agent-bundle'", "pack:dry-run": "pnpm build && npm pack ./packages/agent-bundle --dry-run --json", "lint:release": "attw --pack --profile esm-only packages/agent-bundle && attw --pack --profile esm-only packages/rsc-runtime && attw --pack --profile esm-only packages/rsc-markdown-stream && attw --pack --profile esm-only packages/create-agent-bundle && node scripts/check-declaration-imports.mjs --strict packages/agent-bundle packages/rsc-runtime packages/rsc-markdown-stream packages/create-agent-bundle", diff --git a/packages/agent-bundle/README.md b/packages/agent-bundle/README.md index 3bf5db550..b686f57be 100644 --- a/packages/agent-bundle/README.md +++ b/packages/agent-bundle/README.md @@ -1158,8 +1158,6 @@ then Hooks and Scripts, then the interactive MCP App. Run the complete local delivery gate with `pnpm check && pnpm check:release`. `pnpm check:release` is release-only: its exact package-script components are `pnpm pack:dry-run`, `pnpm lint:release`, and `pnpm test:packed:release`, and it does not replace -`pnpm check`. `pnpm release` runs that release gate before `changeset publish`. -Native Claude/Codex smokes stay intentionally opt-in and skipped in ordinary CI. -npm publishing is deferred until the release owner picks the final package name/scope; -pkg.pr.new previews are the interim channel, and the first npm release will use npm -package provenance (`publishConfig.provenance` is already set). +`pnpm check`. A Version Packages merge runs that gate after the reusable Package preview workflow +publishes and resolves every commit-addressed pkg.pr.new artifact. Native Claude/Codex smokes stay +intentionally opt-in and skipped in ordinary CI. pkg.pr.new is the package distribution channel. diff --git a/packages/agent-bundle/tests/release-outcome-summary.test.ts b/packages/agent-bundle/tests/release-outcome-summary.test.ts index 751f21831..6e092b10a 100644 --- a/packages/agent-bundle/tests/release-outcome-summary.test.ts +++ b/packages/agent-bundle/tests/release-outcome-summary.test.ts @@ -18,30 +18,27 @@ const summarize = async (extra: NodeJS.ProcessEnv) => { return stdout; }; -it('reports version-maintenance-only as NOT PUBLISHED with skipped qualification stages', async () => { +it('reports version maintenance separately from a versioned release', async () => { expect(await summarize({ - PUBLISH_ENABLED: 'false', HAS_CHANGESETS: 'true', - PUBLISHED: 'false', QUALIFY_OUTCOME: 'skipped', CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'skipped', + PREVIEW_OUTCOME: 'success', JOB_STATUS: 'success', })).toContain([ 'outcome: version-maintenance-only', `workflow_sha: ${candidateSha}`, 'candidate_sha: (not qualified)', - 'publication: NOT PUBLISHED', + 'distribution: no versioned release', '', 'stages:', '- version-maintenance: executed', '- qualification: skipped', - '- publication: skipped', - '- registry-verification: skipped', + '- pkg.pr.new-preview: resolved', ].join('\n')); }); -it('reports a registry-verified candidate as published with pack evidence and pending follow-ups', async () => { +it('reports a qualified Version Packages commit only after its previews resolve', async () => { const evidenceFile = join(await mkdtemp(join(tmpdir(), 'release-outcome-')), 'evidence.json'); await writeFile(evidenceFile, `${JSON.stringify({ executedBins: ['agent-bundle'], @@ -61,123 +58,56 @@ it('reports a registry-verified candidate as published with pack evidence and pe workspaceRefs: [], })}\n`); const stdout = await summarize({ - PUBLISH_ENABLED: 'false', HAS_CHANGESETS: 'false', - PUBLISHED: 'false', QUALIFY_OUTCOME: 'success', CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'success', + PREVIEW_OUTCOME: 'success', JOB_STATUS: 'success', EVIDENCE_FILE: evidenceFile, }); expect(stdout).toContain([ - 'outcome: published', + 'outcome: preview-release', `workflow_sha: ${candidateSha}`, `candidate_sha: ${candidateSha}`, - 'publication: published', + 'distribution: pkg.pr.new previews resolved', '', 'stages:', '- version-maintenance: skipped', '- qualification: executed', - '- publication: already-on-registry', - '- registry-verification: executed', + '- pkg.pr.new-preview: resolved', ].join('\n')); expect(stdout).toContain('- agent-bundle@0.1.0 agent-bundle-0.1.0.tgz sha256:deadbeef'); expect(stdout).toContain('- @agent-bundle/runtime dependencies rsc-markdown-stream: ^0.1.0'); expect(stdout).toContain('workspace-only refs:\n- none'); expect(stdout).toContain('- packed-release: executed'); - expect(stdout).toContain('- #688 schema-label provenance: pending'); }); -it('reports cancelled qualification as failed', async () => { +it('reports failed qualification as failed', async () => { expect(await summarize({ - PUBLISH_ENABLED: 'false', HAS_CHANGESETS: 'false', - PUBLISHED: 'false', - QUALIFY_OUTCOME: 'cancelled', + QUALIFY_OUTCOME: 'failure', CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'skipped', - JOB_STATUS: 'cancelled', + PREVIEW_OUTCOME: 'success', + JOB_STATUS: 'failure', })).toContain('outcome: failed\n'); }); -it('reports published from registry proof even when the action flag stayed false', async () => { - expect(await summarize({ - PUBLISH_ENABLED: 'true', - HAS_CHANGESETS: 'false', - PUBLISHED: 'false', - QUALIFY_OUTCOME: 'skipped', - CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'success', - JOB_STATUS: 'success', - })).toContain([ - 'outcome: published', - `workflow_sha: ${candidateSha}`, - `candidate_sha: ${candidateSha}`, - 'publication: published', - '', - 'stages:', - '- version-maintenance: skipped', - '- qualification: skipped', - '- publication: already-on-registry', - '- registry-verification: executed', - ].join('\n')); -}); - -it('reports a qualified candidate without registry proof as NOT PUBLISHED maintenance, not a green outcome', async () => { +it('never reports a release without preview proof', async () => { expect(await summarize({ - PUBLISH_ENABLED: 'false', HAS_CHANGESETS: 'false', - PUBLISHED: 'false', QUALIFY_OUTCOME: 'success', CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'skipped', - JOB_STATUS: 'success', - })).toContain([ - 'outcome: version-maintenance-only', - `workflow_sha: ${candidateSha}`, - `candidate_sha: ${candidateSha}`, - 'publication: NOT PUBLISHED', - '', - 'stages:', - '- version-maintenance: skipped', - '- qualification: executed', - '- publication: skipped', - '- registry-verification: skipped', - ].join('\n')); -}); - -it('reports a Version Packages merge whose registry check failed as failed', async () => { - expect(await summarize({ - PUBLISH_ENABLED: 'false', - HAS_CHANGESETS: 'false', - PUBLISHED: 'false', - QUALIFY_OUTCOME: 'success', - CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'failure', - JOB_STATUS: 'failure', - })).toContain('outcome: failed\nworkflow_sha'); -}); - -it('reports published only when publish is enabled and registry succeeded', async () => { - expect(await summarize({ - PUBLISH_ENABLED: 'true', - HAS_CHANGESETS: 'false', - PUBLISHED: 'true', - QUALIFY_OUTCOME: 'skipped', - CHANGESETS_OUTCOME: 'success', - REGISTRY_OUTCOME: 'success', + PREVIEW_OUTCOME: 'skipped', JOB_STATUS: 'success', })).toContain([ - 'outcome: published', + 'outcome: failed', `workflow_sha: ${candidateSha}`, `candidate_sha: ${candidateSha}`, - 'publication: published', + 'distribution: no versioned release', '', 'stages:', '- version-maintenance: skipped', '- qualification: executed', - '- publication: executed', - '- registry-verification: executed', + '- pkg.pr.new-preview: skipped', ].join('\n')); }); diff --git a/packages/agent-bundle/tests/support/shared-pack.ts b/packages/agent-bundle/tests/support/shared-pack.ts index 99408c920..dee1e81d1 100644 --- a/packages/agent-bundle/tests/support/shared-pack.ts +++ b/packages/agent-bundle/tests/support/shared-pack.ts @@ -136,8 +136,8 @@ const packOnce = async (packageName: SharedPackPackage): Promise => /** * Run-level release tarball for a public package. `test:packed` builds and * `pnpm pack`s each package exactly once per run (scripts/run-packed-tests.mjs; - * pnpm's packer rather than npm's because `pnpm publish` is what ships, and - * it rewrites `workspace:` ranges — scripts/pnpm-pack.mjs) and shares the + * pnpm's packer rather than npm's because pkg.pr.new needs its `workspace:` + * range rewrites — scripts/pnpm-pack.mjs) and shares the * result through AGENT_BUNDLE_SHARED_PACK_DIR, so every pack-and-install * suite consumes the same tarball a release would publish instead of * re-packing (and previously rebuilding) per test file. diff --git a/packages/agent-bundle/tests/verify-preview-artifacts.test.ts b/packages/agent-bundle/tests/verify-preview-artifacts.test.ts new file mode 100644 index 000000000..a8197c5a7 --- /dev/null +++ b/packages/agent-bundle/tests/verify-preview-artifacts.test.ts @@ -0,0 +1,89 @@ +import { execFile as executeFile } from 'node:child_process'; +import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +import { expect, it } from '@rstest/core'; + +const execFile = promisify(executeFile); +const scriptPath = join(dirname(fileURLToPath(import.meta.url)), '../../../scripts/verify-preview-artifacts.sh'); +const sha = 'b435f7b9179271cbff81d3e40d14ee342cbd65dd'; +const packages = { + 'packages/agent-bundle': 'agent-bundle', + 'packages/create-agent-bundle': 'create-agent-bundle', + 'packages/rsc-markdown-stream': 'rsc-markdown-stream', + 'packages/rsc-runtime': '@agent-bundle/runtime', +}; +const previewUrl = (name: string) => + `https://pkg.pr.new/ScriptedAlchemy/agent-bundle/${name}@${sha}`; + +const setup = async (available: ReadonlySet, failFirst = 0) => { + const root = await mkdtemp(join(tmpdir(), 'verify-preview-')); + for (const [dir, name] of Object.entries(packages)) { + await mkdir(join(root, dir), { recursive: true }); + await writeFile(join(root, dir, 'package.json'), `${JSON.stringify({ name })}\n`); + } + const bin = join(root, 'bin'); + await mkdir(bin); + const fakeCurl = join(bin, 'curl'); + await writeFile(fakeCurl, [ + '#!/usr/bin/env bash', + 'url="${@: -1}"', + `echo "$url" >> "${root}/calls.log"`, + `count=$(grep -Fxc -- "$url" "${root}/calls.log")`, + `[ "$count" -le ${failFirst} ] && exit 22`, + ...[...available].map((url) => `[ "$url" = "${url}" ] && exit 0`), + 'exit 22', + ].join('\n')); + await chmod(fakeCurl, 0o755); + const run = (revision = sha) => execFile('bash', [scriptPath, revision], { + cwd: root, + env: { + PATH: `${bin}:${process.env['PATH']}`, + PREVIEW_ATTEMPTS: '3', + PREVIEW_RETRY_SECONDS: '0', + }, + }); + const calls = async () => (await readFile(join(root, 'calls.log'), 'utf8')).trim().split('\n'); + return { calls, root, run }; +}; + +const allAvailable = new Set(Object.values(packages).map(previewUrl)); + +it('passes when every publishable package preview resolves', async () => { + const { calls, run } = await setup(allAvailable); + const { stdout } = await run(); + for (const name of Object.values(packages)) { + expect(stdout).toContain(`preview ${previewUrl(name)}`); + } + expect(await calls()).toHaveLength(4); +}); + +it('retries a preview that appears after replication lag', async () => { + const { calls, run } = await setup(allAvailable, 1); + const { stdout } = await run(); + expect(stdout).toContain(`attempt 1/3: ${previewUrl('agent-bundle')} not available yet`); + expect(await calls()).toHaveLength(8); +}); + +it('fails and names every preview that remains unavailable', async () => { + const available = new Set([previewUrl('agent-bundle'), previewUrl('rsc-markdown-stream')]); + const { calls, run } = await setup(available); + const failure = await run().catch((error: Error & { code?: number; stdout?: string }) => error); + expect(failure).toMatchObject({ code: 1 }); + expect(failure.stdout).toContain([ + '::error::Missing pkg.pr.new previews:', + previewUrl('@agent-bundle/runtime'), + previewUrl('create-agent-bundle'), + ].join(' ')); + expect(await calls()).toHaveLength(2 + 3 * 2); +}); + +it('fails without a commit SHA', async () => { + const { run } = await setup(allAvailable); + const failure = await run('').catch((error: Error & { code?: number; stdout?: string }) => error); + expect(failure).toMatchObject({ code: 1 }); + expect(failure.stdout).toContain('::error::A commit SHA is required'); +}); diff --git a/packages/agent-bundle/tests/verify-registry-versions.test.ts b/packages/agent-bundle/tests/verify-registry-versions.test.ts deleted file mode 100644 index c2ea58f9e..000000000 --- a/packages/agent-bundle/tests/verify-registry-versions.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { execFile as executeFile } from 'node:child_process'; -import { chmod, mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { promisify } from 'node:util'; - -import { expect, it } from '@rstest/core'; - -const execFile = promisify(executeFile); -const scriptPath = join(dirname(fileURLToPath(import.meta.url)), '../../../scripts/verify-registry-versions.sh'); - -const packages = { - 'packages/agent-bundle': { name: 'agent-bundle', version: '0.2.1' }, - 'packages/create-agent-bundle': { name: 'create-agent-bundle', version: '0.1.1' }, - 'packages/rsc-markdown-stream': { name: 'rsc-markdown-stream', version: '0.1.2' }, - 'packages/rsc-runtime': { name: '@agent-bundle/runtime', version: '0.1.1' }, -}; - -/** - * Stand up a tree with the four publishable package.json files and a fake - * `npm` on PATH that answers `npm view version` from `registry` - * (spec -> version), optionally failing the first `failFirst` calls per spec - * to exercise the retry loop. Calls are appended to `calls.log`. - */ -const setup = async (registry: Record, failFirst = 0) => { - const root = await mkdtemp(join(tmpdir(), 'verify-registry-')); - for (const [dir, manifest] of Object.entries(packages)) { - await mkdir(join(root, dir), { recursive: true }); - await writeFile(join(root, dir, 'package.json'), `${JSON.stringify(manifest)}\n`); - } - const bin = join(root, 'bin'); - await mkdir(bin); - const fakeNpm = join(bin, 'npm'); - await writeFile(fakeNpm, [ - '#!/usr/bin/env bash', - `echo "$*" >> "${root}/calls.log"`, - '[ "$1" = view ] || exit 1', - `count=$(grep -c -- "view $2 " "${root}/calls.log")`, - `[ "$count" -le ${failFirst} ] && exit 1`, - ...Object.entries(registry).map(([spec, version]) => `[ "$2" = "${spec}" ] && { echo "${version}"; exit 0; }`), - 'exit 1', - ].join('\n')); - await chmod(fakeNpm, 0o755); - const run = () => execFile('bash', [scriptPath], { - cwd: root, - env: { PATH: `${bin}:${process.env['PATH']}`, REGISTRY_ATTEMPTS: '3', REGISTRY_RETRY_SECONDS: '0' }, - }); - const calls = async () => (await readFile(join(root, 'calls.log'), 'utf8')).trim().split('\n'); - return { calls, root, run }; -}; - -const allPublished = Object.fromEntries( - Object.values(packages).map(({ name, version }) => [`${name}@${version}`, version]), -); - -it('passes when every publishable version resolves on the registry', async () => { - const { calls, run } = await setup(allPublished); - const { stdout } = await run(); - for (const { name, version } of Object.values(packages)) expect(stdout).toContain(`registry ${name}@${version}`); - expect(await calls()).toHaveLength(4); -}); - -it('retries a version that appears after replication lag', async () => { - const { calls, run } = await setup(allPublished, 1); - const { stdout } = await run(); - expect(stdout).toContain('attempt 1/3: agent-bundle@0.2.1 not on npm yet'); - expect(stdout).toContain('registry agent-bundle@0.2.1'); - expect(await calls()).toHaveLength(8); -}); - -it('fails and names every version the registry lacks after exhausting attempts', async () => { - const { calls, run } = await setup({ 'agent-bundle@0.2.1': '0.2.1', 'rsc-markdown-stream@0.1.2': '0.1.2' }); - const failure = await run().catch((error: Error & { code?: number; stdout?: string }) => error); - expect(failure).toMatchObject({ code: 1 }); - expect(failure.stdout).toContain('::error::Not on npm: @agent-bundle/runtime@0.1.1 create-agent-bundle@0.1.1.'); - expect(await calls()).toHaveLength(2 + 3 * 2); -}); - -it('fails on a package.json without a version instead of comparing empty strings', async () => { - const { root, run } = await setup(allPublished); - await writeFile(join(root, 'packages/rsc-runtime/package.json'), '{"name":"@agent-bundle/runtime"}\n'); - const failure = await run().catch((error: Error & { code?: number; stdout?: string }) => error); - expect(failure).toMatchObject({ code: 1 }); - expect(failure.stdout).toContain('::error::packages/rsc-runtime/package.json has no name/version to verify.'); -}); diff --git a/packages/create-agent-bundle/README.md b/packages/create-agent-bundle/README.md index f45405a8c..27fc56038 100644 --- a/packages/create-agent-bundle/README.md +++ b/packages/create-agent-bundle/README.md @@ -4,15 +4,8 @@ Scaffold a new [agent-bundle](https://github.com/ScriptedAlchemy/agent-bundle) plugin project from a checked-in template: one `agent-bundle.config.ts`, the entry-file conventions, a passing test, and a delivery gate, ready to run. -```sh -npm create agent-bundle@latest my-plugin -# or -npx create-agent-bundle my-plugin --template mcp-server -``` - -Until the first npm release is cut, install the scaffolder from the -[pkg.pr.new preview channel](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md) -instead of the npm registry: +Run the scaffolder from the +[pkg.pr.new preview channel](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md): ```sh npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin @@ -66,20 +59,9 @@ Preview scaffolders pin `agent-bundle` and `@agent-bundle/runtime` to exact [pkg.pr.new](https://pkg.pr.new) tarballs from one commit SHA. A runtime template overridden from a preview scaffolder accepts another exact pkg.pr.new URL or the unversioned local names `agent-bundle.tgz` and -`agent-bundle-runtime.tgz`; versioned registry or local overrides require the -matching npm scaffolder release. - -An npm release records its compatible compiler and runtime versions as -optional peers in the packed `create-agent-bundle` manifest. The scaffolder -pins those two recorded versions independently — it never derives the runtime -version from the compiler version — and rejects a runtime-bearing scaffold -whose `--framework-version` does not match the recorded compiler. A local -compiler tarball selects the sibling runtime tarball with the recorded runtime -version and validates both package names and versions before writing the -project. -Until this project owns the npm package names, use the preview command above; -a locally packed release record does not make the unrelated registry package -safe to install. +`agent-bundle-runtime.tgz`. A local compiler tarball selects the sibling +runtime tarball with the recorded runtime version and validates both package +names and versions before writing the project. ## License diff --git a/packages/create-agent-bundle/templates/minimal/README.md b/packages/create-agent-bundle/templates/minimal/README.md index ebaccb3a9..2a398c11d 100644 --- a/packages/create-agent-bundle/templates/minimal/README.md +++ b/packages/create-agent-bundle/templates/minimal/README.md @@ -60,9 +60,9 @@ pool at the `mcp-in-memory` level, as a working example. ## The agent-bundle dependency -agent-bundle has no npm release yet; this project pins a -[pkg.pr.new](https://pkg.pr.new) preview tarball of it. To move to a newer -preview (or a real release once one exists), change the `agent-bundle` entry +agent-bundle is distributed through [pkg.pr.new](https://pkg.pr.new); this +project pins one commit-addressed preview tarball. To move to a newer preview, +change the `agent-bundle` entry in `devDependencies` — see [Preview packages](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md) for the URL forms. diff --git a/packages/rsc-runtime/README.md b/packages/rsc-runtime/README.md index a6190ca3b..20d7dfebd 100644 --- a/packages/rsc-runtime/README.md +++ b/packages/rsc-runtime/README.md @@ -1,8 +1,8 @@ # `@agent-bundle/runtime` Agent Document contracts and React-owned Flight execution for Agent Bundle routes. -No npm release is cut yet; install the pkg.pr.new preview of any `main` commit or pull -request — see [Preview packages](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md). +Install the pkg.pr.new preview of any `main` commit or pull request — see +[Preview packages](https://github.com/ScriptedAlchemy/agent-bundle/blob/main/docs/preview-packages.md). The runtime executes route models through React-owned RSC/Flight behind the `AgentRenderDispatcher` execution-host seam. Incremental Flight decoding @@ -69,8 +69,8 @@ minor the suite proves, inside the `^19.1.0` its Flight binding `react-server-dom-rspack` `0.1.0` (an exact dependency) accepts — and react-dom itself insists that `react` and `react-dom` match exactly; the proof example compiles them with `rsbuild-plugin-rsc` `0.1.1`. `rsc-markdown-stream` -is declared `workspace:^`, which `pnpm publish` ships as the caret of the -version released beside it. The package does not own application state, +is declared `workspace:^`, which pnpm's packer rewrites to the caret of the +version packed beside it. The package does not own application state, persistence, a concrete execution host, or host packaging. Node 22.19 or newer is required. diff --git a/scripts/local-ci.mjs b/scripts/local-ci.mjs index 2473e2310..716aec683 100644 --- a/scripts/local-ci.mjs +++ b/scripts/local-ci.mjs @@ -486,7 +486,7 @@ const main = async () => { '', ...tableLines, '', - 'Not covered locally: dependency-review (GitHub-side), package previews and npm publish (publish-side), native host smokes (opt-in). See docs/local-ci.md.', + 'Not covered locally: dependency-review (GitHub-side), pkg.pr.new publication (publish-side), native host smokes (opt-in). See docs/local-ci.md.', '', ].join('\n'); diff --git a/scripts/pnpm-pack.mjs b/scripts/pnpm-pack.mjs index 522cdf11f..6949531e7 100644 --- a/scripts/pnpm-pack.mjs +++ b/scripts/pnpm-pack.mjs @@ -1,6 +1,6 @@ /** - * Packs one workspace package the way a release ships it: `changeset publish` - * runs `pnpm publish`, whose packer rewrites `workspace:` ranges to the packed + * Packs one workspace package the way pkg.pr.new ships it. pnpm's packer + * rewrites `workspace:` ranges to the packed * sibling's version (`@agent-bundle/runtime`'s `rsc-markdown-stream: * workspace:^` becomes `^`), applies `publishConfig` overrides, and * drops the prepublish scripts. `npm pack` would leave `workspace:^` in the diff --git a/scripts/release-outcome-summary.sh b/scripts/release-outcome-summary.sh index 3d3db7967..dfadd16e2 100644 --- a/scripts/release-outcome-summary.sh +++ b/scripts/release-outcome-summary.sh @@ -2,26 +2,21 @@ # Release packages workflow summary: outcome, stages, and optional pack evidence. set -euo pipefail -publish_enabled=${PUBLISH_ENABLED:-false} has_changesets=${HAS_CHANGESETS:-false} -published=${PUBLISHED:-false} qualify_outcome=${QUALIFY_OUTCOME:-skipped} changesets_outcome=${CHANGESETS_OUTCOME:-skipped} -registry_outcome=${REGISTRY_OUTCOME:-skipped} +preview_outcome=${PREVIEW_OUTCOME:-skipped} job_status=${JOB_STATUS:-success} workflow_sha=${CANDIDATE_SHA:-} if [ "$changesets_outcome" = failure ] || [ "$changesets_outcome" = cancelled ] \ || [ "$qualify_outcome" = failure ] || [ "$qualify_outcome" = cancelled ] \ - || [ "$registry_outcome" = failure ] || [ "$registry_outcome" = cancelled ] \ + || [ "$preview_outcome" = failure ] || [ "$preview_outcome" = cancelled ] \ + || { [ "$qualify_outcome" = success ] && [ "$preview_outcome" != success ]; } \ || [ "$job_status" = failure ] || [ "$job_status" = cancelled ]; then outcome=failed -# Registry verification is the publication proof; the action's `published` -# flag only says whether its publish script printed tag lines. A qualified -# candidate that is not on npm is a failed registry step, never a green -# "qualified-without-publish". -elif [ "$registry_outcome" = success ]; then - outcome=published +elif [ "$qualify_outcome" = success ]; then + outcome=preview-release else outcome=version-maintenance-only fi @@ -32,7 +27,7 @@ else version_maintenance=skipped fi -if [ "$qualify_outcome" = success ] || [ "$published" = true ]; then +if [ "$qualify_outcome" = success ]; then qualification=executed elif [ "$qualify_outcome" = failure ] || [ "$qualify_outcome" = cancelled ]; then qualification=failed @@ -40,27 +35,19 @@ else qualification=skipped fi -if [ "$published" = true ]; then - publication=executed -elif [ "$registry_outcome" = success ]; then - publication=already-on-registry +if [ "$preview_outcome" = success ]; then + preview=resolved +elif [ "$preview_outcome" = failure ] || [ "$preview_outcome" = cancelled ]; then + preview=failed else - publication=skipped + preview=skipped fi -if [ "$registry_outcome" = success ]; then - registry=executed -elif [ "$registry_outcome" = failure ]; then - registry=failed -else - registry=skipped -fi - -if [ "$outcome" = published ]; then - publication_line='publication: published' +if [ "$outcome" = preview-release ]; then + distribution_line='distribution: pkg.pr.new previews resolved' candidate_sha=$workflow_sha else - publication_line='publication: NOT PUBLISHED' + distribution_line='distribution: no versioned release' if [ "$qualification" = executed ]; then candidate_sha=$workflow_sha else @@ -72,13 +59,12 @@ cat </dev/null; then + found=true + break + fi + echo "attempt $attempt/$attempts: $url not available yet" + [ "$attempt" -lt "$attempts" ] && sleep "$delay" + done + if $found; then + echo "preview $url" + else + missing+=("$url") + fi +done + +if [ ${#missing[@]} -gt 0 ]; then + echo "::error::Missing pkg.pr.new previews: ${missing[*]}" + exit 1 +fi diff --git a/scripts/verify-registry-versions.sh b/scripts/verify-registry-versions.sh deleted file mode 100755 index a6a005069..000000000 --- a/scripts/verify-registry-versions.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Release packages: fail unless every publishable package.json version resolves -# on npm. Retries cover registry replication lag right after `changeset publish`. -set -euo pipefail - -attempts=${REGISTRY_ATTEMPTS:-8} -delay=${REGISTRY_RETRY_SECONDS:-15} -missing=() -for dir in \ - packages/agent-bundle \ - packages/rsc-runtime \ - packages/rsc-markdown-stream \ - packages/create-agent-bundle -do - name=$(node -p "JSON.parse(require('node:fs').readFileSync('$dir/package.json','utf8')).name") - version=$(node -p "JSON.parse(require('node:fs').readFileSync('$dir/package.json','utf8')).version") - if [ -z "$name" ] || [ -z "$version" ] || [ "$version" = undefined ]; then - echo "::error::$dir/package.json has no name/version to verify." - exit 1 - fi - found=false - for ((attempt = 1; attempt <= attempts; attempt++)); do - if [ "$(npm view "$name@$version" version 2>/dev/null || true)" = "$version" ]; then - found=true - break - fi - echo "attempt $attempt/$attempts: $name@$version not on npm yet" - [ "$attempt" -lt "$attempts" ] && sleep "$delay" - done - if $found; then - echo "registry $name@$version" - else - missing+=("$name@$version") - fi -done - -if [ ${#missing[@]} -gt 0 ]; then - echo "::error::Not on npm: ${missing[*]}. The tree says these versions shipped; the registry disagrees (publishing disabled, token missing, or changeset publish failed)." - exit 1 -fi diff --git a/website/docs/en/contributing/index.mdx b/website/docs/en/contributing/index.mdx index 86c65b59e..3353b6125 100644 --- a/website/docs/en/contributing/index.mdx +++ b/website/docs/en/contributing/index.mdx @@ -70,17 +70,14 @@ pnpm changeset Pick the affected packages and the bump, and describe the change from the reader's side — a changeset is release-note copy, not a commit message. `pnpm version-packages` applies pending -changesets, and publishing runs `pnpm check:release` before `changeset publish`. Private -workspace packages, including the examples and this site, are not versioned or tagged. +changesets, and a Version Packages merge runs `pnpm check:release` after its pkg.pr.new previews +resolve. Private workspace packages, including the examples and this site, are not versioned. -Nothing is published to npm yet. The current release channel is the pkg.pr.new preview +The package distribution channel is the pkg.pr.new preview tarballs described in [Preview packages](../guide/distribution/preview-packages.mdx). Each `Release packages` run writes an explicit outcome to the workflow summary: -`version-maintenance-only` (Version Packages PR refreshed), `published` (every publishable -`package.json` version resolved on npm; the stage list says whether this run published or -the versions were already on the registry), or `failed`. A Version Packages merge whose versions -are not on npm — publishing disabled, token missing, or a silent `changeset publish` -failure — is `failed` and **NOT PUBLISHED**, never a green qualification. +`version-maintenance-only` (Version Packages PR refreshed), `preview-release` (the merge commit +passed release gates and all four commit-addressed pkg.pr.new URLs resolved), or `failed`. ## Native host smokes are opt-in @@ -101,9 +98,9 @@ rather than reporting a pass it did not earn. That boundary is the whole point o levels in [Testing](../guide/development/testing.mdx): a level is never reported as a receipt for a stronger one. -Dependency review, package previews, and the release publish workflow are hosted-only for +Dependency review, package previews, and the Release packages workflow are hosted-only for structural reasons — the first reads GitHub's advisory database against the pull-request diff, -and the other two are publish-side effects rather than checks. +and the other two publish to pkg.pr.new; Release packages also verifies the remote commit URLs. ## Working on the Workbench UI diff --git a/website/docs/en/guide/distribution/preview-packages.mdx b/website/docs/en/guide/distribution/preview-packages.mdx index b7f0f67dd..bf8c55770 100644 --- a/website/docs/en/guide/distribution/preview-packages.mdx +++ b/website/docs/en/guide/distribution/preview-packages.mdx @@ -1,14 +1,13 @@ --- -description: 'The pkg.pr.new preview channel that stands in for npm: how previews are published, how to pin them, and what changes at the first npm release.' +description: 'The pkg.pr.new package channel: how previews are published, verified, and pinned by commit SHA.' --- # Preview packages -Nothing is published to npm yet, deliberately. The current package names are placeholders, and -npm publishing is deferred until the final name is chosen. Until then, **pkg.pr.new is the -release channel**: every CI package-preview run publishes real, installable tarballs of all four -publishable workspace packages to a free continuous-release registry keyed by commit SHA and pull -request. +**pkg.pr.new is the package distribution channel.** Every CI package-preview run publishes real, +installable tarballs of all four publishable workspace packages to a free continuous-release +registry keyed by commit SHA and pull request. Consumers pin the commit-addressed URLs; no npm +registry credential is needed or expected. The install commands and the runtime pairing rule are in [Installation](../start/installation.mdx). This page is the channel itself: where previews come @@ -16,15 +15,16 @@ from, how far you can trust them, and what pinning actually guarantees. ## Where previews come from -`.github/workflows/package-preview.yml` runs `pnpm preview:publish` after a full build, on every -pull request and on every push to `main`. It publishes `packages/agent-bundle`, +`.github/workflows/package-preview.yml` runs `pnpm preview:publish` after a full build. Pull +requests invoke it directly; `.github/workflows/release.yml` reuses it for every push to `main`. +It publishes `packages/agent-bundle`, `packages/rsc-runtime`, `packages/rsc-markdown-stream` (the Markdown renderer `@agent-bundle/runtime` depends on), and `packages/create-agent-bundle` with `--previewVersion --peerDeps --no-compact --no-template`. -Runs are grouped per PR and per branch with `cancel-in-progress`, so a newer push cancels the -superseded build: only the latest preview of a PR or of `main` matters. A `main` commit that was -overtaken before its preview published has no installable snapshot; pin the tip instead. +Pull request runs are grouped by PR with `cancel-in-progress`, so a newer push cancels the +superseded PR build. On `main`, the serialized `Release packages` workflow waits for the reusable +preview job and does not cancel an earlier release run. The "Publish pkg.pr.new preview" check on a PR or commit links to the exact URLs for that build. Previews are built from the same `pnpm build` output the release gates verify — but they are not @@ -59,28 +59,18 @@ npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@@` URLs. The release job depends on +that proof and qualifies the exact same merge SHA, so green means the packed gates passed and every +commit-addressed package is installable. + +`pnpm check:release` runs `pnpm pack:dry-run`, `pnpm lint:release`, and +`pnpm test:packed:release`. `lint:release` runs `attw` with the `esm-only` profile on the packed `agent-bundle`, `@agent-bundle/runtime`, `rsc-markdown-stream`, and `create-agent-bundle` tarballs, then `scripts/check-declaration-imports.mjs` over the same inventories: every `.d.ts` a consumer can diff --git a/website/docs/en/guide/start/installation.mdx b/website/docs/en/guide/start/installation.mdx index f96c79c27..199fd6553 100644 --- a/website/docs/en/guide/start/installation.mdx +++ b/website/docs/en/guide/start/installation.mdx @@ -1,5 +1,5 @@ --- -description: 'Install agent-bundle from the pkg.pr.new preview channel, and what changes once the first npm release exists.' +description: 'Install agent-bundle from pkg.pr.new and pin compiler, runtime, and scaffolder packages to one commit.' --- # Installation @@ -15,10 +15,8 @@ in the artifact manifest. ## Install from the preview channel -Nothing is published to npm yet. This is deliberate: the current package names are -placeholders, and the `agent-bundle` name on npm currently belongs to an unrelated project. -Until the first release, install the preview tarballs CI publishes for every pull request and -for the tip of `main`: +Packages are distributed through pkg.pr.new, not the npm registry. Install the preview tarballs +CI publishes for every pull request and for the tip of `main`: ```sh npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ @@ -44,10 +42,9 @@ preview version of the runtime built from the same commit, so a matched pair ins stock npm. Mixing two different SHAs fails with `ERESOLVE` by design — the range to inspect is the one in the installed `agent-bundle` package. -A preview scaffolder has no npm release-pair record. For a runtime template, an explicit -`--framework-version` must therefore be an exact pkg.pr.new compiler URL or the local -`agent-bundle.tgz` beside `agent-bundle-runtime.tgz`. Versioned registry or local tarball -overrides require the matching released scaffolder. +A preview scaffolder derives its compiler/runtime pair from the shared commit SHA. For a runtime +template, an explicit `--framework-version` must therefore be an exact pkg.pr.new compiler URL or +the local `agent-bundle.tgz` beside `agent-bundle-runtime.tgz`. pnpm and yarn accept the same URLs (`pnpm add `, `yarn add agent-bundle@`). @@ -82,26 +79,18 @@ compatible minors. Existing projects still pinned to `zod@4.4.3`, or any version `4.5.4`, must upgrade that direct dependency before installing this runtime — npm otherwise rejects the required peer with `ERESOLVE`. -## Once npm releases exist +## Pin a Version Packages commit -The commands below are the **future** installation path. They do not work yet, because no npm -release has been cut: +A Version Packages merge bumps repository versions and publishes the resulting commit through +pkg.pr.new. Consumers still pin the merge SHA: ```sh -npm install --save-dev agent-bundle -npm create agent-bundle@latest my-plugin +npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin ``` -The packed `create-agent-bundle` release records its compatible `agent-bundle` and -`@agent-bundle/runtime` versions as optional peers. `npm create` pins those recorded versions -independently instead of copying the compiler version onto the runtime package. For example, the -compiler may be `0.2.0` while its runtime is `0.1.0`. A runtime-bearing template rejects a -`--framework-version` outside that release pair; install the matching scaffolder release instead. -Local compiler tarballs use the recorded runtime version in the sibling tarball name and verify -both packed manifests before writing the project. - -The first npm release will use npm package provenance. Until the release owner picks the final -package name and license, pkg.pr.new previews are the release channel. +Use the same SHA for `@agent-bundle/runtime`. The preview scaffolder derives that same-SHA compiler +and runtime pair automatically. No npm registry credential is needed. ## Verify the install diff --git a/website/docs/zh/contributing/index.mdx b/website/docs/zh/contributing/index.mdx index 8be7fdacf..4e8804fa9 100644 --- a/website/docs/zh/contributing/index.mdx +++ b/website/docs/zh/contributing/index.mdx @@ -64,15 +64,14 @@ pnpm changeset ``` 选择受影响的包与版本级别,并从读者的角度描述这次改动——changeset 是发布说明的文案,不是提交信息。 -`pnpm version-packages` 应用待处理的 changeset,而发布会在 `changeset publish` 之前先运行 -`pnpm check:release`。私有工作区包,包括示例与本站点,不参与版本管理,也不打标签。 +`pnpm version-packages` 应用待处理的 changeset;合并 Version Packages 后,会先解析 pkg.pr.new +预览,再运行 `pnpm check:release`。私有工作区包,包括示例与本站点,不参与版本管理。 -目前还没有任何东西发布到 npm。当前的发布通道是 pkg.pr.new 预览 tarball,见 +包分发通道是 pkg.pr.new 预览 tarball,见 [预览包](../guide/distribution/preview-packages.mdx)。 每次 `Release packages` 运行都会在工作流摘要中写出明确结果:`version-maintenance-only` -(刷新了 Version Packages PR)、`published`(每个可发布包的 `package.json` 版本都能在 npm 上解析; -阶段列表会说明是本次运行发布的,还是版本此前已在注册表上)或 `failed`。若合并 Version Packages 后版本不在 npm 上——未启用发布、缺少 token,或 `changeset publish` -静默失败——结果为 `failed` 且写明 **NOT PUBLISHED**,绝不会是绿色的资格认定。 +(刷新了 Version Packages PR)、`preview-release`(merge commit 通过发布门禁,且全部四个按 commit +固定的 pkg.pr.new URL 都可解析)或 `failed`。 ## 原生宿主 smoke 需要显式开启 @@ -90,8 +89,9 @@ pnpm test:host-install:session:claude 而不是报告一个它没有挣到的通过。这条边界正是[测试](../guide/development/testing.mdx)中证明级别的全部 意义所在:一个级别绝不会被当作更强级别的收据来报告。 -依赖审查、包预览与发布工作流出于结构性原因只在托管侧运行——第一项要针对 pull request 的 diff 查询 -GitHub 的安全公告数据库,另外两项是发布的副作用,而不是检查。 +依赖审查、包预览与 Release packages 工作流出于结构性原因只在托管侧运行——第一项要针对 pull +request 的 diff 查询 GitHub 的安全公告数据库,另外两项会发布到 pkg.pr.new;Release packages 还会 +验证远程 commit URL。 ## 开发 Workbench 界面 diff --git a/website/docs/zh/guide/distribution/preview-packages.mdx b/website/docs/zh/guide/distribution/preview-packages.mdx index 6f9395b8a..32ffccabd 100644 --- a/website/docs/zh/guide/distribution/preview-packages.mdx +++ b/website/docs/zh/guide/distribution/preview-packages.mdx @@ -1,26 +1,27 @@ --- -description: '代替 npm 的 pkg.pr.new 预览通道:预览包如何发布、如何固定版本,以及首个 npm 版本发布后会发生什么变化。' +description: 'pkg.pr.new 包通道:预览包如何发布、验证,以及如何按 commit SHA 固定版本。' --- # 预览包 -目前尚未向 npm 发布任何内容,这是刻意为之。现有的包名只是占位符,npm 发布被推迟到最终包名确定之后。 -在那之前,**pkg.pr.new 就是发布通道**:每次 CI 的 package-preview 运行都会把全部四个可发布的工作区包 -以真实、可安装的 tarball 形式发布到一个按 commit SHA 与 pull request 索引的免费持续发布注册表。 +**pkg.pr.new 是包分发通道。** 每次 CI 的 package-preview 运行都会把全部四个可发布的工作区包 +以真实、可安装的 tarball 形式发布到一个按 commit SHA 与 pull request 索引的免费持续发布注册表。消费者按 +commit URL 固定版本;不需要也不应配置 npm 注册表凭据。 安装命令与 runtime 配对规则在[安装](../start/installation.mdx)中。本页讲的是通道本身:预览包从哪里来、 你能信任它到什么程度,以及固定版本究竟保证了什么。 ## 预览包从哪里来 -`.github/workflows/package-preview.yml` 在一次完整构建之后运行 `pnpm preview:publish`,覆盖每个 -pull request 与每次推送到 `main`。它以 `--previewVersion --peerDeps --no-compact --no-template` -发布 `packages/agent-bundle`、`packages/rsc-runtime`、`packages/rsc-markdown-stream`(`@agent-bundle/runtime` -所依赖的 Markdown 渲染器)与 `packages/create-agent-bundle`。 +`.github/workflows/package-preview.yml` 在一次完整构建之后运行 `pnpm preview:publish`。pull request +直接调用它;`.github/workflows/release.yml` 在每次推送到 `main` 时复用它。它以 +`--previewVersion --peerDeps --no-compact --no-template` 发布 `packages/agent-bundle`、 +`packages/rsc-runtime`、`packages/rsc-markdown-stream`(`@agent-bundle/runtime` 所依赖的 Markdown +渲染器)与 `packages/create-agent-bundle`。 -运行按 PR 和按分支分组并启用 `cancel-in-progress`,因此更新的推送会取消被取代的构建:只有 PR 或 `main` -的最新一次预览才有意义。若某个 `main` 提交在其预览发布之前就被后续提交取代,则它没有可安装的快照; -请改为固定到最新提交。 +pull request 运行按 PR 分组并启用 `cancel-in-progress`,因此更新的推送会取消被取代的 PR 构建。 +在 `main` 上,串行的 `Release packages` 工作流会等待可复用的 preview job,且不会取消较早的 release +运行。 PR 或提交上的「Publish pkg.pr.new preview」检查会链接到该次构建的确切 URL。预览包由发布门禁所校验的 同一份 `pnpm build` 输出构建而来——但它们不是 npm 正式版本,并且携带预览版本号。 @@ -50,24 +51,15 @@ npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@@` URL。release job 依赖这项证明, +并对完全相同的 merge SHA 执行资格认定,所以绿色表示打包门禁已经通过,且每个按 commit 固定的包都可安装。 -在启用这条路径之前,发布负责人必须解决两件事:最终的包名与许可证,以及 `agent-bundle` 仓库层面的 -`"access": "restricted"` 策略——目前它并没有被 `publishConfig.access` 覆盖。 - -`Release packages` 工作流会记录 `version-maintenance-only`、`published` 或 `failed` 之一, -并列出已执行与已跳过的阶段。资格认定针对精确的版本化候选 -SHA,而不是预版本的 main 提交。成功刷新 Version Packages PR,或既没有刷新该 PR 也没有对版本化候选做资格认定的 -`main` 推送,结果都是 `version-maintenance-only`。合并 Version Packages 之后,以及启用发布期间的每次 `main` -推送,都必须以每个可发布包的 `package.json` 版本在 npm 上可解析结束(`scripts/verify-registry-versions.sh`); -否则该次运行为 `failed` 并写明 **NOT PUBLISHED**。 - -`pnpm release` 会在发布之前运行发布门禁 —— `pnpm pack:dry-run`、`pnpm lint:release` 与 +`pnpm check:release` 会运行 `pnpm pack:dry-run`、`pnpm lint:release` 与 `pnpm test:packed:release`。`lint:release` 先对打包后的 `agent-bundle`、`@agent-bundle/runtime`、`rsc-markdown-stream` 与 `create-agent-bundle` tarball 以 `esm-only` profile 运行 `attw`,再用 `scripts/check-declaration-imports.mjs` 检查同一份文件清单:消费者能从 `exports` 触达的每个 `.d.ts` diff --git a/website/docs/zh/guide/start/installation.mdx b/website/docs/zh/guide/start/installation.mdx index 93f35abd6..f8f2a286b 100644 --- a/website/docs/zh/guide/start/installation.mdx +++ b/website/docs/zh/guide/start/installation.mdx @@ -1,5 +1,5 @@ --- -description: '从 pkg.pr.new 预览通道安装 agent-bundle,以及首个 npm 版本发布后会发生什么变化。' +description: '从 pkg.pr.new 安装 agent-bundle,并将 compiler、runtime 与 scaffolder 固定到同一个 commit。' --- # 安装 @@ -14,8 +14,7 @@ description: '从 pkg.pr.new 预览通道安装 agent-bundle,以及首个 npm ## 从预览通道安装 -目前尚未向 npm 发布任何内容。这是刻意为之:现有的包名只是占位符,而 npm 上的 `agent-bundle` 这个名字 -当前属于一个无关项目。在首个正式版本发布之前,请安装 CI 为每个 pull request 以及 `main` 最新提交发布的预览包: +包通过 pkg.pr.new 分发,而不是 npm 注册表。请安装 CI 为每个 pull request 以及 `main` 最新提交发布的预览包: ```sh npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ @@ -39,9 +38,9 @@ npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@5685 内部的这个 peer 范围改写为同一提交构建出的 runtime 精确预览版本,因此配对安装用原生 npm 即可完成。 混用两个不同的 SHA 会按设计以 `ERESOLVE` 失败——需要检查的范围位于已安装的 `agent-bundle` 包中。 -预览脚手架没有 npm 发布配对记录。因此,对需要 runtime 的模板显式传入 `--framework-version` 时,必须使用 -精确的 pkg.pr.new compiler URL,或使用相邻的本地 `agent-bundle.tgz` 与 -`agent-bundle-runtime.tgz`。带版本号的 registry 或本地 tarball 覆盖需要匹配的正式发布脚手架。 +预览脚手架从共享 commit SHA 推导 compiler/runtime 配对。因此,对需要 runtime 的模板显式传入 +`--framework-version` 时,必须使用精确的 pkg.pr.new compiler URL,或使用相邻的本地 +`agent-bundle.tgz` 与 `agent-bundle-runtime.tgz`。 pnpm 与 yarn 接受同样的 URL(`pnpm add `、`yarn add agent-bundle@`)。 @@ -73,23 +72,17 @@ to type '5'`)。同一 minor 的两份安装仍能通过类型检查;peer 的版本的现有项目,必须先升级该依赖再安装此 runtime,否则 npm 会以 `ERESOLVE` 拒绝所需的 peer。 -## 等到 npm 正式发布之后 +## 固定 Version Packages commit -下面这些命令是**未来**的安装路径。它们现在还不可用,因为尚未发布任何 npm 版本: +合并 Version Packages 会提升仓库内版本,并通过 pkg.pr.new 发布结果 commit。消费者仍然固定 merge SHA: ```sh -npm install --save-dev agent-bundle -npm create agent-bundle@latest my-plugin +npm i -D https://pkg.pr.new/ScriptedAlchemy/agent-bundle/agent-bundle@ +npx https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@ my-plugin ``` -打包后的 `create-agent-bundle` 版本会把兼容的 `agent-bundle` 与 `@agent-bundle/runtime` 版本记录为可选 -peer。`npm create` 会分别固定这两个记录值,而不是把 compiler 版本复制给 runtime 包。例如 compiler -可以是 `0.2.0`,而对应 runtime 是 `0.1.0`。带 runtime 的模板会拒绝超出该发布配对的 -`--framework-version`;此时应安装与 compiler 匹配的脚手架版本。本地 compiler tarball 会在相邻 -runtime tarball 的文件名中使用记录的 runtime 版本,并在写入项目之前校验两个包的清单。 - -首个 npm 版本将使用 npm package provenance。在发布负责人确定最终包名与许可证之前,pkg.pr.new 预览 -就是发布通道。 +`@agent-bundle/runtime` 使用相同的 SHA。预览 scaffolder 会自动推导同 SHA 的 compiler 与 runtime +配对。不需要 npm 注册表凭据。 ## 验证安装