Skip to content

Release

Release #57

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
type: choice
options:
- create
- rc
- finalize
version:
description: 'Version: X.Y.0 (minor/major) or X.Y.Z with Z>0 (hotfix/patch)'
required: true
type: string
auto_cherry_pick:
description: 'Hotfix create only: auto-cherry-pick fix:/chore: commits from origin/next (fallback origin/main) since base tag'
required: false
type: boolean
default: true
dry_run:
description: 'Dry run (skip npm publish, tagging, and push)'
required: false
type: boolean
default: false
concurrency:
group: release-${{ inputs.version }}
cancel-in-progress: false
env:
NODE_VERSION: 24
jobs:
validate-version:
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
outputs:
branch: ${{ steps.validate.outputs.branch }}
is_major: ${{ steps.validate.outputs.is_major }}
is_hotfix: ${{ steps.validate.outputs.is_hotfix }}
base_tag: ${{ steps.validate.outputs.base_tag }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Validate version format
id: validate
env:
VERSION: ${{ inputs.version }}
ACTION: ${{ inputs.action }}
run: |
set -euo pipefail
IS_HOTFIX="false"
IS_MAJOR="false"
BASE_TAG=""
if echo "$VERSION" | grep -qE '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.0$'; then
# Minor or major release (X.Y.0)
BRANCH="release/${VERSION}"
if echo "$VERSION" | grep -qE '^(0|[1-9][0-9]*)\.0\.0$'; then
IS_MAJOR="true"
fi
elif echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[1-9][0-9]*$'; then
# Patch / hotfix release (X.Y.Z, Z>0)
IS_HOTFIX="true"
if [ "$ACTION" = "rc" ]; then
echo "::error::Hotfix (patch) releases skip the rc action — run create, then finalize."
exit 1
fi
BRANCH="hotfix/${VERSION}"
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
TARGET_TAG="v${VERSION}"
# semver-correct base tag: highest vMAJOR_MINOR.* strictly below TARGET_TAG
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
| sort -V \
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
if [ -z "$BASE_TAG" ]; then
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
exit 1
fi
else
echo "::error::Version '$VERSION' is invalid. Use X.Y.0 (minor/major) or X.Y.Z with Z>0 (hotfix), no leading zeros."
exit 1
fi
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
echo "is_major=$IS_MAJOR" >> "$GITHUB_OUTPUT"
echo "is_hotfix=$IS_HOTFIX" >> "$GITHUB_OUTPUT"
echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT"
- name: Reject already-published versions
env:
VERSION: ${{ inputs.version }}
run: |
for pkg in @opengsd/gsd-core; do
if npm view "$pkg@$VERSION" version >/dev/null 2>&1; then
echo "::error::$pkg@$VERSION is already published on npm — bump to a new version"
exit 1
fi
done
create:
needs: validate-version
if: inputs.action == 'create'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Check branch doesn't already exist
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
run: |
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "::error::Branch $BRANCH already exists. Delete it first or use rc/finalize."
exit 1
fi
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Create release branch
if: needs.validate-version.outputs.is_hotfix != 'true'
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
VERSION: ${{ inputs.version }}
IS_MAJOR: ${{ needs.validate-version.outputs.is_major }}
run: |
git checkout -b "$BRANCH"
npm version "$VERSION" --no-git-tag-version
git add package.json package-lock.json
git commit -m "chore: bump version to ${VERSION} for release"
git push origin "$BRANCH"
echo "## Release branch created" >> "$GITHUB_STEP_SUMMARY"
echo "- Branch: \`$BRANCH\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Version: \`$VERSION\`" >> "$GITHUB_STEP_SUMMARY"
if [ "$IS_MAJOR" = "true" ]; then
echo "- Type: **Major** (will start with beta pre-releases)" >> "$GITHUB_STEP_SUMMARY"
else
echo "- Type: **Minor** (will start with RC pre-releases)" >> "$GITHUB_STEP_SUMMARY"
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Next: run this workflow with \`rc\` action to publish a pre-release to \`next\`" >> "$GITHUB_STEP_SUMMARY"
- name: Create hotfix branch from base tag (skeleton)
if: needs.validate-version.outputs.is_hotfix == 'true'
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
git checkout -b "$BRANCH" "$BASE_TAG"
# Push the skeleton up-front so a later cherry-pick conflict leaves a
# remote artefact the operator can fetch, resolve, and re-push.
if [ "$DRY_RUN" != "true" ]; then
git push -u origin "$BRANCH"
fi
- name: Cherry-pick fix/chore commits from origin/next since base tag
if: ${{ needs.validate-version.outputs.is_hotfix == 'true' && inputs.auto_cherry_pick }}
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
# Under the next-branch model, day-to-day fixes land on `next` first
# and only reach `main` via release back-merge. So `next` is the
# canonical cherry-pick source. Fall back to `main` if `next` doesn't
# exist yet (legacy single-branch repos) or as a transition guard.
if git ls-remote --exit-code origin next >/dev/null 2>&1; then
git fetch origin next:refs/remotes/origin/next
SOURCE="origin/next"
else
git fetch origin main:refs/remotes/origin/main
SOURCE="origin/main"
fi
echo "Cherry-pick source: $SOURCE"
# `git cherry $BASE_TAG $SOURCE` lists every commit on the source not
# patch-equivalent in BASE_TAG. + means needs picking, - means
# already applied (skipped silently).
CANDIDATES=$(git cherry "$BASE_TAG" "$SOURCE" | awk '/^\+ / {print $2}')
if [ -z "$CANDIDATES" ]; then
echo "No commits on $SOURCE beyond $BASE_TAG."
echo "## Cherry-pick summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Base: \`$BASE_TAG\` (source: \`$SOURCE\`) — no commits to consider." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
# Re-order chronologically (oldest first) for predictable application.
ORDERED=$(git log --reverse --format='%H' "$BASE_TAG..$SOURCE" \
| grep -F -f <(echo "$CANDIDATES") || true)
INCLUDED=""
SKIPPED=""
while IFS= read -r SHA; do
[ -z "$SHA" ] && continue
SUBJECT=$(git log -1 --format='%s' "$SHA")
# fix: or chore:, optional scope, optional ! breaking marker
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
echo "→ cherry-picking $SHA $SUBJECT"
if ! git cherry-pick -x "$SHA"; then
# Abort restores HEAD to the last successful pick. On real
# runs, push that state so the operator can fetch, resolve
# $SHA manually, and finalize with auto_cherry_pick=false.
git cherry-pick --abort || true
if [ "$DRY_RUN" != "true" ]; then
git push --force-with-lease origin "$BRANCH" || git push origin "$BRANCH" || true
fi
{
echo "## Cherry-pick conflict"
echo ""
echo "Failed at: \`${SHA}\` — \`${SUBJECT}\`"
echo ""
if [ "$DRY_RUN" = "true" ]; then
echo "**Dry run:** branch was not pushed, so the picks below were discarded with the runner."
if [ -n "$INCLUDED" ]; then
echo ""
echo "Already-applied picks (lost — must be re-applied before resolving \`${SHA}\`):"
echo ""
echo "$INCLUDED"
fi
echo ""
echo "**To resolve:** re-run \`create\` with \`auto_cherry_pick=true\` (real, not dry-run) to materialize the partial branch on origin, then resolve \`${SHA}\` manually. Re-running with \`auto_cherry_pick=false\` would recreate the branch from \`${BASE_TAG}\` and lose every pick listed above."
else
echo "Branch \`${BRANCH}\` was pushed with picks applied up to (but not including) the conflicting commit."
echo ""
echo "**To resolve:** \`git fetch origin && git checkout ${BRANCH} && git cherry-pick -x ${SHA}\`, fix the conflict, push, then re-run \`finalize\` with \`auto_cherry_pick=false\`."
fi
} >> "$GITHUB_STEP_SUMMARY"
echo "::error::Cherry-pick of $SHA failed. See summary."
exit 1
fi
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
else
echo " skip $SHA $SUBJECT (not fix/chore)"
SKIPPED="${SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
fi
done <<< "$ORDERED"
{
echo "## Cherry-pick summary"
echo ""
echo "Base: \`$BASE_TAG\`"
echo ""
if [ -n "$INCLUDED" ]; then
echo "### Included (fix/chore)"
echo ""
echo "$INCLUDED"
else
echo "_No fix/chore commits to include._"
echo ""
fi
if [ -n "$SKIPPED" ]; then
echo "### Skipped (feat/refactor/etc — not auto-included)"
echo ""
echo "$SKIPPED"
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Bump hotfix version and push
if: needs.validate-version.outputs.is_hotfix == 'true'
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
VERSION: ${{ inputs.version }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
npm version "$VERSION" --no-git-tag-version
git add package.json package-lock.json
git commit -m "chore: bump version to $VERSION for hotfix"
if [ "$DRY_RUN" != "true" ]; then
git push origin "$BRANCH"
else
echo "DRY RUN — branch not pushed."
fi
{
echo "## Hotfix branch created"
echo ""
echo "- Branch: \`$BRANCH\`"
echo "- Based on: \`$BASE_TAG\`"
echo "- Apply additional manual fixes if needed, then run \`finalize\`."
} >> "$GITHUB_STEP_SUMMARY"
install-smoke-rc:
needs: validate-version
if: inputs.action == 'rc'
permissions:
contents: read
uses: ./.github/workflows/install-smoke.yml
with:
ref: ${{ needs.validate-version.outputs.branch }}
rc:
needs: [validate-version, install-smoke-rc]
if: inputs.action == 'rc'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
id-token: write
environment: npm-publish
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.validate-version.outputs.branch }}
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
- name: Determine pre-release version
id: prerelease
env:
VERSION: ${{ inputs.version }}
IS_MAJOR: ${{ needs.validate-version.outputs.is_major }}
run: |
# Determine pre-release type: major → beta, minor → rc
if [ "$IS_MAJOR" = "true" ]; then
PREFIX="beta"
else
PREFIX="rc"
fi
# Find next pre-release number by checking existing tags
N=1
while git tag -l "v${VERSION}-${PREFIX}.${N}" | grep -q .; do
N=$((N + 1))
done
PRE_VERSION="${VERSION}-${PREFIX}.${N}"
echo "pre_version=$PRE_VERSION" >> "$GITHUB_OUTPUT"
echo "prefix=$PREFIX" >> "$GITHUB_OUTPUT"
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Bump to pre-release version
env:
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
run: |
npm version "$PRE_VERSION" --no-git-tag-version
- name: Install and test
run: |
npm ci
node scripts/check-npm-integrity.cjs
npm run test:coverage:unit
- name: Preview CHANGELOG (non-destructive)
env:
VERSION: ${{ inputs.version }}
run: |
# Non-destructive CHANGELOG preview for the version under test (#759):
# renders the curated section finalize will promote, without writing
# CHANGELOG.md or consuming .changeset fragments. Surfaced in the job
# summary so RC testers see the upcoming release notes.
#
# Render to a file as a standalone command so a non-zero exit (e.g. a
# malformed fragment) fails the step. The default GitHub Linux shell
# is `bash -e` without pipefail, so a `node | tee` pipeline would mask
# a node failure behind tee's exit 0.
PREVIEW_FILE="${RUNNER_TEMP:-/tmp}/changelog-preview.md"
node scripts/changeset/cli.cjs render \
--version "$VERSION" --date "$(date -u +%F)" --preview > "$PREVIEW_FILE"
{
echo "### CHANGELOG preview for v${VERSION} (not yet promoted)"
echo ''
cat "$PREVIEW_FILE"
} >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
cat "$PREVIEW_FILE"
- name: Commit pre-release version bump
env:
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
run: |
git add package.json package-lock.json
git commit -m "chore: bump to ${PRE_VERSION}"
# npm bundled with Node 24 (pinned via setup-node) already supports trusted publishing (#318)
- name: Dry-run publish validation
run: npm publish --dry-run --tag next
- name: Tag and push
if: ${{ !inputs.dry_run }}
env:
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
BRANCH: ${{ needs.validate-version.outputs.branch }}
run: |
if git rev-parse -q --verify "refs/tags/v${PRE_VERSION}" >/dev/null; then
EXISTING_SHA=$(git rev-parse "refs/tags/v${PRE_VERSION}")
HEAD_SHA=$(git rev-parse HEAD)
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
echo "::error::Tag v${PRE_VERSION} already exists pointing to different commit"
exit 1
fi
echo "Tag v${PRE_VERSION} already exists on current commit; skipping tag"
else
git tag "v${PRE_VERSION}"
fi
git push origin "$BRANCH" --tags
- name: Publish to npm (next)
if: ${{ !inputs.dry_run }}
# --provenance is automatic under OIDC trusted publishing
run: npm publish --provenance --access public --tag next
- name: Create GitHub pre-release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
run: |
gh release create "v${PRE_VERSION}" \
--title "v${PRE_VERSION}" \
--generate-notes \
--prerelease
# Reformat the auto-generated notes into the curated
# Install + Feature/Enhancement/Fix format.
node scripts/release-notes/format-github-release-notes.cjs \
--tag "v${PRE_VERSION}" --prerelease --apply
- name: Post Discord pre-release announcement
if: ${{ !inputs.dry_run }}
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
run: |
node scripts/release-notes/discord-release-summary.cjs \
--tag "v${PRE_VERSION}" \
--repo "$GITHUB_REPOSITORY" \
--post \
--allow-missing-webhook
- name: Verify publish
if: ${{ !inputs.dry_run }}
env:
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
run: node scripts/verify-npm-publish.cjs --package @opengsd/gsd-core --version "$PRE_VERSION" --dist-tag next
- name: Summary
env:
PRE_VERSION: ${{ steps.prerelease.outputs.pre_version }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
echo "## Pre-release v${PRE_VERSION}" >> "$GITHUB_STEP_SUMMARY"
if [ "$DRY_RUN" = "true" ]; then
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
else
echo "- Published to npm as \`next\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Install: \`npx @opengsd/gsd-core@next\`" >> "$GITHUB_STEP_SUMMARY"
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "To publish another pre-release: run \`rc\` again" >> "$GITHUB_STEP_SUMMARY"
echo "To finalize: run \`finalize\` action" >> "$GITHUB_STEP_SUMMARY"
install-smoke-finalize:
needs: validate-version
if: inputs.action == 'finalize'
permissions:
contents: read
uses: ./.github/workflows/install-smoke.yml
with:
ref: ${{ needs.validate-version.outputs.branch }}
finalize:
needs: [validate-version, install-smoke-finalize]
if: inputs.action == 'finalize'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
id-token: write
environment: npm-publish
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.validate-version.outputs.branch }}
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Set final version
env:
VERSION: ${{ inputs.version }}
run: |
npm version "$VERSION" --no-git-tag-version --allow-same-version
git add package.json package-lock.json
git diff --cached --quiet || git commit -m "chore: finalize v${VERSION}"
- name: Install and test
env:
NODE_OPTIONS: --max-old-space-size=6144
run: |
npm ci
node scripts/check-npm-integrity.cjs
npm run test:coverage:unit
- name: Promote CHANGELOG (render fragments)
env:
VERSION: ${{ inputs.version }}
run: |
node scripts/changeset/cli.cjs render \
--version "$VERSION" --date "$(date -u +%F)" --allow-empty
git add -A .changeset CHANGELOG.md
# Diff-preview guard: surface exactly what was promoted, in the log and
# the job summary, before the commit lands.
{
echo "### CHANGELOG promotion for v${VERSION}"
echo '```diff'
git diff --cached -- CHANGELOG.md
echo '```'
} >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
git --no-pager diff --cached -- CHANGELOG.md
git diff --cached --quiet || git commit -m "chore: promote CHANGELOG for v${VERSION}"
- name: Verify CHANGELOG promoted
env:
VERSION: ${{ inputs.version }}
run: |
node scripts/changeset/cli.cjs verify --version "$VERSION" --changelog CHANGELOG.md
# npm bundled with Node 24 (pinned via setup-node) already supports trusted publishing (#318)
- name: Dry-run publish validation
run: npm publish --dry-run
- name: Create PR to merge release back to main
if: ${{ !inputs.dry_run }}
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GSD_BOT_PR_TOKEN || secrets.GITHUB_TOKEN }}
BRANCH: ${{ needs.validate-version.outputs.branch }}
VERSION: ${{ inputs.version }}
run: |
# Non-fatal: repos that disable "Allow GitHub Actions to create and
# approve pull requests" cause this step to fail with GraphQL 403.
# The release itself (tag + npm publish + GitHub Release) must still
# proceed. Open the merge-back PR manually afterwards with:
# gh pr create --base main --head release/${VERSION} \
# --title "chore: merge release v${VERSION} to main"
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists; updating"
gh pr edit "$EXISTING_PR" \
--title "chore: merge release v${VERSION} to main" \
--body "Merge release branch back to main after v${VERSION} stable release." \
|| echo "::warning::Could not update merge-back PR (likely PR-creation policy disabled). Open it manually after release."
else
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: merge release v${VERSION} to main" \
--body "Merge release branch back to main after v${VERSION} stable release." \
|| echo "::warning::Could not create merge-back PR (likely PR-creation policy disabled). Open it manually after release."
fi
- name: Tag and push
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ inputs.version }}
BRANCH: ${{ needs.validate-version.outputs.branch }}
run: |
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
EXISTING_SHA=$(git rev-parse "refs/tags/v${VERSION}")
HEAD_SHA=$(git rev-parse HEAD)
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
echo "::error::Tag v${VERSION} already exists pointing to different commit"
exit 1
fi
echo "Tag v${VERSION} already exists on current commit; skipping tag"
else
git tag "v${VERSION}"
fi
git push origin "$BRANCH" --tags
- name: Publish to npm (latest)
if: ${{ !inputs.dry_run }}
# --provenance is automatic under OIDC trusted publishing
run: npm publish --provenance --access public
- name: Create GitHub Release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
gh release create "v${VERSION}" \
--title "v${VERSION}" \
--generate-notes \
--latest
# Reformat the auto-generated notes into the curated
# Install + Feature/Enhancement/Fix format.
node scripts/release-notes/format-github-release-notes.cjs \
--tag "v${VERSION}" --latest --apply
- name: Post Discord release announcement
if: ${{ !inputs.dry_run }}
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_CHANGELOG_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
node scripts/release-notes/discord-release-summary.cjs \
--tag "v${VERSION}" \
--repo "$GITHUB_REPOSITORY" \
--post \
--allow-missing-webhook
- name: Verify publish
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ inputs.version }}
run: node scripts/verify-npm-publish.cjs --package @opengsd/gsd-core --version "$VERSION" --dist-tag latest
- name: Summary
env:
VERSION: ${{ inputs.version }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
echo "## Release v${VERSION}" >> "$GITHUB_STEP_SUMMARY"
if [ "$DRY_RUN" = "true" ]; then
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
else
echo "- Published to npm as \`latest\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Tagged \`v${VERSION}\`" >> "$GITHUB_STEP_SUMMARY"
echo "- PR created to merge back to main" >> "$GITHUB_STEP_SUMMARY"
echo "- Install: \`npx @opengsd/gsd-core@latest\`" >> "$GITHUB_STEP_SUMMARY"
fi