Merge pull request #963 from nteract/click-testing-fixes #49
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*"] | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| # `contents: write` is required for `gh release create` to publish | |
| # the GitHub Release at the end of the workflow. `id-token: write` | |
| # stays for npm provenance. | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: Checkout source code | |
| uses: actions/checkout@v6 | |
| - name: Use Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22.x | |
| cache: "npm" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install dependencies | |
| run: npm install --legacy-peer-deps | |
| - name: Build library | |
| run: npm run dist | |
| - name: Build MCP server | |
| run: npm run build:mcp | |
| - name: Run tests with coverage | |
| run: npx vitest run --coverage | |
| - name: Run type check | |
| run: npm run typescript | |
| - name: Run test type check | |
| run: npm run typescript:tests | |
| - name: Check chart spec registry round-trip | |
| run: npm run check:chart-specs | |
| - name: Check capability matrix freshness | |
| run: npm run check:capabilities | |
| - name: Check blog metadata registry freshness | |
| run: npm run check:blog-entries | |
| - name: Verify TypeScript declarations | |
| run: | | |
| for f in dist/semiotic.d.ts dist/semiotic-xy.d.ts dist/semiotic-ordinal.d.ts dist/semiotic-network.d.ts dist/semiotic-geo.d.ts dist/semiotic-realtime.d.ts dist/semiotic-ai.d.ts dist/semiotic-data.d.ts dist/semiotic-server.d.ts dist/semiotic-themes.d.ts; do | |
| if [ ! -f "$f" ]; then | |
| echo "MISSING: $f — aborting release" | |
| exit 1 | |
| fi | |
| done | |
| echo "All declaration files present" | |
| - name: Determine npm dist-tag | |
| id: dist-tag | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| if echo "$VERSION" | grep -qE '[-](alpha|beta|rc)'; then | |
| echo "tag=beta" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=latest" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Pack-and-import smoke test runs BEFORE publish (not inside | |
| # prepublishOnly). Running `npm pack` inside the prepublishOnly | |
| # hook of `npm publish` can hit a re-entrancy edge case on some | |
| # npm versions where the inner pack returns 0 without producing | |
| # a tarball, breaking the smoke check (seen on Node 22 / npm 10 | |
| # in CI). Splitting it out keeps the validation while sidestepping | |
| # the lifecycle interaction. | |
| - name: Pack-and-import smoke test | |
| run: npm run check:pack | |
| - name: Dry-run publish (validate package) | |
| run: npm publish --dry-run --tag ${{ steps.dist-tag.outputs.tag }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Publish to npm | |
| run: npm publish --provenance --access public --tag ${{ steps.dist-tag.outputs.tag }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| - name: Post-publish smoke test | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| TAG=${{ steps.dist-tag.outputs.tag }} | |
| echo "Waiting for npm to propagate semiotic@${VERSION}..." | |
| for i in 1 2 3 4 5; do | |
| PUBLISHED=$(npm view semiotic@${VERSION} version 2>/dev/null || echo "") | |
| if [ "$PUBLISHED" = "$VERSION" ]; then | |
| echo "Package available on npm" | |
| break | |
| fi | |
| echo "Attempt $i: not yet available, waiting 15s..." | |
| sleep 15 | |
| done | |
| if [ "$PUBLISHED" != "$VERSION" ]; then | |
| echo "WARNING: Package not yet visible on npm registry after 75s" | |
| echo "This is likely a propagation delay — check manually" | |
| exit 0 | |
| fi | |
| # Install in a temp project and verify imports | |
| TMPDIR=$(mktemp -d) | |
| cd "$TMPDIR" | |
| npm init -y > /dev/null 2>&1 | |
| npm install semiotic@${VERSION} react react-dom > /dev/null 2>&1 | |
| node -e " | |
| const assert = require('assert'); | |
| // Main entry | |
| const s = require('semiotic'); | |
| assert(s.BarChart, 'BarChart not exported from semiotic'); | |
| assert(s.LineChart, 'LineChart not exported from semiotic'); | |
| assert(s.ForceDirectedGraph, 'ForceDirectedGraph not exported from semiotic'); | |
| // Sub-path entries | |
| const xy = require('semiotic/xy'); | |
| assert(xy.LineChart, 'LineChart not exported from semiotic/xy'); | |
| const ord = require('semiotic/ordinal'); | |
| assert(ord.BarChart, 'BarChart not exported from semiotic/ordinal'); | |
| const net = require('semiotic/network'); | |
| assert(net.SankeyDiagram, 'SankeyDiagram not exported from semiotic/network'); | |
| const rt = require('semiotic/realtime'); | |
| assert(rt.RealtimeLineChart, 'RealtimeLineChart not exported from semiotic/realtime'); | |
| const srv = require('semiotic/server'); | |
| assert(srv.renderChart, 'renderChart not exported from semiotic/server'); | |
| const themes = require('semiotic/themes'); | |
| assert(themes.resolveThemePreset, 'resolveThemePreset not exported from semiotic/themes'); | |
| const utils = require('semiotic/utils'); | |
| assert(utils.validateProps, 'validateProps not exported from semiotic/utils'); | |
| console.log('All smoke tests passed — 8 entry points verified'); | |
| " | |
| rm -rf "$TMPDIR" | |
| - name: Create or update GitHub Release | |
| # Pulls the matching CHANGELOG section so the GH release page | |
| # mirrors the version's entry. Falls back to `--generate-notes` | |
| # (auto-built from PR titles) if the section is missing or empty | |
| # so a release page is still created either way. Without this | |
| # step a successful npm publish leaves the GitHub Releases page | |
| # stuck on the previous version (the v3.2.3 → v3.4.2 backfill | |
| # in 2026-04-28 fixed exactly that gap). | |
| # | |
| # Idempotent on workflow re-runs: if the release for this tag | |
| # already exists, `gh release edit` updates the title/notes in | |
| # place rather than failing on the conflict. Without that, a | |
| # re-run for any reason (transient infra failure, manually | |
| # re-triggered after a fix) would error out at this step. | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| NOTES_FILE="$(mktemp)" | |
| awk -v ver="$VERSION" ' | |
| BEGIN { flag = 0 } | |
| $0 ~ "^## \\[" ver "\\]" { flag = 1; next } | |
| flag && /^## \[/ { exit } | |
| flag { print } | |
| ' CHANGELOG.md > "$NOTES_FILE" | |
| if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then | |
| echo "Release $GITHUB_REF_NAME already exists; updating in place" | |
| if [ -s "$NOTES_FILE" ]; then | |
| gh release edit "$GITHUB_REF_NAME" \ | |
| --title "Semiotic $GITHUB_REF_NAME" \ | |
| --notes-file "$NOTES_FILE" | |
| else | |
| echo "No CHANGELOG section for $VERSION; leaving existing notes unchanged" | |
| gh release edit "$GITHUB_REF_NAME" \ | |
| --title "Semiotic $GITHUB_REF_NAME" | |
| fi | |
| else | |
| if [ -s "$NOTES_FILE" ]; then | |
| gh release create "$GITHUB_REF_NAME" \ | |
| --title "Semiotic $GITHUB_REF_NAME" \ | |
| --notes-file "$NOTES_FILE" \ | |
| --verify-tag | |
| else | |
| echo "No CHANGELOG section for $VERSION; falling back to --generate-notes" | |
| gh release create "$GITHUB_REF_NAME" \ | |
| --title "Semiotic $GITHUB_REF_NAME" \ | |
| --generate-notes \ | |
| --verify-tag | |
| fi | |
| fi | |
| rm -f "$NOTES_FILE" |