Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 102 additions & 37 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
name: Publish

on:
release:
workflow_run:
workflows:
- Release Please
types:
- published
- completed

permissions:
actions: read
contents: read

concurrency:
Expand All @@ -15,72 +18,107 @@ concurrency:
jobs:
verify:
name: Verify the immutable release artifact
if: github.event_name == 'release'
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
dist-tag: ${{ steps.version.outputs.dist-tag }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Check out the published release tag
- name: Check out the current main branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
ref: refs/tags/${{ github.event.release.tag_name }}
- name: Reject an untrusted release target
ref: refs/heads/main
- name: Download the exact Release Please result
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-please-result-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
path: release-please-result
github-token: ${{ github.token }}
repository: ${{ github.repository }}
run-id: ${{ github.event.workflow_run.id }}
- name: Reject an untrusted Release Please workflow run
id: trust
env:
EXPECTED_BUGS_URL: https://github.com/cometapi-dev/cometapi-node/issues
EXPECTED_REPOSITORY: cometapi-dev/cometapi-node
EXPECTED_REPOSITORY_URL: git+https://github.com/cometapi-dev/cometapi-node.git
RELEASE_IMMUTABLE: ${{ github.event.release.immutable }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
EXPECTED_WORKFLOW: Release Please
EXPECTED_WORKFLOW_PATH: .github/workflows/release-please.yml
RELEASE_RESULT: release-please-result/result.json
WORKFLOW_SHA: ${{ github.event.workflow_run.head_sha }}
shell: bash
run: |
set -euo pipefail
if [[ "$GITHUB_REPOSITORY" != "$EXPECTED_REPOSITORY" ]]; then
echo "Publication is restricted to $EXPECTED_REPOSITORY; received $GITHUB_REPOSITORY." >&2
exit 1
fi
if [[ "$RELEASE_IMMUTABLE" != "true" ]]; then
echo "Publication requires a GitHub release with immutable=true." >&2
exit 1
fi

release_ref="refs/tags/${RELEASE_TAG}"
release_commit="$(git rev-parse --verify "${release_ref}^{commit}")"
head_commit="$(git rev-parse HEAD)"
if [[ "$head_commit" != "$release_commit" ]]; then
echo "Checked-out commit $head_commit does not match $release_ref ($release_commit)." >&2
if [[ "$head_commit" != "$WORKFLOW_SHA" ]]; then
echo "The successful Release Please SHA is no longer the exact main tip." >&2
exit 1
fi

git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
if ! git merge-base --is-ancestor "$release_commit" refs/remotes/origin/main; then
echo "Release commit $release_commit is not reachable from origin/main." >&2
if [[ "$(git rev-parse refs/remotes/origin/main)" != "$WORKFLOW_SHA" ]]; then
echo "origin/main moved after the successful Release Please run." >&2
exit 1
fi

node <<'EOF'
const manifest = require("./package.json");
const expectedRepository = process.env.EXPECTED_REPOSITORY_URL;
const expectedBugs = process.env.EXPECTED_BUGS_URL;
node --input-type=module <<'EOF'
import { appendFileSync, readFileSync } from "node:fs";
import {
validateReleasePleaseActionResult,
validateReleaseWorkflowRun,
} from "./scripts/release-workflow-validation.mjs";

const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
const run = validateReleaseWorkflowRun(event, {
checkedOutSha: process.env.WORKFLOW_SHA,
repository: process.env.EXPECTED_REPOSITORY,
workflowName: process.env.EXPECTED_WORKFLOW,
workflowPath: process.env.EXPECTED_WORKFLOW_PATH,
});
const manifest = JSON.parse(readFileSync("package.json", "utf8"));
if (
manifest.repository?.type !== "git" ||
manifest.repository?.url !== expectedRepository
manifest.repository?.url !== process.env.EXPECTED_REPOSITORY_URL
) {
throw new Error(
`package.json repository must equal ${expectedRepository}.`,
`package.json repository must equal ${process.env.EXPECTED_REPOSITORY_URL}.`,
);
}
if (manifest.bugs?.url !== expectedBugs) {
throw new Error(`package.json bugs.url must equal ${expectedBugs}.`);
if (manifest.bugs?.url !== process.env.EXPECTED_BUGS_URL) {
throw new Error(`package.json bugs.url must equal ${process.env.EXPECTED_BUGS_URL}.`);
}
const actionResult = JSON.parse(
readFileSync(process.env.RELEASE_RESULT, "utf8"),
);
const release = validateReleasePleaseActionResult(actionResult, {
releaseCommit: run.releaseCommit,
repository: process.env.EXPECTED_REPOSITORY,
runAttempt: run.runAttempt,
runId: run.runId,
version: manifest.version,
workflowName: process.env.EXPECTED_WORKFLOW,
workflowPath: process.env.EXPECTED_WORKFLOW_PATH,
});
appendFileSync(
process.env.GITHUB_OUTPUT,
[
`release-commit=${release.releaseCommit}`,
`release-tag=${release.tag}`,
`release-url=${release.htmlUrl}`,
`release-version=${release.version}`,
"",
].join("\n"),
);
EOF

echo "release-commit=${release_commit}" >> "$GITHUB_OUTPUT"
- name: Set up Node.js 24
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand All @@ -91,16 +129,43 @@ jobs:
- name: Verify release metadata and derive the npm dist-tag
id: version
env:
RELEASE_IS_PRERELEASE: ${{ github.event.release.prerelease }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_TAG: ${{ steps.trust.outputs.release-tag }}
shell: bash
run: |
set -euo pipefail
node scripts/validate-release.mjs \
--tag "$RELEASE_TAG" \
--release-prerelease "$RELEASE_IS_PRERELEASE" \
--require-final \
--require-releasable-docs >> "$GITHUB_OUTPUT"
- name: Verify the exact immutable GitHub release and tag
id: release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_COMMIT: ${{ steps.trust.outputs.release-commit }}
RELEASE_HTML_URL: ${{ steps.trust.outputs.release-url }}
RELEASE_TAG: ${{ steps.trust.outputs.release-tag }}
shell: bash
run: |
set -euo pipefail
release_json="$RUNNER_TEMP/github-release.json"
gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" > "$release_json"
git fetch --no-tags origin \
"+refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
tag_commit="$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}")"

RELEASE_JSON="$release_json" TAG_COMMIT="$tag_commit" \
node --input-type=module <<'EOF'
import { readFileSync } from "node:fs";
import { validateGitHubRelease } from "./scripts/release-workflow-validation.mjs";

const release = JSON.parse(readFileSync(process.env.RELEASE_JSON, "utf8"));
validateGitHubRelease(release, {
htmlUrl: process.env.RELEASE_HTML_URL,
releaseCommit: process.env.RELEASE_COMMIT,
tag: process.env.RELEASE_TAG,
tagCommit: process.env.TAG_COMMIT,
});
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Install locked dependencies
Expand Down Expand Up @@ -134,7 +199,7 @@ jobs:
run: |
npm run test:package -- \
--tarball "${{ steps.pack.outputs.tarball }}" \
--tag "${{ github.event.release.tag_name }}"
--tag "${{ steps.trust.outputs.release-tag }}"
npm run test:examples -- --tarball "${{ steps.pack.outputs.tarball }}"
npm run test:fixtures -- --tarball "${{ steps.pack.outputs.tarball }}"
- name: Upload the verified release artifact
Expand Down
Loading