Skip to content

Merge develop for v2.4.1 release #16

Merge develop for v2.4.1 release

Merge develop for v2.4.1 release #16

Workflow file for this run

name: Publish to npm
# Publishes @gravitykit/mcp via npm Trusted Publishing (OIDC) — no NPM_TOKEN
# secret to rotate or leak; OIDC also generates provenance attestations.
#
# One-time setup on npmjs.com: package @gravitykit/mcp → Settings →
# Trusted Publisher → GitHub Actions, repository GravityKit/MCP,
# workflow publish.yml.
#
# Triggers on a v* tag (the release) and workflow_dispatch (manual re-run).
# The version guard makes both idempotent — an already-published version is a
# no-op, so re-running or re-tagging never errors.
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write # create the GitHub Release
id-token: write # OIDC token for npm trusted publishing + provenance
concurrency:
group: npm-publish
cancel-in-progress: false
jobs:
publish:
name: Publish @gravitykit/mcp
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false # npm auth comes from OIDC, not git
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
- name: Update npm (trusted publishing requires npm >= 11.5)
# Pinned to the 11.x major so a future npm major can't change publish
# behavior mid-release.
run: npm install -g npm@11
- name: Install dependencies
run: npm ci
- name: Skip when this version is already published
id: guard
# Only a definitive "version not found" may proceed to publish; any other
# npm view failure (network, registry outage) fails the job so a transient
# error can't slip past the idempotency check. Some npm versions report a
# missing version as exit 0 with empty output, so success requires
# non-empty output too.
run: |
VERSION="$(node -p "require('./package.json').version")"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if OUTPUT="$(npm view "@gravitykit/mcp@$VERSION" version 2>&1)"; then
STATUS=0
else
STATUS=$?
fi
if [ "$STATUS" -eq 0 ] && [ -n "$OUTPUT" ]; then
echo "published=true" >> "$GITHUB_OUTPUT"
echo "@gravitykit/mcp@$VERSION is already on npm — nothing to publish."
elif [ "$STATUS" -eq 0 ] || printf '%s' "$OUTPUT" | grep -qiE 'E404|not found|no match found'; then
echo "published=false" >> "$GITHUB_OUTPUT"
else
echo "::error::Could not determine whether @gravitykit/mcp@$VERSION is published (npm view exit $STATUS):" >&2
printf '%s\n' "$OUTPUT" >&2
exit 1
fi
- name: Publish to npm (OIDC trusted publishing + provenance)
if: steps.guard.outputs.published == 'false'
# prepublishOnly runs the full offline gate (tests + publint + doc guard)
# automatically before the package is published.
run: npm publish --access public
- name: Create GitHub Release
id: release
if: steps.guard.outputs.published == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.guard.outputs.version }}"
if gh release view "v$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release v$VERSION already exists — skipping."
exit 0
fi
gh release create "v$VERSION" \
--repo "$GITHUB_REPOSITORY" \
--title "v$VERSION" \
--verify-tag \
--notes "See [CHANGELOG.md](https://github.com/$GITHUB_REPOSITORY/blob/main/CHANGELOG.md) for details.
Install: \`npm install @gravitykit/mcp@$VERSION\` · [View on npm](https://www.npmjs.com/package/@gravitykit/mcp/v/$VERSION)"
# Signals the Slack step that a release was actually created this run
# (not an idempotent skip), so announcements never double-fire.
echo "created=true" >> "$GITHUB_OUTPUT"
- name: Announce release to Slack
# Mirrors gktools' "Notify Slack": POST {link,version,changelog,product}
# to SLACK_RELEASE_NOTIFICATION_WEBHOOK so GravityKit MCP shows up in
# #release-announcements like every other plugin. Only after a release is
# actually created; never fails the job.
if: steps.release.outputs.created == 'true'
env:
SLACK_RELEASE_NOTIFICATION_WEBHOOK: ${{ secrets.SLACK_RELEASE_NOTIFICATION_WEBHOOK }}
# Bind expressions through env (quoted shell vars below) instead of
# interpolating ${{ }} into the run script — avoids template-to-shell injection.
VERSION: ${{ steps.guard.outputs.version }}
REPO: ${{ github.repository }}
run: |
if [ -z "${SLACK_RELEASE_NOTIFICATION_WEBHOOK:-}" ]; then
echo "SLACK_RELEASE_NOTIFICATION_WEBHOOK not set — skipping Slack announcement."
exit 0
fi
LINK="https://github.com/$REPO/releases/tag/v$VERSION"
# Extract this version's CHANGELOG section: lines between "## [VERSION]"
# and the next "## [" header.
NOTES="$(awk -v ver="$VERSION" '
$0 ~ "^## \\[" ver "\\]" { capture=1; next }
capture && /^## \[/ { exit }
capture { print }
' CHANGELOG.md)"
# jq builds the JSON so newlines/quotes in the changelog are escaped.
PAYLOAD="$(jq -n \
--arg link "$LINK" \
--arg version "$VERSION" \
--arg changelog "$NOTES" \
--arg product "GravityKit MCP" \
'{link:$link,version:$version,changelog:$changelog,product:$product}')"
# Non-fatal, like gktools: a bad Slack response only warns.
RESPONSE="$(curl -sS -X POST \
-H 'Content-Type: application/json' \
--data "$PAYLOAD" \
"$SLACK_RELEASE_NOTIFICATION_WEBHOOK" || true)"
echo "Slack response: $RESPONSE"
if ! printf '%s' "$RESPONSE" | grep -q '"ok":true'; then
echo "::warning::Slack release announcement did not return \"ok\":true."
fi