From f4e34d40007b48dd973d00b73c2370942e44c991 Mon Sep 17 00:00:00 2001 From: Ted Kim Date: Tue, 7 Jul 2026 18:29:38 -0400 Subject: [PATCH 1/2] ci: add release please flow --- .github/workflows/publish-cli.yml | 142 +++++---------------------- .github/workflows/release-please.yml | 23 +++++ .release-please-manifest.json | 3 + CONTRIBUTING.md | 22 ++++- release-please-config.json | 11 +++ 5 files changed, 76 insertions(+), 125 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 28aa0fb..08cce57 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -2,8 +2,6 @@ name: Publish CLI on: push: - branches: - - main tags: - "v*.*.*" - "!v*.*.*-nightly.*" @@ -27,15 +25,11 @@ jobs: - name: Validate release ref run: | set -euo pipefail - if [ "$GITHUB_REF" = "refs/heads/main" ]; then - exit 0 - fi STABLE_SEMVER_RE='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' - NIGHTLY_SEMVER_RE='^v0\.0\.[0-9]+-nightly\.[0-9]{8}\.[0-9]+$' - if [[ ! "$GITHUB_REF_NAME" =~ $STABLE_SEMVER_RE ]] && [[ ! "$GITHUB_REF_NAME" =~ $NIGHTLY_SEMVER_RE ]]; then + if [[ ! "$GITHUB_REF_NAME" =~ $STABLE_SEMVER_RE ]]; then echo "Unsupported release tag: ${GITHUB_REF_NAME}" - echo "Release tags must use stable SemVer form vMAJOR.MINOR.PATCH or nightly form v0.0.N-nightly.YYYYMMDD.NUMBER." + echo "Release tags must use stable SemVer form vMAJOR.MINOR.PATCH." exit 1 fi @@ -55,53 +49,37 @@ jobs: release_tag: ${{ steps.release.outputs.release_tag }} release_title: ${{ steps.release.outputs.release_title }} cli_version: ${{ steps.release.outputs.cli_version }} + npm_version: ${{ steps.release.outputs.npm_version }} prerelease: ${{ steps.release.outputs.prerelease }} latest: ${{ steps.release.outputs.latest }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: "24" - name: Resolve release version id: release run: | set -euo pipefail - NIGHTLY_SEMVER_RE='^v0\.0\.[0-9]+-nightly\.[0-9]{8}\.[0-9]+$' - - if [ "$GITHUB_REF" = "refs/heads/main" ]; then - DATE_PART="$(date -u +%Y%m%d)" - LATEST_STABLE_PATCH="$({ - git tag --list 'v0.0.*' || true - } | awk '/^v0\.0\.[0-9]+$/ { sub(/^v0\.0\./, "", $0); print $0 }' | sort -n | tail -1)" - NEXT_PATCH=$(( ${LATEST_STABLE_PATCH:-0} + 1 )) - NIGHTLY_PREFIX="v0.0.${NEXT_PATCH}-nightly.${DATE_PART}." - LATEST_NIGHTLY_NUMBER=0 - while IFS= read -r tag; do - suffix="${tag#${NIGHTLY_PREFIX}}" - if [[ "$suffix" =~ ^[0-9]+$ ]] && (( 10#$suffix > LATEST_NIGHTLY_NUMBER )); then - LATEST_NIGHTLY_NUMBER=$((10#$suffix)) - fi - done < <(git tag --list "${NIGHTLY_PREFIX}*") - RELEASE_TAG="${NIGHTLY_PREFIX}$((LATEST_NIGHTLY_NUMBER + 1))" - PRERELEASE="true" - LATEST="false" - else - RELEASE_TAG="$GITHUB_REF_NAME" - if [[ "$GITHUB_REF_NAME" =~ $NIGHTLY_SEMVER_RE ]]; then - PRERELEASE="true" - LATEST="false" - else - PRERELEASE="false" - LATEST="auto" - fi + RELEASE_TAG="$GITHUB_REF_NAME" + NPM_VERSION="$(node -p "require('./package.json').version")" + + if [ "v${NPM_VERSION}" != "$RELEASE_TAG" ]; then + echo "Release tag ${RELEASE_TAG} does not match package.json version ${NPM_VERSION}." + echo "Stable releases must be created from the Release Please release PR commit." + exit 1 fi { echo "release_tag=${RELEASE_TAG}" echo "release_title=${RELEASE_TAG}" echo "cli_version=${RELEASE_TAG}" - echo "prerelease=${PRERELEASE}" - echo "latest=${LATEST}" + echo "npm_version=${NPM_VERSION}" + echo "prerelease=false" + echo "latest=auto" } >> "$GITHUB_OUTPUT" check: @@ -276,59 +254,6 @@ jobs: fi } - append_historical_assets() { - local version="$1" - local asset - local versioned_asset - local base - local assets=() - - while IFS= read -r asset; do - assets+=("${asset##*/}") - done < <(find dist/release-assets -maxdepth 1 -type f | sort) - - for asset in "${assets[@]}"; do - case "$asset" in - SHA256SUMS) - continue - ;; - *.exe.sigstore.json) - base="${asset%.exe.sigstore.json}" - versioned_asset="${base}-${version}.exe.sigstore.json" - ;; - *.sh.sigstore.json) - base="${asset%.sh.sigstore.json}" - versioned_asset="${base}-${version}.sh.sigstore.json" - ;; - *.sigstore.json) - base="${asset%.sigstore.json}" - versioned_asset="${base}-${version}.sigstore.json" - ;; - *.exe) - base="${asset%.exe}" - versioned_asset="${base}-${version}.exe" - ;; - *.sh) - base="${asset%.sh}" - versioned_asset="${base}-${version}.sh" - ;; - *) - versioned_asset="${asset}-${version}" - ;; - esac - cp "dist/release-assets/${asset}" "dist/release-assets/${versioned_asset}" - done - - ( - cd dist/release-assets - local versioned_assets=() - while IFS= read -r asset; do - versioned_assets+=("${asset#./}") - done < <(find . -maxdepth 1 -type f -name "*${version}*" ! -name "SHA256SUMS-${version}" | sort) - shasum -a 256 "${versioned_assets[@]}" > "SHA256SUMS-${version}" - ) - } - RELEASE_FLAGS=() if [ "$RELEASE_PRERELEASE" = "true" ]; then RELEASE_FLAGS+=(--prerelease) @@ -338,23 +263,7 @@ jobs: fi RELEASE_NOTES="" - if [ "$RELEASE_PRERELEASE" = "true" ]; then - RELEASE_NOTES="Automated nightly build from ${GITHUB_SHA}." - fi - - if [ "$GITHUB_REF" = "refs/heads/main" ]; then - NIGHTLY_ALIAS_NOTES="$(printf 'Latest nightly build: %s\n\nCommit: %s' "$RELEASE_TITLE" "$GITHUB_SHA")" - if ! git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1; then - git tag "$RELEASE_TAG" "$GITHUB_SHA" - git push origin "refs/tags/${RELEASE_TAG}" - fi - append_historical_assets "$RELEASE_TAG" - git tag -f nightly "$GITHUB_SHA" - git push origin refs/tags/nightly --force - publish_release nightly "$RELEASE_TITLE" "$NIGHTLY_ALIAS_NOTES" --prerelease --latest=false - else - publish_release "$RELEASE_TAG" "$RELEASE_TITLE" "$RELEASE_NOTES" "${RELEASE_FLAGS[@]}" - fi + publish_release "$RELEASE_TAG" "$RELEASE_TITLE" "$RELEASE_NOTES" "${RELEASE_FLAGS[@]}" - name: Verify uploaded Linux binary run: | @@ -362,9 +271,6 @@ jobs: TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT DOWNLOAD_RELEASE_TAG="$RELEASE_TAG" - if [ "$GITHUB_REF" = "refs/heads/main" ]; then - DOWNLOAD_RELEASE_TAG="nightly" - fi gh release download "$DOWNLOAD_RELEASE_TAG" \ --pattern "volcano-linux-amd64*" \ --dir "$TMP_DIR" @@ -401,24 +307,20 @@ jobs: - name: Upgrade npm run: npm install -g npm@11 - - name: Resolve npm version + - name: Verify npm package version id: npm_version env: CLI_VERSION: ${{ needs.resolve-release.outputs.cli_version }} run: | set -euo pipefail - VERSION="${CLI_VERSION#v}" - if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Refusing to publish non-stable version to npm: ${CLI_VERSION}" + VERSION="$(node -p "require('./package.json').version")" + if [ "v${VERSION}" != "$CLI_VERSION" ]; then + echo "Refusing to publish npm version ${VERSION} for release ${CLI_VERSION}." + echo "package.json must match the stable release tag." exit 1 fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - name: Set package version - run: | - npm version "${{ steps.npm_version.outputs.version }}" \ - --no-git-tag-version --allow-same-version - - name: Verify release assets exist env: VERSION: ${{ steps.npm_version.outputs.version }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..3d3c655 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,23 @@ +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + # Use a PAT so Release Please tags/releases trigger publish-cli.yml. + # GITHUB_TOKEN-created refs do not trigger follow-on workflow runs. + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..63c033c --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.4" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 402a00e..30e5165 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,13 +88,25 @@ The publish workflow builds signed binaries for `linux-amd64`, `linux-arm64`, `macos-amd64`, `macos-arm64`, and `windows-amd64`. It publishes stable release assets from SemVer tags. -To cut a stable release, merge the release commit to `main`, tag that commit -with a SemVer tag such as `v1.2.3`, and push the tag. Prerelease and build -metadata tags are not stable release tags. The publish workflow also rejects -stable release tags that are not reachable from `origin/main`. +Stable releases are managed by Release Please. Normal feature and fix commits +land on `main` using Conventional Commit messages. Release Please opens or +updates a release PR that bumps `package.json`, updates +`.release-please-manifest.json`, and updates `CHANGELOG.md`. Merge that release +PR to cut a stable release. Release Please then creates the SemVer tag and +GitHub Release, such as `v1.2.3`, and the tag-triggered publish workflow +attaches signed binaries and publishes npm. -Required repository variable for stable release publishing: +Do not manually bump `package.json` outside a Release Please release PR. The +publish workflow rejects stable tags when `package.json` does not match the tag, +or when the tag is not reachable from `origin/main`. +Prerelease and build metadata tags are not stable release tags. Nightly builds +are intentionally separate from the stable Release Please flow. + +Required repository secret and variable for stable release publishing: + +- `RELEASE_PLEASE_TOKEN` (a GitHub token that can create release PRs, tags, and + releases that trigger follow-on workflows) - `VOLCANO_FIRST_PARTY_DEVICE_CLIENT_ID_PRODUCTION` Release assets include platform binaries, adjacent `.sigstore.json` bundles, diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..27fe16f --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "bootstrap-sha": "363ccef4a5c92b78795ee65cab4dd491685e0030", + "include-component-in-tag": false, + "packages": { + ".": { + "release-type": "node", + "package-name": "@volcano.dev/cli", + "changelog-path": "CHANGELOG.md" + } + } +} From 4d4b9126c053435dcffe98ae754008a13a8e2833 Mon Sep 17 00:00:00 2001 From: Ted Kim Date: Tue, 7 Jul 2026 18:59:29 -0400 Subject: [PATCH 2/2] ci: address release flow review --- .github/workflows/publish-cli.yml | 120 ++++++++++++++++++++++++--- .github/workflows/release-please.yml | 2 +- CONTRIBUTING.md | 3 +- 3 files changed, 112 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 08cce57..465be78 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -2,6 +2,8 @@ name: Publish CLI on: push: + branches: + - main tags: - "v*.*.*" - "!v*.*.*-nightly.*" @@ -25,6 +27,9 @@ jobs: - name: Validate release ref run: | set -euo pipefail + if [ "$GITHUB_REF" = "refs/heads/main" ]; then + exit 0 + fi STABLE_SEMVER_RE='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' if [[ ! "$GITHUB_REF_NAME" =~ $STABLE_SEMVER_RE ]]; then @@ -49,7 +54,6 @@ jobs: release_tag: ${{ steps.release.outputs.release_tag }} release_title: ${{ steps.release.outputs.release_title }} cli_version: ${{ steps.release.outputs.cli_version }} - npm_version: ${{ steps.release.outputs.npm_version }} prerelease: ${{ steps.release.outputs.prerelease }} latest: ${{ steps.release.outputs.latest }} steps: @@ -64,22 +68,44 @@ jobs: id: release run: | set -euo pipefail - RELEASE_TAG="$GITHUB_REF_NAME" - NPM_VERSION="$(node -p "require('./package.json').version")" - if [ "v${NPM_VERSION}" != "$RELEASE_TAG" ]; then - echo "Release tag ${RELEASE_TAG} does not match package.json version ${NPM_VERSION}." - echo "Stable releases must be created from the Release Please release PR commit." - exit 1 + if [ "$GITHUB_REF" = "refs/heads/main" ]; then + DATE_PART="$(date -u +%Y%m%d)" + LATEST_STABLE_PATCH="$({ + git tag --list 'v0.0.*' || true + } | awk '/^v0\.0\.[0-9]+$/ { sub(/^v0\.0\./, "", $0); print $0 }' | sort -n | tail -1)" + NEXT_PATCH=$(( ${LATEST_STABLE_PATCH:-0} + 1 )) + NIGHTLY_PREFIX="v0.0.${NEXT_PATCH}-nightly.${DATE_PART}." + LATEST_NIGHTLY_NUMBER=0 + while IFS= read -r tag; do + suffix="${tag#${NIGHTLY_PREFIX}}" + if [[ "$suffix" =~ ^[0-9]+$ ]] && (( 10#$suffix > LATEST_NIGHTLY_NUMBER )); then + LATEST_NIGHTLY_NUMBER=$((10#$suffix)) + fi + done < <(git tag --list "${NIGHTLY_PREFIX}*") + RELEASE_TAG="${NIGHTLY_PREFIX}$((LATEST_NIGHTLY_NUMBER + 1))" + PRERELEASE="true" + LATEST="false" + else + RELEASE_TAG="$GITHUB_REF_NAME" + NPM_VERSION="$(node -p "require('./package.json').version")" + + if [ "v${NPM_VERSION}" != "$RELEASE_TAG" ]; then + echo "Release tag ${RELEASE_TAG} does not match package.json version ${NPM_VERSION}." + echo "Stable releases must be created from the Release Please release PR commit." + exit 1 + fi + + PRERELEASE="false" + LATEST="auto" fi { echo "release_tag=${RELEASE_TAG}" echo "release_title=${RELEASE_TAG}" echo "cli_version=${RELEASE_TAG}" - echo "npm_version=${NPM_VERSION}" - echo "prerelease=false" - echo "latest=auto" + echo "prerelease=${PRERELEASE}" + echo "latest=${LATEST}" } >> "$GITHUB_OUTPUT" check: @@ -254,6 +280,59 @@ jobs: fi } + append_historical_assets() { + local version="$1" + local asset + local versioned_asset + local base + local assets=() + + while IFS= read -r asset; do + assets+=("${asset##*/}") + done < <(find dist/release-assets -maxdepth 1 -type f | sort) + + for asset in "${assets[@]}"; do + case "$asset" in + SHA256SUMS) + continue + ;; + *.exe.sigstore.json) + base="${asset%.exe.sigstore.json}" + versioned_asset="${base}-${version}.exe.sigstore.json" + ;; + *.sh.sigstore.json) + base="${asset%.sh.sigstore.json}" + versioned_asset="${base}-${version}.sh.sigstore.json" + ;; + *.sigstore.json) + base="${asset%.sigstore.json}" + versioned_asset="${base}-${version}.sigstore.json" + ;; + *.exe) + base="${asset%.exe}" + versioned_asset="${base}-${version}.exe" + ;; + *.sh) + base="${asset%.sh}" + versioned_asset="${base}-${version}.sh" + ;; + *) + versioned_asset="${asset}-${version}" + ;; + esac + cp "dist/release-assets/${asset}" "dist/release-assets/${versioned_asset}" + done + + ( + cd dist/release-assets + local versioned_assets=() + while IFS= read -r asset; do + versioned_assets+=("${asset#./}") + done < <(find . -maxdepth 1 -type f -name "*${version}*" ! -name "SHA256SUMS-${version}" | sort) + shasum -a 256 "${versioned_assets[@]}" > "SHA256SUMS-${version}" + ) + } + RELEASE_FLAGS=() if [ "$RELEASE_PRERELEASE" = "true" ]; then RELEASE_FLAGS+=(--prerelease) @@ -263,7 +342,23 @@ jobs: fi RELEASE_NOTES="" - publish_release "$RELEASE_TAG" "$RELEASE_TITLE" "$RELEASE_NOTES" "${RELEASE_FLAGS[@]}" + if [ "$RELEASE_PRERELEASE" = "true" ]; then + RELEASE_NOTES="Automated nightly build from ${GITHUB_SHA}." + fi + + if [ "$GITHUB_REF" = "refs/heads/main" ]; then + NIGHTLY_ALIAS_NOTES="$(printf 'Latest nightly build: %s\n\nCommit: %s' "$RELEASE_TITLE" "$GITHUB_SHA")" + if ! git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1; then + git tag "$RELEASE_TAG" "$GITHUB_SHA" + git push origin "refs/tags/${RELEASE_TAG}" + fi + append_historical_assets "$RELEASE_TAG" + git tag -f nightly "$GITHUB_SHA" + git push origin refs/tags/nightly --force + publish_release nightly "$RELEASE_TITLE" "$NIGHTLY_ALIAS_NOTES" --prerelease --latest=false + else + publish_release "$RELEASE_TAG" "$RELEASE_TITLE" "$RELEASE_NOTES" "${RELEASE_FLAGS[@]}" + fi - name: Verify uploaded Linux binary run: | @@ -271,6 +366,9 @@ jobs: TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT DOWNLOAD_RELEASE_TAG="$RELEASE_TAG" + if [ "$GITHUB_REF" = "refs/heads/main" ]; then + DOWNLOAD_RELEASE_TAG="nightly" + fi gh release download "$DOWNLOAD_RELEASE_TAG" \ --pattern "volcano-linux-amd64*" \ --dir "$TMP_DIR" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3d3c655..86b4c7a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,7 +14,7 @@ jobs: release-please: runs-on: ubuntu-latest steps: - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4 with: # Use a PAT so Release Please tags/releases trigger publish-cli.yml. # GITHUB_TOKEN-created refs do not trigger follow-on workflow runs. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30e5165..8b4ba02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,8 @@ publish workflow rejects stable tags when `package.json` does not match the tag, or when the tag is not reachable from `origin/main`. Prerelease and build metadata tags are not stable release tags. Nightly builds -are intentionally separate from the stable Release Please flow. +remain automated from `main` and publish to the mutable `nightly` GitHub Release; +they are not npm releases and are separate from the stable Release Please flow. Required repository secret and variable for stable release publishing: