mirror of
https://github.com/przeprogramowani/10x-cli.git
synced 2026-09-19 03:30:01 +08:00
43d6a69088
* fix(release): wait for npm metadata before verifying publish Direct publish-npm verified immediately after npm accepted 1.22.1, while registry metadata still lacked dist.tarball. Poll until integrity exists, then keep the strict pack/gitHead compare. When the version is already on npm and matches the pack from cli_sha, skip publish and only complete tag/Release. * chore(release): prepare v1.22.2 --------- Co-authored-by: Claude <noreply@anthropic.com>
147 lines
7.2 KiB
YAML
147 lines
7.2 KiB
YAML
name: Publish npm
|
|
|
|
# Direct publication of an exact, already-tested source SHA with the
|
|
# repository's existing NPM_TOKEN. No Toolkit receipt, lease or coordinator
|
|
# contract: those are handled outside this job. The package is packed and
|
|
# smoke-installed from the exact SHA first, publication happens once, and the
|
|
# actual registry bytes are verified afterwards (never republished).
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
cli_sha:
|
|
description: Exact full source SHA to publish (must be an ancestor of master)
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: publish-npm
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
publish:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Require an exact full SHA
|
|
run: node -e 'if (!/^[a-f0-9]{40}$/.test(process.env.CLI_SHA || "")) throw new Error("cli_sha must be a full SHA")'
|
|
env:
|
|
CLI_SHA: ${{ inputs.cli_sha }}
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
ref: ${{ inputs.cli_sha }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- name: Require the SHA to be on master
|
|
run: git merge-base --is-ancestor "${{ inputs.cli_sha }}" origin/master
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: "1.3.8"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
- run: npm install --global npm@11.12.1 --ignore-scripts
|
|
- run: bun install --frozen-lockfile
|
|
- run: bun run build
|
|
- name: Pack outside the checkout and smoke the pack in an isolated installation
|
|
run: |
|
|
set -eu
|
|
OUT="$RUNNER_TEMP/release-package"; mkdir -p "$OUT"
|
|
npm pack --json --ignore-scripts --pack-destination "$OUT" > "$OUT/pack.json"
|
|
node -e '
|
|
const [p] = JSON.parse(require("fs").readFileSync(process.env.OUT + "/pack.json", "utf8"));
|
|
if (p.name !== "@przeprogramowani/10x-cli") throw new Error("Unexpected package name");
|
|
require("fs").writeFileSync(process.env.OUT + "/candidate.json", JSON.stringify({ version: p.version, filename: p.filename, integrity: p.integrity, files: p.files.map((f) => f.path).sort() }));
|
|
console.log(p.version, p.integrity, p.files.map((f) => f.path).sort().join(","));
|
|
'
|
|
SMOKE="$RUNNER_TEMP/release-pack-smoke"; mkdir -p "$SMOKE"
|
|
FILE=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.OUT + "/candidate.json","utf8")).filename')
|
|
npm install --ignore-scripts --no-audit --no-fund --prefix "$SMOKE" "$OUT/$FILE"
|
|
node "$SMOKE/node_modules/@przeprogramowani/10x-cli/dist/index.mjs" --version
|
|
node "$SMOKE/node_modules/@przeprogramowani/10x-cli/dist/index.mjs" --help > /dev/null
|
|
echo "OUT=$OUT" >> "$GITHUB_ENV"
|
|
env:
|
|
OUT: ${{ runner.temp }}/release-package
|
|
- name: Decide whether to publish or resume a matching registry copy
|
|
id: decide
|
|
run: node scripts/publish-npm-verify.mjs decide
|
|
env:
|
|
OUT: ${{ runner.temp }}/release-package
|
|
CLI_SHA: ${{ inputs.cli_sha }}
|
|
- name: Publish the exact directory once
|
|
if: steps.decide.outputs.action == 'publish'
|
|
run: |
|
|
set -eu
|
|
VERSION=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.OUT + "/candidate.json","utf8")).version')
|
|
if npm view "@przeprogramowani/10x-cli@$VERSION" version >/dev/null 2>&1; then
|
|
echo "::error::@przeprogramowani/10x-cli@$VERSION already published; never republish"; exit 1
|
|
fi
|
|
node -e 'require("fs").writeFileSync(process.env.NPM_CONFIG_USERCONFIG, "registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=" + process.env.NPM_TOKEN + "\n", { mode: 0o600 })'
|
|
npm publish . --ignore-scripts --access public
|
|
env:
|
|
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/release-npmrc
|
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
- name: Verify actual registry bytes against the pack
|
|
run: node scripts/publish-npm-verify.mjs verify
|
|
env:
|
|
OUT: ${{ runner.temp }}/release-package
|
|
CLI_SHA: ${{ inputs.cli_sha }}
|
|
- name: Tag the published source
|
|
# The tag may already exist (pushed by hand, or a previous attempt).
|
|
# Peel it to its commit: an annotated tag's ref SHA is the tag object,
|
|
# not the commit, so comparing ref SHAs alone reports a false mismatch.
|
|
run: |
|
|
set -eu
|
|
VERSION=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.OUT + "/candidate.json","utf8")).version')
|
|
TAG="v${VERSION}"
|
|
git fetch --quiet origin "refs/tags/${TAG}:refs/tags/${TAG}" 2>/dev/null || true
|
|
EXISTING=$(git rev-parse --verify --quiet "refs/tags/${TAG}^{commit}" || true)
|
|
if [ -z "$EXISTING" ]; then
|
|
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/tags/${TAG}" -f sha="${CLI_SHA}" > /dev/null
|
|
echo "Created tag ${TAG} at ${CLI_SHA}"
|
|
elif [ "$EXISTING" != "${CLI_SHA}" ]; then
|
|
echo "::error::Tag ${TAG} exists at ${EXISTING}, not the published source ${CLI_SHA}"; exit 1
|
|
else
|
|
echo "Tag ${TAG} already points at ${CLI_SHA}"
|
|
fi
|
|
echo "TAG=${TAG}" >> "$GITHUB_ENV"
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CLI_SHA: ${{ inputs.cli_sha }}
|
|
- name: Create the GitHub Release with the verified tarball
|
|
# Idempotent: an existing release for the tag is left untouched.
|
|
run: |
|
|
set -eu
|
|
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
|
|
echo "Release ${TAG} already exists; not modified"; exit 0
|
|
fi
|
|
FILE=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.OUT + "/candidate.json","utf8")).filename')
|
|
INTEGRITY=$(node -p 'JSON.parse(require("fs").readFileSync(process.env.OUT + "/published-result.json","utf8")).registryIntegrity')
|
|
NOTES="$RUNNER_TEMP/release-notes.md"
|
|
{
|
|
echo "Published on npm from commit \`${CLI_SHA}\`: https://www.npmjs.com/package/@przeprogramowani/10x-cli/v/${TAG#v}"
|
|
echo
|
|
echo "Tarball integrity: \`${INTEGRITY}\` (the attached tarball is byte-identical to the registry artifact)."
|
|
echo
|
|
} > "$NOTES"
|
|
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --target "$CLI_SHA" \
|
|
--title "$TAG" --notes-file "$NOTES" --generate-notes \
|
|
"$OUT/$FILE#npm package tarball (${TAG#v})"
|
|
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json url,tagName,assets --jq '{url,tagName,assets:[.assets[].name]}'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CLI_SHA: ${{ inputs.cli_sha }}
|
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
|
with:
|
|
name: published-npm-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: |
|
|
${{ runner.temp }}/release-package/candidate.json
|
|
${{ runner.temp }}/release-package/published-result.json
|
|
if-no-files-found: error
|
|
retention-days: 90
|