Release #7
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: | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: "What to release" | |
| type: choice | |
| options: [auto, extension, mcp-server, both] | |
| default: auto | |
| bump: | |
| description: "Version bump (ignored when target=auto)" | |
| type: choice | |
| options: [patch, minor, major] | |
| default: patch | |
| dry_run: | |
| description: "Dry run — build & test only, skip publish/commit/tag" | |
| type: boolean | |
| default: false | |
| env: | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" | |
| # tsup's --dts worker hits Node's default ~2GB heap during the mcp-server | |
| # build (70+ entry points + DTS emission). Lift the cap to 4GB. | |
| NODE_OPTIONS: "--max-old-space-size=4096" | |
| permissions: | |
| contents: write | |
| id-token: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| # ── Setup ────────────────────────────────────────────────── | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| registry-url: "https://registry.npmjs.org" | |
| - run: npm ci | |
| # Workaround for npm/cli#4828 — same as ci.yml | |
| - name: Install platform-specific tailwind oxide binding | |
| run: npm install --no-save --workspaces=false @tailwindcss/oxide-linux-x64-gnu | |
| # ── Compute target versions ──────────────────────────────── | |
| # auto: use scripts/bump-version.mjs (smart, based on git diff) | |
| # else: for each requested target, pick max(local, published) and bump | |
| - name: Smart bump versions (auto mode) | |
| if: inputs.target == 'auto' | |
| id: smart_bump | |
| run: | | |
| node scripts/bump-version.mjs | |
| MCP_VERSION=$(node -p "require('./packages/mcp-server/package.json').version") | |
| EXT_VERSION=$(node -p "require('./packages/extension/package.json').version") | |
| TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| MCP_OLD="0.0.0" | |
| EXT_OLD="0.0.0" | |
| if [ -n "$TAG" ]; then | |
| MCP_OLD=$(git show "${TAG}:packages/mcp-server/package.json" 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).version)}catch{console.log('0.0.0')}})" || echo "0.0.0") | |
| EXT_OLD=$(git show "${TAG}:packages/extension/package.json" 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).version)}catch{console.log('0.0.0')}})" || echo "0.0.0") | |
| fi | |
| if [ "$MCP_VERSION" != "$MCP_OLD" ]; then echo "mcp_changed=true" >> "$GITHUB_OUTPUT"; else echo "mcp_changed=false" >> "$GITHUB_OUTPUT"; fi | |
| if [ "$EXT_VERSION" != "$EXT_OLD" ]; then echo "ext_changed=true" >> "$GITHUB_OUTPUT"; else echo "ext_changed=false" >> "$GITHUB_OUTPUT"; fi | |
| echo "mcp_version=${MCP_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "ext_version=${EXT_VERSION}" >> "$GITHUB_OUTPUT" | |
| - name: Bump extension version (manual mode) | |
| if: inputs.target != 'auto' && (inputs.target == 'extension' || inputs.target == 'both') | |
| id: ext_version | |
| run: | | |
| PKG="packages/extension/package.json" | |
| LOCAL=$(node -p "require('./$PKG').version") | |
| PUBLISHED=$(npx --yes vsce show Nskha.perplexity-vscode --json 2>/dev/null \ | |
| | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{console.log(JSON.parse(d).versions[0].version)}catch{console.log('0.0.0')}})" || echo "0.0.0") | |
| echo "Local: ${LOCAL}, Marketplace: ${PUBLISHED}" | |
| BASE=$(node -e " | |
| const a = '${LOCAL}'.split('.').map(Number); | |
| const b = '${PUBLISHED}'.split('.').map(Number); | |
| const cmp = a[0]-b[0] || a[1]-b[1] || a[2]-b[2]; | |
| console.log(cmp >= 0 ? '${LOCAL}' : '${PUBLISHED}'); | |
| ") | |
| NEXT=$(node -e " | |
| const [major, minor, patch] = '${BASE}'.split('.').map(Number); | |
| const bump = '${{ inputs.bump }}'; | |
| if (bump === 'major') console.log((major+1)+'.0.0'); | |
| else if (bump === 'minor') console.log(major+'.'+(minor+1)+'.0'); | |
| else console.log(major+'.'+minor+'.'+(patch+1)); | |
| ") | |
| echo "current=${BASE}" >> $GITHUB_OUTPUT | |
| echo "next=${NEXT}" >> $GITHUB_OUTPUT | |
| node -e " | |
| const fs = require('fs'); | |
| const p = '$PKG'; | |
| const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); | |
| pkg.version = '${NEXT}'; | |
| fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| echo "Extension: ${BASE} → ${NEXT}" | |
| env: | |
| VSCE_PAT: ${{ secrets.VSCE_PAT }} | |
| - name: Bump MCP server version (manual mode) | |
| if: inputs.target != 'auto' && (inputs.target == 'mcp-server' || inputs.target == 'both') | |
| id: mcp_version | |
| run: | | |
| PKG="packages/mcp-server/package.json" | |
| LOCAL=$(node -p "require('./$PKG').version") | |
| PUBLISHED=$(npm view perplexity-user-mcp version 2>/dev/null || echo "0.0.0") | |
| echo "Local: ${LOCAL}, npm: ${PUBLISHED}" | |
| BASE=$(node -e " | |
| const a = '${LOCAL}'.split('.').map(Number); | |
| const b = '${PUBLISHED}'.split('.').map(Number); | |
| const cmp = a[0]-b[0] || a[1]-b[1] || a[2]-b[2]; | |
| console.log(cmp >= 0 ? '${LOCAL}' : '${PUBLISHED}'); | |
| ") | |
| NEXT=$(node -e " | |
| const [major, minor, patch] = '${BASE}'.split('.').map(Number); | |
| const bump = '${{ inputs.bump }}'; | |
| if (bump === 'major') console.log((major+1)+'.0.0'); | |
| else if (bump === 'minor') console.log(major+'.'+(minor+1)+'.0'); | |
| else console.log(major+'.'+minor+'.'+(patch+1)); | |
| ") | |
| echo "current=${BASE}" >> $GITHUB_OUTPUT | |
| echo "next=${NEXT}" >> $GITHUB_OUTPUT | |
| node -e " | |
| const fs = require('fs'); | |
| const p = '$PKG'; | |
| const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); | |
| pkg.version = '${NEXT}'; | |
| fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| echo "MCP server: ${BASE} → ${NEXT}" | |
| # ── Build & Test ─────────────────────────────────────────── | |
| - name: Build all packages | |
| run: npm run build | |
| - name: Typecheck | |
| run: npm run typecheck | |
| - name: Test | |
| run: npm test | |
| # ── Compute "did this target change?" flags ──────────────── | |
| - name: Resolve effective targets | |
| id: targets | |
| run: | | |
| if [ "${{ inputs.target }}" = "auto" ]; then | |
| echo "publish_ext=${{ steps.smart_bump.outputs.ext_changed }}" >> $GITHUB_OUTPUT | |
| echo "publish_mcp=${{ steps.smart_bump.outputs.mcp_changed }}" >> $GITHUB_OUTPUT | |
| echo "ext_next=${{ steps.smart_bump.outputs.ext_version }}" >> $GITHUB_OUTPUT | |
| echo "mcp_next=${{ steps.smart_bump.outputs.mcp_version }}" >> $GITHUB_OUTPUT | |
| else | |
| if [ "${{ inputs.target }}" = "extension" ] || [ "${{ inputs.target }}" = "both" ]; then | |
| echo "publish_ext=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "publish_ext=false" >> $GITHUB_OUTPUT | |
| fi | |
| if [ "${{ inputs.target }}" = "mcp-server" ] || [ "${{ inputs.target }}" = "both" ]; then | |
| echo "publish_mcp=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "publish_mcp=false" >> $GITHUB_OUTPUT | |
| fi | |
| echo "ext_next=${{ steps.ext_version.outputs.next }}" >> $GITHUB_OUTPUT | |
| echo "mcp_next=${{ steps.mcp_version.outputs.next }}" >> $GITHUB_OUTPUT | |
| fi | |
| # ── Package extension VSIX ───────────────────────────────── | |
| - name: Package VSIX | |
| id: vsix | |
| if: steps.targets.outputs.publish_ext == 'true' | |
| working-directory: packages/extension | |
| run: | | |
| npm run prepare:package-deps | |
| npx @vscode/vsce package --no-dependencies | |
| VSIX=$(ls *.vsix | head -1) | |
| echo "file=packages/extension/${VSIX}" >> $GITHUB_OUTPUT | |
| echo "Packaged: ${VSIX}" | |
| # ── Publish extension ────────────────────────────────────── | |
| - name: Publish extension to VS Code Marketplace | |
| if: | | |
| !inputs.dry_run && | |
| steps.targets.outputs.publish_ext == 'true' | |
| run: npx @vscode/vsce publish --packagePath "${{ steps.vsix.outputs.file }}" | |
| env: | |
| VSCE_PAT: ${{ secrets.VSCE_PAT }} | |
| - name: Publish extension to Open VSX | |
| if: | | |
| !inputs.dry_run && | |
| steps.targets.outputs.publish_ext == 'true' && | |
| env.OVSX_PAT != '' | |
| run: npx --yes ovsx publish "${{ steps.vsix.outputs.file }}" --pat $OVSX_PAT | |
| env: | |
| OVSX_PAT: ${{ secrets.OVSX_PAT }} | |
| # ── Publish MCP server ───────────────────────────────────── | |
| - name: Publish MCP server to npm | |
| if: | | |
| !inputs.dry_run && | |
| steps.targets.outputs.publish_mcp == 'true' | |
| working-directory: packages/mcp-server | |
| run: npm publish --provenance --access public | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # ── Commit, tag, push ────────────────────────────────────── | |
| - name: Commit version bumps and create tags | |
| if: "!inputs.dry_run" | |
| run: | | |
| MSG_PARTS="" | |
| TAGS="" | |
| if [ "${{ steps.targets.outputs.publish_ext }}" = "true" ]; then | |
| git add packages/extension/package.json | |
| MSG_PARTS="${MSG_PARTS} extension v${{ steps.targets.outputs.ext_next }}" | |
| TAGS="${TAGS} extension/v${{ steps.targets.outputs.ext_next }}" | |
| fi | |
| if [ "${{ steps.targets.outputs.publish_mcp }}" = "true" ]; then | |
| git add packages/mcp-server/package.json | |
| MSG_PARTS="${MSG_PARTS} mcp-server v${{ steps.targets.outputs.mcp_next }}" | |
| TAGS="${TAGS} mcp-server/v${{ steps.targets.outputs.mcp_next }}" | |
| fi | |
| # Only commit if we actually have something to commit | |
| if [ -n "$MSG_PARTS" ]; then | |
| git commit -m "release:${MSG_PARTS}" || true | |
| for TAG in $TAGS; do | |
| git tag "$TAG" || true | |
| done | |
| git push origin HEAD --tags | |
| else | |
| echo "Nothing to commit (no targets selected to publish)." | |
| fi | |
| # ── GitHub Releases ──────────────────────────────────────── | |
| - name: Create extension GitHub Release | |
| if: | | |
| !inputs.dry_run && | |
| steps.targets.outputs.publish_ext == 'true' | |
| run: | | |
| gh release create "extension/v${{ steps.targets.outputs.ext_next }}" \ | |
| "${{ steps.vsix.outputs.file }}" \ | |
| --title "Extension v${{ steps.targets.outputs.ext_next }}" \ | |
| --generate-notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create MCP server GitHub Release | |
| if: | | |
| !inputs.dry_run && | |
| steps.targets.outputs.publish_mcp == 'true' | |
| run: | | |
| gh release create "mcp-server/v${{ steps.targets.outputs.mcp_next }}" \ | |
| --title "MCP Server v${{ steps.targets.outputs.mcp_next }}" \ | |
| --generate-notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ── Summary ──────────────────────────────────────────────── | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Release Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- Mode: \`${{ inputs.target }}\`" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| echo "- **DRY RUN — nothing published**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "${{ steps.targets.outputs.publish_ext }}" = "true" ]; then | |
| echo "- Extension: published \`v${{ steps.targets.outputs.ext_next }}\`" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "${{ steps.targets.outputs.publish_mcp }}" = "true" ]; then | |
| echo "- MCP server: published \`v${{ steps.targets.outputs.mcp_next }}\`" >> $GITHUB_STEP_SUMMARY | |
| fi |