@@ -9,11 +9,62 @@ permissions:
99 contents : read
1010
1111# Cancel an in-flight run when a newer commit is pushed to the same PR/branch.
12+ # Main runs may publish releases, so do not cancel them once started.
1213concurrency :
1314 group : ci-${{ github.workflow }}-${{ github.ref }}
14- cancel-in-progress : true
15+ cancel-in-progress : ${{ github.event_name == 'pull_request' }}
1516
1617jobs :
18+ detect-alpha-release :
19+ if : github.event_name == 'push' && github.ref == 'refs/heads/main'
20+ runs-on : depot-ubuntu-24.04-4
21+ timeout-minutes : 5
22+ outputs :
23+ should_release : ${{ steps.detect.outputs.should_release }}
24+ steps :
25+ - uses : actions/checkout@v6
26+ with :
27+ fetch-depth : 0
28+
29+ - name : Detect release-relevant changes
30+ id : detect
31+ env :
32+ BEFORE : ${{ github.event.before }}
33+ run : |
34+ set -euo pipefail
35+ base="$BEFORE"
36+ if [ -z "$base" ] || [[ "$base" =~ ^0+$ ]] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
37+ base="$(git rev-parse HEAD^ 2>/dev/null || true)"
38+ fi
39+ if [ -n "$base" ]; then
40+ git diff --name-only "$base" HEAD > "$RUNNER_TEMP/release-changed-files"
41+ else
42+ git diff-tree --no-commit-id --name-only -r HEAD > "$RUNNER_TEMP/release-changed-files"
43+ fi
44+
45+ node --input-type=module <<'NODE'
46+ import { appendFileSync, readFileSync } from "node:fs";
47+ import { PUBLIC_PACKAGE_MANIFESTS } from "./scripts/release-alpha-train.mjs";
48+
49+ const changed = readFileSync(`${process.env.RUNNER_TEMP}/release-changed-files`, "utf8")
50+ .split("\n")
51+ .map((line) => line.trim())
52+ .filter(Boolean);
53+ const publicPackageReleasePaths = new Set(
54+ PUBLIC_PACKAGE_MANIFESTS.flatMap((manifestPath) => [
55+ manifestPath,
56+ manifestPath.replace(/package\.json$/, "CHANGELOG.md"),
57+ ]),
58+ );
59+ const releaseRelevant = changed.filter(
60+ (path) => path.startsWith(".changeset/") || publicPackageReleasePaths.has(path),
61+ );
62+ const shouldRelease = releaseRelevant.length > 0;
63+
64+ console.log(`release-relevant changes: ${releaseRelevant.join(", ") || "none"}`);
65+ appendFileSync(process.env.GITHUB_OUTPUT, `should_release=${String(shouldRelease)}\n`);
66+ NODE
67+
1768 openapi-lint :
1869 runs-on : depot-ubuntu-24.04-4
1970 timeout-minutes : 5
@@ -260,7 +311,7 @@ jobs:
260311 # changeset, so no consumer-visible change merges without a release note.
261312 # Private packages (apps, demos, mocks, lint, design-tokens, ui-react, ...)
262313 # are never published and so do not require a changeset. Publishing itself
263- # runs on pushes to main in release-npm.yml .
314+ # runs on pushes to main in the release-alpha-train job below .
264315 #
265316 # The check lives in scripts/check-changeset-required.mjs so the publishable
266317 # package list and rules are testable and shared, rather than duplicated in
@@ -701,3 +752,195 @@ jobs:
701752 ) || true
702753 fi
703754 if [ -n "${VERDACCIO_PID:-}" ]; then kill "$VERDACCIO_PID" 2>/dev/null || true; fi
755+
756+ ci-success :
757+ if : always()
758+ runs-on : depot-ubuntu-24.04-4
759+ timeout-minutes : 5
760+ needs :
761+ - openapi-lint
762+ - go-unit-test
763+ - go-integration-test-postgres
764+ - go-integration-test-spanner
765+ - node-check
766+ - changeset-check
767+ - node-e2e
768+ - goreleaser-snapshot
769+ - npm-pack-smoke
770+ - go-smoke-test-embedded-postgres
771+ - quickstart-smoke
772+ - consumer-journey-e2e
773+ steps :
774+ - name : Check CI gate results
775+ env :
776+ NEEDS_JSON : ${{ toJson(needs) }}
777+ run : |
778+ node <<'NODE'
779+ const needs = JSON.parse(process.env.NEEDS_JSON);
780+ const allowedSkipped = new Set(["changeset-check"]);
781+ const failed = Object.entries(needs).filter(([name, job]) => {
782+ if (job.result === "success") return false;
783+ if (job.result === "skipped" && allowedSkipped.has(name)) return false;
784+ return true;
785+ });
786+
787+ if (failed.length > 0) {
788+ for (const [name, job] of failed) {
789+ console.error(`${name}: ${job.result}`);
790+ }
791+ process.exit(1);
792+ }
793+
794+ console.log("CI gate passed");
795+ NODE
796+
797+ release-alpha-train :
798+ if : github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-alpha-release.outputs.should_release == 'true'
799+ runs-on : depot-ubuntu-24.04-8
800+ needs : [detect-alpha-release, ci-success]
801+ permissions :
802+ contents : write
803+ pull-requests : write
804+ packages : write
805+ id-token : write
806+ steps :
807+ # Mint a short-lived installation token for the release GitHub App so the
808+ # changesets PR/commits trigger CI (GITHUB_TOKEN-authored ones do not).
809+ - name : Generate a token for the release app
810+ id : app-token
811+ uses : actions/create-github-app-token@v2
812+ with :
813+ app-id : ${{ secrets.RELEASE_APP_ID }}
814+ private-key : ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
815+
816+ - name : Checkout
817+ uses : actions/checkout@v6
818+ with :
819+ fetch-depth : 0
820+ token : ${{ steps.app-token.outputs.token }}
821+
822+ - name : Set up pnpm
823+ uses : pnpm/action-setup@v5
824+ with :
825+ run_install : false
826+
827+ - name : Set up Node.js
828+ uses : actions/setup-node@v6
829+ with :
830+ node-version-file : .nvmrc
831+ registry-url : " https://registry.npmjs.org"
832+ cache : pnpm
833+
834+ # Trusted publishing requires npm >= 11.5.1; the pinned pnpm delegates
835+ # the actual publish to the npm CLI, so the runner's npm must be current.
836+ - name : Update npm for trusted publishing (OIDC)
837+ run : npm install -g npm@latest
838+
839+ - name : Install dependencies
840+ run : corepack pnpm install --frozen-lockfile
841+
842+ - name : Build packages
843+ run : corepack pnpm nx run-many -t build
844+
845+ - name : Create release PR or publish to npm
846+ id : changesets
847+ uses : changesets/action@v1
848+ with :
849+ version : corepack pnpm changeset version
850+ publish : corepack pnpm changeset publish
851+ title : " chore: version packages"
852+ commit : " chore: version packages"
853+ createGithubReleases : false
854+ env :
855+ # App token (not GITHUB_TOKEN) so the Version Packages PR triggers CI.
856+ GITHUB_TOKEN : ${{ steps.app-token.outputs.token }}
857+ # Empty so the action does not pass an undefined token; OIDC handles auth.
858+ NPM_TOKEN : " "
859+ # npm provenance is only supported for public source repositories.
860+ # Keep trusted publishing via OIDC, but disable provenance until this
861+ # repository is public.
862+ NPM_CONFIG_PROVENANCE : " false"
863+
864+ - name : Inspect alpha release train candidate
865+ id : alpha-status
866+ env :
867+ PUBLISHED : ${{ steps.changesets.outputs.published }}
868+ run : |
869+ set -euo pipefail
870+ alpha_env="$RUNNER_TEMP/alpha-release-status.env"
871+ node scripts/release-alpha-train.mjs status --published "$PUBLISHED" --remote false | tee "$alpha_env"
872+ cat "$alpha_env" >> "$GITHUB_OUTPUT"
873+
874+ - name : Login to GHCR
875+ if : ${{ steps.alpha-status.outputs.should_complete == 'true' }}
876+ uses : docker/login-action@v4
877+ with :
878+ registry : ghcr.io
879+ username : ${{ github.actor }}
880+ password : ${{ secrets.GITHUB_TOKEN }}
881+
882+ - name : Prepare alpha release train
883+ if : ${{ steps.alpha-status.outputs.should_complete == 'true' }}
884+ id : alpha
885+ env :
886+ PUBLISHED : ${{ steps.changesets.outputs.published }}
887+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
888+ run : |
889+ set -euo pipefail
890+ alpha_env="$RUNNER_TEMP/alpha-release.env"
891+ node scripts/release-alpha-train.mjs prepare --published "$PUBLISHED" --out-dir "$RUNNER_TEMP/alpha-release" | tee "$alpha_env"
892+ cat "$alpha_env" >> "$GITHUB_OUTPUT"
893+
894+ - name : Set up QEMU
895+ if : ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
896+ uses : docker/setup-qemu-action@v4
897+
898+ - name : Set up Docker Buildx
899+ if : ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
900+ uses : docker/setup-buildx-action@v4
901+
902+ - name : Create and push Go release tag
903+ if : ${{ steps.alpha.outputs.create_tag == 'true' }}
904+ env :
905+ TAG : ${{ steps.alpha.outputs.tag }}
906+ TITLE : ${{ steps.alpha.outputs.title }}
907+ run : |
908+ set -euo pipefail
909+ git config user.name "zitadel-release"
910+ git config user.email "noreply@zitadel.com"
911+ git tag -a "$TAG" -m "$TITLE"
912+ git push origin "$TAG"
913+
914+ - name : Prune npm package tags for GoReleaser
915+ if : ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
916+ run : |
917+ set -euo pipefail
918+ git tag -l '@zitadel/*' | while read -r tag; do
919+ git tag -d "$tag"
920+ done
921+
922+ - name : Run GoReleaser
923+ if : ${{ steps.alpha.outputs.run_goreleaser == 'true' }}
924+ uses : goreleaser/goreleaser-action@v7
925+ with :
926+ distribution : goreleaser
927+ version : " ~> v2"
928+ args : release --clean
929+ env :
930+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
931+
932+ - name : Update GitHub Release notes
933+ if : ${{ steps.alpha.outputs.update_release == 'true' }}
934+ env :
935+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
936+ TAG : ${{ steps.alpha.outputs.tag }}
937+ TITLE : ${{ steps.alpha.outputs.title }}
938+ NOTES_PATH : ${{ steps.alpha.outputs.notes_path }}
939+ run : |
940+ set -euo pipefail
941+ gh release edit "$TAG" \
942+ --draft \
943+ --prerelease \
944+ --latest=false \
945+ --title "$TITLE" \
946+ --notes-file "$NOTES_PATH"
0 commit comments