chore: drop the demo recording tapes #32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Publish `@orcarouter/code-review` to npm when its version is not on the registry yet. | |
| # | |
| # The trigger is every push to main, but the decision is "is this exact version | |
| # already published?" — not "did package.json change in this commit?". Diffing | |
| # against the previous commit looks equivalent and is not: a squash merge, a | |
| # force push, or a revert all make "the previous commit" the wrong thing to | |
| # compare against, and the failure is silent in both directions (a missed | |
| # publish, or a publish attempt that dies on E409). Asking the registry is one | |
| # HTTP call and has no such states. | |
| # | |
| # So: bump `version` in package.json when you want a release. Every other | |
| # commit no-ops with a green check. | |
| # | |
| # Publishing is irreversible in practice — npm's unpublish window is 72 hours | |
| # and a withdrawn version number can never be reused. Everything that can be | |
| # checked before the point of no return is checked below, and the job fails | |
| # closed if any of it is off. | |
| # | |
| # NOTE: this workflow deliberately creates no git tags. The npm package version | |
| # and the action's own `v1.x` release tags are SEPARATE version lines (the | |
| # action is on v1.4.x while the CLI starts at 1.0.0). Tagging `v1.0.0` here | |
| # would collide with the action's tag series and could move consumers' | |
| # `uses: ...@v1` onto the wrong commit. Action tags stay manual — see RELEASE.md. | |
| name: Publish | |
| concurrency: | |
| group: publish-npm | |
| cancel-in-progress: false # never abort a run that may be mid-publish | |
| on: | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write # provenance attestation; not used for auth | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # the v1 tag check below needs the tag objects | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| registry-url: "https://registry.npmjs.org" | |
| # ---- Is there anything to do? ------------------------------------ | |
| # | |
| # `npm view <pkg>@<version>` prints the version when it exists, exits | |
| # non-zero (E404) when the package or version does not. Both are normal | |
| # answers here, so the failure is swallowed and only the output is read. | |
| - id: check | |
| name: Check whether this version is already published | |
| run: | | |
| set -uo pipefail | |
| NAME=$(node -p "require('./package.json').name") | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "name=$NAME" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| PUBLISHED=$(npm view "$NAME@$VERSION" version 2>/dev/null || true) | |
| if [ -n "$PUBLISHED" ]; then | |
| echo "needed=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::$NAME@$VERSION is already on npm — nothing to publish." | |
| else | |
| echo "needed=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::$NAME@$VERSION is not on npm — will publish." | |
| fi | |
| # ---- Everything below runs only for a real release --------------- | |
| - name: Verify the three versions are in sync | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -euo pipefail | |
| PKG=$(node -p "require('./package.json').version") | |
| PLUGIN=$(node -p "require('./.claude-plugin/plugin.json').version") | |
| MARKET=$(node -p "require('./.claude-plugin/marketplace.json').metadata.version") | |
| echo "package.json=$PKG plugin.json=$PLUGIN marketplace.json=$MARKET" | |
| if [ "$PKG" != "$PLUGIN" ] || [ "$PKG" != "$MARKET" ]; then | |
| echo "::error::Version mismatch. package.json, .claude-plugin/plugin.json and" \ | |
| ".claude-plugin/marketplace.json must move together (see RELEASE.md)." | |
| exit 1 | |
| fi | |
| # The installers generate a workflow pinned to `@v1`. If that tag predates | |
| # scripts/settings.mjs and scripts/report.mjs, every new user gets working | |
| # reviews with a silently inert Settings tab and empty Analytics — no | |
| # error, just two dead console tabs. Refuse to ship an installer into that. | |
| - name: Verify the v1 tag can serve what the installer generates | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -euo pipefail | |
| if ! git rev-parse -q --verify refs/tags/v1 >/dev/null; then | |
| echo "::error::No v1 tag. The generated workflow pins @v1 — see RELEASE.md." | |
| exit 1 | |
| fi | |
| for f in scripts/settings.mjs scripts/report.mjs; do | |
| if ! git cat-file -e "v1:$f" 2>/dev/null; then | |
| echo "::error::v1 is missing $f. Move the v1 tag before publishing (RELEASE.md)." | |
| exit 1 | |
| fi | |
| done | |
| echo "v1 = $(git rev-parse --short v1), ships settings.mjs and report.mjs" | |
| - name: Test | |
| if: steps.check.outputs.needed == 'true' | |
| run: node --test scripts/*.test.mjs | |
| # A tarball that omits bin/ or skills/ still publishes cleanly and then | |
| # fails on the user's first `npx`. Assert the payload before shipping it. | |
| - name: Verify the tarball carries the CLI and the skill | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -euo pipefail | |
| npm pack --dry-run --json > /tmp/pack.json | |
| node -e ' | |
| const files = require("/tmp/pack.json")[0].files.map(f => f.path); | |
| const required = [ | |
| "bin/orcacode-review.mjs", | |
| "bin/platforms.mjs", | |
| "bin/i18n.mjs", | |
| "bin/harness.mjs", | |
| "bin/review.mjs", | |
| "bin/selection.mjs", | |
| "bin/localconfig.mjs", | |
| "scripts/postfilter.mjs", | |
| "scripts/severity.mjs", | |
| "rules/severity-instruction.md", | |
| "rules/output-shape.md", | |
| "vendor/open-code-review/LICENSE", | |
| "vendor/open-code-review/system_rules.json", | |
| "vendor/open-code-review/rule_docs/default.md", | |
| "skills/orca-review-action/SKILL.md", | |
| "skills/orca-review/SKILL.md", | |
| "skills/orca-review/references/contract.md", | |
| ]; | |
| const missing = required.filter(f => !files.includes(f)); | |
| if (missing.length) { | |
| console.error("::error::tarball is missing: " + missing.join(", ")); | |
| process.exit(1); | |
| } | |
| console.log("tarball ok — " + files.length + " files"); | |
| ' | |
| # Install the real tarball and run it through npm's real `.bin` shim. | |
| # | |
| # This exists because 1.0.0 shipped a CLI that did nothing. npm installs a | |
| # `bin` as a symlink, so argv[1] is the link while import.meta.url is its | |
| # target; the entry-point guard compared them without realpath and was | |
| # false for every npx and every global install. The process exited 0 | |
| # having printed nothing, and every check above passed. `node bin/...` | |
| # never takes that path, so only an actual install can catch its kind. | |
| - name: Smoke-test the packed tarball | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -euo pipefail | |
| TARBALL="$PWD/$(npm pack --json | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8"))[0].filename')" | |
| SMOKE=$(mktemp -d) | |
| cd "$SMOKE" | |
| npm init -y >/dev/null 2>&1 | |
| npm install --no-audit --no-fund --silent "$TARBALL" | |
| SHIM=./node_modules/.bin/orcacode-review | |
| test -L "$SHIM" || echo "::warning::$SHIM is not a symlink — this platform cannot exercise that path" | |
| PRINTED=$("$SHIM" --version) | |
| echo "shim printed: '$PRINTED'" | |
| if [ "$PRINTED" != "${{ steps.check.outputs.version }}" ]; then | |
| echo "::error::the installed bin printed '$PRINTED', expected '${{ steps.check.outputs.version }}'." \ | |
| "An empty value means the entry-point guard rejected npm's symlink." | |
| exit 1 | |
| fi | |
| # A real subcommand, so the check covers more than argument parsing. | |
| "$SHIM" skill list --lang en | grep -q "claude" || { | |
| echo "::error::\`skill list\` did not list the platform catalog"; exit 1; } | |
| echo "tarball smoke test passed" | |
| - name: Publish | |
| if: steps.check.outputs.needed == 'true' | |
| run: npm publish --access public --provenance | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # `npm publish` exiting 0 does not mean anyone can install it. | |
| # | |
| # A scoped package defaults to RESTRICTED. When 1.0.2 first went out the | |
| # log read `+ @orcarouter/code-review@1.0.2` and the job was green, while | |
| # the registry 404'd and the package page 403'd for everyone outside the | |
| # org — despite both `--access public` and `publishConfig.access`. | |
| # | |
| # So: assert it, then prove it. `npm access set` is idempotent and fixes | |
| # the state; the anonymous fetch is what actually decides, because it asks | |
| # the question a user asks — can a stranger install this? | |
| - name: Force public access | |
| if: steps.check.outputs.needed == 'true' | |
| continue-on-error: true # the verification below is the real gate | |
| run: npm access set status=public "${{ steps.check.outputs.name }}" | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Verify the published version is installable by a stranger | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| set -uo pipefail | |
| NAME='${{ steps.check.outputs.name }}' | |
| VERSION='${{ steps.check.outputs.version }}' | |
| ENCODED=$(node -pe 'encodeURIComponent(process.argv[1])' "$NAME") | |
| # Fetch the PACKUMENT, not the per-version document. | |
| # | |
| # `npm install` reads the packument and picks a version out of it, so | |
| # that is the document whose freshness actually decides whether a | |
| # stranger can install. The /<name>/<version> route is a colder path: | |
| # it 404'd for more than three minutes after two correct publishes, | |
| # failing builds that had shipped fine. Checking the wrong document | |
| # was the bug, not the timeout. | |
| # | |
| # No auth header on purpose — an authenticated read succeeds against a | |
| # restricted package and would prove nothing. | |
| for attempt in $(seq 1 20); do | |
| BODY=$(curl -sS -H 'Cache-Control: no-cache' "https://registry.npmjs.org/$ENCODED" || echo '') | |
| FOUND=$(printf '%s' "$BODY" | node -pe ' | |
| try { | |
| const d = JSON.parse(require("fs").readFileSync(0, "utf8")); | |
| d.versions?.[process.argv[1]] ? "yes" : "no"; | |
| } catch { "no" } | |
| ' "$VERSION" 2>/dev/null || echo no) | |
| if [ "$FOUND" = "yes" ]; then | |
| echo "$NAME@$VERSION is in the public packument — installable" | |
| exit 0 | |
| fi | |
| echo "attempt $attempt/20: not in the public packument yet" | |
| sleep 15 | |
| done | |
| echo "::error::$NAME@$VERSION published but is absent from the public packument after 5 minutes." | |
| echo "::error::Either it is restricted — fix with: npm access set status=public $NAME" | |
| echo "::error::or the CDN is unusually slow. Check https://registry.npmjs.org/$ENCODED by hand." | |
| exit 1 | |
| - name: Summary | |
| if: steps.check.outputs.needed == 'true' | |
| run: | | |
| { | |
| echo "### Published \`${{ steps.check.outputs.name }}@${{ steps.check.outputs.version }}\`" | |
| echo | |
| echo '```' | |
| echo "npx ${{ steps.check.outputs.name }}@${{ steps.check.outputs.version }} skill list" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" |