Release #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Exact release tag, for example v2.3.0" | |
| required: true | |
| type: string | |
| action: | |
| description: "Create a new tag first, or retry an existing tagged release" | |
| required: true | |
| default: prepare-and-release | |
| type: choice | |
| options: | |
| - prepare-and-release | |
| - retry-release | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| id-token: write | |
| concurrency: | |
| group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} | |
| jobs: | |
| prepare_tag: | |
| if: github.event_name == 'workflow_dispatch' && inputs.action == 'prepare-and-release' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate requested tag | |
| shell: bash | |
| run: | | |
| if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "::error::Invalid release tag: $RELEASE_TAG" | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| - name: Validate main and ensure the tag is new | |
| shell: bash | |
| run: | | |
| git fetch origin main --tags --force | |
| git checkout --detach origin/main | |
| expected_tag="v$(node -p 'require("./package.json").version')" | |
| if [ "$RELEASE_TAG" != "$expected_tag" ]; then | |
| echo "::error::$RELEASE_TAG does not match package.json version tag $expected_tag" | |
| exit 1 | |
| fi | |
| if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then | |
| echo "::error::$RELEASE_TAG already exists. Use action=retry-release instead" | |
| exit 1 | |
| fi | |
| - run: npm run check | |
| - run: npm test | |
| - run: npm run test:package | |
| - name: Validate release metadata before tagging | |
| env: | |
| RELEASE_REF_TYPE: tag | |
| RELEASE_REF_NAME: ${{ env.RELEASE_TAG }} | |
| run: node scripts/release-metadata.js --notes "$RUNNER_TEMP/release-notes.md" | |
| - name: Create and push the validated tag | |
| shell: bash | |
| run: | | |
| validated_commit="$(git rev-parse HEAD)" | |
| main_commit="$(git rev-parse origin/main)" | |
| if [ "$validated_commit" != "$main_commit" ]; then | |
| echo "::error::Refusing to tag a commit that is not the exact current main commit" | |
| exit 1 | |
| fi | |
| if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then | |
| echo "::error::$RELEASE_TAG appeared during validation; refusing to move or overwrite it" | |
| exit 1 | |
| fi | |
| git tag "$RELEASE_TAG" "$validated_commit" | |
| git push origin "refs/tags/$RELEASE_TAG" | |
| remote_commit="$(git ls-remote --tags origin "refs/tags/$RELEASE_TAG" | awk '{print $1}')" | |
| if [ "$remote_commit" != "$validated_commit" ]; then | |
| echo "::error::Remote tag verification failed for $RELEASE_TAG" | |
| exit 1 | |
| fi | |
| echo "Created $RELEASE_TAG at $validated_commit. Publication starts only after this job completes." | |
| release: | |
| needs: prepare_tag | |
| if: >- | |
| always() && | |
| github.repository == 'Honguan/codex-model-router' && | |
| ( | |
| github.event_name == 'push' || | |
| (github.event_name == 'workflow_dispatch' && inputs.action == 'retry-release') || | |
| needs.prepare_tag.result == 'success' | |
| ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate release request | |
| shell: bash | |
| run: | | |
| if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "::error::Invalid release tag: $RELEASE_TAG" | |
| exit 1 | |
| fi | |
| if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ inputs.action }}" = "prepare-and-release" ] && [ "${{ needs.prepare_tag.result }}" != "success" ]; then | |
| echo "::error::Tag preparation did not complete successfully" | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ env.RELEASE_TAG }} | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| package-manager-cache: false | |
| registry-url: https://registry.npmjs.org | |
| - name: Use an OIDC-capable npm CLI | |
| run: npm install --global npm@11.18.0 | |
| - name: Verify the existing release tag | |
| shell: bash | |
| run: | | |
| git fetch origin main --tags --force | |
| if ! git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then | |
| echo "::error::$RELEASE_TAG does not exist on the remote; publication is forbidden" | |
| exit 1 | |
| fi | |
| tag_commit="$(git rev-list -n 1 "$RELEASE_TAG")" | |
| remote_commit="$(git ls-remote --tags origin "refs/tags/$RELEASE_TAG" | awk '{print $1}')" | |
| if [ "$tag_commit" != "$remote_commit" ]; then | |
| echo "::error::Local and remote tag commits differ for $RELEASE_TAG" | |
| exit 1 | |
| fi | |
| if [ "$(git rev-parse HEAD)" != "$tag_commit" ]; then | |
| echo "::error::Checkout is not at the exact $RELEASE_TAG commit" | |
| exit 1 | |
| fi | |
| if ! git merge-base --is-ancestor "$tag_commit" origin/main; then | |
| echo "::error::$RELEASE_TAG is not reachable from main" | |
| exit 1 | |
| fi | |
| expected_tag="v$(node -p 'require("./package.json").version')" | |
| if [ "$RELEASE_TAG" != "$expected_tag" ]; then | |
| echo "::error::$RELEASE_TAG does not match package.json version tag $expected_tag" | |
| exit 1 | |
| fi | |
| - run: npm run check | |
| - run: npm test | |
| - run: npm run test:package | |
| - name: Validate release metadata and extract notes | |
| id: metadata | |
| env: | |
| RELEASE_REF_TYPE: tag | |
| RELEASE_REF_NAME: ${{ env.RELEASE_TAG }} | |
| run: node scripts/release-metadata.js --notes "$RUNNER_TEMP/release-notes.md" | |
| - name: Inspect npm package state | |
| id: npm | |
| env: | |
| PACKAGE_NAME: ${{ steps.metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.metadata.outputs.version }} | |
| shell: bash | |
| run: | | |
| existing_name="$(npm view "$PACKAGE_NAME" name 2>/dev/null || true)" | |
| if [ "$existing_name" != "$PACKAGE_NAME" ]; then | |
| echo "::error::$PACKAGE_NAME does not exist on npm. Trusted Publishing must be configured on an existing package" | |
| exit 1 | |
| fi | |
| published="$(npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version 2>/dev/null || true)" | |
| if [ "$published" = "$PACKAGE_VERSION" ]; then | |
| echo "publish=false" >> "$GITHUB_OUTPUT" | |
| echo "${PACKAGE_NAME}@${PACKAGE_VERSION} already exists on npm" | |
| else | |
| echo "publish=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish to npm with Trusted Publishing and provenance | |
| if: steps.npm.outputs.publish == 'true' | |
| run: npm publish --access public --provenance | |
| - name: Wait for npm registry propagation | |
| env: | |
| PACKAGE_NAME: ${{ steps.metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.metadata.outputs.version }} | |
| shell: bash | |
| run: | | |
| for attempt in 1 2 3 4 5 6 7 8 9 10 11 12; do | |
| published="$(npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version 2>/dev/null || true)" | |
| if [ "$published" = "$PACKAGE_VERSION" ]; then | |
| echo "Verified ${PACKAGE_NAME}@${PACKAGE_VERSION} on npm" | |
| exit 0 | |
| fi | |
| echo "Waiting for npm registry propagation (${attempt}/12)" | |
| sleep 10 | |
| done | |
| echo "::error::Unable to verify ${PACKAGE_NAME}@${PACKAGE_VERSION} on npm" | |
| exit 1 | |
| - name: Verify the public package end to end | |
| env: | |
| PACKAGE_NAME: ${{ steps.metadata.outputs.name }} | |
| PACKAGE_VERSION: ${{ steps.metadata.outputs.version }} | |
| run: node scripts/verify-published.js "$PACKAGE_NAME" "$PACKAGE_VERSION" | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_NOTES: ${{ steps.metadata.outputs.notes }} | |
| shell: bash | |
| run: | | |
| if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then | |
| echo "$RELEASE_TAG already exists" | |
| else | |
| gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes-file "$RELEASE_NOTES" | |
| fi |