Merge pull request #2735 from artokun/release/0.52.172 #483
Workflow file for this run
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: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: {} | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write | |
| contents: write | |
| steps: | |
| # fetch-depth: 0 for the same reason as ci.yml: `npm test` runs check:blog-stale, which | |
| # reads git history. On the default depth-1 clone it cannot, and now skips — which would | |
| # make it inert on exactly the path this file says must not enforce less than CI. | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| registry-url: https://registry.npmjs.org | |
| - run: npm ci | |
| - run: npm run build | |
| - run: npm test | |
| # These three run in ci.yml on every PR, and used to be ABSENT here — so a | |
| # tag could publish what a pull request would have blocked. `npm test` does | |
| # not cover any of them: a commit with a correct registry but a stale | |
| # model-facing tool name, or prose whose counts have drifted, passed the tests | |
| # and shipped in dist/. A release path that enforces less than CI makes CI | |
| # advisory. | |
| - run: npm run check:vocabulary | |
| - run: npm run vocab:export -- --check | |
| - run: node scripts/asset-counts.mjs --check | |
| # The fourth CI gate, missed when the other three were added here: change a | |
| # tool's description or schema without regenerating its MDX page, tag that | |
| # commit, and every other release step passes while PR CI would have rejected | |
| # it — shipping a Tool Reference that documents a surface that no longer exists. | |
| - name: Tool Reference is current (docs:gen must be a no-op) | |
| run: | | |
| npm run docs:gen | |
| git add --intent-to-add -A docs/ | |
| if ! git diff --exit-code -- docs/; then | |
| echo "::error::docs/ is stale — run 'npm run docs:gen' and commit before tagging." | |
| exit 1 | |
| fi | |
| # Gate the publish on a real install of the packed tarball: catches a | |
| # missing-file packaging regression (e.g. 0.17.0, where the files | |
| # allowlist dropped scripts/ while a lifecycle script still pointed into | |
| # it) BEFORE it reaches npm. | |
| - name: Pack & install smoke | |
| run: node scripts/smoke-install.mjs | |
| # `npm publish` takes its version from package.json, NOT from the tag, so a | |
| # mismatch silently ships the wrong version under the right-looking release. | |
| # Fail instead. | |
| - name: Tag matches package.json version | |
| run: | | |
| set -euo pipefail | |
| tag_version="${GITHUB_REF_NAME#v}" | |
| pkg_version="$(node -p 'require("./package.json").version')" | |
| # `${GITHUB_REF_NAME#v}` strips exactly ONE leading "v" and validates | |
| # nothing, so several malformed tags used to sail through and be published | |
| # under a version npm had silently rewritten: | |
| # vv1.0.0 -> "v1.0.0" matched a package.json of "v1.0.0", npm published 1.0.0 | |
| # v01.2.3 -> "01.2.3" matched, npm published 1.2.3 | |
| # v1.2.3- and v-1.0.0 routed as prereleases, then npm rejected them | |
| # v -> "" classified as a final release before failing | |
| # Anchor the whole thing to canonical SemVer instead. Build metadata is | |
| # excluded from the grammar on purpose — see below. | |
| if ! printf '%s' "$tag_version" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))(\.((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)))*)?$'; then | |
| echo "::error::tag ${GITHUB_REF_NAME} is not a canonical vMAJOR.MINOR.PATCH[-prerelease] tag (no leading zeros, no build metadata, exactly one leading v)" | |
| exit 1 | |
| fi | |
| # Build metadata is rejected outright, because a string match on it is a | |
| # LIE about what ships: npm's publish path runs semver.clean(), which | |
| # STRIPS build metadata, so tag v1.2.3+build.1 with a byte-identical | |
| # package.json version publishes as plain 1.2.3. The guard below would | |
| # pass while the tag no longer identifies the registry version. We never | |
| # use build metadata, so refuse it rather than preserve a distinction npm | |
| # discards. It also removes the only case where a '+' hyphen could be | |
| # mistaken for the prerelease separator in the dist-tag step. | |
| case "$tag_version" in | |
| *+*) | |
| echo "::error::build metadata is not supported in release tags (npm strips it via semver.clean, so the published version would not match the tag): ${GITHUB_REF_NAME}" | |
| exit 1 | |
| ;; | |
| esac | |
| if [ "$tag_version" != "$pkg_version" ]; then | |
| echo "::error::tag ${GITHUB_REF_NAME} does not match package.json version ${pkg_version}" | |
| exit 1 | |
| fi | |
| echo "tag ${GITHUB_REF_NAME} == package.json ${pkg_version}" | |
| # Which npm dist-tag this lands on is a SAFETY decision, not a formality: | |
| # src/services/self-update.ts only ever moves users FORWARD (it gates on | |
| # isNewer(latest, current)), so a bad `latest` cannot be walked back — | |
| # already-updated users never return. Prereleases therefore publish to | |
| # `next` and are promoted deliberately once soaked: | |
| # npm dist-tag add comfyui-mcp@X.Y.Z latest | |
| # Detection is the semver rule: a hyphen after the numeric core means | |
| # prerelease (v0.49.0-rc.1). Anything malformed also resolves to `next`, | |
| # which is the fail-safe direction. | |
| # | |
| # The hyphen is tested against the version ONLY, not the whole ref, and | |
| # build metadata is already rejected by the step above. Testing the raw ref | |
| # for any '-' was wrong: SemVer permits hyphens INSIDE build metadata, so | |
| # v0.49.0+build-1 is a FINAL release that would have been routed to `next` | |
| # and marked prerelease on the GitHub Release. | |
| - name: Resolve npm dist-tag | |
| id: disttag | |
| run: | | |
| set -euo pipefail | |
| version="${GITHUB_REF_NAME#v}" | |
| if [[ "${version}" == *-* ]]; then | |
| echo "value=next" >> "$GITHUB_OUTPUT" | |
| echo "prerelease=true" >> "$GITHUB_OUTPUT" | |
| echo "${GITHUB_REF_NAME} is a prerelease -> npm dist-tag 'next'" | |
| else | |
| echo "value=latest" >> "$GITHUB_OUTPUT" | |
| echo "prerelease=false" >> "$GITHUB_OUTPUT" | |
| echo "${GITHUB_REF_NAME} is a final release -> npm dist-tag 'latest'" | |
| fi | |
| - name: Publish to npm | |
| run: npm publish --provenance --access public --tag "${{ steps.disttag.outputs.value }}" | |
| # The release body is THIS VERSION's CHANGELOG section, not GitHub's | |
| # synthesised "What's Changed". | |
| # | |
| # `generate_release_notes: true` alone asks GitHub to diff against a | |
| # previous release IT chooses. With versions cut back-to-back (and | |
| # prereleases interleaved) that base is routinely many versions old, so | |
| # v0.50.31 — whose changelog entry is a single line — shipped ~13,600 | |
| # characters listing every PR across a dozen releases. A reader cannot | |
| # tell what changed in the thing they just installed, which is the one | |
| # question a release page exists to answer. | |
| # | |
| # CHANGELOG.md is already curated per version, in our words, so it is both | |
| # shorter and more honest. Extraction failing is NOT fatal: we fall back to | |
| # the generated notes rather than publishing an empty release body. | |
| - name: Build release notes from CHANGELOG | |
| id: relnotes | |
| run: | | |
| set -euo pipefail | |
| if node scripts/changelog-section.mjs "${GITHUB_REF_NAME}" > RELEASE_NOTES.md; then | |
| echo "from_changelog=true" >> "$GITHUB_OUTPUT" | |
| echo "release notes for ${GITHUB_REF_NAME} ($(wc -c < RELEASE_NOTES.md) chars):" | |
| cat RELEASE_NOTES.md | |
| else | |
| echo "from_changelog=false" >> "$GITHUB_OUTPUT" | |
| rm -f RELEASE_NOTES.md | |
| echo "::warning::No CHANGELOG section for ${GITHUB_REF_NAME}; falling back to generated notes." | |
| fi | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 | |
| with: | |
| body_path: ${{ steps.relnotes.outputs.from_changelog == 'true' && 'RELEASE_NOTES.md' || '' }} | |
| generate_release_notes: ${{ steps.relnotes.outputs.from_changelog != 'true' }} | |
| prerelease: ${{ steps.disttag.outputs.prerelease == 'true' }} |