Skip to content

v0.2.21

v0.2.21 #69

Workflow file for this run

name: Publish to npm
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Skip npm publish steps (build/validate only)'
required: false
default: false
type: boolean
dev:
description: 'Publish a dev prerelease version (auto-bumps to -dev.<run_id> and publishes with the dev dist-tag)'
required: false
default: false
type: boolean
release:
types: [published]
jobs:
# Step 0: Build UI (required before Rust binaries)
build-ui:
name: Build UI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build dependencies
run: |
pnpm --filter @leanspec/ui-components build
pnpm --filter @leanspec/ai-worker build
- name: Build UI
run: pnpm --filter @leanspec/ui build
- name: Upload UI dist artifact
uses: actions/upload-artifact@v4
with:
name: ui-dist
path: packages/ui/dist
retention-days: 30
# Step 1: Build Rust binaries for ALL platforms
rust-binaries:
needs: build-ui # UI must be built first
name: Build Rust binaries (${{ matrix.platform }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: x86_64-apple-darwin
platform: darwin-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: darwin-arm64
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-x64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows-x64
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- name: Setup Node.js (for version sync)
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Compute effective version
id: version
shell: bash
run: |
set -euo pipefail
base_version=$(node -p "require('./package.json').version.replace(/-dev\\..*$/, '')")
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dev }}" = "true" ]; then
echo "version=${base_version}-dev.${{ github.run_id }}" >> "$GITHUB_OUTPUT"
else
echo "version=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
fi
- name: Sync Rust workspace version
shell: bash
env:
LEANSPEC_VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
node <<'NODE'
const fs = require('fs');
const version = process.env.LEANSPEC_VERSION;
if (!version) {
console.error('Missing LEANSPEC_VERSION');
process.exit(1);
}
const cargoTomlPath = 'rust/Cargo.toml';
let content = fs.readFileSync(cargoTomlPath, 'utf8');
const re = /(\[workspace\.package\][\s\S]*?\nversion = \")([^\"]+)(\")/m;
if (!re.test(content)) {
console.error('Unable to locate [workspace.package] version in rust/Cargo.toml');
process.exit(1);
}
content = content.replace(re, `$1${version}$3`);
fs.writeFileSync(cargoTomlPath, content);
console.log(`✓ Set rust workspace version to ${version}`);
NODE
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist
path: packages/ui/dist
- name: Build CLI binary
working-directory: rust
run: cargo build --release --target ${{ matrix.target }} --package leanspec-cli
- name: Build MCP binary
working-directory: rust
run: cargo build --release --target ${{ matrix.target }} --package leanspec-mcp
- name: Build HTTP server binary
working-directory: rust
run: cargo build --release --target ${{ matrix.target }} --package leanspec-http
- name: Verify HTTP server binary version
shell: bash
env:
LEANSPEC_VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
expected="$LEANSPEC_VERSION"
bin="rust/target/${{ matrix.target }}/release/leanspec-http"
if [ "${{ runner.os }}" = "Windows" ]; then
bin="${bin}.exe"
fi
actual=$($bin --version | awk '{print $2}')
echo "Expected: $expected"
echo "Actual: $actual"
if [ "$actual" != "$expected" ]; then
echo "❌ Version mismatch: binary reports $actual but expected $expected"
exit 1
fi
- name: Prepare artifacts
shell: bash
run: |
mkdir -p dist/${{ matrix.platform }}
if [ "${{ runner.os }}" = "Windows" ]; then
cp rust/target/${{ matrix.target }}/release/lean-spec.exe dist/${{ matrix.platform }}/
cp rust/target/${{ matrix.target }}/release/leanspec-mcp.exe dist/${{ matrix.platform }}/
cp rust/target/${{ matrix.target }}/release/leanspec-http.exe dist/${{ matrix.platform }}/
else
cp rust/target/${{ matrix.target }}/release/lean-spec dist/${{ matrix.platform }}/
cp rust/target/${{ matrix.target }}/release/leanspec-mcp dist/${{ matrix.platform }}/
cp rust/target/${{ matrix.target }}/release/leanspec-http dist/${{ matrix.platform }}/
fi
echo "=== Prepared artifacts ==="
ls -la dist/${{ matrix.platform }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.platform }}
path: dist/${{ matrix.platform }}
retention-days: 30
# Step 2: Publish platform-specific packages (MUST happen before main packages)
publish-platform:
needs: rust-binaries
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download all platform binaries
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Copy binaries to platform packages
run: ./scripts/copy-platform-binaries.sh artifacts/
- name: Generate platform package manifests
run: pnpm tsx scripts/generate-platform-manifests.ts
- name: Add platform dependencies to parent packages
run: pnpm tsx scripts/add-platform-deps.ts
- name: Validate platform binaries
run: pnpm tsx scripts/validate-platform-binaries.ts
- name: Auto-bump dev version
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dev }}
run: |
pnpm tsx scripts/bump-dev-version.ts ${{ github.run_id }}
pnpm sync-versions
- name: Sync versions (stable release)
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dev }}
run: |
pnpm sync-versions
- name: Publish platform packages
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: |
TAG_ARG=""
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dev }}" = "true" ]; then
TAG_ARG="--tag dev"
fi
pnpm tsx scripts/publish-platform-packages.ts $TAG_ARG
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Step 3: Publish main packages (AFTER platform packages are available)
publish-main:
needs: publish-platform
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Download all platform binaries
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Download UI dist
uses: actions/download-artifact@v4
with:
name: ui-dist
path: packages/ui/dist
- name: Copy binaries for validation
run: ./scripts/copy-platform-binaries.sh artifacts/
- name: Generate platform package manifests
run: pnpm tsx scripts/generate-platform-manifests.ts
- name: Add platform dependencies to parent packages
run: pnpm tsx scripts/add-platform-deps.ts
- name: Ensure CLI binaries are executable
run: |
chmod +x packages/cli/binaries/linux-x64/lean-spec || true
- name: Auto-bump dev version
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dev }}
run: |
pnpm tsx scripts/bump-dev-version.ts ${{ github.run_id }}
pnpm sync-versions
- name: Sync versions (stable release)
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dev }}
run: |
pnpm sync-versions
- name: Wait for platform packages to propagate
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
shell: bash
run: |
set -euo pipefail
TAG="latest"
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dev }}" = "true" ]; then
TAG="dev"
fi
packages=(
"@leanspec/cli-darwin-arm64"
"@leanspec/cli-linux-x64"
"@leanspec/mcp-darwin-arm64"
"@leanspec/mcp-linux-x64"
"@leanspec/http-darwin-arm64"
"@leanspec/http-linux-x64"
)
echo "Waiting 30 seconds before checking to allow all packages to finish publishing..."
sleep 30
# Exponential backoff: start with short waits, increase gradually
wait_times=(5 5 10 10 15 15 20 20 30 30 30 30 40 40 40 40 50 50 60 60)
for attempt in {1..20}; do
wait_time=${wait_times[$((attempt-1))]}
echo ""
echo "Attempt ${attempt}/20: checking npm registry..."
missing=0
missing_packages=()
for pkg in "${packages[@]}"; do
if npm view "$pkg@$TAG" version >/dev/null 2>&1; then
echo " ✓ $pkg@$TAG available"
else
echo " ✗ $pkg@$TAG not available"
missing=1
missing_packages+=("$pkg")
fi
done
if [ "$missing" -eq 0 ]; then
echo ""
echo "✅ All platform packages available!"
exit 0
fi
echo ""
echo "Missing ${#missing_packages[@]} package(s): ${missing_packages[*]}"
echo "Waiting ${wait_time}s for npm registry propagation..."
sleep "$wait_time"
done
echo ""
echo "❌ Platform packages not visible after 20 attempts."
echo "Missing packages: ${missing_packages[*]}"
exit 1
- name: Build TypeScript packages
run: pnpm build
- name: Validate specs
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: node bin/lean-spec.js validate --warnings-only
- name: Prepare packages for npm publish
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: pnpm prepare-publish
- name: Verify no workspace:* protocol after preparation
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: pnpm tsx scripts/validate-no-workspace-protocol.ts
- name: Publish main packages
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: |
TAG_ARG=""
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dev }}" = "true" ]; then
TAG_ARG="--tag dev"
fi
pnpm tsx scripts/publish-main-packages.ts $TAG_ARG
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Restore packages after publish
if: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }}
run: pnpm restore-packages
- name: Summary
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dry_run }}" = "true" ]; then
echo "✅ Dry run completed successfully"
echo ""
echo "All checks passed. Ready to publish for real."
exit 0
fi
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.dev }}" = "true" ]; then
echo "✅ Dev version published successfully"
echo ""
echo "Test installation:"
echo " npm install -g lean-spec@dev"
exit 0
fi
echo "✅ Published main packages successfully"
echo ""
echo "Verify installation:"
echo " npm install -g lean-spec@latest"
echo " lean-spec --version"
- name: Verify publish success
run: |
echo ""
echo "✅ All packages published successfully!"
echo ""
echo "Published:"
echo " - CLI with platform binaries (darwin-x64, darwin-arm64, linux-x64, windows-x64)"
echo " - MCP with platform binaries"
echo " - UI Components"
echo " - HTTP Server"
echo " - UI"
echo ""
echo "Users can now install with:"
echo " npm install -g lean-spec"