v0.13.1 #67
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 Publish | |
| on: | |
| release: | |
| types: | |
| - published | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: "Release tag to publish (for example: v0.8.1)" | |
| required: true | |
| type: string | |
| dry_run: | |
| description: "Dry run (skip actual publish)" | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| resolve-version: | |
| name: Resolve Release Version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.resolve.outputs.version }} | |
| npm_dist_tag: ${{ steps.resolve.outputs.npm_dist_tag }} | |
| dry_run: ${{ steps.resolve.outputs.dry_run }} | |
| raw_tag: ${{ steps.resolve.outputs.raw_tag }} | |
| should_publish: ${{ steps.resolve.outputs.should_publish }} | |
| steps: | |
| - name: Resolve version and npm dist-tag | |
| id: resolve | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| RAW_TAG="${{ github.event.inputs.release_tag }}" | |
| DRY_RUN_INPUT="${{ github.event.inputs.dry_run }}" | |
| else | |
| RAW_TAG="${{ github.event.release.tag_name }}" | |
| DRY_RUN_INPUT="false" | |
| fi | |
| if [[ -z "${RAW_TAG}" ]]; then | |
| echo "Release tag is empty" | |
| exit 1 | |
| fi | |
| VERSION="${RAW_TAG#v}" | |
| if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "Release tag '${RAW_TAG}' is not a valid semver tag (expected vX.Y.Z)" | |
| exit 1 | |
| fi | |
| # Allow asset-only releases (for example models-v1) without failing publish CI. | |
| echo "Non-semver release tag '${RAW_TAG}' detected; skipping npm publish workflow." | |
| echo "raw_tag=${RAW_TAG}" >> "${GITHUB_OUTPUT}" | |
| echo "version=" >> "${GITHUB_OUTPUT}" | |
| echo "npm_dist_tag=" >> "${GITHUB_OUTPUT}" | |
| echo "dry_run=false" >> "${GITHUB_OUTPUT}" | |
| echo "should_publish=false" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| NPM_DIST_TAG="latest" | |
| if [[ "${VERSION}" == *-* ]]; then | |
| NPM_DIST_TAG="next" | |
| fi | |
| DRY_RUN="false" | |
| if [[ "${DRY_RUN_INPUT}" == "true" ]]; then | |
| DRY_RUN="true" | |
| fi | |
| echo "raw_tag=${RAW_TAG}" >> "${GITHUB_OUTPUT}" | |
| echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" | |
| echo "npm_dist_tag=${NPM_DIST_TAG}" >> "${GITHUB_OUTPUT}" | |
| echo "dry_run=${DRY_RUN}" >> "${GITHUB_OUTPUT}" | |
| echo "should_publish=true" >> "${GITHUB_OUTPUT}" | |
| echo "Resolved release tag: ${RAW_TAG}" | |
| echo "Resolved version: ${VERSION}" | |
| echo "Resolved npm dist-tag: ${NPM_DIST_TAG}" | |
| echo "Dry run: ${DRY_RUN}" | |
| echo "Should publish: true" | |
| build-native: | |
| name: Build Native (${{ matrix.target }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: resolve-version | |
| if: needs.resolve-version.outputs.should_publish == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-pc-windows-msvc | |
| os: windows-latest | |
| - target: x86_64-apple-darwin | |
| os: macos-latest | |
| - target: aarch64-apple-darwin | |
| os: macos-latest | |
| - target: x86_64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| - target: x86_64-unknown-linux-musl | |
| os: ubuntu-latest | |
| - target: aarch64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| cache: npm | |
| - name: Setup Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Install cross-compilation tools (aarch64-linux-gnu) | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu | |
| - name: Install cross-compilation tools (x86_64-linux-musl) | |
| if: matrix.target == 'x86_64-unknown-linux-musl' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y musl-tools | |
| - name: Cache Cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| native/target | |
| key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('native/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.target }}-cargo- | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts --legacy-peer-deps | |
| - name: Setup tree-sitter | |
| uses: ./.github/actions/setup-tree-sitter | |
| - name: Build native addon | |
| run: npx napi build --release --cargo-cwd native --config native/package.json --target ${{ matrix.target }} native | |
| env: | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc | |
| - name: Upload native artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: bindings-${{ matrix.target }} | |
| path: native/*.node | |
| if-no-files-found: error | |
| verify-packed-install: | |
| name: Verify Packed Install (${{ matrix.os }}, accelerators=${{ matrix.accelerators }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: | |
| - resolve-version | |
| - build-native | |
| if: needs.resolve-version.outputs.should_publish == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| accelerators: included | |
| - os: windows-latest | |
| accelerators: disabled | |
| - os: ubuntu-latest | |
| accelerators: included | |
| - os: macos-latest | |
| accelerators: included | |
| env: | |
| VERSION: ${{ needs.resolve-version.outputs.version }} | |
| SDL_MCP_SKIP_MODEL_DOWNLOAD: "1" | |
| SDL_MCP_STRICT_TREE_SITTER_POSTINSTALL: "1" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| cache: npm | |
| - name: Refresh package-lock for release publish | |
| run: npm install --package-lock-only --ignore-scripts --legacy-peer-deps | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts --legacy-peer-deps | |
| - name: Setup tree-sitter | |
| uses: ./.github/actions/setup-tree-sitter | |
| - name: Set package versions from release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm version "${VERSION}" --no-git-tag-version --allow-same-version | |
| node scripts/sync-native-version.mjs "${VERSION}" | |
| - name: Download Windows native artifact | |
| if: matrix.os == 'windows-latest' && matrix.accelerators == 'included' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: bindings-x86_64-pc-windows-msvc | |
| path: native/artifacts/bindings-x86_64-pc-windows-msvc | |
| - name: Stage Windows native package | |
| if: matrix.os == 'windows-latest' && matrix.accelerators == 'included' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| native_node=$(find native/artifacts/bindings-x86_64-pc-windows-msvc -name "*.node" -type f | head -n 1) | |
| test -n "${native_node}" | |
| cp "${native_node}" native/npm/win32-x64-msvc/sdl-mcp-native.win32-x64-msvc.node | |
| - name: Build runtime package | |
| run: npm run build:all | |
| - name: Pack main package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-pack | |
| npm pack --pack-destination release-pack | |
| if [[ "${{ matrix.os }}" == "windows-latest" && "${{ matrix.accelerators }}" == "included" ]]; then | |
| # The main publish is gated by this job, so verify with the native tarballs built in this run. | |
| npm pack ./native/npm/win32-x64-msvc --pack-destination release-pack | |
| npm pack ./native --pack-destination release-pack | |
| fi | |
| - name: Install packed package and verify runtime | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| tarball=$(node -e "const fs=require('fs');const path=require('path');const f='sdl-mcp-'+process.env.VERSION+'.tgz';if(!fs.existsSync(path.join('release-pack',f)))process.exit(1);console.log(path.resolve('release-pack',f).replace(/\\\\/g,'/'));") | |
| fixture_test=$(node -e "console.log(require('path').resolve('tests/integration/live-index-symbol-fts-crash.test.ts').replace(/\\\\/g,'/'))") | |
| fixture_child=$(node -e "console.log(require('path').resolve('tests/fixtures/ladybug/windows-fts-clean-env-child.mjs').replace(/\\\\/g,'/'))") | |
| context_source=$(node -e "console.log(require('path').resolve('src/context/profiles.ts').replace(/\\\\/g,'/'))") | |
| install_args=("${tarball}" "--legacy-peer-deps") | |
| if [[ "${{ matrix.os }}" == "windows-latest" && "${{ matrix.accelerators }}" == "included" ]]; then | |
| native_tarball=$(node -e "console.log(require('path').resolve('release-pack/sdl-mcp-native-'+process.env.VERSION+'.tgz').replace(/\\\\/g,'/'))") | |
| native_platform_tarball=$(node -e "console.log(require('path').resolve('release-pack/sdl-mcp-native-win32-x64-msvc-'+process.env.VERSION+'.tgz').replace(/\\\\/g,'/'))") | |
| install_args+=("${native_tarball}" "${native_platform_tarball}") | |
| fi | |
| smoke_dir=$(node -e "const fs=require('fs');const os=require('os');const path=require('path');console.log(fs.mkdtempSync(path.join(os.tmpdir(),'sdl-mcp-pack-smoke-')).replace(/\\\\/g,'/'));") | |
| cd "${smoke_dir}" | |
| npm init -y > /dev/null | |
| npm pkg set type=module | |
| npm install "${install_args[@]}" | |
| node node_modules/sdl-mcp/scripts/postinstall-tree-sitter.mjs --verify-only | |
| openssl_package="node_modules/@sdl-mcp/ladybug-openssl-win32-x64/package.json" | |
| ladybug_platform_package="node_modules/@ladybugdb/core-win32-x64/package.json" | |
| if [[ "${{ matrix.os }}" == "windows-latest" ]]; then | |
| node -e "const fs=require('fs');if(!fs.existsSync('$ladybug_platform_package'))throw new Error('missing Ladybug platform binary: $ladybug_platform_package')" | |
| fi | |
| if [[ "${{ matrix.os }}" == "windows-latest" && "${{ matrix.accelerators }}" == "included" ]]; then | |
| node -e "const fs=require('fs');for(const file of ['$openssl_package','node_modules/sdl-mcp-native/package.json','node_modules/sdl-mcp-native-win32-x64-msvc/package.json'])if(!fs.existsSync(file))throw new Error('missing packed-install accelerator: '+file)" | |
| elif [[ "${{ matrix.os }}" != "windows-latest" ]]; then | |
| node -e "const fs=require('fs');if(fs.existsSync('$openssl_package'))throw new Error('Windows OpenSSL package was not filtered')" | |
| fi | |
| if [[ "${{ matrix.os }}" == "windows-latest" ]]; then | |
| # The disabled MCP probe reads the root config explicitly; the clean-env FTS | |
| # child uses the repository fallback under config/. | |
| printf '%s\n' '{"repos":[],"policy":{}}' > sdlmcp.config.json | |
| mkdir -p config | |
| cp sdlmcp.config.json config/sdlmcp.config.json | |
| fi | |
| if [[ "${{ matrix.os }}" == "windows-latest" && "${{ matrix.accelerators }}" == "disabled" ]]; then | |
| node --input-type=module <<'NODE' | |
| import assert from "node:assert/strict"; | |
| import path from "node:path"; | |
| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | |
| import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; | |
| const client = new Client({ name: "release-packed-install", version: "1.0.0" }); | |
| const transport = new StdioClientTransport({ | |
| command: process.execPath, | |
| args: ["node_modules/sdl-mcp/dist/main.js"], | |
| env: { | |
| ...process.env, | |
| NODE_ENV: "test", | |
| SDL_CONFIG: path.resolve("sdlmcp.config.json"), | |
| SDL_GRAPH_DB_PATH: path.resolve("packed-install-graph.lbug"), | |
| SDL_MCP_DISABLE_NATIVE_ADDON: "1", | |
| SDL_CONSOLE_LOGGING: "false", | |
| }, | |
| }); | |
| try { | |
| await client.connect(transport); | |
| const response = await client.listTools(); | |
| assert.ok(response.tools.some((tool) => tool.name === "sdl.info")); | |
| const info = await client.callTool({ name: "sdl.info", arguments: { redactPaths: true } }); | |
| assert.equal(info.structuredContent?.ladybug?.available, true); | |
| assert.equal(info.structuredContent?.native?.disabledByEnv, true); | |
| } finally { | |
| await client.close().catch(() => {}); | |
| } | |
| NODE | |
| fi | |
| if [[ "${{ matrix.os }}" == "windows-latest" ]]; then | |
| # Node 24 does not strip TypeScript under node_modules. Build a temporary harness beside it. | |
| mkdir -p "${smoke_dir}/tests/integration" "${smoke_dir}/tests/fixtures/ladybug" "${smoke_dir}/src/context" | |
| cp "${fixture_test}" "${smoke_dir}/tests/integration/live-index-symbol-fts-crash.test.ts" | |
| cp "${fixture_child}" "${smoke_dir}/tests/fixtures/ladybug/windows-fts-clean-env-child.mjs" | |
| cp "${context_source}" "${smoke_dir}/src/context/profiles.ts" | |
| cp -R node_modules/sdl-mcp/dist "${smoke_dir}/dist" | |
| if [[ "${{ matrix.accelerators }}" == "included" ]]; then | |
| SDL_LADYBUG_WINDOWS_FTS_TEST_MODE=fixed-regression SDL_LADYBUG_WINDOWS_FTS_REQUIRE_MODE=1 \ | |
| node --experimental-strip-types --test-concurrency=1 --test tests/integration/live-index-symbol-fts-crash.test.ts | |
| else | |
| SDL_MCP_DISABLE_NATIVE_ADDON=1 node --experimental-strip-types --test --test-name-pattern="probes upstream" tests/integration/live-index-symbol-fts-crash.test.ts | |
| fi | |
| fi | |
| publish: | |
| name: Publish npm Packages | |
| runs-on: ubuntu-latest | |
| needs: | |
| - resolve-version | |
| - build-native | |
| - verify-packed-install | |
| if: needs.resolve-version.outputs.should_publish == 'true' | |
| env: | |
| VERSION: ${{ needs.resolve-version.outputs.version }} | |
| NPM_DIST_TAG: ${{ needs.resolve-version.outputs.npm_dist_tag }} | |
| DRY_RUN: ${{ needs.resolve-version.outputs.dry_run }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| registry-url: https://registry.npmjs.org | |
| cache: npm | |
| - name: Refresh package-lock for release publish | |
| run: npm install --package-lock-only --ignore-scripts --legacy-peer-deps | |
| - name: Install dependencies | |
| run: npm ci --ignore-scripts --legacy-peer-deps | |
| - name: Setup tree-sitter | |
| uses: ./.github/actions/setup-tree-sitter | |
| - name: Set package versions from release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm version "${VERSION}" --no-git-tag-version --allow-same-version | |
| node scripts/sync-native-version.mjs "${VERSION}" | |
| - name: Validate package version sync | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node -e " | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const expected = process.env.VERSION; | |
| const pkg = (p) => JSON.parse(fs.readFileSync(p, 'utf8')); | |
| const root = pkg('package.json'); | |
| const native = pkg(path.join('native', 'package.json')); | |
| const createPackage = pkg(path.join('packages', 'create-sdl-mcp', 'package.json')); | |
| if (root.version !== expected) throw new Error('package.json version mismatch'); | |
| if (createPackage.version !== expected) throw new Error('packages/create-sdl-mcp/package.json version mismatch'); | |
| for (const depName of ['sdl-mcp-native', 'sdl-mcp-watchman', 'sdl-mcp-watchman-linux-x64', 'sdl-mcp-watchman-win32-x64']) { | |
| if ((root.optionalDependencies || {})[depName] !== expected) { | |
| throw new Error('package.json optionalDependencies.' + depName + ' mismatch'); | |
| } | |
| } | |
| if (native.version !== expected) throw new Error('native/package.json version mismatch'); | |
| for (const [name, depVersion] of Object.entries(native.optionalDependencies || {})) { | |
| if (depVersion !== expected) throw new Error('native optional dependency mismatch: ' + name); | |
| } | |
| for (const dir of fs.readdirSync(path.join('native', 'npm'))) { | |
| const p = path.join('native', 'npm', dir, 'package.json'); | |
| if (!fs.existsSync(p)) continue; | |
| const platformPkg = pkg(p); | |
| if (platformPkg.version !== expected) { | |
| throw new Error('platform package version mismatch: ' + platformPkg.name); | |
| } | |
| } | |
| const watchman = pkg(path.join('watchman', 'package.json')); | |
| if (watchman.version !== expected) throw new Error('watchman/package.json version mismatch'); | |
| for (const [name, depVersion] of Object.entries(watchman.optionalDependencies || {})) { | |
| if (depVersion !== expected) throw new Error('watchman optional dependency mismatch: ' + name); | |
| } | |
| for (const dir of fs.readdirSync(path.join('watchman', 'npm'))) { | |
| const p = path.join('watchman', 'npm', dir, 'package.json'); | |
| if (!fs.existsSync(p)) continue; | |
| const platformPkg = pkg(p); | |
| if (platformPkg.version !== expected) { | |
| throw new Error('Watchman platform package version mismatch: ' + platformPkg.name); | |
| } | |
| } | |
| console.log('Version sync validation passed for ' + expected); | |
| " | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: native/artifacts | |
| - name: Place artifacts into platform npm packages | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| declare -A TARGET_TO_DIR=( | |
| ["x86_64-pc-windows-msvc"]="win32-x64-msvc" | |
| ["x86_64-apple-darwin"]="darwin-x64" | |
| ["aarch64-apple-darwin"]="darwin-arm64" | |
| ["x86_64-unknown-linux-gnu"]="linux-x64-gnu" | |
| ["x86_64-unknown-linux-musl"]="linux-x64-musl" | |
| ["aarch64-unknown-linux-gnu"]="linux-arm64-gnu" | |
| ) | |
| declare -A TARGET_TO_FILE=( | |
| ["x86_64-pc-windows-msvc"]="sdl-mcp-native.win32-x64-msvc.node" | |
| ["x86_64-apple-darwin"]="sdl-mcp-native.darwin-x64.node" | |
| ["aarch64-apple-darwin"]="sdl-mcp-native.darwin-arm64.node" | |
| ["x86_64-unknown-linux-gnu"]="sdl-mcp-native.linux-x64-gnu.node" | |
| ["x86_64-unknown-linux-musl"]="sdl-mcp-native.linux-x64-musl.node" | |
| ["aarch64-unknown-linux-gnu"]="sdl-mcp-native.linux-arm64-gnu.node" | |
| ) | |
| for target in "${!TARGET_TO_DIR[@]}"; do | |
| src_dir="native/artifacts/bindings-${target}" | |
| dst_dir="native/npm/${TARGET_TO_DIR[${target}]}" | |
| dst_file="${TARGET_TO_FILE[${target}]}" | |
| src_file=$(find "${src_dir}" -name "*.node" -type f | head -n 1 || true) | |
| if [[ -z "${src_file}" ]]; then | |
| echo "Missing native artifact for ${target} in ${src_dir}" | |
| exit 1 | |
| fi | |
| cp "${src_file}" "${dst_dir}/${dst_file}" | |
| echo "Copied ${src_file} -> ${dst_dir}/${dst_file}" | |
| done | |
| - name: Verify platform package binaries exist | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for dir in native/npm/*/; do | |
| pkg_name=$(node -e "console.log(require('./${dir}package.json').name)") | |
| node_count=$(find "${dir}" -name "*.node" -type f | wc -l) | |
| if [[ "${node_count}" -eq 0 ]]; then | |
| echo "Missing .node binary for ${pkg_name}" | |
| exit 1 | |
| fi | |
| echo "OK ${pkg_name}: ${node_count} binary file(s)" | |
| done | |
| - name: Publish platform native packages | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| FAILED=0 | |
| for dir in native/npm/*/; do | |
| pkg_name=$(node -e "console.log(require('./${dir}package.json').name)") | |
| echo "Publishing ${pkg_name}@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "${pkg_name}@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP ${pkg_name}@${VERSION} already exists" | |
| continue | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish "${dir}" --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| continue | |
| fi | |
| if npm publish "${dir}" --access public --tag "${NPM_DIST_TAG}" 2>&1; then | |
| echo "OK ${pkg_name}@${VERSION} published" | |
| else | |
| # Tolerate "already published" errors (race with publish-native workflow) | |
| recheck=$(npm view "${pkg_name}@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP ${pkg_name}@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL ${pkg_name}@${VERSION} publish failed" | |
| FAILED=1 | |
| fi | |
| fi | |
| done | |
| if [[ "${FAILED}" -eq 1 ]]; then | |
| echo "ERROR: Some platform packages failed to publish" | |
| exit 1 | |
| fi | |
| - name: Publish native umbrella package | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| echo "Publishing sdl-mcp-native@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "sdl-mcp-native@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp-native@${VERSION} already exists" | |
| exit 0 | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish native/ --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| exit 0 | |
| fi | |
| if npm publish native/ --access public --tag "${NPM_DIST_TAG}" --ignore-scripts 2>&1; then | |
| echo "OK sdl-mcp-native@${VERSION} published" | |
| else | |
| recheck=$(npm view "sdl-mcp-native@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp-native@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL sdl-mcp-native@${VERSION} publish failed" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Prepare Watchman platform packages | |
| run: node scripts/prepare-watchman-packages.mjs | |
| - name: Verify Watchman package binaries exist | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for dir in watchman/npm/*/; do | |
| pkg_name=$(node -e "console.log(require('./${dir}package.json').name)") | |
| binary_path=$(node -e "console.log(require('./${dir}package.json').sdlMcp.watchmanBinary)") | |
| if [[ ! -f "${dir}${binary_path}" ]]; then | |
| echo "Missing Watchman binary for ${pkg_name}: ${dir}${binary_path}" | |
| exit 1 | |
| fi | |
| echo "OK ${pkg_name}: ${binary_path}" | |
| done | |
| - name: Publish platform Watchman packages | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| FAILED=0 | |
| for dir in watchman/npm/*/; do | |
| pkg_name=$(node -e "console.log(require('./${dir}package.json').name)") | |
| echo "Publishing ${pkg_name}@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "${pkg_name}@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP ${pkg_name}@${VERSION} already exists" | |
| continue | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish "${dir}" --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| continue | |
| fi | |
| if npm publish "${dir}" --access public --tag "${NPM_DIST_TAG}" 2>&1; then | |
| echo "OK ${pkg_name}@${VERSION} published" | |
| else | |
| recheck=$(npm view "${pkg_name}@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP ${pkg_name}@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL ${pkg_name}@${VERSION} publish failed" | |
| FAILED=1 | |
| fi | |
| fi | |
| done | |
| if [[ "${FAILED}" -eq 1 ]]; then | |
| echo "ERROR: Some Watchman platform packages failed to publish" | |
| exit 1 | |
| fi | |
| - name: Publish Watchman umbrella package | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| echo "Publishing sdl-mcp-watchman@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "sdl-mcp-watchman@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp-watchman@${VERSION} already exists" | |
| exit 0 | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish watchman/ --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| exit 0 | |
| fi | |
| if npm publish watchman/ --access public --tag "${NPM_DIST_TAG}" --ignore-scripts 2>&1; then | |
| echo "OK sdl-mcp-watchman@${VERSION} published" | |
| else | |
| recheck=$(npm view "sdl-mcp-watchman@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp-watchman@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL sdl-mcp-watchman@${VERSION} publish failed" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Build runtime package | |
| run: npm run build:all | |
| - name: Publish main package | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| echo "Publishing sdl-mcp@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "sdl-mcp@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp@${VERSION} already exists" | |
| exit 0 | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish . --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| exit 0 | |
| fi | |
| if npm publish . --access public --tag "${NPM_DIST_TAG}" 2>&1; then | |
| echo "OK sdl-mcp@${VERSION} published" | |
| else | |
| recheck=$(npm view "sdl-mcp@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP sdl-mcp@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL sdl-mcp@${VERSION} publish failed" | |
| exit 1 | |
| fi | |
| fi | |
| - name: Publish create-sdl-mcp wrapper package | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| echo "Publishing create-sdl-mcp@${VERSION} (${NPM_DIST_TAG})" | |
| existing=$(npm view "create-sdl-mcp@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${existing}" == "${VERSION}" ]]; then | |
| echo "SKIP create-sdl-mcp@${VERSION} already exists" | |
| exit 0 | |
| fi | |
| if [[ "${DRY_RUN}" == "true" ]]; then | |
| npm publish packages/create-sdl-mcp/ --access public --tag "${NPM_DIST_TAG}" --dry-run | |
| exit 0 | |
| fi | |
| if npm publish packages/create-sdl-mcp/ --access public --tag "${NPM_DIST_TAG}" 2>&1; then | |
| echo "OK create-sdl-mcp@${VERSION} published" | |
| else | |
| recheck=$(npm view "create-sdl-mcp@${VERSION}" version 2>/dev/null || true) | |
| if [[ "${recheck}" == "${VERSION}" ]]; then | |
| echo "SKIP create-sdl-mcp@${VERSION} already published (confirmed after retry)" | |
| else | |
| echo "FAIL create-sdl-mcp@${VERSION} publish failed" | |
| exit 1 | |
| fi | |
| fi |