Skip to content

Release CLI

Release CLI #23

Workflow file for this run

name: Release CLI
# Fast path: the minimal host-Node CLI + the on-demand Web runtime. This is the
# always-green release that makes `npm i -g acryl` quick. Desktop is released
# separately via release-desktop.yml so a CLI change never rebuilds Electron.
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release-cli-${{ github.ref }}
cancel-in-progress: false
env:
DSH_TELEMETRY_DISABLED: '1'
ACRYL_CAPABILITY_BASELINE: acryl-capability-baseline-v1
jobs:
cli:
name: cli-${{ matrix.target }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- { target: darwin-arm64, os: macos-latest, ext: tar.gz, cli: acryl }
- { target: linux-arm64, os: ubuntu-24.04-arm, ext: tar.gz, cli: acryl }
- { target: linux-x64, os: ubuntu-latest, ext: tar.gz, cli: acryl }
- { target: windows-x64, os: windows-latest, ext: zip, cli: acryl.cmd }
steps:
- name: Enable Git longpaths
shell: bash
run: git config --global core.longpaths true
- uses: actions/checkout@v5
with: { submodules: recursive }
- uses: actions/setup-node@v5
with: { node-version: 24, package-manager-cache: false }
- uses: pnpm/action-setup@v4
with: { version: 11.8.0, run_install: false }
- run: corepack enable
- run: corepack pnpm install --frozen-lockfile
- name: Build and verify CLI surface
run: |
corepack pnpm --filter acryl-control run build
corepack pnpm --filter acryl-harness-runtime run build
corepack pnpm --filter acryl-tui run build
corepack pnpm --filter acryl-tui run typecheck
corepack pnpm --filter acryl-tui run test
node --test scripts/cli-archive-platform.test.mjs scripts/inspect-artifact.test.mjs
node scripts/verify-npm-entrypoint.mjs
- run: node scripts/build-cli-archive.mjs ${{ matrix.target }} release-artifacts
- name: Clean-runtime smoke and release receipt
shell: bash
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
START_MS="$(node -p 'Date.now()')"
rm -rf /tmp/acryl-cli-smoke && mkdir -p /tmp/acryl-cli-smoke
ARCHIVE="release-artifacts/acryl-cli-${{ matrix.target }}.${{ matrix.ext }}"
if [ "${{ matrix.target }}" = windows-x64 ]; then
unzip -q "$ARCHIVE" -d /tmp/acryl-cli-smoke
CLI_WIN="$(cygpath -w /tmp/acryl-cli-smoke/acryl-cli-${{ matrix.target }}/bin/${{ matrix.cli }})"
cmd //c "$CLI_WIN" --version
(cd /tmp/acryl-cli-smoke && cmd //c "$CLI_WIN" tui --json)
else
tar -xf "$ARCHIVE" -C /tmp/acryl-cli-smoke
CLI="/tmp/acryl-cli-smoke/acryl-cli-${{ matrix.target }}/bin/${{ matrix.cli }}"
node --version
"$CLI" --version
(cd /tmp/acryl-cli-smoke && "$CLI" tui --json)
fi
END_MS="$(node -p 'Date.now()')"
BYTES="$(( $(du -sk /tmp/acryl-cli-smoke | awk '{print $1}') * 1024 ))"
node scripts/release-manifest.mjs receipt cli '${{ matrix.target }}' "${RELEASE_TAG#v}" "$(basename "$ARCHIVE")" "$ARCHIVE" release-artifacts/release-receipt.json "$BYTES" "$((END_MS - START_MS))"
- uses: actions/upload-artifact@v7
with:
name: acryl-cli-${{ matrix.target }}-${{ github.ref_name }}
path: release-artifacts/
if-no-files-found: error
retention-days: 14
web:
name: web-${{ matrix.target }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- { target: darwin-arm64, os: macos-latest, ext: tar.gz }
- { target: linux-arm64, os: ubuntu-24.04-arm, ext: tar.gz }
- { target: linux-x64, os: ubuntu-latest, ext: tar.gz }
- { target: windows-x64, os: windows-latest, ext: tar.gz }
steps:
- name: Enable Git longpaths
shell: bash
run: git config --global core.longpaths true
- uses: actions/checkout@v5
with: { submodules: recursive }
- uses: actions/setup-node@v5
with: { node-version: 24, package-manager-cache: false }
- uses: pnpm/action-setup@v4
with: { version: 11.8.0, run_install: false }
- run: corepack enable
- run: corepack pnpm install --frozen-lockfile
- name: Build and verify Web surface
run: |
corepack pnpm --filter acryl-control run build
corepack pnpm --filter acryl-harness-runtime run build
corepack pnpm --filter acryl-web run build
corepack pnpm --filter acryl-web run typecheck
corepack pnpm --filter acryl-web run test
node --test scripts/web-archive-contract.test.mjs
node scripts/verify-npm-web-entrypoint.mjs
- run: node scripts/build-web-archive.mjs ${{ matrix.target }} 24.19.0 release-artifacts
- name: Smoke Web archive and create release receipt
shell: bash
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
ARCHIVE="release-artifacts/acryl-web-${{ matrix.target }}.${{ matrix.ext }}"
rm -rf /tmp/acryl-web-smoke && mkdir -p /tmp/acryl-web-smoke
tar -xf "$ARCHIVE" -C /tmp/acryl-web-smoke
if [ "${{ matrix.target }}" = windows-x64 ]; then
WEB_WIN="$(cygpath -w /tmp/acryl-web-smoke/acryl-web-${{ matrix.target }}/runtime/bin/acryl-web.cmd)"
cmd //c "$WEB_WIN" --json
else
env PATH=/usr/bin:/bin /tmp/acryl-web-smoke/acryl-web-${{ matrix.target }}/runtime/bin/acryl-web --json
fi
node scripts/release-manifest.mjs receipt web '${{ matrix.target }}' "${RELEASE_TAG#v}" "$(basename "$ARCHIVE")" "$ARCHIVE" release-artifacts/release-receipt.json
- uses: actions/upload-artifact@v7
with:
name: acryl-web-${{ matrix.target }}-${{ github.ref_name }}
path: release-artifacts/
if-no-files-found: error
retention-days: 14
manifest:
name: verify complete cli+web release
needs: [cli, web]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with: { persist-credentials: false }
- uses: actions/download-artifact@v7
with: { path: artifacts }
- name: Generate and validate authoritative release manifest
shell: bash
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
node scripts/release-manifest.mjs verify-versions "${RELEASE_TAG#v}" package.json acryl-control/package.json acryl-harness-runtime/package.json acryl-tui/package.json acryl-web/package.json acryl-desktop/package.json acryl-npm-launcher/package.json
mapfile -t RECEIPTS < <(find artifacts -name release-receipt.json -type f | sort)
test "${#RECEIPTS[@]}" -eq 8
node scripts/release-manifest.mjs generate "${RELEASE_TAG#v}" "$ACRYL_CAPABILITY_BASELINE" artifacts/acryl-release-manifest.json "${RECEIPTS[@]}"
node scripts/release-manifest.mjs validate artifacts/acryl-release-manifest.json
- uses: actions/upload-artifact@v7
with:
name: acryl-release-manifest-${{ github.ref_name }}
path: artifacts/acryl-release-manifest.json
if-no-files-found: error
retention-days: 14
npm-publish:
name: publish npm target packages then selector
needs: [manifest]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with: { submodules: recursive }
- uses: actions/setup-node@v5
with: { node-version: 24, package-manager-cache: false }
- uses: pnpm/action-setup@v4
with: { version: 11.8.0, run_install: false }
- run: corepack enable
- run: corepack pnpm install --frozen-lockfile
- name: Build standalone Web npm package
run: |
corepack pnpm --filter acryl-control run build
corepack pnpm --filter acryl-harness-runtime run build
corepack pnpm --filter acryl-web run build
corepack pnpm --filter acryl-web run typecheck
corepack pnpm --filter acryl-web run test
- uses: actions/download-artifact@v7
with: { path: artifacts }
- name: Assemble immutable target packages
run: |
mkdir -p release-artifacts
find artifacts -type f \( -name 'acryl-cli-*.tar.gz' -o -name 'acryl-cli-*.zip' \) -exec cp {} release-artifacts/ \;
ACRYL_CLI_ARTIFACT_DIR=release-artifacts node scripts/publish-npm-cli.mjs --pack-only npm-packages
- name: Publish target runtimes before selector
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
for PACKAGE in npm-packages/acryl-cli-*.tgz; do npm publish "$PACKAGE" --access public; done
npm publish "$(find npm-packages -maxdepth 1 -name 'acryl-*.tgz' ! -name 'acryl-cli-*.tgz')" --access public
- name: Publish standalone Web package
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: node scripts/publish-npm-web.mjs
release:
name: promote complete GitHub release
needs: [manifest, npm-publish]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with: { persist-credentials: false }
- uses: actions/download-artifact@v7
with: { path: artifacts }
- name: Revalidate complete promotion gate
run: node scripts/release-manifest.mjs validate "$(find artifacts -name acryl-release-manifest.json -type f -print -quit)"
- name: Create GitHub release only after every required surface succeeds
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ACRYL ${{ github.ref_name }}
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
files: artifacts/**/*