Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 15 additions & 24 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -194,17 +186,16 @@ 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
permissions:
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
Expand Down
38 changes: 12 additions & 26 deletions .github/workflows/tag_and_release.yml
Original file line number Diff line number Diff line change
@@ -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<version>` 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<version>`; 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:
Expand All @@ -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 }}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/designs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>` 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<version>` 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<version>`), it releases that tag again; dispatched on a branch, it is a test build, which uploads artifacts and publishes nothing.

## The build

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion web/sdk/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
Loading