Skip to content

Release

Release #125

Workflow file for this run

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"