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.
***http.createServer async callback — unhandled promise rejections crash test server**: (gotcha) \`http.createServer(async (req, res) => { await ... })\` — Node accepts async callbacks but ignores their return value. If the inner \`await\` throws, it causes an unhandled promise rejection that crashes the test server. Fix: wrap entire async callback body in \`try...catch\` and call \`res.end()\` or \`res.destroy()\` in the catch block. Applies to \`test/mocks/server.ts\` and any test HTTP server using async request handlers.
***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); ... })\`.
***Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: (gotcha) Vitest worker pool + CI issues: (1) On GitHub Actions, io\_uring crashes Node.js workers (exit 134/SIGABRT). Fix: set \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` env var in CI — it's a kernel capability issue, not a Node version issue. (2) Tests internally calling \`Bun.spawn\` must be skipped in Vitest Node workers via \`skipIf\`. (3) npm build smoke test uses system Node — \`setup-node\` (with \`node-version: ${{ matrix.node }}\`) must not be deleted from the npm build CI job; smoke test on \`dist/bin.cjs\` rejects Node < 22.15 and fails silently if setup-node is missing. (4) Vitest 4 removed \`test(name, fn, { timeout })\` signature — options must be second arg: \`test(name, { timeout }, fn)\`. Bare numeric timeout \`beforeAll(fn, 60\_000)\` remains valid.
* **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.
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\`. 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\`. PRESERVE intentional Bun usage: \`Bun.build()\` in \`script/build.ts\` for native binary compilation must stay Bun; \`build-binary\` CI job retains \`oven-sh/setup-bun\`; \`script/nod \[truncated — entry too long]
***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 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.
88
+
* **Always wait for Sentry Seer and Cursor BugBot CI jobs before merging and address all unresolved review comments**: (preference) PR/CI discipline: Run adversarial review rounds (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: fix errors immediately with minimal surgical changes — prefix unused/shadowing vars with \`\_\`, use optional chaining. 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. Always exclude \`build/\` and \`dist/\` from Biome analysis to avoid type-limit warnings. Biome version mismatch: local may pass while CI fails — CI is authoritative; apply \`biome format --write\` on failing file. Lazy \`require()\` in test fixtures bypasses Vite's \`.js→.ts\` resolver — use top-level \`import\` instead.
0 commit comments