Release #125
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: Release | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| types: [completed] | |
| workflow_dispatch: | |
| inputs: | |
| publish: | |
| description: Publish npm packages | |
| type: boolean | |
| default: true | |
| ci_run_id: | |
| description: CI run ID to consume when dispatching manually | |
| type: string | |
| required: false | |
| permissions: | |
| contents: read | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| plan: | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.head_branch == 'main' && | |
| github.event.workflow_run.event == 'push' | |
| ) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_ship: ${{ steps.plan.outputs.should_ship }} | |
| tag: ${{ steps.plan.outputs.tag }} | |
| version: ${{ steps.plan.outputs.version }} | |
| is_prerelease: ${{ steps.plan.outputs.is_prerelease }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| fetch-depth: 0 | |
| - uses: ./.github/actions/omnifs-just | |
| - name: Decide whether to ship | |
| id: plan | |
| run: | | |
| set -euo pipefail | |
| plan="$(just release-plan-json)" | |
| echo "$plan" | |
| version="$(jq -r '.version' <<< "$plan")" | |
| # Semver prerelease: any version with a hyphen (e.g. 0.2.0-dev.0, 1.0.0-rc.1). | |
| if [[ "$version" == *-* ]]; then | |
| is_prerelease=true | |
| else | |
| is_prerelease=false | |
| fi | |
| { | |
| echo "should_ship=$(jq -r '.should_ship' <<< "$plan")" | |
| echo "tag=$(jq -r '.tag' <<< "$plan")" | |
| echo "version=$version" | |
| echo "is_prerelease=$is_prerelease" | |
| } >> "$GITHUB_OUTPUT" | |
| jq -r '.release_notes // empty' <<< "$plan" > release-notes.md | |
| - name: Upload release notes | |
| if: steps.plan.outputs.should_ship == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-notes | |
| path: release-notes.md | |
| retention-days: 1 | |
| github-release: | |
| if: needs.plan.outputs.should_ship == 'true' | |
| needs: plan | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| persist-credentials: false | |
| - name: Download release notes | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: release-notes | |
| path: . | |
| - name: Resolve CI workflow run | |
| id: ci_run | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| INPUT_CI_RUN_ID: ${{ inputs.ci_run_id || '' }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -n "$INPUT_CI_RUN_ID" ]]; then | |
| echo "run_id=$INPUT_CI_RUN_ID" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ "${{ github.event_name }}" == "workflow_run" ]]; then | |
| echo "run_id=${{ github.event.workflow_run.id }}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| run_id="$(gh run list --workflow ci.yml --branch main --limit 30 --json databaseId,conclusion \ | |
| | jq -r '[.[] | select(.conclusion == "success")] | first | .databaseId')" | |
| if [[ -z "$run_id" || "$run_id" == "null" ]]; then | |
| echo "no successful ci.yml run found on main" >&2 | |
| exit 1 | |
| fi | |
| echo "run_id=$run_id" >> "$GITHUB_OUTPUT" | |
| - name: Download CI release artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| run-id: ${{ steps.ci_run.outputs.run_id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| path: release-assets | |
| - name: Verify release assets | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| cli=(release-assets/omnifs-cli-*/*.tar.xz) | |
| provider_wasms=(release-assets/omnifs-wasm/omnifs_provider_*.wasm) | |
| tool_wasms=(release-assets/omnifs-wasm/omnifs_tool_archive.wasm) | |
| test_wasms=(release-assets/omnifs-wasm/test_provider.wasm) | |
| if (( ${#cli[@]} < 4 )); then | |
| printf 'expected 4 omnifs-cli archives, found %d\n' "${#cli[@]}" >&2 | |
| find release-assets -type f >&2 || true | |
| exit 1 | |
| fi | |
| if (( ${#provider_wasms[@]} == 0 )); then | |
| printf 'expected omnifs_provider_*.wasm files\n' >&2 | |
| find release-assets -type f >&2 || true | |
| exit 1 | |
| fi | |
| if (( ${#tool_wasms[@]} != 1 )); then | |
| printf 'expected omnifs_tool_archive.wasm\n' >&2 | |
| find release-assets -type f >&2 || true | |
| exit 1 | |
| fi | |
| if (( ${#test_wasms[@]} != 1 )); then | |
| printf 'expected CI artifact to include test_provider.wasm for host tests\n' >&2 | |
| find release-assets -type f >&2 || true | |
| exit 1 | |
| fi | |
| - name: Publish GitHub release and assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.plan.outputs.tag }} | |
| name: omnifs v${{ needs.plan.outputs.version }} | |
| body_path: release-notes.md | |
| target_commitish: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| files: | | |
| release-assets/omnifs-cli-*/*.tar.xz | |
| release-assets/omnifs-cli-*/*.tar.xz.sha256 | |
| release-assets/omnifs-wasm/omnifs_provider_*.wasm | |
| release-assets/omnifs-wasm/omnifs_tool_archive.wasm | |
| fail_on_unmatched_files: true | |
| prerelease: ${{ needs.plan.outputs.is_prerelease == 'true' }} | |
| make_latest: ${{ needs.plan.outputs.is_prerelease == 'true' && 'false' || 'true' }} | |
| promote: | |
| if: needs.plan.outputs.should_ship == 'true' | |
| needs: plan | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| persist-credentials: false | |
| - uses: ./.github/actions/omnifs-docker | |
| - name: Promote CI image to release tags | |
| id: promote | |
| env: | |
| TAG: ${{ needs.plan.outputs.tag }} | |
| VERSION: ${{ needs.plan.outputs.version }} | |
| RELEASE_COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| run: | | |
| set -euo pipefail | |
| digest="$(scripts/ci/promote-image.sh \ | |
| "${{ env.REGISTRY }}" \ | |
| "${{ env.IMAGE_NAME }}" \ | |
| "$RELEASE_COMMIT_SHA" \ | |
| "$TAG" \ | |
| "$VERSION")" | |
| echo "digest=$digest" >> "$GITHUB_OUTPUT" | |
| - name: Attest image build provenance | |
| uses: actions/attest-build-provenance@v4.1.0 | |
| with: | |
| subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| subject-digest: ${{ steps.promote.outputs.digest }} | |
| push-to-registry: true | |
| create-storage-record: false | |
| npm-platforms: | |
| if: needs.plan.outputs.should_ship == 'true' | |
| needs: [plan, github-release, promote] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| actions: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| persist-credentials: false | |
| - uses: ./.github/actions/omnifs-just | |
| - name: Resolve CI workflow run | |
| id: ci_run | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| INPUT_CI_RUN_ID: ${{ inputs.ci_run_id || '' }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -n "$INPUT_CI_RUN_ID" ]]; then | |
| echo "run_id=$INPUT_CI_RUN_ID" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ "${{ github.event_name }}" == "workflow_run" ]]; then | |
| echo "run_id=${{ github.event.workflow_run.id }}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| run_id="$(gh run list --workflow ci.yml --branch main --limit 30 --json databaseId,conclusion \ | |
| | jq -r '[.[] | select(.conclusion == "success")] | first | .databaseId')" | |
| echo "run_id=$run_id" >> "$GITHUB_OUTPUT" | |
| - name: Download platform CLI archives from CI | |
| uses: actions/download-artifact@v8 | |
| with: | |
| run-id: ${{ steps.ci_run.outputs.run_id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| pattern: omnifs-cli-* | |
| path: dist-download | |
| merge-multiple: false | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| registry-url: https://registry.npmjs.org | |
| - name: Stage npm platform packages | |
| env: | |
| VERSION: ${{ needs.plan.outputs.version }} | |
| SHOULD_PUBLISH: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }} | |
| NPM_TAG: ${{ needs.plan.outputs.is_prerelease == 'true' && 'dev' || 'latest' }} | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| just npm-sync | |
| mapfile -t platforms < <(jq -r 'keys[]' npm/platforms.json) | |
| for platform in "${platforms[@]}"; do | |
| archive="$(find dist-download -type f -name "omnifs-cli-${platform}.tar.xz" | head -n 1)" | |
| if [[ -z "$archive" ]]; then | |
| echo "missing CLI archive for $platform" >&2 | |
| find dist-download -type f >&2 || true | |
| exit 1 | |
| fi | |
| extract_dir="$RUNNER_TEMP/extract-$platform" | |
| rm -rf "$extract_dir" | |
| mkdir -p "$extract_dir" | |
| tar -xJf "$archive" -C "$extract_dir" | |
| binary="$extract_dir/omnifs" | |
| if [[ ! -x "$binary" ]]; then | |
| echo "missing omnifs binary in $archive" >&2 | |
| find "$extract_dir" -type f >&2 || true | |
| exit 1 | |
| fi | |
| if [[ "$platform" == "linux-x64" ]]; then | |
| actual="$("$binary" --version)" | |
| expected="omnifs ${VERSION}" | |
| if [[ "$actual" != "$expected" ]]; then | |
| printf 'CLI version mismatch: expected %s, got %s\n' "$expected" "$actual" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| pkg="./npm/platform/$platform" | |
| mkdir -p "$pkg/bin" | |
| cp "$binary" "$pkg/bin/omnifs" | |
| chmod 755 "$pkg/bin/omnifs" | |
| npm pack "$pkg" --pack-destination "$RUNNER_TEMP" | |
| if [[ "$SHOULD_PUBLISH" == "true" ]]; then | |
| npm publish "$pkg" --access public --provenance --tag "$NPM_TAG" | |
| fi | |
| done | |
| npm-root: | |
| if: needs.plan.outputs.should_ship == 'true' | |
| needs: [plan, github-release, promote, npm-platforms] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| persist-credentials: false | |
| - uses: ./.github/actions/omnifs-just | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| registry-url: https://registry.npmjs.org | |
| - name: Sync and validate npm metadata | |
| run: | | |
| just npm-sync | |
| just npm-validate | |
| - name: Stage root package | |
| run: just npm-pack-root "$RUNNER_TEMP" | |
| - name: Publish root package | |
| if: >- | |
| github.event_name != 'workflow_dispatch' || | |
| inputs.publish | |
| env: | |
| NPM_TAG: ${{ needs.plan.outputs.is_prerelease == 'true' && 'dev' || 'latest' }} | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: npm publish ./npm/omnifs --access public --provenance --tag "$NPM_TAG" |