diff --git a/.changeset/wave-4-phase-i-release-pipeline.md b/.changeset/wave-4-phase-i-release-pipeline.md new file mode 100644 index 0000000..67f8bcc --- /dev/null +++ b/.changeset/wave-4-phase-i-release-pipeline.md @@ -0,0 +1,12 @@ +--- +"tokenometer": minor +"@tokenometer/core": minor +--- + +Unified release pipeline (Phase I): one merge of the Version Packages PR +publishes tokenometer + @tokenometer/core to npm with provenance, creates +the GitHub Release (which republishes the Action to GitHub Marketplace), +publishes the VS Code extension to VS Code Marketplace + Open VSX, runs +a post-publish smoke test, verifies the Marketplace listing, and triggers +the Vercel deploy hook. Local `npm run smoke` runs the full sweep (lint, +typecheck, test, build, benchmarks, CLI smoke). diff --git a/.github/release-notes-v1.0.0.md b/.github/release-notes-v1.0.0.md index e73db31..e2ed726 100644 --- a/.github/release-notes-v1.0.0.md +++ b/.github/release-notes-v1.0.0.md @@ -1,9 +1,9 @@ # Tokenometer v1.0.0 — Production-ready LLM cost calculator, latency benchmark, and CI guardrail. -> Note: this is a **DRAFT**. Edit before publishing the GitHub Release. All -> items below have already landed on `main` (Waves 2 + 3); the only items -> still in-flight at the time this draft was written are the marketplace -> publish, smoke tests, and release pipeline polish (Phase I). +> Note: this is a **DRAFT**. Edit before publishing the GitHub Release. +> All items below have landed on `main` (Waves 2 + 3 + 4 Phase I). The +> release pipeline itself is now unified — one merge of the Version +> Packages PR ships every artifact below in a single workflow run. ## Highlights @@ -47,9 +47,9 @@ - **VS Code / Cursor extension** — status bar shows live `model · tokens · USD` for the active prompt file. Settings: `tokenometer.model`, `tokenometer.format`, `tokenometer.warnOnCostAbove`. Commands: - *Tokenometer: Switch model*, *Tokenometer: Show details*. Marketplace - listing arrives with this release; until then, `npm run package:vsix - --workspace=@tokenometer/vscode` builds a side-loadable `.vsix`. + *Tokenometer: Switch model*, *Tokenometer: Show details*. Published + to the VS Code Marketplace and Open VSX (Cursor / VSCodium read from + Open VSX) by the unified release workflow. - **Claude Code skill** (`tokenometer-cost-check`) — drop into `~/.claude/skills/tokenometer/SKILL.md` and Claude Code agents will reach for `npx tokenometer` when asked anything cost- or latency-shaped. @@ -59,9 +59,14 @@ Cohere catalog, which `@tokenlens/models` doesn't ship at v1.3.0). - **Honest `approximate` flag** — every output row is tagged so you always know whether a number came from a real tokenizer or an estimate. -- **Unified release pipeline** — one workflow publishes both - `tokenometer` and `@tokenometer/core` so the CLI and library never - drift. +- **Unified release pipeline** — one merge of the Version Packages PR + publishes `tokenometer` + `@tokenometer/core` to npm with provenance, + creates the GitHub Release (which republishes the Action to GitHub + Marketplace), publishes the VS Code extension to the VS Code Marketplace + + Open VSX, fires the Vercel deploy hook for the playground, and runs + a post-publish smoke test against the just-published npm versions — + all from one workflow run, so CLI / library / Action / extension / + playground never drift apart. ## v1.0.0 launch surface diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4af306c..b7746d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,40 @@ +# Tokenometer release pipeline. +# +# One trigger, every artifact: +# 1. Changesets opens / merges a "Version Packages" PR. Merging it lands here. +# 2. The `release` job publishes `tokenometer` + `@tokenometer/core` to npm +# with provenance (Wave 1) and creates a GitHub Release on the new tag. +# The GitHub Marketplace listens for that Release to re-publish the +# Action's Marketplace listing (no extra step needed). +# 3. The same job then publishes the VS Code extension to the VS Code +# Marketplace via `vsce` and to Open VSX via `ovsx` (Cursor / Codium). +# 4. Marketplace listing is HTTP-verified (best-effort — propagation can lag). +# 5. The Vercel deploy hook is fired so the playground rebuilds against the +# new published packages. +# 6. A separate `smoke-test` job runs on a fresh runner against the +# just-published versions on npm, so we catch any "publish succeeded but +# the published artifact is broken" class of bug before users hit it. +# +# Required GitHub repository secrets: +# NPM_TOKEN — npm automation token. Set since Wave 1. +# Used by `changesets/action@v1` to publish to npm. +# VSCE_PAT — Personal Access Token from https://dev.azure.com +# (Marketplace publisher → Manage Tokens). Scope: +# "Marketplace → Acquire + Manage". Used by `vsce` +# to publish to https://marketplace.visualstudio.com. +# If absent, the VS Code Marketplace publish step +# is skipped (the workflow logs a clear notice). +# OVSX_PAT — Personal Access Token from https://open-vsx.org +# (User Settings → Access Tokens). Used by `ovsx` +# to publish to https://open-vsx.org. If absent, +# the Open VSX publish step is skipped. Cursor +# and VSCodium read from Open VSX, not the VS +# Code Marketplace, so this is highly recommended. +# VERCEL_DEPLOY_HOOK — Deploy hook URL from Vercel project settings +# (Settings → Git → Deploy Hooks). Optional; the +# playground also rebuilds automatically on every +# push to main. If absent, the step is skipped. + name: release on: @@ -6,7 +43,7 @@ on: workflow_dispatch: inputs: dry-run: - description: 'Dry run (skip npm publish, skip GitHub Release).' + description: 'Dry run (skip npm publish, skip GitHub Release, skip Marketplace publishes, skip Vercel hook).' required: false default: 'false' @@ -22,6 +59,8 @@ concurrency: jobs: release: runs-on: ubuntu-latest + outputs: + published: ${{ steps.changesets.outputs.published }} steps: - uses: actions/checkout@v4 with: @@ -68,19 +107,128 @@ jobs: npm pkg set 'dependencies.@tokenometer/core'="$PUBLISHED_VERSION" --workspace=packages/cli npm pkg set 'dependencies.@tokenometer/core'="$PUBLISHED_VERSION" --workspace=packages/action - # Phase I (Wave 4) will add: Marketplace verify, VS Code Marketplace publish, - # Open VSX publish, Vercel deploy hook, smoke test against published versions. + # ── Phase I: VS Code Marketplace + Open VSX ───────────────────────────── + # The .vsix is built fresh after the version bump so it carries the new + # version metadata. We don't store the .vsix as a release asset; the + # Marketplaces are the canonical distribution surface. + - name: Build VS Code extension .vsix + if: ${{ steps.changesets.outputs.published == 'true' }} + run: npm run package:vsix --workspace=packages/vscode + + - name: Locate built .vsix + if: ${{ steps.changesets.outputs.published == 'true' }} + id: vsix + run: | + VSIX_PATH=$(ls packages/vscode/*.vsix | head -1) + echo "path=$VSIX_PATH" >> $GITHUB_OUTPUT + echo "Built: $VSIX_PATH" + + - name: Publish VS Code extension to Marketplace + if: ${{ steps.changesets.outputs.published == 'true' && env.VSCE_PAT != '' }} + run: npx --yes @vscode/vsce publish --packagePath "${{ steps.vsix.outputs.path }}" --pat "$VSCE_PAT" + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Notice — VS Code Marketplace skipped + if: ${{ steps.changesets.outputs.published == 'true' && env.VSCE_PAT == '' }} + run: echo "VSCE_PAT not set, skipping VS Code Marketplace publish." >> $GITHUB_STEP_SUMMARY + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Publish to Open VSX + if: ${{ steps.changesets.outputs.published == 'true' && env.OVSX_PAT != '' }} + run: npx --yes ovsx publish "${{ steps.vsix.outputs.path }}" --pat "$OVSX_PAT" + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} + + - name: Notice — Open VSX skipped + if: ${{ steps.changesets.outputs.published == 'true' && env.OVSX_PAT == '' }} + run: echo "OVSX_PAT not set, skipping Open VSX publish." >> $GITHUB_STEP_SUMMARY + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} + + # ── Phase I: GitHub Marketplace verify ────────────────────────────────── + # The Marketplace listing is republished by GitHub when a Release is + # created from a tag. Verification is best-effort — propagation can take + # several minutes, so a non-200 here doesn't fail the run. + - name: Verify GitHub Marketplace listing + if: ${{ steps.changesets.outputs.published == 'true' }} + run: | + sleep 30 + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://github.com/marketplace/actions/tokenometer) + if [ "$HTTP_CODE" = "200" ]; then + echo "✓ Marketplace listing reachable (HTTP $HTTP_CODE)" >> $GITHUB_STEP_SUMMARY + else + echo "⚠ Marketplace listing returned HTTP $HTTP_CODE — may still be propagating" >> $GITHUB_STEP_SUMMARY + fi + + # ── Phase I: Vercel deploy hook ───────────────────────────────────────── + # The playground also rebuilds automatically on every push to main, so + # this is mostly a belt-and-suspenders trigger to make sure the just- + # published @tokenometer/core lands on the live site immediately. + - name: Trigger Vercel deploy + if: ${{ steps.changesets.outputs.published == 'true' && env.VERCEL_DEPLOY_HOOK != '' }} + run: curl -fsSL -X POST "$VERCEL_DEPLOY_HOOK" + env: + VERCEL_DEPLOY_HOOK: ${{ secrets.VERCEL_DEPLOY_HOOK }} + + - name: Notice — Vercel deploy hook skipped + if: ${{ steps.changesets.outputs.published == 'true' && env.VERCEL_DEPLOY_HOOK == '' }} + run: echo "VERCEL_DEPLOY_HOOK not set, skipping explicit Vercel trigger (Vercel still auto-rebuilds on push to main)." >> $GITHUB_STEP_SUMMARY + env: + VERCEL_DEPLOY_HOOK: ${{ secrets.VERCEL_DEPLOY_HOOK }} + - name: Summary if: ${{ steps.changesets.outputs.published == 'true' }} run: | - echo "### Published" >> $GITHUB_STEP_SUMMARY - echo "- https://www.npmjs.com/package/@tokenometer/core" >> $GITHUB_STEP_SUMMARY - echo "- https://www.npmjs.com/package/tokenometer" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "GitHub Release on the new tag triggers the Marketplace re-publish for the Action." >> $GITHUB_STEP_SUMMARY + VERSION=$(node -p "require('./packages/cli/package.json').version") + { + echo "### Published" + echo "- npm: tokenometer@${VERSION} — https://www.npmjs.com/package/tokenometer" + echo "- npm: @tokenometer/core@${VERSION} — https://www.npmjs.com/package/@tokenometer/core" + echo "- GitHub Release: v${VERSION} — https://github.com/faraa2m/tokenometer/releases/tag/v${VERSION}" + echo "- GitHub Marketplace: https://github.com/marketplace/actions/tokenometer" + echo "- VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=faraa2m.tokenometer" + echo "- Open VSX: https://open-vsx.org/extension/faraa2m/tokenometer" + echo "- Playground deploy: https://tokenometer.vercel.app" + } >> $GITHUB_STEP_SUMMARY - name: Dry run notice if: ${{ inputs.dry-run == 'true' }} run: | echo "### Dry run" >> $GITHUB_STEP_SUMMARY - echo "Skipped Changesets publish step." >> $GITHUB_STEP_SUMMARY + echo "Skipped Changesets publish step. All Phase I publish/marketplace/Vercel steps also skip because they gate on \`steps.changesets.outputs.published == 'true'\`." >> $GITHUB_STEP_SUMMARY + + # ── Phase I: Smoke-test job ────────────────────────────────────────────── + # Runs on a fresh runner against the JUST-published versions on npm. This + # catches the "publish succeeded but the published artifact is broken" class + # of bug (missing files, wrong bin shebang, dependency not bundled, etc.) + # before any user hits it. Best-effort for the Action — the full Action + # smoke-test requires a fixture PR setup that can't run from the publishing + # repo itself. + smoke-test: + needs: release + if: needs.release.outputs.published == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org/' + + - name: Smoke test published CLI + run: | + VERSION=$(node -p "require('./packages/cli/package.json').version") + echo "Smoke-testing tokenometer@$VERSION" + npx --yes tokenometer@$VERSION --version + echo "test prompt" > /tmp/smoke.md + npx --yes tokenometer@$VERSION /tmp/smoke.md --model claude-opus-4-7 --output sarif | jq -e '.version == "2.1.0"' >/dev/null + echo "✓ tokenometer@$VERSION CLI smoke passed" >> $GITHUB_STEP_SUMMARY + + - name: Smoke test Action against fixture PR + run: | + # Skip if fixture isn't available; this is best-effort. + # Full implementation would dispatch a workflow on a fixture repo + # whose `prompt-cost.yml` references `faraa2m/tokenometer@v$VERSION`. + echo "Action smoke test placeholder — full implementation requires a fixture PR setup." >> $GITHUB_STEP_SUMMARY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47e88ab..63ab9a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,9 @@ Node ≥20 required. | Build all packages | `npm run build` | | Run benchmarks (offline) | `npm run benchmarks` | | Run benchmarks (empirical, needs `ANTHROPIC_API_KEY`) | `npm run benchmarks:empirical` | +| Full local pre-release smoke | `npm run smoke` | -Before opening a PR: `npm run lint && npm run typecheck && npm test && npm run build`. CI runs the same steps. +Before opening a PR: `npm run lint && npm run typecheck && npm test && npm run build`. CI runs the same steps. For a release-readiness check, `npm run smoke` runs the full sweep (lint, typecheck, test, build, web build, benchmarks, plus CLI smoke checks against the built `dist/` — SARIF, `--by-file`, auto-detect, Action bundle present, VS Code extension dist present). ## Code style @@ -92,13 +93,25 @@ Pick the affected packages, the bump type (`patch` / `minor` / `major`), and wri ## Releasing -Pushes to `main` trigger `.github/workflows/release.yml`: +The release pipeline is **fully unified** — one merge of the Version Packages PR ships everything. Pushes to `main` trigger `.github/workflows/release.yml`: 1. If `.changeset/` has any pending changesets, the workflow opens (or updates) a **Version Packages** PR that auto-bumps `package.json` versions and auto-generates `CHANGELOG.md` entries. -2. Merging that PR triggers the same workflow to publish the bumped versions to npm with provenance. -3. A GitHub Release is created on the new tag — the GitHub Marketplace listens to that for the Action's update. - -Phase I (Wave 4) extends this to also publish the VS Code extension to the Marketplace + Open VSX, run a post-publish smoke test, and trigger a Vercel deploy. +2. Merging that PR triggers the same workflow to: + - Publish `tokenometer` + `@tokenometer/core` to npm with provenance. + - Create a GitHub Release on the new tag (GitHub Marketplace listens to that and republishes the Action's listing). + - Build a fresh `.vsix` and publish the VS Code extension to the **VS Code Marketplace** (`vsce`) and **Open VSX** (`ovsx`, the registry Cursor + VSCodium read from). + - HTTP-verify the GitHub Marketplace listing (best-effort — propagation can take a few minutes). + - Trigger the **Vercel deploy hook** so the playground rebuilds against the new published packages. +3. A separate `smoke-test` job then runs on a fresh runner against the **just-published** npm versions (`npx tokenometer@`) so a broken publish surfaces in CI rather than user reports. + +Before merging the Version Packages PR, you can sanity-check the build locally with `npm run smoke` — same gates the workflow runs, plus CLI smoke against the local `dist/`. + +Required secrets in repo settings → Secrets and variables → Actions: + +- `NPM_TOKEN` — npm automation token. +- `VSCE_PAT` — Personal Access Token from https://dev.azure.com (Marketplace publisher → Manage Tokens, scope: Marketplace → Acquire + Manage). If absent, the VS Code Marketplace step is skipped with a clear notice. +- `OVSX_PAT` — Personal Access Token from https://open-vsx.org (User Settings → Access Tokens). If absent, the Open VSX step is skipped. +- `VERCEL_DEPLOY_HOOK` — Deploy hook URL from Vercel project settings (optional; the playground also rebuilds automatically on every push to main). ## Questions diff --git a/package-lock.json b/package-lock.json index 59d2711..54036bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1028,6 +1028,40 @@ "prettier": "^2.7.1" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -1642,6 +1676,287 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@node-rs/crc32": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32/-/crc32-1.10.6.tgz", + "integrity": "sha512-+llXfqt+UzgoDzT9of5vPQPGqTAVCohU74I9zIBkNo5TH6s2P31DFJOGsJQKN207f0GHnYv5pV3wh3BCY/un/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/crc32-android-arm-eabi": "1.10.6", + "@node-rs/crc32-android-arm64": "1.10.6", + "@node-rs/crc32-darwin-arm64": "1.10.6", + "@node-rs/crc32-darwin-x64": "1.10.6", + "@node-rs/crc32-freebsd-x64": "1.10.6", + "@node-rs/crc32-linux-arm-gnueabihf": "1.10.6", + "@node-rs/crc32-linux-arm64-gnu": "1.10.6", + "@node-rs/crc32-linux-arm64-musl": "1.10.6", + "@node-rs/crc32-linux-x64-gnu": "1.10.6", + "@node-rs/crc32-linux-x64-musl": "1.10.6", + "@node-rs/crc32-wasm32-wasi": "1.10.6", + "@node-rs/crc32-win32-arm64-msvc": "1.10.6", + "@node-rs/crc32-win32-ia32-msvc": "1.10.6", + "@node-rs/crc32-win32-x64-msvc": "1.10.6" + } + }, + "node_modules/@node-rs/crc32-android-arm-eabi": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz", + "integrity": "sha512-vZAMuJXm3TpWPOkkhxdrofWDv+Q+I2oO7ucLRbXyAPmXFNDhHtBxbO1rk9Qzz+M3eep8ieS4/+jCL1Q0zacNMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-android-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz", + "integrity": "sha512-Vl/JbjCinCw/H9gEpZveWCMjxjcEChDcDBM8S4hKay5yyoRCUHJPuKr4sjVDBeOm+1nwU3oOm6Ca8dyblwp4/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz", + "integrity": "sha512-kARYANp5GnmsQiViA5Qu74weYQ3phOHSYQf0G+U5wB3NB5JmBHnZcOc46Ig21tTypWtdv7u63TaltJQE41noyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz", + "integrity": "sha512-Q99bevJVMfLTISpkpKBlXgtPUItrvTWKFyiqoKH5IvscZmLV++NH4V13Pa17GTBmv9n18OwzgQY4/SRq6PQNVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-freebsd-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz", + "integrity": "sha512-66hpawbNjrgnS9EDMErta/lpaqOMrL6a6ee+nlI2viduVOmRZWm9Rg9XdGTK/+c4bQLdtC6jOd+Kp4EyGRYkAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm-gnueabihf": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz", + "integrity": "sha512-E8Z0WChH7X6ankbVm8J/Yym19Cq3otx6l4NFPS6JW/cWdjv7iw+Sps2huSug+TBprjbcEA+s4TvEwfDI1KScjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz", + "integrity": "sha512-LmWcfDbqAvypX0bQjQVPmQGazh4dLiVklkgHxpV4P0TcQ1DT86H/SWpMBMs/ncF8DGuCQ05cNyMv1iddUDugoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz", + "integrity": "sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz", + "integrity": "sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz", + "integrity": "sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-wasm32-wasi": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz", + "integrity": "sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/crc32-win32-arm64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz", + "integrity": "sha512-x50AXiSxn5Ccn+dCjLf1T7ZpdBiV1Sp5aC+H2ijhJO4alwznvXgWbopPRVhbp2nj0i+Gb6kkDUEyU+508KAdGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-ia32-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz", + "integrity": "sha512-DpDxQLaErJF9l36aghe1Mx+cOnYLKYo6qVPqPL9ukJ5rAGLtCdU0C+Zoi3gs9ySm8zmbFgazq/LvmsZYU42aBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-x64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz", + "integrity": "sha512-5B1vXosIIBw1m2Rcnw62IIfH7W9s9f7H7Ma0rRuhT8HR4Xh8QCgw6NJSI2S2MCngsGktYnAhyUvs81b7efTyQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2818,6 +3133,17 @@ "resolved": "packages/web", "link": true }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3879,6 +4205,13 @@ "license": "ISC", "optional": true }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cockatiel": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", @@ -4103,6 +4436,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -4116,6 +4467,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4629,6 +4998,27 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -4821,6 +5211,23 @@ "node": ">= 6" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -4877,6 +5284,19 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5119,6 +5539,19 @@ "license": "ISC", "optional": true }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", @@ -5187,6 +5620,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-it-type": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/is-it-type/-/is-it-type-5.1.3.tgz", + "integrity": "sha512-AX2uU0HW+TxagTgQXOJY7+2fbFHemC7YFBwN1XqD8qQMKdtfbOC8OC3fUb4s5NU59a3662Dzwto8tWDdZYRXxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalthis": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6208,6 +6654,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6243,6 +6699,39 @@ "dev": true, "license": "MIT" }, + "node_modules/ovsx": { + "version": "0.10.12", + "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.12.tgz", + "integrity": "sha512-WwMj1iQDvCk02029oxPnkFXsPrHZ+WzmoNW5pJ8JGepHtL30i2JE4s3C3wqzQqj6a35vx2hp0gV3TdfefGmvMg==", + "dev": true, + "license": "EPL-2.0", + "dependencies": { + "@vscode/vsce": "^3.7.1", + "commander": "^6.2.1", + "follow-redirects": "^1.16.0", + "is-ci": "^2.0.0", + "leven": "^3.1.0", + "semver": "^7.6.0", + "tmp": "^0.2.3", + "yauzl-promise": "^4.0.0" + }, + "bin": { + "ovsx": "bin/ovsx" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ovsx/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -7317,6 +7806,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-invariant": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/simple-invariant/-/simple-invariant-2.0.1.tgz", + "integrity": "sha512-1sbhsxqI+I2tqlmjbz99GXNmZtr6tKIyEgGGnJw/MKGblalqk/XoOYYFJlBzTKZCxx8kLaD3FD5s9BEEjx5Pyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8407,6 +8906,21 @@ "node": ">=12" } }, + "node_modules/yauzl-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yauzl-promise/-/yauzl-promise-4.0.0.tgz", + "integrity": "sha512-/HCXpyHXJQQHvFq9noqrjfa/WpQC2XYs3vI7tBiAi4QiIU1knvYhZGaO1QPjwIVMdqflxbmwgMXtYeaRiAE0CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@node-rs/crc32": "^1.7.0", + "is-it-type": "^5.1.2", + "simple-invariant": "^2.0.1" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/yazl": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", @@ -8952,6 +9466,7 @@ "@types/vscode": "^1.85.0", "@vscode/vsce": "^3.2.1", "esbuild": "^0.24.0", + "ovsx": "^0.10.1", "typescript": "^5.7.2" }, "engines": { diff --git a/package.json b/package.json index f6f9de8..926e0ce 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "typecheck": "tsc -b", "benchmarks": "node benchmarks/run.mjs", "benchmarks:empirical": "node benchmarks/run.mjs --empirical", - "benchmarks:regenerate": "node benchmarks/run.mjs --regenerate" + "benchmarks:regenerate": "node benchmarks/run.mjs --regenerate", + "smoke": "bash scripts/smoke.sh" }, "devDependencies": { "@biomejs/biome": "^1.9.4", diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 8c6615c..6a6d97a 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -102,7 +102,7 @@ "lint": "biome check .", "typecheck": "tsc -b", "test": "vitest run", - "package:vsix": "vsce package --no-dependencies" + "package:vsix": "npm run build && vsce package --no-dependencies" }, "dependencies": { "@tokenometer/core": "0.0.2" @@ -112,6 +112,7 @@ "@types/vscode": "^1.85.0", "@vscode/vsce": "^3.2.1", "esbuild": "^0.24.0", + "ovsx": "^0.10.1", "typescript": "^5.7.2" } } diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100755 index 0000000..ce46b69 --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "▸ npm run lint" +npm run lint + +echo "▸ npm run typecheck" +npm run typecheck + +echo "▸ npm test" +npm test + +echo "▸ npm run build" +npm run build + +echo "▸ npm run build -w @tokenometer/web" +npm run build -w @tokenometer/web + +echo "▸ npm run benchmarks" +npm run benchmarks + +echo "▸ tokenometer --version" +node packages/cli/dist/index.js --version + +echo "▸ tokenometer SARIF smoke" +node packages/cli/dist/index.js README.md --model claude-opus-4-7 --output sarif | jq -e '.version == "2.1.0"' >/dev/null + +echo "▸ tokenometer --by-file smoke" +node packages/cli/dist/index.js README.md packages/cli/README.md --model claude-opus-4-7 --by-file >/dev/null + +echo "▸ tokenometer auto-detect smoke (ANTHROPIC_API_KEY=fake)" +ANTHROPIC_API_KEY=fake-key node packages/cli/dist/index.js README.md >/dev/null + +echo "▸ Action bundle present" +test -f packages/action/dist/index.cjs + +echo "▸ VS Code extension dist present" +test -f packages/vscode/dist/extension.js + +echo "✅ All smoke checks passed."