Skip to content

Commit eddd2b8

Browse files
committed
refactor: replace bun run with tsx/pnpm across scripts and CI
- Convert 12 script files from Bun APIs to Node equivalents (Bun.file/write/Glob → readFile/writeFile/tinyglobby) - Update shebangs: bun → tsx in all scripts except build.ts - Update package.json: bun run → tsx/pnpm run - Update CI: remove setup-bun from all jobs except build-binary - build.ts intentionally kept on Bun (uses Bun.build compiler)
1 parent 5e19e7c commit eddd2b8

24 files changed

Lines changed: 576 additions & 203 deletions

.github/workflows/ci.yml

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ jobs:
126126
# `ref` empty so checkout defaults to GITHUB_REF (the pull_request
127127
# merge SHA, always fetchable from the base repo with github.token).
128128
ref: ${{ steps.token.outcome == 'success' && (github.head_ref || github.ref_name) || '' }}
129-
- uses: oven-sh/setup-bun@v2
130-
with:
131-
bun-version: "1.3.13"
132129
- uses: pnpm/action-setup@v4
130+
- uses: actions/setup-node@v6
131+
with:
132+
node-version: "22"
133133
- uses: actions/cache@v5
134134
id: cache
135135
with:
@@ -138,11 +138,11 @@ jobs:
138138
- if: steps.cache.outputs.cache-hit != 'true'
139139
run: pnpm install --frozen-lockfile
140140
- name: Generate API Schema
141-
run: bun run generate:schema
141+
run: pnpm run generate:schema
142142
- name: Generate docs and skill files
143-
run: bun run generate:docs
143+
run: pnpm run generate:docs
144144
- name: Validate fragments
145-
run: bun run check:fragments
145+
run: pnpm run check:fragments
146146
- name: Check skill files
147147
id: check-skill
148148
run: |
@@ -171,7 +171,7 @@ jobs:
171171
- name: Fail for fork PRs with stale generated files
172172
if: (steps.check-skill.outputs.stale == 'true' || steps.check-sections.outputs.stale == 'true') && steps.token.outcome != 'success'
173173
run: |
174-
echo "::error::Generated files are out of date. Run 'bun run generate:docs' locally and commit the result."
174+
echo "::error::Generated files are out of date. Run 'pnpm run generate:docs' locally and commit the result."
175175
exit 1
176176
177177
lint:
@@ -181,23 +181,23 @@ jobs:
181181
runs-on: ubuntu-latest
182182
steps:
183183
- uses: actions/checkout@v6
184-
- uses: oven-sh/setup-bun@v2
185-
with:
186-
bun-version: "1.3.13"
187184
- uses: pnpm/action-setup@v4
185+
- uses: actions/setup-node@v6
186+
with:
187+
node-version: "22"
188188
- uses: actions/cache@v5
189189
id: cache
190190
with:
191191
path: node_modules
192192
key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'patches/**') }}
193193
- if: steps.cache.outputs.cache-hit != 'true'
194194
run: pnpm install --frozen-lockfile
195-
- run: bun run generate:schema
196-
- run: bun run lint
197-
- run: bun run typecheck
198-
- run: bun run check:deps
199-
- run: bun run check:errors
200-
- run: bun run check:patches
195+
- run: pnpm run generate:schema
196+
- run: pnpm run lint
197+
- run: pnpm run typecheck
198+
- run: pnpm run check:deps
199+
- run: pnpm run check:errors
200+
- run: pnpm run check:patches
201201

202202
test-unit:
203203
name: Unit Tests
@@ -211,9 +211,6 @@ jobs:
211211
statuses: write
212212
steps:
213213
- uses: actions/checkout@v6
214-
- uses: oven-sh/setup-bun@v2
215-
with:
216-
bun-version: "1.3.13"
217214
- uses: pnpm/action-setup@v4
218215
- uses: actions/setup-node@v6
219216
with:
@@ -226,9 +223,9 @@ jobs:
226223
- if: steps.cache.outputs.cache-hit != 'true'
227224
run: pnpm install --frozen-lockfile
228225
- name: Generate API Schema
229-
run: bun run generate:schema
226+
run: pnpm run generate:schema
230227
- name: Unit Tests
231-
run: bun run test:unit
228+
run: pnpm run test:unit
232229
- name: Coverage Report
233230
uses: getsentry/codecov-action@main
234231
with:
@@ -253,6 +250,9 @@ jobs:
253250
with:
254251
bun-version: "1.3.13"
255252
- uses: pnpm/action-setup@v4
253+
- uses: actions/setup-node@v6
254+
with:
255+
node-version: "22"
256256
- uses: actions/cache@v5
257257
id: cache
258258
with:
@@ -637,9 +637,6 @@ jobs:
637637
runs-on: ubuntu-latest
638638
steps:
639639
- uses: actions/checkout@v6
640-
- uses: oven-sh/setup-bun@v2
641-
with:
642-
bun-version: "1.3.13"
643640
- uses: pnpm/action-setup@v4
644641
- uses: actions/setup-node@v6
645642
with:
@@ -659,14 +656,14 @@ jobs:
659656
- name: Make binary executable
660657
run: chmod +x dist-bin/sentry-linux-x64
661658
- name: Generate API Schema
662-
run: bun run generate:schema
659+
run: pnpm run generate:schema
663660
- name: E2E Tests
664661
env:
665662
SENTRY_CLI_BINARY: ${{ github.workspace }}/dist-bin/sentry-linux-x64
666663
# Pass API key only when skill files changed — the skill-eval e2e test
667664
# auto-skips when the key is absent, so non-skill PRs aren't affected.
668665
ANTHROPIC_API_KEY: ${{ needs.changes.outputs.skill == 'true' && secrets.ANTHROPIC_API_KEY || '' }}
669-
run: bun run test:e2e
666+
run: pnpm run test:e2e
670667

671668
build-npm:
672669
name: Build npm Package (Node ${{ matrix.node }})
@@ -679,9 +676,6 @@ jobs:
679676
node: ["22", "24"]
680677
steps:
681678
- uses: actions/checkout@v6
682-
- uses: oven-sh/setup-bun@v2
683-
with:
684-
bun-version: "1.3.13"
685679
- uses: pnpm/action-setup@v4
686680
- uses: actions/setup-node@v6
687681
with:
@@ -697,7 +691,7 @@ jobs:
697691
env:
698692
# Environment-scoped (production) — see note in build-binary.
699693
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
700-
run: bun run bundle
694+
run: pnpm run bundle
701695
- name: Smoke test (Node.js)
702696
run: node dist/bin.cjs --help
703697
- run: npm pack
@@ -723,9 +717,6 @@ jobs:
723717
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
724718
steps:
725719
- uses: actions/checkout@v6
726-
- uses: oven-sh/setup-bun@v2
727-
with:
728-
bun-version: "1.3.13"
729720
- uses: pnpm/action-setup@v4
730721
# Astro 6 requires Node >= 22.12. Pin an explicit version so the docs
731722
# build doesn't rely on whatever ships on the runner image.
@@ -750,16 +741,16 @@ jobs:
750741
- name: Make binary executable
751742
run: chmod +x dist-bin/sentry-linux-x64
752743
- name: Generate docs content
753-
run: bun run generate:schema && bun run generate:docs
744+
run: pnpm run generate:schema && pnpm run generate:docs
754745
- name: Build Docs
755746
working-directory: docs
756747
env:
757748
PUBLIC_SENTRY_ENVIRONMENT: production
758749
SENTRY_RELEASE: ${{ steps.version.outputs.version }}
759750
PUBLIC_SENTRY_RELEASE: ${{ steps.version.outputs.version }}
760751
run: |
761-
bun install --frozen-lockfile
762-
bun run build
752+
pnpm install --frozen-lockfile
753+
pnpm run build
763754
# Inject debug IDs and upload sourcemaps. The inject step adds
764755
# //# debugId= and the _sentryDebugIds IIFE to deployed JS files.
765756
# Both steps require SENTRY_AUTH_TOKEN (the CLI checks auth on startup).

.lore.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,26 @@
5353

5454
### Gotcha
5555

56-
<!-- lore:019dc095-9ce4-7fe1-89ab-12efeffddcee -->
57-
* **Biome noUselessUndefined also rejects () => {} empty arrow callbacks**: Biome lint traps (run \`bun run lint\` not \`lint:fix\` before pushing): (1) \`noUselessUndefined\`+\`noEmptyBlockStatements\`: use \`function noop():void{/\* noop \*/}\` not \`()=>undefined\`. (2) \`noExcessiveCognitiveComplexity\` caps at 15; \`biome-ignore\` on SAME line as function definition. (3) \`noPrecisionLoss\` on int >2^53 — use \`Number(string)\`. (4) \`noIncrementDecrement\` — use \`i+=1\`. (5) \`useYield\` on \`async \*func()\` needs \`biome-ignore\`. (6) Plugin forbids raw \`metadata\` table queries — use \`getMetadata\`/\`setMetadata\`/\`clearMetadata\`. (7) \`noMisplacedAssertion\` — inline \`biome-ignore\` above each \`expect()\`, NOT file-level. (8) \`AuthError(reason, message?)\`: \`new AuthError("expired", "Token expired")\`. (9) \`noShadow\`: \`vi.hoisted()\` inner vars shadowing outer destructured names — prefix inner vars with \`\_\`; unused outer destructured names from \`vi.hoisted()\` classes trigger \`noUnusedVariables\` — remove them. (10) \`noNamespaceImport\` on \`import \*\` in tests — add \`biome-ignore\` for \`vi.mocked()\` partial mocks. (11) \`noSkippedTests\` on intentional \`test.skip\` — add \`biome-ignore\` with explanation. Tests aren't type-checked but ARE lint-checked. Biome hits type limit on large files — split o \[truncated — entry too long]
58-
59-
<!-- lore:019e4b21-84ea-7abb-a2b7-f8bac1cf28dc -->
60-
* **io\_uring crash on GitHub Actions — set UV\_USE\_IO\_URING=0**: GitHub Actions runners have kernels that don't support io\_uring properly. Node.js (via libuv) crashes with \`libuv: io\_uring\_enter(getevents): Operation not supported\` + exit code 134 (SIGABRT). Affects both Node 22 and 24. Fix: set \`UV\_USE\_IO\_URING=0\` env var in CI job steps to disable io\_uring in libuv. Trap: looks like a Node version issue because it appears in test runs, but switching Node versions doesn't help — it's a kernel capability issue on the runner.
61-
6256
<!-- lore:019db776-111b-73db-b4ad-b762dfd4808f -->
6357
* **MastraClient has no dispose API — use AbortController for cleanup**: MastraClient has no \`close()\`/\`dispose()\` API — cleanup via \`ClientOptions.abortSignal\` (constructor) or per-prompt \`signal\`. Without explicit abort, fetch keep-alive sockets hold the event loop alive past natural exit. Pattern in \`src/lib/init/wizard-runner.ts\`: create \`AbortController\` per \`runWizard\`, pass \`abortSignal: controller.signal\` to \`new MastraClient(...)\`, abort via \`using \_ = { \[Symbol.dispose]: () => controller.abort() }\`. Custom \`fetch\` wrapper must preserve \`init.signal\` via spread. Tests capture \`ClientOptions\` via \`spyOn(MastraClient.prototype, 'getWorkflow').mockImplementation(function() { capturedOpts.push(this.options); ... })\`.
6458

59+
<!-- lore:019e4b91-b405-74b6-b135-660ab3a76149 -->
60+
* **npm build smoke test uses system Node — setup-node step must not be deleted**: (gotcha) npm build smoke test uses system Node — \`setup-node\` step must not be deleted: The Build npm Package CI job runs a smoke test on \`dist/bin.cjs\` which rejects Node < 22.15. If \`setup-node\` (with \`node-version: ${{ matrix.node }}\`) is deleted, the smoke test runs against the runner's system Node (v20 on ubuntu-latest) and fails. Trap: the build step itself may succeed — only the smoke test reveals the missing setup. Always verify \`setup-node\` is present in the npm build job after any CI config refactor.
61+
6562
<!-- lore:019d9b86-868d-7ff4-b2a6-74fdd1c9d56e -->
6663
* **process.stdin.isTTY unreliable in Bun — use isatty(0) and backfill for clack**: \`process.stdin.isTTY\` unreliable — use \`isatty(0)\` from \`node:tty\`. Bun's single-file binary can leave \`process.stdin.isTTY === undefined\` on TTY fds. \`@clack/core\` gates \`setRawMode(true)\` on \`input.isTTY\`, silently disabling raw mode. Fix: backfill \`process.stdin.isTTY = true\` when \`isatty(0)\` confirms. Debugging: \`src/lib/init/tty-diagnostics.ts\` \`dumpTtyDiagnostics(label)\` — no-op unless \`SENTRY\_INIT\_DIAGNOSTICS=1\`.
6764

6865
<!-- lore:019e471c-df3f-7e78-9df7-6b36d2d258db -->
6966
* **SQLite transaction() ROLLBACK can throw, discarding original error**: (gotcha) SQLite transaction ROLLBACK error-swallowing trap: In \`src/lib/db/sqlite.ts\`, \`transaction()\` catches errors and runs \`this.db.exec('ROLLBACK')\`. If ROLLBACK itself throws, the original error is lost. Fix: \`const origErr = e; try { this.db.exec('ROLLBACK'); } catch (rbErr) { log.debug(...); } throw origErr;\`
7067

68+
<!-- lore:019e4b77-95bc-7176-aca7-1ea4ad9254e9 -->
69+
* **Vitest 4 removed test(name, fn, options) signature — options must be second arg**: (gotcha) Vitest 4 removed \`test(name, fn, { timeout })\` signature — options must be second arg. Fix: \`test(name, { timeout }, fn)\`. Trap: old signature was valid in Vitest 3 and looks natural (options last, like Jest/Mocha). Note: bare numeric timeout \`beforeAll(fn, 60\_000)\` remains valid — only \`{ timeout: N }\` object as last arg to \`test()\` is broken. Use sed/script bulk-fix across E2E test files.
70+
71+
<!-- lore:019e4b91-b3fb-7e62-b607-8d1ec425a6a0 -->
72+
* **Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: (gotcha) Vitest worker pool + io\_uring crash on GitHub Actions: On GitHub Actions, io\_uring crashes Node.js workers (exit 134/SIGABRT) with \`libuv: io\_uring\_enter(getevents): Operation not supported\`. Fix: set \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` env var in CI job steps. Trap: looks like a Node version issue — switching versions doesn't help, it's a kernel capability issue. Also: tests that internally call \`Bun.spawn\` (e.g. \`test/commands/local/run.test.ts\`) must be skipped in Vitest Node workers via \`skipIf\` since Bun globals are unavailable.
73+
7174
<!-- lore:019e464f-8fcb-7d16-b7ee-f3b157e1565e -->
72-
* **whichSync must use 'command -v' not 'which' for PATH-restricted lookups**: (gotcha) Bun→Node.js API replacements: \`Bun.which(cmd,{PATH})\` → \`whichSync()\` from \`src/lib/which.ts\` (uses 'command -v'). \`Bun.spawn\` → \`spawn(cmd,args,{stdio:\['pipe','pipe','pipe'],...opts})\`; \`proc.exited\` → \`new Promise(r=>proc.on('close',c=>r(c??1)))\`; stdout via \`proc.stdout.on('data',(d)=>{out+=d;})\`. \*\*CRITICAL: always attach \`proc.on('error',noop)\` — Node crashes on unhandled spawn errors.\*\* \`Bun.spawnSync\` → \`spawnSync\`; \`proc.success\`→\`proc.status===0\`. \`Bun.write\`→\`writeFileSync\`. \`Bun.sleep(ms)\`→\`import {setTimeout as sleepMs} from 'node:timers/promises'\`. \`new Bun.Glob(p).match(i)\`→\`picomatch(p,{dot:true})(i)\`. \`Bun.randomUUIDv7()\`→\`uuidv7()\`. \`Bun.semver.order()\`→\`compare()\` from \`semver\` (guard with \`semverValid(v)\`). \`Bun.file().writer()\`→\`createWriteStream\` — never pass \`resolve\` directly to \`writer.end()\`; use \`writer.end((err?)=>err?reject(err):resolve())\`. Node version: \`engines.node >=22.15\` (zstd requires 22.15+). CI builds \`\["22","24"]\`; E2E jobs MUST use \`actions/setup-node\` with \`node-version: 22\` — \`ubuntu-latest\` defaults to Node 20.
75+
* **whichSync must use 'command -v' not 'which' for PATH-restricted lookups**: (gotcha) Bun→Node.js API replacements: \`Bun.which(cmd,{PATH})\` → \`whichSync()\` from \`src/lib/which.ts\` (uses 'command -v'). \`Bun.spawn\` → \`spawn(cmd,args,{stdio:\['pipe','pipe','pipe'],...opts})\`; \`proc.exited\` → \`new Promise(r=>proc.on('close',c=>r(c??1)))\`; stdout via \`proc.stdout.on('data',(d)=>{out+=d;})\`. \*\*CRITICAL: always attach \`proc.on('error',noop)\` — Node crashes on unhandled spawn errors.\*\* \`Bun.spawnSync\` → \`spawnSync\`; \`proc.success\`→\`proc.status===0\`. \`Bun.write\`→\`writeFileSync\`. \`Bun.sleep(ms)\`→\`import {setTimeout as sleepMs} from 'node:timers/promises'\`. \`new Bun.Glob(p).match(i)\`→\`picomatch(p,{dot:true})(i)\`. \`Bun.randomUUIDv7()\`→\`uuidv7()\`. \`Bun.semver.order()\`→\`compare()\` from \`semver\` (guard with \`semverValid(v)\`). \`Bun.file().writer()\`→\`createWriteStream\`. Node version: \`engines.node >=22.15\` (zstd requires 22.15+). CI builds \`\["22","24"]\`; E2E jobs MUST use \`actions/setup-node\` with \`node-version: 22\`. Tests using \`Bun.spawn\` internally must be skipped in Vitest Node workers via \`skipIf\`.
7376

7477
<!-- lore:019db0c9-9cc7-7352-b1f2-61b34b87b252 -->
7578
* **Whole-buffer matchAll slower than split+test when aggregated over many files**: (gotcha) Grep/scan traps in \`src/lib/scan/\`: (1) Whole-buffer \`regex.exec\` 12× faster per-file but ~1.6× SLOWER over 10k files — early-exit at \`maxResults\` via \`mapFilesConcurrent.onResult\` wins. (2) Literal prefilter is FILE-LEVEL gate (\`indexOf\`→skip); per-line verify breaks cross-newline patterns and Unicode length-changing \`toLowerCase\`. (3) Extractor \`hasTopLevelAlternation\`+\`skipGroup\` must call \`skipCharacterClass\` (PCRE \`\[]abc]\` ≠ JS empty class). (4) Wake-latch race: use latched \`pendingWake\` flag, not \`let notify=null; await new Promise(r=>notify=r)\`. (5) \`mapFilesConcurrent\` filters \`null\` but NOT \`\[]\` — return \`null\` for no-op files. (6) \`collectGlob\`/\`collectGrep\` must NOT forward \`maxResults\` to iterator; drain uncapped, set \`truncated=true\`.
@@ -84,5 +87,8 @@
8487

8588
### Preference
8689

90+
<!-- lore:019e4bd4-28e3-7c82-a325-ce8f868402cd -->
91+
* **Always migrate Bun-specific APIs to Node.js equivalents with explicit mapping rules**: When working on projects that use Bun APIs, the user systematically replaces them with Node.js equivalents following explicit mapping rules: \`Bun.file().text()\`\`readFile()\`, \`Bun.file().json()\`\`JSON.parse(readFile())\`, \`Bun.file().exists()\`\`access().then(...)\`, \`Bun.write()\`\`writeFile()\`, \`Bun.Glob\`\`tinyglobby\`, shebangs \`#!/usr/bin/env bun\`\`#!/usr/bin/env tsx\`, etc. The user works in phases across all affected files (scripts, src/, CI configs, package.json), tracks exclusions explicitly, and expects comprehensive coverage. Always apply the full canonical mapping, import from \`node:fs/promises\` or \`node:fs\`, and update all affected files including build scripts and CI workflows.
92+
8793
<!-- lore:019e4a9c-430a-74a8-a5e4-8dd98c672cef -->
88-
* **Always wait for Sentry Seer and Cursor BugBot CI jobs before merging and address all unresolved review comments**: (preference) Lint discipline: fix errors immediately with minimal, surgical changes — prefix unused/shadowing vars with \`\_\`, use optional chaining, rename rather than restructure. No broad refactors. After fixing, re-run lint to confirm exit code 0 before committing/pushing. Intentional \`test.skip\` from known Vitest limitations: suppress with inline ignore. \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22 — top-level import crashes before any try/catch. After removing \`bun:sqlite\` fallback, remove dead branches like \`this.db.query ?? this.db.prepare\` and \`typeof this.db.transaction === 'function'\` guards entirely. (preference) Adversarial PR review + CI monitoring: run 4-5 rounds of adversarial review (security, edge cases, error handling, lint, test coverage), severity-tiered (CRITICAL/MEDIUM/LOW/NON-BLOCKING), explicit MERGE/NO-MERGE verdict. Wait for 'Sentry Seer' and 'Cursor BugBot' CI jobs; address all unresolved comments. \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs, silently skipping jobs; make a real file change to trigger CI.
94+
* **Always wait for Sentry Seer and Cursor BugBot CI jobs before merging and address all unresolved review comments**: (preference) Adversarial PR review + CI discipline: Run 4-5 rounds of adversarial review (security, edge cases, error handling, lint, test coverage), severity-tiered (CRITICAL/MEDIUM/LOW/NON-BLOCKING), explicit MERGE/NO-MERGE verdict. Wait for 'Sentry Seer' and 'Cursor BugBot' CI jobs; address all unresolved comments. \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs, silently skipping jobs; make a real file change to trigger CI. Lint discipline: fix errors immediately with minimal surgical changes — prefix unused/shadowing vars with \`\_\`, use optional chaining, rename rather than restructure. Re-run lint to confirm exit code 0 before committing. \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22 — top-level import crashes before any try/catch; remove dead \`bun:sqlite\` fallback branches entirely after migration.

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,13 @@ bun run --env-file=.env.local cli --help
143143

144144
<!-- GENERATED:START dev-scripts -->
145145
```bash
146-
bun run build # Build for current platform
147-
bun run typecheck # Type checking
148-
bun run lint # Check for issues
149-
bun run lint:fix # Auto-fix issues
150-
bun run test:unit # Run unit tests
151-
bun run test:e2e # Run end-to-end tests
152-
bun run generate:docs # Regenerate command docs and skills
146+
pnpm run build # Build for current platform
147+
pnpm run typecheck # Type checking
148+
pnpm run lint # Check for issues
149+
pnpm run lint:fix # Auto-fix issues
150+
pnpm run test:unit # Run unit tests
151+
pnpm run test:e2e # Run end-to-end tests
152+
pnpm run generate:docs # Regenerate command docs and skills
153153
```
154154
<!-- GENERATED:END dev-scripts -->
155155

0 commit comments

Comments
 (0)