Skip to content

fix infra build in publish.yml #1534

fix infra build in publish.yml

fix infra build in publish.yml #1534

Workflow file for this run

name: "publish"
# Three trigger flows:
#
# push to main — Full release flow: process changesets, publish docs,
# and (when a version-bump PR just merged) prepare,
# review, and publish to npm/crates.io/GitHub Releases.
# Requires manual approval via the slang-release environment.
#
# workflow_dispatch — Replays the push-to-main path (e.g. to retry a
# flaky publish). Requires manual approval.
#
# pull_request — Prepare and review only: builds the npm tarball
# that would be published and validates it, but
# skips the actual upload. No approval needed.
#
# Job topology — split into prepare → review → publish to enforce that the bytes
# reviewed are the bytes uploaded:
#
# Trigger repo-check changesets prepare review notify-deploy publish
# ─────────────── ────────── ──────────────── ───────── ───────── ───────────── ───────
# PR ✓ gate ⊘ skip (no push) ✓ build ✓ validate ⊘ skip ⊘ skip
# push (pending) ✓ gate ✓ has changesets ✓ build ✓ validate ⊘ skip ⊘ skip
# push (no-op) ✓ gate ✓ no new version ✓ build ✓ validate ⊘ skip ⊘ skip
# push (release) ✓ gate ✓ new version ✓ build ✓ validate ✓ notify ✓ publish
#
# Every job has an explicit 'if' condition — no job relies on implicit skip
# propagation from needs. The one exception is prepare, which uses !cancelled()
# to override skip propagation from the changesets job (skipped on PRs).
on:
# Run using manual triggers from GitHub UI:
# https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow
workflow_dispatch: {}
# Run on pushes to 'main' branch:
push:
branches:
- "main"
# Run on PRs for build and validation:
pull_request: {}
permissions: {}
# Serialize runs for the same PR or branch. Different PRs/branches can run concurrently.
concurrency:
group: "${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref_name }}"
cancel-in-progress: false
jobs:
# ───────────────────────────────────────────────
# Gate: only run on the main repo (not forks).
# ───────────────────────────────────────────────
repo-check:
if: >-
(github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name == github.repository)
|| github.ref_name == 'main'
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
steps:
- run: "true"
# ───────────────────────────────────────────────
# Changesets: process version bumps and publish docs
# Runs on push to main and dispatch (not PRs)
# ───────────────────────────────────────────────
changesets:
needs:
- "repo-check"
if: >-
github.event_name == 'push'
|| github.event_name == 'workflow_dispatch'
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
permissions:
contents: "write" # to create new branches
pull-requests: "write" # to create new pull requests for changesets
outputs:
hasChangesets: "${{ steps.runChangesets.outputs.hasChangesets }}"
publishNeeded: "${{ steps.checkVersion.outputs.publishNeeded || 'false' }}"
steps:
- name: "Checkout Repository"
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # Full history is needed by 'git/restore-mtime' to find each file's last commit time.
- name: "Free up disk space"
uses: "./.github/actions/free-disk-space"
- name: "Restore file mtimes from git"
uses: "./.github/actions/git/restore-mtime"
- name: "Restore Dependencies Cache"
uses: "./.github/actions/cache/dependencies/restore"
- name: "Restore Cargo Target Cache"
uses: "./.github/actions/cache/cargo-target/restore"
- name: "infra setup"
run: "./scripts/bin/infra setup"
# Consume the changesets, and create a git stash, to be popped by the next step:
- name: "infra publish changesets"
run: "./scripts/bin/infra publish changesets"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: "Create/Update Changesets Pull Request"
id: "runChangesets"
uses: "changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b" # v1.8.0
with:
title: "Bump Slang Version"
commit: "Bump Slang Version"
createGithubReleases: false
version: "git stash pop" # Stash created by 'infra publish changesets'
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: "Check if publish needed"
id: "checkVersion"
if: "${{ steps.runChangesets.outputs.hasChangesets == 'false' }}"
run: |
RESULT="$(./scripts/bin/infra publish github-release --check-only)"
echo "publishNeeded=${RESULT}" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: "infra publish mkdocs --target main-branch"
run: "./scripts/bin/infra publish mkdocs --target main-branch"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: "infra publish mkdocs --target latest-release"
if: "${{ steps.runChangesets.outputs.hasChangesets == 'false' }}"
run: "./scripts/bin/infra publish mkdocs --target latest-release"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
# ───────────────────────────────────────────────
# Prepare: build the npm tarball for downstream review and publish.
# ───────────────────────────────────────────────
prepare:
needs:
- "repo-check"
- "changesets"
# !cancelled() overrides GitHub's default skip-propagation (needed because
# changesets is skipped on PRs). It also overrides failure-propagation,
# so repo-check and changesets must be re-asserted explicitly.
if: >-
!cancelled()
&& needs.repo-check.result == 'success'
&& (needs.changesets.result == 'success' || needs.changesets.result == 'skipped')
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
permissions:
contents: "read"
outputs:
artifact-name: "${{ steps.prepare.outputs.artifact-name }}"
tarball-name: "${{ steps.prepare.outputs.tarball-name }}"
steps:
- name: "Checkout Repository"
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # Full history is needed by 'git/restore-mtime' to find each file's last commit time.
- name: "Free up disk space"
uses: "./.github/actions/free-disk-space"
# Skip cache + mtime restore on a real release so the published artifact
# comes from a clean build with no side inputs.
- name: "Restore file mtimes from git"
if: "${{ needs.changesets.outputs.publishNeeded != 'true' }}"
uses: "./.github/actions/git/restore-mtime"
- name: "Restore Dependencies Cache"
if: "${{ needs.changesets.outputs.publishNeeded != 'true' }}"
uses: "./.github/actions/cache/dependencies/restore"
- name: "Restore Cargo Target Cache"
if: "${{ needs.changesets.outputs.publishNeeded != 'true' }}"
uses: "./.github/actions/cache/cargo-target/restore"
- name: "infra setup"
run: "./scripts/bin/infra setup"
- name: "Prepare publish artifacts"
id: "prepare"
uses: "./.github/actions/publish/prepare"
- name: "infra publish mkdocs --dry-run"
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
./scripts/bin/infra publish mkdocs --target main-branch --dry-run
- name: "Upload publish artifacts"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "${{ steps.prepare.outputs.artifact-name }}"
path: "target/publish-artifacts/"
if-no-files-found: "error"
retention-days: 7
# ───────────────────────────────────────────────
# Review: download the prepared artifact and dry-run publish against what would be uploaded.
# ───────────────────────────────────────────────
review:
needs:
- "repo-check"
- "prepare"
if: >-
!cancelled()
&& needs.repo-check.result == 'success'
&& needs.prepare.result == 'success'
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
permissions:
contents: "read"
steps:
- name: "Checkout Repository"
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
with:
persist-credentials: false
fetch-depth: 0 # Full history is needed by 'git/restore-mtime' to find each file's last commit time.
- name: "Free up disk space"
uses: "./.github/actions/free-disk-space"
# Review only validates the prepared artifact; the publishable bytes come from
# `prepare`, not from this job's `target/`. Cache restore here is purely a
# speedup for `cargo publish --dry-run`'s verify-build and has no provenance impact.
- name: "Restore file mtimes from git"
uses: "./.github/actions/git/restore-mtime"
- name: "Restore Dependencies Cache"
uses: "./.github/actions/cache/dependencies/restore"
- name: "Restore Cargo Target Cache"
uses: "./.github/actions/cache/cargo-target/restore"
- name: "infra setup"
run: "./scripts/bin/infra setup"
- name: "Download publish artifacts"
uses: "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8.0.1
with:
name: "${{ needs.prepare.outputs.artifact-name }}"
path: "target/publish-artifacts/"
- name: "infra publish npm --dry-run"
# No tarball means prepare packed nothing (version already on npm) — nothing to validate.
if: "${{ needs.prepare.outputs.tarball-name != '' }}"
run: "./scripts/bin/infra publish npm --dry-run --tarball target/publish-artifacts/${{ needs.prepare.outputs.tarball-name }}"
- name: "infra publish cargo --dry-run"
run: "./scripts/bin/infra publish cargo --dry-run"
- name: "Publish review summary"
run: |
VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
ARTIFACT_NAME='${{ needs.prepare.outputs.artifact-name }}'
{
echo "## Publish Review Summary"
echo ""
echo "**Version:** \`${VERSION}\`"
echo "**Artifact:** \`${ARTIFACT_NAME}\` — download from the run's Artifacts panel to inspect."
echo ""
echo "### What was validated against the prepared artifact"
echo "- \`pnpm publish --dry-run <tarball>\` (npm pack/structure check)"
echo "- \`cargo publish --dry-run\` batched workspace verify-build (catches cross-crate path-dep rewrites)"
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo ""
echo "### Skipped (only on push/dispatch)"
echo "- Actual npm/cargo publishing"
echo "- GitHub release creation"
fi
} >> "$GITHUB_STEP_SUMMARY"
# ───────────────────────────────────────────────
# Notify: Slack notification that review is requested
# ───────────────────────────────────────────────
notify-deploy:
needs:
- "changesets"
- "prepare"
- "review"
if: >-
github.event_name != 'pull_request'
&& needs.changesets.outputs.hasChangesets == 'false'
&& needs.changesets.outputs.publishNeeded == 'true'
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
steps:
- name: "Notify review requested"
continue-on-error: true
uses: "slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c" # v3.0.3
with:
webhook: "${{ secrets.PUBLISHING_NOTIFICATIONS_SLACK_WEBHOOK_URL }}"
webhook-type: "incoming-webhook"
payload: |
{
"text": "Slang release — Review requested",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Slang release — Review requested"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Commit:* <${{ github.event.head_commit.url || format('{0}/{1}/commit/{2}', github.server_url, github.repository, github.sha) }}|View commit>\n*Approve:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Open workflow run>"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered by *${{ github.actor }}* via `${{ github.event_name }}`"
}
]
}
]
}
# ───────────────────────────────────────────────
# Publish: actual publishing with environment gate
# Only runs on push to main or dispatch (never PRs).
# ───────────────────────────────────────────────
publish:
needs:
- "changesets"
- "prepare"
- "review"
- "notify-deploy"
# Redundant with the needs chain, but explicit for safety: never publish
# from PRs, when there are pending changesets, or when the version is
# already published.
if: >-
github.event_name != 'pull_request'
&& needs.changesets.outputs.hasChangesets == 'false'
&& needs.changesets.outputs.publishNeeded == 'true'
runs-on: "ubuntu-24.04" # _SLANG_DEV_CONTAINER_BASE_IMAGE_ (keep in sync)
environment: "slang-release"
permissions:
contents: "write" # to create new releases
id-token: "write" # OIDC token (needed for trusted publishing)
steps:
- name: "Notify deployment starting"
continue-on-error: true
uses: "slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c" # v3.0.3
with:
webhook: "${{ secrets.PUBLISHING_NOTIFICATIONS_SLACK_WEBHOOK_URL }}"
webhook-type: "incoming-webhook"
payload: |
{
"text": "Slang release — Deployment starting",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Slang release — Deployment starting"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Open workflow run>"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered by *${{ github.actor }}*"
}
]
}
]
}
- name: "Checkout Repository"
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
with:
persist-credentials: false
- name: "Download publish artifacts"
uses: "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8.0.1
with:
name: "${{ needs.prepare.outputs.artifact-name }}"
path: "target/publish-artifacts/"
# Compile infra_cli with no registry token in scope.
- name: "Build infra_cli (before any token)"
run: "./scripts/bin/infra --help"
- name: "infra publish npm"
# No tarball means prepare packed nothing (version already on npm) — nothing to publish.
if: "${{ needs.prepare.outputs.tarball-name != '' }}"
run: "./scripts/bin/infra publish npm --tarball target/publish-artifacts/${{ needs.prepare.outputs.tarball-name }}"
- name: "Exchange Cargo Token"
id: "exchangeCargoToken"
uses: "rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe" # v1.0.4
- name: "infra publish cargo"
run: "./scripts/bin/infra publish cargo"
env:
CARGO_REGISTRY_TOKEN: "${{ steps.exchangeCargoToken.outputs.token }}"
- name: "infra publish github-release"
run: "./scripts/bin/infra publish github-release"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"