name: Release native npm package
on:
push:
tags:
- "v*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
build:
if: github.repository == 'Weavatrix/weavatrix-refactor'
strategy:
fail-fast: false
matrix:
include:
- platform: win32-x64
runner: windows-2025
target: x86_64-pc-windows-msvc
binary: target/x86_64-pc-windows-msvc/release/weavatrix-refactor.exe
- platform: win32-arm64
runner: windows-11-arm
target: aarch64-pc-windows-msvc
binary: target/aarch64-pc-windows-msvc/release/weavatrix-refactor.exe
- platform: darwin-x64
runner: macos-15-intel
target: x86_64-apple-darwin
binary: target/x86_64-apple-darwin/release/weavatrix-refactor
- platform: darwin-arm64
runner: macos-15
target: aarch64-apple-darwin
binary: target/aarch64-apple-darwin/release/weavatrix-refactor
- platform: linux-x64
runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
binary: target/x86_64-unknown-linux-gnu/release/weavatrix-refactor
- platform: linux-arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
binary: target/aarch64-unknown-linux-gnu/release/weavatrix-refactor
runs-on: ${{ matrix.runner }}
steps:
- name: Check out tagged source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with:
persist-credentials: false
- name: Install the declared Rust toolchain and target
shell: bash
run: |
rustup toolchain install 1.89.0 --profile minimal
rustup target add --toolchain 1.89.0 "${{ matrix.target }}"
- name: Build the release binary
shell: bash
run: cargo +1.89.0 build --release --locked --target "${{ matrix.target }}"
- name: Verify the native binary identity
shell: bash
run: |
set -euo pipefail
manifest() { node -p "require('./npm/weavatrix-refactor/package.json').$1"; }
crate() {
cargo +1.89.0 metadata --locked --format-version 1 |
node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>process.stdout.write(JSON.parse(s).packages.find(p=>p.name===process.argv[1]).version))" "$1"
}
PRODUCT_VERSION="$(manifest version)"
ENGINE_VERSION="$(manifest weavatrixEngineVersion)"
REFACTOR_VERSION="$(manifest weavatrixRefactorVersion)"
CONTRACT_VERSION="$(node -p "require('./contract/refactor-tools.v1.json').contractVersion")"
[[ "$PRODUCT_VERSION" == "$(crate weavatrix-refactor)" ]]
[[ "$ENGINE_VERSION" == "$(crate weavatrix-rust)" ]]
[[ "$REFACTOR_VERSION" == "$(crate weavatrix-rust-refactor)" ]]
EXPECTED="weavatrix-refactor $PRODUCT_VERSION (engine $ENGINE_VERSION, refactor $REFACTOR_VERSION, contract v$CONTRACT_VERSION)"
ACTUAL="$('${{ matrix.binary }}' --version)"
[[ "$ACTUAL" == "$EXPECTED" ]] || { echo "got: $ACTUAL"; echo "want: $EXPECTED"; exit 1; }
- name: Normalize the binary artifact layout
shell: bash
run: |
mkdir -p "artifacts/${{ matrix.platform }}"
cp "${{ matrix.binary }}" "artifacts/${{ matrix.platform }}/"
- name: Preserve the verified native binary
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: binary-${{ matrix.platform }}
path: artifacts
if-no-files-found: error
retention-days: 7
publish:
if: github.repository == 'Weavatrix/weavatrix-refactor'
needs: build
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
steps:
- name: Check out tagged source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with:
persist-credentials: false
- name: Set up Node.js for npm trusted publishing
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Download all verified platform binaries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: binary-*
path: binaries
merge-multiple: true
- name: Assemble and verify the universal package
shell: bash
run: |
set -euo pipefail
VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').version")"
MCP_NAME="$(node -p "require('./npm/weavatrix-refactor/package.json').mcpName")"
SERVER_NAME="$(node -p "require('./server.json').name")"
SERVER_VERSION="$(node -p "require('./server.json').version")"
SERVER_DESCRIPTION_LENGTH="$(node -p "require('./server.json').description.length")"
SERVER_PACKAGE="$(node -p "require('./server.json').packages[0].identifier")"
SERVER_PACKAGE_VERSION="$(node -p "require('./server.json').packages[0].version")"
SERVER_COMMAND="$(node -p "require('./server.json').packages[0].packageArguments[0].value")"
[[ "$GITHUB_REF_NAME" == "v$VERSION" ]]
[[ "$SERVER_NAME" == "io.github.Weavatrix/weavatrix-refactor" ]]
[[ "$MCP_NAME" == "$SERVER_NAME" ]]
[[ "$SERVER_VERSION" == "$VERSION" ]]
[[ "$SERVER_PACKAGE" == "weavatrix-refactor" ]]
[[ "$SERVER_PACKAGE_VERSION" == "$VERSION" ]]
[[ "$SERVER_COMMAND" == "mcp" ]]
(( SERVER_DESCRIPTION_LENGTH > 0 && SERVER_DESCRIPTION_LENGTH <= 100 ))
node scripts/build-npm-packages.mjs universal binaries "$VERSION"
cmp server.json npm/dist/weavatrix-refactor/server.json
npm pack ./npm/dist/weavatrix-refactor --dry-run --json |
node scripts/npm-pack-json.mjs require-files README.md LICENSE server.json bin/weavatrix-refactor.mjs bin/weavatrix-refactor-mcp.mjs
node -e "const p=require('./npm/dist/weavatrix-refactor/package.json');if(p.scripts||p.dependencies||p.optionalDependencies)process.exit(1)"
! find npm/dist/weavatrix-refactor -type f -path '*node_modules*' -print -quit | grep .
- name: Smoke-test the installed package boundary
shell: bash
run: |
set -euo pipefail
PACKAGE="$(npm pack ./npm/dist/weavatrix-refactor --json | node scripts/npm-pack-json.mjs filename)"
ROOT="$(mktemp -d)"
trap 'rm -rf "$ROOT" "$PACKAGE"' EXIT
npm install --prefix "$ROOT" --ignore-scripts --no-audit --no-fund "./$PACKAGE"
VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').version")"
ENGINE_VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').weavatrixEngineVersion")"
REFACTOR_VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').weavatrixRefactorVersion")"
CONTRACT_VERSION="$(node -p "require('./contract/refactor-tools.v1.json').contractVersion")"
[[ "$("$ROOT/node_modules/.bin/weavatrix-refactor" --version)" \
== "weavatrix-refactor $VERSION (engine $ENGINE_VERSION, refactor $REFACTOR_VERSION, contract v$CONTRACT_VERSION)" ]]
CATALOG="$("$ROOT/node_modules/.bin/weavatrix-refactor" list-tools)"
for tool in rename_symbol rename_related_symbols change_signature organize_imports \
edit_symbol bulk_replace move_file move_symbol delete_readiness \
apply_edit_plan rollback_last_apply; do
grep -qF "\"$tool\"" <<<"$CATALOG" || { echo "$tool is missing from the catalog" >&2; exit 1; }
done
! grep -qF '"graph_stats"' <<<"$CATALOG" || { echo "default catalog leaks Core tools" >&2; exit 1; }
if "$ROOT/node_modules/.bin/weavatrix-refactor" list-tools --profile=full >/dev/null 2>&1; then
echo "removed full profile is still callable" >&2
exit 1
fi
- name: Leave npm publishing to the maintainer machine
shell: bash
run: |
echo "The six binaries are verified here. npm publish runs from the maintainer machine and does not use this workflow."
- name: Install verified MCP Registry publisher
shell: bash
run: |
curl --fail --location --proto '=https' --tlsv1.2 \
--output mcp-publisher.tar.gz \
https://github.com/modelcontextprotocol/registry/releases/download/v1.8.0/mcp-publisher_linux_amd64.tar.gz
echo "1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf mcp-publisher.tar.gz" | sha256sum --check
tar --extract --gzip --file mcp-publisher.tar.gz mcp-publisher
chmod +x mcp-publisher
- name: Publish server metadata to the official MCP Registry
shell: bash
run: |
set -euo pipefail
VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').version")"
./mcp-publisher validate server.json
REGISTRY_RESPONSE="$(curl --fail --silent --show-error 'https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.Weavatrix%2Fweavatrix-refactor')"
if jq --exit-status --arg version "$VERSION" \
'.servers[]?.server | select(.name == "io.github.Weavatrix/weavatrix-refactor" and .version == $version)' \
<<<"$REGISTRY_RESPONSE" >/dev/null; then
echo "MCP Registry already contains Weavatrix Refactor $VERSION; skipping publish"
else
./mcp-publisher login github-oidc
./mcp-publisher publish
fi
- name: Verify the official MCP Registry artifact
shell: bash
run: |
set -euo pipefail
VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').version")"
REGISTRY_URL="https://registry.modelcontextprotocol.io/v0.1/servers/io.github.Weavatrix%2Fweavatrix-refactor/versions/$VERSION"
for attempt in $(seq 1 180); do
echo "MCP Registry verification attempt $attempt/180"
if curl --fail --silent --show-error "$REGISTRY_URL" |
jq --exit-status --arg version "$VERSION" \
'.server.name == "io.github.Weavatrix/weavatrix-refactor" and
.server.version == $version and
.server.packages[0].identifier == "weavatrix-refactor" and
.server.packages[0].version == $version' >/dev/null; then
break
fi
sleep 5
done
curl --fail --silent --show-error "$REGISTRY_URL" |
jq --exit-status --arg version "$VERSION" \
'.server.name == "io.github.Weavatrix/weavatrix-refactor" and
.server.version == $version and
.server.packages[0].identifier == "weavatrix-refactor" and
.server.packages[0].version == $version' >/dev/null
release:
if: github.repository == 'Weavatrix/weavatrix-refactor'
needs: publish
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Check out the verified tag
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with:
fetch-depth: 0
persist-credentials: false
- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
VERSION="$(node -p "require('./npm/weavatrix-refactor/package.json').version")"
[[ "$GITHUB_REF_NAME" == "v$VERSION" ]]
[[ "$(git rev-list -n 1 "$GITHUB_REF_NAME")" == "$GITHUB_SHA" ]]
if ! gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release create "$GITHUB_REF_NAME" \
--verify-tag \
--generate-notes \
--title "Weavatrix Refactor $GITHUB_REF_NAME"
fi
gh api "repos/$GITHUB_REPOSITORY/releases/tags/$GITHUB_REF_NAME" |
jq --exit-status --arg tag "$GITHUB_REF_NAME" \
'.tag_name == $tag and (.draft | not) and (.prerelease | not)' >/dev/null