Skip to content

Publish tarball to GitHub Release #5

Publish tarball to GitHub Release

Publish tarball to GitHub Release #5

name: Publish tarball to GitHub Release
# Manual fallback for the tarball distribution channel. Packs
# @anarchitecture/ghost and attaches the .tgz to a GitHub Release, so consumers can:
#
# npm install https://github.com/block/ghost/releases/download/<tag>/<file>.tgz
#
# The normal path is release.yml, which attaches the tarball automatically on
# every Changesets publish. This workflow is dispatch-only so a Changesets-
# created tag does not fire both workflows and race on the same Release. Use it
# to (re)cut a tarball for an existing version without a fresh npm publish.
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (must match packages/ghost/package.json)"
required: true
ref:
description: "Git ref to pack and attach to the release"
required: true
default: "main"
permissions:
contents: write
concurrency:
group: tarball-release-${{ github.ref }}
cancel-in-progress: false
jobs:
tarball:
name: Pack and release
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref }}
# No setup-node and no pnpm/action-setup — both would trip zizmor's
# cache-poisoning rule because any GitHub-official setup action is
# treated as cache-capable regardless of whether caching is enabled.
# ubuntu-latest ships Node 20 + corepack, which satisfies our
# engines.node (>=18). Corepack reads `packageManager` from root
# package.json to install the exact pinned pnpm version with no
# cross-branch cache store.
- run: node --version && corepack enable
- name: Validate requested version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
PACKAGE_VERSION="$(node -p "require('./packages/ghost/package.json').version")"
if [ "$PACKAGE_VERSION" != "$INPUT_VERSION" ]; then
echo "Requested version $INPUT_VERSION does not match packages/ghost/package.json version $PACKAGE_VERSION."
exit 1
fi
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @anarchitecture/ghost build
# `pnpm --filter <pkg> pack` writes the tarball to the workspace root,
# not the package dir, in pnpm 10. Force it into a known staging dir so
# the release step can glob a single location deterministically.
- name: Pack
run: |
mkdir -p dist-tarball
pnpm --filter @anarchitecture/ghost pack --pack-destination "$GITHUB_WORKSPACE/dist-tarball"
ls -la dist-tarball
# Resolve the release tag. Inputs from workflow_dispatch are attacker-
# controlled (anyone with Actions write can trigger). Pass them in via
# `env:` and reference as shell variables so they can't be interpolated
# as shell syntax — that's what the semgrep shell-injection rule wants.
- name: Resolve tag
id: tag
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
TAG="anarchitecture-ghost@$INPUT_VERSION"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "target=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.tag.outputs.tag }}
TARGET_SHA: ${{ steps.tag.outputs.target }}
INPUT_VERSION: ${{ inputs.version }}
run: |
ASSET="dist-tarball/anarchitecture-ghost-${INPUT_VERSION}.tgz"
if [ ! -f "$ASSET" ]; then
echo "Expected packed asset missing: $ASSET"
exit 1
fi
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TAG" --target "$TARGET_SHA"
gh release upload "$TAG" "$ASSET" --clobber
else
gh release create "$TAG" \
--title "$TAG" \
--target "$TARGET_SHA" \
--generate-notes \
"$ASSET"
fi