Release #25
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 publish' | |
| type: choice | |
| options: | |
| - extension | |
| - mcp-server | |
| - both | |
| default: extension | |
| bump: | |
| description: 'Version bump type' | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| default: patch | |
| dry_run: | |
| description: 'Dry run — build & test only, skip publish/commit/tag' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| id-token: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| 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: pnpm/action-setup@v4 | |
| with: | |
| version: 9 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| cache: 'pnpm' | |
| registry-url: 'https://registry.npmjs.org' | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm install -wD @vscode/vsce ovsx | |
| # ── Compute versions ─────────────────────────────────────── | |
| # For each target, we check both package.json AND the registry. | |
| # We bump from whichever is higher, so we never collide with | |
| # an already-published version. | |
| - name: Bump extension version | |
| id: ext_version | |
| if: inputs.target == 'extension' || inputs.target == 'both' | |
| run: | | |
| PKG="packages/extension/package.json" | |
| LOCAL=$(node -p "require('./$PKG').version") | |
| # Query VS Code Marketplace for the latest published version | |
| PUBLISHED=$(npx vsce show Nskha.airtable-formula --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}" | |
| # Pick the higher version as the base for bumping | |
| 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 | |
| # Write new version to package.json | |
| 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 | |
| id: mcp_version | |
| if: inputs.target == 'mcp-server' || inputs.target == 'both' | |
| run: | | |
| PKG="packages/mcp-server/package.json" | |
| LOCAL=$(node -p "require('./$PKG').version") | |
| # Query npm for the latest published version | |
| PUBLISHED=$(npm view airtable-user-mcp version 2>/dev/null || echo "0.0.0") | |
| echo "Local: ${LOCAL}, npm: ${PUBLISHED}" | |
| # Pick the higher version as the base for bumping | |
| 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 | |
| # Write new version to package.json | |
| 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: pnpm build | |
| - name: Run tests | |
| run: pnpm test | |
| # ── Package extension VSIX ───────────────────────────────── | |
| - name: Package VSIX | |
| id: vsix | |
| if: inputs.target == 'extension' || inputs.target == 'both' | |
| run: | | |
| # Prepare deps for VSIX | |
| node scripts/prepare-package-deps.mjs | |
| # Copy README with SVG→PNG transform | |
| node -e " | |
| const fs = require('fs'); | |
| let readme = fs.readFileSync('README.md', 'utf8'); | |
| readme = readme.replace(/<img src=\"[^\"]*airtable\\.svg\"[^/]*\\/>/, | |
| '<img src=\"https://raw.githubusercontent.com/Automations-Project/VSCode-Airtable-Formula/main/packages/extension/images/icon.png\" alt=\"Airtable Formula\" width=\"80\" />'); | |
| readme = readme.replace(/\| <img src=\"[^\"]*claude\\.svg\"[^|]*\|[^\n]*\n/, | |
| '| Claude Desktop | Claude Code | Cursor | Windsurf | Cline | Amp |\n'); | |
| readme = readme.replace(/<img src=\"[^\"]*mcp\\.svg\"[^/]*\\/>/g, ''); | |
| fs.writeFileSync('packages/extension/README.md', readme); | |
| " | |
| # Package | |
| cd packages/extension | |
| npx 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 && | |
| (inputs.target == 'extension' || inputs.target == 'both') | |
| run: npx vsce publish --packagePath "${{ steps.vsix.outputs.file }}" | |
| env: | |
| VSCE_PAT: ${{ secrets.VSCE_PAT }} | |
| - name: Publish extension to Open VSX | |
| if: | | |
| !inputs.dry_run && | |
| (inputs.target == 'extension' || inputs.target == 'both') | |
| run: npx 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 && | |
| (inputs.target == 'mcp-server' || inputs.target == 'both') | |
| working-directory: packages/mcp-server | |
| run: npm publish --provenance --access public | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # ── Commit, tag, push ────────────────────────────────────── | |
| - name: Commit version bump and create tags | |
| if: "!inputs.dry_run" | |
| run: | | |
| TARGETS="" | |
| TAGS="" | |
| # Stage modified package.json files | |
| if [[ "${{ inputs.target }}" == "extension" || "${{ inputs.target }}" == "both" ]]; then | |
| git add packages/extension/package.json | |
| TARGETS="${TARGETS} extension v${{ steps.ext_version.outputs.next }}" | |
| TAGS="${TAGS} extension/v${{ steps.ext_version.outputs.next }}" | |
| fi | |
| if [[ "${{ inputs.target }}" == "mcp-server" || "${{ inputs.target }}" == "both" ]]; then | |
| git add packages/mcp-server/package.json | |
| TARGETS="${TARGETS} mcp-server v${{ steps.mcp_version.outputs.next }}" | |
| TAGS="${TAGS} mcp-server/v${{ steps.mcp_version.outputs.next }}" | |
| fi | |
| # Commit | |
| git commit -m "release:${TARGETS}" | |
| # Create tags | |
| for TAG in $TAGS; do | |
| git tag "$TAG" | |
| done | |
| # Push commit and tags | |
| git push origin main --tags | |
| # ── GitHub Releases ──────────────────────────────────────── | |
| - name: Create extension GitHub Release | |
| if: | | |
| !inputs.dry_run && | |
| (inputs.target == 'extension' || inputs.target == 'both') | |
| run: | | |
| gh release create "extension/v${{ steps.ext_version.outputs.next }}" \ | |
| "${{ steps.vsix.outputs.file }}" \ | |
| --title "Extension v${{ steps.ext_version.outputs.next }}" \ | |
| --generate-notes \ | |
| --target main | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create MCP server GitHub Release | |
| if: | | |
| !inputs.dry_run && | |
| (inputs.target == 'mcp-server' || inputs.target == 'both') | |
| run: | | |
| gh release create "mcp-server/v${{ steps.mcp_version.outputs.next }}" \ | |
| --title "MCP Server v${{ steps.mcp_version.outputs.next }}" \ | |
| --generate-notes \ | |
| --target main | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ── Summary ──────────────────────────────────────────────── | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Release Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ inputs.dry_run }}" == "true" ]]; then | |
| echo "**DRY RUN — nothing published**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ inputs.target }}" == "extension" || "${{ inputs.target }}" == "both" ]]; then | |
| echo "- Extension: ${{ steps.ext_version.outputs.current }} → **${{ steps.ext_version.outputs.next }}**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ inputs.target }}" == "mcp-server" || "${{ inputs.target }}" == "both" ]]; then | |
| echo "- MCP Server: ${{ steps.mcp_version.outputs.current }} → **${{ steps.mcp_version.outputs.next }}**" >> $GITHUB_STEP_SUMMARY | |
| fi |