Simplify v0.8.6 release distribution #8
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 MCP Registry | |
| on: | |
| push: | |
| tags: ["v*"] | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| MCP_SERVER_NAME: io.github.flyfish-dev/word-ai | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Compute release version | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| echo "VERSION=${VERSION}" >> "$GITHUB_ENV" | |
| echo "MCPB_PATH=dist/word-ai-${VERSION}.mcpb" >> "$GITHUB_ENV" | |
| echo "MCPB_URL=https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/word-ai-${VERSION}.mcpb" >> "$GITHUB_ENV" | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: "8.0.x" | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| - name: Run MCP smoke checks | |
| run: | | |
| PYTHONPATH=. python -m compileall word_ai_mcp scripts | |
| PYTHONPATH=. python scripts/run_smoke_test.py | |
| PYTHONPATH=. python scripts/run_structure_regression.py | |
| PYTHONPATH=. python scripts/run_outline_regression.py | |
| PYTHONPATH=. python scripts/run_patchset_alias_regression.py | |
| PYTHONPATH=. python scripts/run_engine_selection_regression.py | |
| PYTHONPATH=. python scripts/run_word_session_smoke.py | |
| PYTHONPATH=. python scripts/validate_word_ai_skill.py | |
| - name: Build all native .NET Open XML backends | |
| run: | | |
| PYTHONPATH=. python scripts/publish_native_backends.py --all --clean --json | |
| PYTHONPATH=. python scripts/verify_native_backends.py --require-all | |
| - name: Build deterministic MCPB | |
| run: | | |
| PYTHONPATH=. python scripts/build_mcpb.py --version "$VERSION" --out "$MCPB_PATH" | |
| SHA256="$(shasum -a 256 "$MCPB_PATH" | awk '{print $1}')" | |
| echo "MCPB_SHA256=${SHA256}" >> "$GITHUB_ENV" | |
| - name: npm package smoke | |
| run: | | |
| npm pack --dry-run --json > dist/npm-scoped-pack.json | |
| python - <<'PY' | |
| import json | |
| from pathlib import Path | |
| data = json.loads(Path("dist/npm-scoped-pack.json").read_text())[0] | |
| paths = {item["path"] for item in data["files"]} | |
| required = { | |
| "README.md", | |
| "README.zh-CN.md", | |
| "docs/GETTING_STARTED.md", | |
| "docs/GETTING_STARTED.zh-CN.md", | |
| "docs/DISTRIBUTION.md", | |
| "docs/DISTRIBUTION.zh-CN.md", | |
| "requirements-standalone.txt", | |
| "scripts/publish_native_backends.py", | |
| "scripts/verify_native_backends.py", | |
| "scripts/package_native_assets.py", | |
| "scripts/build_standalone.py", | |
| "scripts/package_quickstart.py", | |
| } | |
| missing = sorted(required - paths) | |
| if missing: | |
| raise SystemExit(f"npm package is missing required files: {missing}") | |
| native_payload = sorted(path for path in paths if path.startswith("dist/native/") or path.startswith("native/")) | |
| if native_payload: | |
| raise SystemExit(f"npm package should not embed native backends; found: {native_payload[:5]}") | |
| print("npm package payload includes docs and native downloader scripts, without oversized native binaries") | |
| PY | |
| - name: Align server.json with tag and MCPB | |
| run: | | |
| python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| path = Path("server.json") | |
| data = json.loads(path.read_text()) | |
| version = os.environ["VERSION"] | |
| server_name = os.environ["MCP_SERVER_NAME"] | |
| mcpb_url = os.environ["MCPB_URL"] | |
| mcpb_sha256 = os.environ["MCPB_SHA256"] | |
| data["name"] = server_name | |
| data["version"] = version | |
| data["packages"] = [ | |
| { | |
| "registryType": "mcpb", | |
| "identifier": mcpb_url, | |
| "fileSha256": mcpb_sha256, | |
| "transport": {"type": "stdio"}, | |
| } | |
| ] | |
| path.write_text(json.dumps(data, indent=2) + "\n") | |
| PY | |
| - name: Validate release metadata | |
| run: | | |
| python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| data = json.loads(Path("server.json").read_text()) | |
| assert data["name"] == os.environ["MCP_SERVER_NAME"] | |
| assert data["version"] == os.environ["VERSION"] | |
| assert data["packages"][0]["registryType"] == "mcpb" | |
| assert data["packages"][0]["identifier"] == os.environ["MCPB_URL"] | |
| assert data["packages"][0]["fileSha256"] == os.environ["MCPB_SHA256"] | |
| assert data["packages"][0]["transport"]["type"] == "stdio" | |
| print("server.json release metadata is valid") | |
| PY | |
| - name: Docker build smoke | |
| run: | | |
| docker build --tag "word-ai:${VERSION}" . | |
| - name: Create or update GitHub release asset | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then | |
| gh release upload "$GITHUB_REF_NAME" "$MCPB_PATH" --clobber | |
| else | |
| NOTES="docs/CHANGELOG_V081.md" | |
| CANDIDATE="docs/CHANGELOG_V$(echo "$VERSION" | tr -d '.').md" | |
| if [ -f "$CANDIDATE" ]; then | |
| NOTES="$CANDIDATE" | |
| fi | |
| gh release create "$GITHUB_REF_NAME" "$MCPB_PATH" \ | |
| --title "Word AI ${VERSION}" \ | |
| --notes-file "$NOTES" | |
| fi | |
| - name: Publish scoped npm package | |
| env: | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| if npm view "@flyfish-dev/word-ai@${VERSION}" version >/dev/null 2>&1; then | |
| echo "@flyfish-dev/word-ai@${VERSION} already exists; skipping." | |
| exit 0 | |
| fi | |
| if [ -n "${NPM_TOKEN:-}" ]; then | |
| printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$HOME/.npmrc" | |
| fi | |
| if ! npm publish --provenance --access public; then | |
| echo "::warning title=Scoped npm publish skipped::Could not publish @flyfish-dev/word-ai@${VERSION}. Configure npm trusted publishing or NPM_TOKEN permissions for the @flyfish-dev scope." | |
| fi | |
| - name: Publish unscoped npm compatibility package | |
| env: | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| if npm view "word-ai-mcp@${VERSION}" version >/dev/null 2>&1; then | |
| echo "word-ai-mcp@${VERSION} already exists; skipping." | |
| exit 0 | |
| fi | |
| if [ -n "${NPM_TOKEN:-}" ]; then | |
| printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$HOME/.npmrc" | |
| fi | |
| python - <<'PY' | |
| import json | |
| from pathlib import Path | |
| path = Path("package.json") | |
| data = json.loads(path.read_text()) | |
| data["name"] = "word-ai-mcp" | |
| data["description"] = "Unscoped compatibility package for the Word AI MCP server and Office.js bridge" | |
| path.write_text(json.dumps(data, indent=2) + "\n") | |
| PY | |
| if ! npm publish --provenance --access public; then | |
| echo "::warning title=Unscoped npm publish skipped::Could not publish word-ai-mcp@${VERSION}. Configure npm trusted publishing or NPM_TOKEN permissions for the package." | |
| fi | |
| - name: Install mcp-publisher | |
| run: | | |
| curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher | |
| chmod +x mcp-publisher | |
| - name: Authenticate to MCP Registry | |
| run: ./mcp-publisher login github-oidc | |
| - name: Validate MCP Registry metadata | |
| run: ./mcp-publisher validate | |
| - name: Publish to MCP Registry | |
| run: ./mcp-publisher publish |