publish #86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: publish | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: Git tag to release, e.g. v0.5.0-rc.0 | |
| required: true | |
| type: string | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_run: | |
| workflows: ['ci'] | |
| types: [completed] | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| publish-stable: | |
| name: publish stable packages | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| steps: | |
| - name: Resolve tag | |
| id: tag | |
| env: | |
| INPUT_TAG: ${{ inputs.tag }} | |
| REF_NAME: ${{ github.ref_name }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${INPUT_TAG:-$REF_NAME}" | |
| if [[ -z "$TAG" || ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "Expected a vX.Y.Z or vX.Y.Z-prerelease tag, got '$TAG'" >&2 | |
| exit 1 | |
| fi | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| VERSION="${TAG#v}" | |
| if [[ "$VERSION" == *-* ]]; then | |
| NPM_TAG="${VERSION#*-}" | |
| NPM_TAG="${NPM_TAG%%.*}" | |
| else | |
| NPM_TAG="latest" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Checkout tag | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.tag.outputs.tag }} | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - name: Validate tag and source versions | |
| env: | |
| TAG: ${{ steps.tag.outputs.tag }} | |
| VERSION: ${{ steps.tag.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| git fetch origin main --tags | |
| if ! git merge-base --is-ancestor "$TAG" origin/main; then | |
| echo "Tag $TAG is not an ancestor of origin/main" >&2 | |
| exit 1 | |
| fi | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const version = process.env.VERSION; | |
| const root = JSON.parse(fs.readFileSync('package.json', 'utf8')).version; | |
| const demo = JSON.parse(fs.readFileSync('demo/package.json', 'utf8')).version; | |
| const flake = fs.readFileSync('flake.nix', 'utf8').match(/version = "([^"]+)";/)?.[1]; | |
| for (const [name, value] of Object.entries({ root, demo, flake })) { | |
| if (value !== version) throw new Error(`${name} version ${value} does not match tag version ${version}`); | |
| } | |
| console.log(`Validated source versions for ${version}`); | |
| NODE | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Setup Zig | |
| uses: ./.github/actions/setup-zig | |
| with: | |
| version: 0.15.2 | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Check formatting | |
| run: bun run fmt | |
| - name: Run linter | |
| run: bun run lint | |
| - name: Check types | |
| run: bun run typecheck | |
| - name: Run tests | |
| run: bun test | |
| - name: Build library | |
| run: bun run build | |
| - name: Check WASM size | |
| run: | | |
| SIZE=$(stat -c%s ghostty-vt.wasm) | |
| echo "WASM size: $SIZE bytes" | |
| if [ "$SIZE" -gt 524288 ]; then | |
| echo "❌ Error: WASM exceeds 512 KiB limit" | |
| exit 1 | |
| fi | |
| - name: Setup Node.js for npm | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Ensure current npm for trusted publishing | |
| run: npm install -g npm@latest | |
| - name: Publish ghostty-web | |
| env: | |
| VERSION: ${{ steps.tag.outputs.version }} | |
| NPM_TAG: ${{ steps.tag.outputs.npm_tag }} | |
| run: | | |
| set -euo pipefail | |
| PACKAGE_NAME=$(node -p "require('./package.json').name") | |
| if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then | |
| echo "${PACKAGE_NAME}@${VERSION} already exists; skipping publish" | |
| npm dist-tag add "${PACKAGE_NAME}@${VERSION}" "${NPM_TAG}" || echo "Unable to repair ${NPM_TAG} dist-tag with current npm credentials; package already exists." | |
| else | |
| npm publish --tag "${NPM_TAG}" --provenance --access public | |
| fi | |
| - name: Pin and validate demo package | |
| env: | |
| VERSION: ${{ steps.tag.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const version = process.env.VERSION; | |
| const path = 'demo/package.json'; | |
| const pkg = JSON.parse(fs.readFileSync(path, 'utf8')); | |
| pkg.version = version; | |
| pkg.dependencies['ghostty-web'] = version; | |
| fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); | |
| console.log(`Pinned @ghostty-web/demo to ghostty-web ${version}`); | |
| NODE | |
| - name: Publish @ghostty-web/demo | |
| working-directory: demo | |
| env: | |
| VERSION: ${{ steps.tag.outputs.version }} | |
| NPM_TAG: ${{ steps.tag.outputs.npm_tag }} | |
| run: | | |
| set -euo pipefail | |
| PACKAGE_NAME=$(node -p "require('./package.json').name") | |
| if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then | |
| echo "${PACKAGE_NAME}@${VERSION} already exists; skipping publish" | |
| npm dist-tag add "${PACKAGE_NAME}@${VERSION}" "${NPM_TAG}" || echo "Unable to repair ${NPM_TAG} dist-tag with current npm credentials; package already exists." | |
| else | |
| npm publish --tag "${NPM_TAG}" --provenance --access public | |
| fi | |
| publish-next: | |
| name: publish next packages | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' | |
| steps: | |
| - name: Checkout CI head | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - name: Skip release commits | |
| id: release-commit | |
| env: | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| SUBJECT=$(git log -1 --format=%s "$HEAD_SHA") | |
| echo "subject=$SUBJECT" | |
| if [[ "$SUBJECT" =~ ^chore\(release\):\ ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Skipping next publish for release commit" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Setup Bun | |
| if: steps.release-commit.outputs.skip != 'true' | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Setup Zig | |
| if: steps.release-commit.outputs.skip != 'true' | |
| uses: ./.github/actions/setup-zig | |
| with: | |
| version: 0.15.2 | |
| - name: Install dependencies | |
| if: steps.release-commit.outputs.skip != 'true' | |
| run: bun install --frozen-lockfile | |
| - name: Build library | |
| if: steps.release-commit.outputs.skip != 'true' | |
| run: bun run build | |
| - name: Setup Node.js for npm | |
| if: steps.release-commit.outputs.skip != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Ensure current npm for trusted publishing | |
| if: steps.release-commit.outputs.skip != 'true' | |
| run: npm install -g npm@latest | |
| - name: Compute next version | |
| if: steps.release-commit.outputs.skip != 'true' | |
| id: version | |
| run: | | |
| set -euo pipefail | |
| LATEST_TAG=$(git describe --tags --abbrev=0) | |
| BASE_VERSION="${LATEST_TAG#v}" | |
| SHORT_SHA=$(git rev-parse --short HEAD) | |
| COMMITS_SINCE_TAG=$(git rev-list --count HEAD ^"$LATEST_TAG") | |
| NEXT_VERSION="${BASE_VERSION}-next.${COMMITS_SINCE_TAG}.g${SHORT_SHA}" | |
| echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Publishing next version $NEXT_VERSION" | |
| - name: Prepare root package | |
| if: steps.release-commit.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| pkg.version = process.env.VERSION; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| NODE | |
| - name: Publish ghostty-web next | |
| if: steps.release-commit.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| PACKAGE_NAME=$(node -p "require('./package.json').name") | |
| if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then | |
| echo "${PACKAGE_NAME}@${VERSION} already exists; skipping" | |
| else | |
| npm publish --tag next --provenance --access public | |
| fi | |
| - name: Prepare demo package | |
| if: steps.release-commit.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('demo/package.json', 'utf8')); | |
| pkg.version = process.env.VERSION; | |
| pkg.dependencies['ghostty-web'] = process.env.VERSION; | |
| fs.writeFileSync('demo/package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| NODE | |
| - name: Publish @ghostty-web/demo next | |
| if: steps.release-commit.outputs.skip != 'true' | |
| working-directory: demo | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| PACKAGE_NAME=$(node -p "require('./package.json').name") | |
| if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then | |
| echo "${PACKAGE_NAME}@${VERSION} already exists; skipping" | |
| else | |
| npm publish --tag next --provenance --access public | |
| fi |