Skip to content

Merge pull request #970 from nteract/3.6.0-preflight #50

Merge pull request #970 from nteract/3.6.0-preflight

Merge pull request #970 from nteract/3.6.0-preflight #50

Workflow file for this run

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"