diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 5bcc9e10..785eb922 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -4,11 +4,11 @@ # on one runner; both sidecars are prebuilt per target by their own repos' # workflows — so this job only *collects* and cross-builds. # -# Normally called by tag_and_release.yml in the same run after it creates the `anfra-v*` -# tag (workflow_call with that tag) — so publishing doesn't depend on a tag-push -# event. The manifest.yml at that commit pins the sidecar releases to embed and the -# version baked into the binary. Also runs on a manual/human `anfra-v*` tag push, or -# workflow_dispatch for a test build (artifacts only, no release). +# Runs on an `anfra-v*` tag push: normally the one tag_and_release.yml pushes with its +# deploy key, or a tag pushed by hand. The manifest.yml at that commit pins the sidecar +# releases to embed and the version baked into the binary. Also runs on +# workflow_dispatch: on a tag it releases that tag, on a branch it's a test build +# (artifacts only, no release). # # A release also publishes the SDK to npm, as @holistics/anfra-sdk at the same version # (the sdk job). @@ -18,17 +18,9 @@ on: push: tags: - "anfra-v*" - workflow_dispatch: # manual test build (uploads artifacts, no release) - workflow_call: - inputs: - tag: - type: string - required: true - secrets: - # Only the cross-repo download token is passed in (least privilege); - # GITHUB_TOKEN is provided to the called workflow automatically. - ANFRA_DIST_TOKEN: - required: true + # On a branch: a test build (uploads artifacts, no release). On a tag (`--ref anfra-v…`): + # (re)releases that tag. + workflow_dispatch: permissions: contents: write # publish the anfra release @@ -37,11 +29,11 @@ permissions: jobs: assemble: runs-on: ubuntu-latest - # Release when called with a tag or on a tag push; TAG is the input (call) or - # the pushed ref. On workflow_dispatch neither is set → artifacts only. + # Release when the ref is a tag (a tag push, or dispatched on a tag); TAG is that ref. + # Dispatched on a branch → artifacts only. env: - TAG: ${{ inputs.tag || github.ref_name }} - IS_RELEASE: ${{ inputs.tag != '' || startsWith(github.ref, 'refs/tags/') }} + TAG: ${{ github.ref_name }} + IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }} steps: - uses: actions/checkout@v4 # on a tag push this is the tag's commit (manifest pinned there) @@ -194,9 +186,8 @@ jobs: # # npm takes the publish from this workflow over OIDC, with no token (trusted publishing), and # records the package's provenance. npm checks the workflow that started the run, so the - # package's settings on npmjs.com trust both: tag_and_release.yml, which calls this one for a - # release, and this one, for a tag pushed by hand. A re-run skips a version npm already has: - # a version is published once. + # package's settings on npmjs.com trust this one, which a tag push starts. A re-run skips a + # version npm already has: a version is published once. sdk: needs: assemble runs-on: ubuntu-latest @@ -204,7 +195,7 @@ jobs: contents: read id-token: write # npm's trusted publishing, and the package's provenance env: - IS_RELEASE: ${{ inputs.tag != '' || startsWith(github.ref, 'refs/tags/') }} + IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 # the version is package.json's packageManager diff --git a/.github/workflows/tag_and_release.yml b/.github/workflows/tag_and_release.yml index 1bfe45f3..a11ad857 100644 --- a/.github/workflows/tag_and_release.yml +++ b/.github/workflows/tag_and_release.yml @@ -1,11 +1,15 @@ # Cut an anfra release. When manifest.yml lands on main with a `version` that has -# no matching tag yet, create `anfra-v` and then assemble + publish in the -# SAME run (via workflow_call) — so no deploy key / PAT is needed for the tag: a -# GITHUB_TOKEN-pushed tag wouldn't trigger build_release.yml's `on: push: tags` -# (GitHub blocks recursive runs), so we invoke it directly instead. +# no matching tag yet, create `anfra-v`; the tag push then triggers +# build_release.yml, which assembles + publishes. +# +# The tag is pushed with a deploy key (ANFRA_DEPLOY_KEY), not GITHUB_TOKEN: +# - the "release tags" ruleset blocks creating tags, and GITHUB_TOKEN can't be a +# bypass actor; deploy keys are; +# - a GITHUB_TOKEN-pushed tag wouldn't trigger build_release.yml's `on: push: tags` +# (GitHub blocks recursive runs); a deploy-key push does. # # Flow to cut a release: bump `version` (and sidecar pins) in manifest.yml in a PR -# -> merge to main -> this tags and publishes. +# -> merge to main -> this tags -> build_release.yml publishes. name: Tag & release anfra on: @@ -17,43 +21,25 @@ on: workflow_dispatch: # manual re-run permissions: - contents: write # create + push the tag; the called assembler publishes the Release - packages: write # the called assembler pushes the release's image to ghcr.io - id-token: write # the called assembler publishes the SDK to npm (trusted publishing, provenance) + contents: read # the tag is pushed with the deploy key, not GITHUB_TOKEN jobs: tag: runs-on: ubuntu-latest - outputs: - tag: ${{ steps.t.outputs.tag }} - created: ${{ steps.t.outputs.created }} steps: - uses: actions/checkout@v4 - # default GITHUB_TOKEN (persist-credentials) is enough to push a tag + with: + ssh-key: ${{ secrets.ANFRA_DEPLOY_KEY }} # git pushes over SSH as the deploy key - name: Create tag if new - id: t run: | version="$(yq '.version' manifest.yml)" test -n "$version" && test "$version" != null || { echo "no version in manifest.yml"; exit 1; } tag="anfra-v$version" - echo "tag=$tag" >> "$GITHUB_OUTPUT" if git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null 2>&1; then echo "$tag already exists — bump manifest.yml version to cut a release" - echo "created=false" >> "$GITHUB_OUTPUT" else git tag "$tag" HEAD git push origin "$tag" - echo "created=true" >> "$GITHUB_OUTPUT" echo "pushed $tag" fi - - release: - needs: tag - if: ${{ needs.tag.outputs.created == 'true' }} - uses: ./.github/workflows/build_release.yml - with: - tag: ${{ needs.tag.outputs.tag }} - # Pass only what the assembler needs (not `inherit`); GITHUB_TOKEN is automatic. - secrets: - ANFRA_DIST_TOKEN: ${{ secrets.ANFRA_DIST_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b7ed864c..2c684924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [0.6.1](https://github.com/holistics/anfra/compare/anfra-v0.6.0...anfra-v0.6.1) (2026-10-12) + ## [0.6.0](https://github.com/holistics/anfra/compare/anfra-v0.5.0...anfra-v0.6.0) (2026-10-12) ### Features diff --git a/docs/designs/release.md b/docs/designs/release.md index 9341c41e..b26e1546 100644 --- a/docs/designs/release.md +++ b/docs/designs/release.md @@ -12,7 +12,7 @@ To cut a release, on a branch: 2. Update the sidecar pins too, if this release takes new ones. A pin change needs a version bump, or no new tag is cut. 3. Merge to `main`. -`tag_and_release.yml` runs on a push to `main` that changes `manifest.yml`. If the version has no tag yet, it creates `anfra-v` and calls `build_release.yml` in the same run. A tag pushed with the workflow's own token would not trigger another workflow, so it calls it directly. `build_release.yml` also runs on a tag pushed by hand, and on `workflow_dispatch` for a test build, which uploads artifacts and publishes nothing. +`tag_and_release.yml` runs on a push to `main` that changes `manifest.yml`. If the version has no tag yet, it creates `anfra-v` and pushes it with a deploy key (`ANFRA_DEPLOY_KEY`), and the push triggers `build_release.yml`. The deploy key is needed twice over: the repository's tag ruleset blocks creating release tags, and the workflow's own token cannot bypass it, while a deploy key can; and a tag pushed with the workflow's own token would not trigger another workflow. `build_release.yml` also runs on a tag pushed by hand, and on `workflow_dispatch`: dispatched on a tag (`gh workflow run build_release.yml --ref anfra-v`), it releases that tag again; dispatched on a branch, it is a test build, which uploads artifacts and publishes nothing. ## The build @@ -47,7 +47,7 @@ To cut a release, on a branch: - **The release's version.** The SDK's API client is generated from this release's `api/openapi.yaml`, so the two go out together, at one version, even when the SDK did not change. `pnpm bump` sets both; CI fails a pull request in which they differ, and a release refuses to go out. - **Published once.** A re-run skips a version npm already has. A prerelease version (`1.2.0-rc.1`) is published under the `next` dist-tag, so `npm install` never picks it. -- **Trusted publishing.** npm takes the publish from the workflow over OIDC, with no token, and records the package's provenance. It is configured on the package's settings on npmjs.com, naming this repository and a workflow. npm checks the workflow that started the run, not the one that publishes, so both are trusted: `tag_and_release.yml`, which calls `build_release.yml` for a release, and `build_release.yml`, which runs on its own for a tag pushed by hand. There is no npm token, and the package's settings disallow tokens, so only these workflows, or a maintainer with two-factor authentication, can publish. +- **Trusted publishing.** npm takes the publish from the workflow over OIDC, with no token, and records the package's provenance. It is configured on the package's settings on npmjs.com, naming this repository and a workflow. npm checks the workflow that started the run: `build_release.yml`, which a tag push starts. There is no npm token, and the package's settings disallow tokens, so only this workflow, or a maintainer with two-factor authentication, can publish. - **Any other build** packs it without publishing, so a broken package shows before a release does. ## Versions diff --git a/manifest.yml b/manifest.yml index 941eabc0..e5d3f566 100644 --- a/manifest.yml +++ b/manifest.yml @@ -4,7 +4,7 @@ # which triggers build_release.yml to download the pinned sidecars, cross-build, # and publish. Always bump `version` when changing a pin — otherwise the tag # already exists and no new release is cut. -version: 0.6.0 +version: 0.6.1 sidecars: anfra_node: anfra-node-v0.3.0 # holistics/holistics-core release tag (anfra_node_build_binaries.yml) canal_query: query-v2.15.0 # holistics/canal release tag (build_and_release_binaries.yml) diff --git a/web/sdk/package.json b/web/sdk/package.json index 473b879d..3d1eb0e5 100644 --- a/web/sdk/package.json +++ b/web/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@holistics/anfra-sdk", - "version": "0.6.0", + "version": "0.6.1", "description": "Data Apps on anfra: the authoring layer (app), what hosts one (host), the core API client (api), and their shared contract (common)", "license": "Apache-2.0", "repository": {