diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index b1dfe7d..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: "Can't log in?" - url: https://github.com/Automations-Project/VSCode-Airtable-Formula/tree/main/packages/mcp-server#authentication - about: Check the auth guide before opening an auth issue - - name: MCP Registry - url: https://registry.modelcontextprotocol.io - about: Find this server and others on the MCP Registry diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml new file mode 100644 index 0000000..7a922a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -0,0 +1,142 @@ +name: Issue Report +description: Report a bug, regression, or request an enhancement +title: "[Issue]: " +labels: + - triage +body: + - type: dropdown + id: issue_type + attributes: + label: Issue Type + description: What kind of issue are you reporting? + options: + - Bug + - Regression + - Performance/Freeze + - Feature Request + - Documentation + - Other + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Area + description: Which part of the extension is affected? + options: + - Beautifier v2 + - Minifier v2 + - Beautifier v1 + - Minifier v1 + - Diagnostics + - Syntax Highlighting + - IntelliSense / Completions + - Commands / Context Menus + - Skills Installer + - Build / Release + - Other + validations: + required: true + + - type: dropdown + id: severity + attributes: + label: Severity / Impact + options: + - Low (minor issue) + - Medium (workflow impacted) + - High (blocking) + validations: + required: true + + - type: input + id: extension_version + attributes: + label: Extension Version + placeholder: e.g. 0.2.0 + validations: + required: true + + - type: input + id: vscode_version + attributes: + label: VS Code Version + placeholder: e.g. 1.86.1 + validations: + required: true + + - type: input + id: os + attributes: + label: OS + placeholder: e.g. Windows 11 / macOS 14 / Ubuntu 22.04 + validations: + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: Short summary of the issue or request. + placeholder: Provide a concise summary. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Required for bugs, regressions, or freezes. + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: false + + - type: textarea + id: expected + attributes: + label: Expected Behavior + placeholder: What should happen? + validations: + required: false + + - type: textarea + id: actual + attributes: + label: Actual Behavior + placeholder: What actually happened? + validations: + required: false + + - type: textarea + id: formula + attributes: + label: Sample Formula / Minimal Repro + description: Provide a minimal formula sample (redact sensitive data). + placeholder: | + Example: + IF({Status}="Open", "Yes", "No") + validations: + required: false + + - type: textarea + id: logs + attributes: + label: Logs / Screenshots + description: Paste extension host logs, error output, or screenshots. + placeholder: Attach logs or screenshots if available. + validations: + required: false + + - type: checkboxes + id: confirmation + attributes: + label: Confirmation + options: + - label: I searched existing issues to avoid duplicates. + required: true + - label: I can share a minimal repro formula if needed. + required: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3638ad8..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,390 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - target: - description: 'What to publish' - type: choice - options: - - extension - - mcp-server - - lsp-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}" - - - name: Bump LSP server version - id: lsp_version - if: inputs.target == 'lsp-server' || inputs.target == 'both' - run: | - PKG="packages/lsp-server/package.json" - LOCAL=$(node -p "require('./$PKG').version") - - # Query npm for the latest published version - PUBLISHED=$(npm view airtable-user-lsp 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 "LSP 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: strip SVGs + collapse to single for VS Code Marketplace - node -e " - const fs = require('fs'); - let readme = fs.readFileSync('README.md', 'utf8'); - // Replace extension icon SVG with PNG - readme = readme.replace(//, - '\"Airtable'); - // Replace claude SVG row in table - readme = readme.replace(/\| /g, ''); - // Collapse all blocks to plain — use the light PNG for the marketplace (white bg) - readme = readme.replace(/[\s\S]*?[\s\S]*?<\/picture>/g, - (_, src, attrs) => ''); - 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 - - # Verify the version actually landed (Open VSX indexes asynchronously) - VERSION="${{ steps.ext_version.outputs.next }}" - echo "Verifying Open VSX indexed v${VERSION}..." - for i in $(seq 1 18); do - FOUND=$(curl -s "https://open-vsx.org/api/Nskha/airtable-formula/${VERSION}" \ - | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{ - try{const p=JSON.parse(d);console.log(p.version||'not-found')} - catch{console.log('error')} - })") - if [[ "$FOUND" == "$VERSION" ]]; then - echo "✓ Open VSX confirmed: v${VERSION}" - break - fi - echo " not indexed yet (attempt $i/18), waiting 10s..." - sleep 10 - done - if [[ "$FOUND" != "$VERSION" ]]; then - echo "::error::Open VSX did not index v${VERSION} within 3 minutes — check token and registry status" - exit 1 - fi - 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') - run: | - npm config set //registry.npmjs.org/:_authToken "${NPM_TOKEN}" - npm whoami && echo "npm auth OK" || echo "WARNING: npm whoami failed — verify NPM_TOKEN secret" - cd packages/mcp-server - npm publish --provenance --access public - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - # ── Publish LSP server ───────────────────────────────────── - - name: Publish LSP server to npm - if: | - !inputs.dry_run && - (inputs.target == 'lsp-server' || inputs.target == 'both') - run: | - npm config set //registry.npmjs.org/:_authToken "${NPM_TOKEN}" - cd packages/lsp-server - npm publish --provenance --access public - env: - NPM_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 - - if [[ "${{ inputs.target }}" == "lsp-server" || "${{ inputs.target }}" == "both" ]]; then - git add packages/lsp-server/package.json - TARGETS="${TARGETS} lsp-server v${{ steps.lsp_version.outputs.next }}" - TAGS="${TAGS} lsp-server/v${{ steps.lsp_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 }} - - - name: Create LSP server GitHub Release - if: | - !inputs.dry_run && - (inputs.target == 'lsp-server' || inputs.target == 'both') - run: | - gh release create "lsp-server/v${{ steps.lsp_version.outputs.next }}" \ - --title "LSP Server v${{ steps.lsp_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 - if [[ "${{ inputs.target }}" == "lsp-server" || "${{ inputs.target }}" == "both" ]]; then - echo "- LSP Server: ${{ steps.lsp_version.outputs.current }} → **${{ steps.lsp_version.outputs.next }}**" >> $GITHUB_STEP_SUMMARY - fi diff --git a/.github/workflows/setup-guide.md b/.github/workflows/setup-guide.md new file mode 100644 index 0000000..a218fcd --- /dev/null +++ b/.github/workflows/setup-guide.md @@ -0,0 +1,102 @@ +# VS Code Extension Release Setup Guide + +## Required Secrets Configuration + +To use the improved release workflow, you'll need to set up the following GitHub repository secrets: + +### 1. VSCE_PAT (Visual Studio Code Marketplace Personal Access Token) +Required for publishing to the VS Code Marketplace. + +**Steps to create VSCE_PAT:** +1. Go to https://dev.azure.com/ +2. Sign in with your Microsoft account (create one if needed) +3. Click on your profile icon → Personal access tokens +4. Click "New Token" +5. Configure the token: + - Name: "VSCode Extension Publishing" + - Organization: "All accessible organizations" + - Expiration: Choose appropriate duration (recommended: 1 year) + - Scopes: Select "Marketplace" → "Manage" +6. Copy the generated token +7. Go to your GitHub repository → Settings → Secrets and variables → Actions +8. Click "New repository secret" +9. Name: VSCE_PAT +10. Value: Paste your copied token + +### 2. OVSX_PAT (Open VSX Registry Personal Access Token) +Required for publishing to the Open VSX Registry (alternative marketplace). + +**Steps to create OVSX_PAT:** +1. Go to https://open-vsx.org/ +2. Sign in with your GitHub account +3. Go to https://open-vsx.org/user-settings/tokens +4. Click "Create a new Access Token" +5. Enter description: "VSCode Extension Publishing" +6. Click "Create" +7. Copy the generated token +8. Go to your GitHub repository → Settings → Secrets and variables → Actions +9. Click "New repository secret" +10. Name: OVSX_PAT +11. Value: Paste your copied token + +## Package.json Requirements + +Your package.json should include: + +```json +{ + "publisher": "your-publisher-name", + "scripts": { + "package": "vsce package", + "build": "your-build-command", + "test": "your-test-command" + } +} +``` + +## Workflow Features + +The improved workflow includes: +- ✅ Node.js 20 support +- ✅ PNPM caching for faster builds +- ✅ Pre-release support +- ✅ Dual marketplace publishing (VS Code + Open VSX) +- ✅ Manual workflow dispatch +- ✅ Comprehensive testing and validation +- ✅ Automatic GitHub release creation +- ✅ Security permissions configuration +- ✅ Build artifact uploading +- ✅ Error handling and validation + +## Usage + +1. **Automatic Release**: Create a GitHub release (tag) to trigger the workflow +2. **Manual Release**: Use GitHub Actions → Run workflow with custom parameters +3. **Pre-release**: Create a pre-release on GitHub to publish as pre-release + +## Troubleshooting + +Common issues and solutions: + +1. **Permission denied**: Ensure your PAT has correct scopes +2. **Publisher not found**: Verify publisher name in package.json matches your Azure DevOps publisher +3. **Build failures**: Check that your build/test scripts work locally first +4. **Missing files**: Ensure VSIX is generated correctly in the package step + +## Migration from Current Workflow + +To migrate from your current workflow: + +1. **Replace your current release.yml** with the improved version +2. **Set up the required secrets** (VSCE_PAT and OVSX_PAT) +3. **Update package.json** to include required scripts +4. **Test the workflow** with a manual dispatch first +5. **Create a test release** to verify everything works + +## Best Practices + +- **Use semantic versioning** for your releases (v1.0.0, v1.1.0, etc.) +- **Test locally** before releasing: `pnpm run build && pnpm run test && pnpm run package` +- **Set up branch protection** to prevent direct pushes to main +- **Use pre-releases** for beta testing before stable releases +- **Monitor the workflow runs** in the Actions tab for any issues \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b086984 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: CI Tests + +"on": + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup headless environment + run: | + sudo apt-get update + sudo apt-get install -y xvfb libasound2-dev libgtk-3-dev libnss3-dev + + - name: Run tests + run: | + xvfb-run -a pnpm test diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8b6fd9f..0000000 --- a/.gitignore +++ /dev/null @@ -1,52 +0,0 @@ -# ============================================================ -# WHITELIST .gitignore -# Ignore everything, then allow only public project files. -# New files are private-by-default — you must opt-in below. -# ============================================================ - -# 1. Ignore everything -* - -# 2. Allow top-level config & docs -!.gitignore -!.npmrc -!package.json -!pnpm-lock.yaml -!pnpm-workspace.yaml -!README.md -!CHANGELOG.md -!LICENSE -!glama.json - -# 4. Allow GitHub config -!.github/ -!.github/** - -# 4. Allow scripts -!scripts/ -!scripts/** - -# 5. Allow packages directory structure -!packages/ -!packages/extension/ -!packages/extension/** -!packages/language-services/ -!packages/language-services/** -!packages/lsp-server/ -!packages/lsp-server/** -!packages/mcp-server/ -!packages/mcp-server/** -!packages/shared/ -!packages/shared/** -!packages/webview/ -!packages/webview/** - -# 6. Re-ignore planning directory, build artifacts, deps & private files inside allowed dirs -.planning/ -**/node_modules/ -**/dist/ -*.vsix -**/PLAN.md -packages/mcp-server/.chrome-profile/ -packages/mcp-server/dev-tools/ -packages/extension/README.md \ No newline at end of file diff --git a/.vscode-test.mjs b/.vscode-test.mjs new file mode 100644 index 0000000..b62ba25 --- /dev/null +++ b/.vscode-test.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from '@vscode/test-cli'; + +export default defineConfig({ + files: 'out/test/**/*.test.js', +}); diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d7a3ca1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c42edc0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5c5ac48 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false, // set this to true to hide the "out" folder with the compiled JS files + "dist": false // set this to true to hide the "dist" folder with the compiled JS files + }, + "search.exclude": { + "out": true, // set this to false to include "out" folder in search results + "dist": true // set this to false to include "dist" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..03e4a88 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,44 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "bun: watch", + "type": "shell", + "command": "bun", + "args": ["run", "watch"], + "isBackground": true, + "problemMatcher": "$esbuild-watch", + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "bun: build", + "type": "shell", + "command": "bun", + "args": ["run", "build"], + "problemMatcher": "$esbuild-watch", + "group": "build" + }, + { + "label": "bun: compile", + "type": "shell", + "command": "bun", + "args": ["run", "compile"], + "problemMatcher": "$tsc", + "group": "build" + }, + { + "label": "bun: check-types", + "type": "shell", + "command": "bun", + "args": ["run", "check-types"], + "problemMatcher": "$tsc", + "group": "build" + } + ] +} diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..159277f --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +.vscode-test/** +out/** +node_modules/** +src/** +.gitignore +.yarnrc +esbuild.js +vsc-extension-quickstart.md +**/tsconfig.json +**/eslint.config.mjs +**/*.map +**/*.ts +**/.vscode-test.* diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c60b4cd..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,313 +0,0 @@ -# Change Log - -All notable changes to the "airtable-formula" extension will be documented in this file. - -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. - -## [Unreleased] - -### MCP Server 2.5.0 — Record Templates (9 new tools) + round-4 user-report fixes (2026-05-01) - -Promotes record templates to a first-class MCP capability. Templates are -the saved row scaffolds that Airtable surfaces under the "+ Add record" -flyout and inside the row-create extension — previously not addressable -through any public or internal API client we had. Discovered by capturing -the rowTemplate endpoints with `pnpm capture:cdp:mutations` while the -reporter exercised every template UI path. - -**New tools (9):** - -| Tool | Category | Purpose | -|:-----|:---------|:--------| -| `list_record_templates` | read | List record templates for a table | -| `create_record_template` | table-write | Create a template (client-side `rtp...` ID like view sections use `vsc...`) | -| `rename_record_template` | table-write | Rename a template | -| `update_record_template_description` | table-write | Set or clear a template's description | -| `set_record_template_cell` | table-write | Set a cell value in a template (static, linked rows, or linked templates) | -| `set_record_template_visible_columns` | table-write | Choose which columns the template surfaces in its UI | -| `duplicate_record_template` | table-write | Clone an existing template | -| `apply_record_template` | table-write | Apply a template — creates a new record using the template's cell defaults | -| `delete_record_template` | table-destructive | Delete a template | - -**Round-4 fixes (carried into 2.5.0 from interim 2.4.4 work):** - -- `set_view_columns` now filters the primary column out of the move step - before calling `moveVisibleColumns` — Airtable rejects any move that - displaces the pinned primary at index 0. -- `reorder_view_fields` `mergePartialFieldOrder` protects the primary - column at index 0; user-requested moves to index 0 are clamped to 1. -- `update_view_group_levels` always sends `emptyGroupState: 'hidden'`; - Airtable's API rejects `'visible'` with INVALID_REQUEST. -- `list_tables` now reads `data.tableById` as a fallback when - `tableSchemas` / `tables` / `tableDatas` are absent (matches the shape - the reporter saw). -- `update_view_filters` `isWithin` corrected: requires `timeZone` (IANA) - and `shouldUseCorrectTimeZoneForFormulaicColumn: true`, no `exactDate`, - modes are `thisCalendarMonth` / `thisCalendarYear` (not `thisMonth` / - `thisYear`). Tool description and AI skill template updated. - -**Counts:** 52 → **61 tools**. `read-only` profile: 8 → 9. `safe-write` -profile: 39 → 47. `full` profile: 52 → 61. - -mcp-server: 2.4.4 → 2.5.0. - -### MCP Server 2.4.3 — `list_view_sections` full fix (carry-over from 2.4.2) - -Probed the schema read response with a fresh patchright session against -a base that has sections (`packages/mcp-server/dev-tools/debug-sections.js`, -new `pnpm probe:sections` script). Found that sidebar sections live at -`data.tableSchemas[N].viewSectionsById` — NOT `viewSections` like the -WebSocket realtime delta model uses. 2.4.0 + 2.4.1 read the wrong key, -which is why the sections array came back empty. - -`listViewSections` now reads from `viewSectionsById` first, falls back -to `viewSections` for fixture/test compatibility, falls back to the -2.4.2 ID-only-from-tableViewOrder path if both are absent. The rich -shape (`name`, `viewOrder`, `pinnedForUserId`, `createdByUserId`) is -fully restored on real bases. - -mcp-server: 2.4.2 → 2.4.3. - -### MCP Server 2.4.2 — User follow-up bugs (report 2026-05-01) - -After upgrading to 2.4.0 and running real end-to-end view-rollout work, -the reporter surfaced two bugs in the new tooling. Both fixed here. - -**Bug 1 — `set_view_columns` orchestration 422'd at the move step** (P1). -The internal step 3 looped per-id `moveVisibleColumns([id], i)` starting -at index 0. That fails reliably because grid views pin the primary -column at visible index 0 (you can't move anything else there) and -per-id moves of an already-correctly-positioned column also fail. -Steps 1 and 2 (hide-all + show-the-set) succeeded, so visibility was -correct but column order was untouched. - -Fix: replace the loop with one batched `moveVisibleColumns(visibleColumnIds, 1)` -call — the entire ordered list is inserted starting at visible index 1, -after the pinned primary. Verified by the reporter's workaround snippet -on 11 grid views (100% success rate). Test updated to match. - -**Bug 2 — `list_view_sections` returned empty `sections`** (P2, partial fix). -On the reporter's base, `table.viewSections` is absent from the cached -`application/{appId}/read` response even when `tableViewOrder` clearly -contains `vsc...` IDs. The full fix needs a network capture from a base -with sections to discover where the section objects actually live in -the schema response (none of our existing captures cover this — they -were either mutations-only or pre-section). - -Partial fix shipped here: when `viewSections` is empty but -`tableViewOrder` contains `vsc...` IDs, surface them as bare-id entries -with `name: null`, `viewOrder: null`, `partial: true`, and a top-level -`introspectionPartial: true` flag with a `introspectionNote` explaining -the limitation. The agent at least knows which section IDs exist and -can pass them to `move_view_to_section` / `rename_view_section` / -`delete_view_section` (all of which still work for side effects). - -mcp-server: 2.4.1 → 2.4.2. - -### MCP Server 2.4.1 — Hotfix: bundled server crashed on startup - -Released hours after 2.4.0. The bundled extension copy of the MCP -server crashed immediately on every spawn with `MODULE_NOT_FOUND` for -`../package.json`, surfacing in MCP clients as `transport error: -transport closed`. No tool ever ran from the bundled launcher. - -Root cause: `index.js` resolved its own version with -`require('../package.json')`. That works when running from source -(`packages/mcp-server/src/index.js` → `packages/mcp-server/package.json`) -but fails when bundled to `packages/extension/dist/mcp/index.mjs` — -`../package.json` resolves to a non-existent -`packages/extension/dist/package.json`. - -Fix: read `version.json` (which `bundle-mcp.mjs` writes alongside the -bundle) first, fall back to `../package.json` for source/npx runs, fall -back to `'unknown'` if both fail. Verified end-to-end against the bundled -launcher with a real `initialize` handshake. - -Anyone whose MCP entries pointed at the bundled launcher -(`~/.airtable-user-mcp/start.mjs` → `dist/mcp/index.mjs`) was affected. -Standalone npx runs (`npx -y airtable-user-mcp`) were not — that path -keeps `package.json` adjacent. mcp-server: 2.4.0 → 2.4.1. - -### MCP Server 2.4.0 — Sidebar sections, view setup, non-grid metadata, user-report bug fixes (user report 2026-04-30) - -**16 net-new tools (52 total, was 36).** All endpoints captured against Airtable's internal API on 2026-04-30 with `pnpm capture:cdp:mutations`. - -#### New features (user report §1.3, §1.4, §3.1) - -**View sections (sidebar grouping) — new `view-section` category, defaults on in `safe-write`:** -- `list_view_sections` — read all sections in a table with their view membership and the table-level mixed `viewOrder` (mixes view IDs and section IDs) -- `create_view_section` — generate a `vsc...` ID and create a section -- `rename_view_section` — change a section's name -- `move_view_to_section` — single tool covering four user actions: move view INTO section, move view OUT to ungrouped, reorder sections among each other, reorder views within a section. Maps to one Airtable endpoint (`moveViewOrViewSection`) -- `delete_view_section` — destroy a section. Verified behavior: views inside the section are NOT deleted — Airtable auto-promotes them into the table-level `viewOrder` at the position the section used to occupy. Lives in the new `view-section-destructive` category, gated to `full` profile - -**View column setup — extend `view-write`:** -- `set_view_columns` — one-shot tool that hides every column, shows only `visibleColumnIds`, places them in left-to-right order, and optionally sets `frozenColumnCount`. Solves the user report's §1.4 "new views show all 168 fields, unusable until manually trimmed" problem in a single call -- `show_or_hide_all_columns` — bulk on/off. Closes the §3.2 doc-promised-but-missing tool -- `move_visible_columns` — move columns to a target index in the *visible-only* ordering -- `move_overall_columns` — same, but in the *overall* (visible + hidden) ordering. Distinct from existing `reorder_view_fields` (which writes the full map) and from `move_visible_columns` (different index space) -- `update_frozen_column_count` — set the frozen-column divider position - -**View presentation (Kanban / Gallery / Calendar) — extend `view-write`:** -- `set_view_cover` — set or clear the cover-image field and choose `fit` vs `crop`. Same endpoints work for both Kanban and Gallery (verified) -- `set_view_color_config` — apply a color config. Currently supports `type: "selectColumn"` (cards colored by a single-select field's choice colors); other types pass through for forward compatibility -- `set_view_cell_wrap` — toggle whether long values wrap (multi-line) or truncate (single-line) -- `set_calendar_date_columns` — set `dateColumnRanges`. Each entry is `{ startColumnId }` for single-point events or `{ startColumnId, endColumnId }` for ranges; the array form lets a Calendar overlay multiple date series at once - -**Form metadata (legacy form views only) — new `form-write` category, opt-in (gated to `full` profile since changes are visible to anyone with the form URL):** -- `set_form_metadata` — bundled tool that fans out to atomic Airtable endpoints for `description`, `afterSubmitMessage`, `redirectUrl`, `refreshAfterSubmit`, `shouldAllowRequestCopyOfResponse`, `shouldAttributeResponses`, `isAirtableBrandingRemoved`. Unset properties are not touched -- `set_form_submission_notification` — per-user email-on-submit toggle (separate because it's per-user, not per-form) - -Note on builder forms: Airtable's "Interfaces" / page-based forms (`page/{pageId}/*` endpoints, layout-engine element trees) are intentionally out of scope — they're a separate product surface that warrants a dedicated tool family. Filed as a follow-up. - -**Categories now in `safe-write`:** `read`, `table-write`, `field-write`, `view-write`, `view-section`. Categories opt-in only via `full` or `custom`: `*-destructive`, `view-section-destructive`, `form-write`, `extension`. - -#### Bug fixes (user report §1.2, §2.x, §4.3) - -- **`isEmpty` / `isNotEmpty` on text + formula(text) + lookup/rollup(text) fields** — auto-rewritten to `=` / `!=` `""` before sending. The internal API rejects the documented operators with `FAILED_STATE_CHECK` on these field types; the rewrite happens client-side using the cached table schema (§2.1, §2.2) -- **`isEmpty` / `isNotEmpty` on linked-record (foreignKey) fields** — now throws a clear error pointing at the helper-formula workaround instead of letting Airtable's opaque `422` through (§2.3) -- **3-level nested filter rejection** — `updateViewFilters` errors now annotate the depth, the leaf operators that failed, and the recommended flatten pattern (`(A AND B) OR (A AND C)` instead of `A AND (B OR C)`) (§2.4, §2.5) -- **`reorder_view_fields` accepts partial maps** — pass only the field IDs you want to move; the tool reads the current `columnOrder`, applies moves in ascending target-position order, and sends the complete map. Avoids the `FAILED_STATE_CHECK` Airtable's internal API returns for single-key payloads (§2.6) -- **`manage_tools` discoverability** — `ListTools` response now includes a dynamic description on `manage_tools` listing every tool hidden by the active profile (e.g. `delete_table` / `delete_field` / `delete_view` in `safe-write`). `get_tool_status` also returns a `disabledByCategory` summary so an LLM can show the user what to enable (root-cause behind §1.2 — the delete tools always existed, they were just filtered) -- **Tool docstring audit** — corrected `update_view_filters`, `show_or_hide_view_columns`, and `reorder_view_fields` descriptions to match actual behavior (§4.3) - -### Fixed (Audit Round 3) -- **Formatter version setting was inert** — Dashboard's "Formatter version" dropdown now actually switches engines. Extension consolidated on `airtableFormula.formula.formatterVersion` and reads this key from all four load sites; legacy `beautifierVersion` / `minifierVersion` remain as fallback for user settings migrated from prior versions -- **Browser-choice changes didn't propagate to IDE MCP configs** — Selecting a different browser (or picking a custom path) now re-writes the MCP entry for every already-configured IDE, so the new `AIRTABLE_BROWSER_CHANNEL` / `AIRTABLE_BROWSER_PATH` env vars take effect immediately instead of staying stale until the next Setup -- **Toggle MCP Tool Category command couldn't toggle table categories** — Added `tableWrite` and `tableDestructive` to the command palette quick-pick; also fixed mis-mapped `fieldWrite` / `viewWrite` file keys that were breaking the label and tool-count display for those rows -- **Formatter commands missing from command palette** — `beautify`, `minify`, `beautifyWithStyle`, `minifyWithLevel`, and `formatWithPreset` are now contributed in `package.json`, with `.formula` language enablement. `beautifyFile` / `minifyFile` added to the explorer context menu for `.formula` files -- **Webview action cards weren't keyboard-accessible** — All clickable `.action-card` divs are now `