You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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)
* **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]
***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.
***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); ... })\`.
***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.
***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\`.
***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.
***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.
* **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\`.
***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\`.
***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.
* **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.
0 commit comments