Skip to content

release

release #22

Workflow file for this run

name: release
on:
workflow_dispatch:
inputs:
tag:
description: Release tag to build and publish, for example v0.8.4
required: true
type: string
concurrency:
group: binary-release
cancel-in-progress: false
jobs:
validate:
runs-on: ubuntu-24.04
outputs:
create_tag: ${{ steps.meta.outputs.create_tag }}
release_sha: ${{ steps.meta.outputs.release_sha }}
tag: ${{ steps.meta.outputs.tag }}
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.10
- uses: pnpm/action-setup@v4
with:
version: 9.12.0
run_install: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm check:ts
- name: Run test suite
run: pnpm test:ts
- name: Build workspace
run: pnpm build:ts
- name: Validate standalone bundle
shell: bash
run: |
set -euo pipefail
bun build packages/cli/src/index.ts \
--compile \
--reject-unresolved \
--outfile="${RUNNER_TEMP}/polaris-standalone" \
--metafile="${RUNNER_TEMP}/polaris-metafile.json"
jq -e \
'any(.inputs | keys[]; endswith("packages/browser/src/index.tsx"))' \
"${RUNNER_TEMP}/polaris-metafile.json" >/dev/null
- name: Validate release metadata
id: meta
shell: bash
env:
REQUESTED_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
root_version="$(node -p "require('./package.json').version")"
cli_version="$(node -p "require('./packages/cli/package.json').version")"
built_version="$(node packages/cli/dist/cli/src/index.js --version)"
if [[ ! "${REQUESTED_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tag must match vMAJOR.MINOR.PATCH." >&2
exit 1
fi
if [[ "${root_version}" != "${cli_version}" || "${root_version}" != "${built_version}" ]]; then
echo "Root, CLI package, and built CLI versions must match." >&2
echo "Root package: ${root_version}" >&2
echo "CLI package: ${cli_version}" >&2
echo "Built CLI: ${built_version}" >&2
exit 1
fi
if [[ "${REQUESTED_TAG}" != "v${root_version}" ]]; then
echo "Tag ${REQUESTED_TAG} does not match Polaris version v${root_version}." >&2
exit 1
fi
create_tag="true"
if git rev-parse -q --verify "refs/tags/${REQUESTED_TAG}" >/dev/null; then
existing_sha="$(git rev-list -n1 "${REQUESTED_TAG}")"
if [[ "${existing_sha}" != "${GITHUB_SHA}" ]]; then
echo "Tag ${REQUESTED_TAG} already exists at ${existing_sha}, not ${GITHUB_SHA}." >&2
exit 1
fi
create_tag="false"
fi
echo "create_tag=${create_tag}" >>"${GITHUB_OUTPUT}"
echo "release_sha=${GITHUB_SHA}" >>"${GITHUB_OUTPUT}"
echo "tag=${REQUESTED_TAG}" >>"${GITHUB_OUTPUT}"
echo "version=${root_version}" >>"${GITHUB_OUTPUT}"
release:
needs: validate
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Create release tag
if: needs.validate.outputs.create_tag == 'true'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_SHA: ${{ needs.validate.outputs.release_sha }}
RELEASE_TAG: ${{ needs.validate.outputs.tag }}
run: |
set -euo pipefail
gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
--method POST \
--field ref="refs/tags/${RELEASE_TAG}" \
--field sha="${RELEASE_SHA}"
- name: Create or resume mutable GitHub release
id: stage
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_SHA: ${{ needs.validate.outputs.release_sha }}
RELEASE_TAG: ${{ needs.validate.outputs.tag }}
run: |
set -euo pipefail
releases="$(
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/releases?per_page=100"
)"
matches="$(
jq -c --arg tag "${RELEASE_TAG}" \
'[.[][] | select(.tag_name == $tag)]' <<<"${releases}"
)"
match_count="$(jq 'length' <<<"${matches}")"
if [[ "${match_count}" -gt 1 ]]; then
echo "Multiple GitHub releases use tag ${RELEASE_TAG}." >&2
exit 1
fi
if [[ "${match_count}" -eq 0 ]]; then
release="$(
gh api "repos/${GITHUB_REPOSITORY}/releases" \
--method POST \
--field tag_name="${RELEASE_TAG}" \
--field target_commitish="${RELEASE_SHA}" \
--field name="${RELEASE_TAG}" \
--field draft=false \
--field prerelease=false \
--field generate_release_notes=true
)"
else
release="$(jq -c '.[0]' <<<"${matches}")"
if [[ "$(jq -r '.prerelease' <<<"${release}")" != "false" ]]; then
echo "Release ${RELEASE_TAG} must not be a prerelease." >&2
exit 1
fi
if [[ "$(jq -r '.immutable // false' <<<"${release}")" == "true" ]]; then
echo "Release ${RELEASE_TAG} is immutable." >&2
exit 1
fi
if [[ "$(jq '.assets | length' <<<"${release}")" -ne 0 ]]; then
echo "Release ${RELEASE_TAG} already has assets; refusing to replace them." >&2
exit 1
fi
if [[ "$(jq -r '.draft' <<<"${release}")" == "true" ]]; then
release_id="$(jq -r '.id' <<<"${release}")"
release="$(
gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
--method PATCH \
--field draft=false
)"
fi
fi
release_id="$(jq -r '.id' <<<"${release}")"
if [[ ! "${release_id}" =~ ^[1-9][0-9]*$ ]]; then
echo "Release ${RELEASE_TAG} has an invalid identifier." >&2
exit 1
fi
echo "release_id=${release_id}" >>"${GITHUB_OUTPUT}"
echo "attempt_started=true" >>"${GITHUB_OUTPUT}"
- id: release
uses: wevm/incur/release@v1
with:
entry: packages/cli/src/index.ts
name: polaris
release_tag: ${{ needs.validate.outputs.tag }}
smoke_command: account
- name: Restore draft release after binary failure
if: ${{ (failure() || cancelled()) && steps.stage.outputs.attempt_started == 'true' }}
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_ID: ${{ steps.stage.outputs.release_id }}
RELEASE_TAG: ${{ needs.validate.outputs.tag }}
run: |
set -euo pipefail
release="$(gh api "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}")"
while IFS= read -r asset_id; do
gh api \
"repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" \
--method DELETE
done < <(jq -r '.assets[].id' <<<"${release}")
gh api "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \
--method PATCH \
--field draft=true >/dev/null
echo "Returned ${RELEASE_TAG} to draft after binary generation failed." >&2