refactor(repo): drop bun for vitest, node, and esbuild - #130
refactor(repo): drop bun for vitest, node, and esbuild#130haydenshively wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce08fe4a6e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c135e89 to
c644283
Compare
Heads-up: GitHub reports this stacked PR as conflicting, and git disagreesGitHub shows There was a real conflict, and it is now fixed. After that merge, git reports no conflict from any angle: GitHub has not recomputed. The usual escape hatches are both closed for a stack:
A rebase onto the updated parent (verified byte-identical tree) and a fresh push did not clear it either. What is needed: someone with the PR open in the browser hitting the stack's Restack / Update control, which is the only path the API leaves for a stacked PR. Merging #129 first would also collapse the stack and moot it. Once the state clears, Checks will run on the next push. Everything is verified locally in the meantime: 1459 pass / 1 skip / 141 files (fork suites included), vitest collects exactly the 141 tracked |
ddacbad to
0244681
Compare
Resolved — the stack is rebased and checks are runningMy previous comment said this needed the Restack control in the browser. That was wrong: the fix is All three PRs now report Two notes on what the rebase needed by hand:
Verified after the restack, per branch:
|
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0244681 to
fc4087e
Compare
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fc4087e to
b9e5524
Compare
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b9e5524 to
9b903a6
Compare
Completes the toolchain consolidation started by the pnpm migration. All 136 test files move from bun:test to vitest (soltag sol`` via its vite adapter, fork-suite env via loadEnv); bots bundle with esbuild and run plain node dist/src/index.js; images are node-slim with no bun binary; CI drops setup-bun and runs pnpm test. Bun APIs replaced: Bun.env->process.env (59), Bun.spawn->node:child_process, Bun.serve->node:http, Bun.file->readFile, Bun.sleep->node:timers/promises, Bun.which, Bun.argv->process.argv, import.meta.dir/main, bun's $ shell->execa, global confirm()->node:readline/promises, and the `bun` module type imports. Bun.build in @repo/contracts also moves to esbuild. Three latent bugs surfaced and are fixed: - 24 floating expect(...).rejects assertions that bun's typings hid. They never asserted anything; oxlint's no-floating-promises catches them under vitest typings. - Anvil fork ports collided once files ran in parallel. bun's runner was serial, so fixed ports were only deconflicted within a bot. Every fork suite now claims a distinct port and the registry is documented. - fetch.preconnect in http-json.utils.ts was a bun-only extension to fetch. Two bounds are raised because the interpreter genuinely changed: a tsx cold start costs ~1.3s against bun's ~0.1s, so the market-making subprocess tests get a 30s ceiling and the FIFO fail-closed probe a 10s bound. Both still prove what they were written to prove. Evidence: vitest collects exactly the same 136 files as bun (diffed, zero delta, including the one test outside test/); @repo/contracts' abis/*.json and dist/index.d.ts are byte-identical across Bun.build->esbuild; the secret path in deploy-railway still reaches stdin and never argv (probed); all 11 projects verified non-vacuous. Full suite green: 1415 pass, 1 skip. CRTR-2822 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review on #130. - tryCatch branched on `fn instanceof Promise`, so an execa subprocess — a thenable that is NOT a native Promise, yet typed `extends Promise<Result>` — fell through to the sync path and got *called*. Every deploy would have reported "Railway CLI not found" from assertCli() before reaching Railway. It now branches on callable and assimilates the thenable via Promise.resolve, which also lets the ad-hoc Promise.resolve() wrappers at the other execa call sites go away so all of them read the same. - Restored `prestart` on all four bots. `start` runs `dist/src/index.js`, which a clean checkout does not have; the images build at image-build time and their CMD never fires a pre-script, so this only affects the documented local path. `{.}...` scopes the build to the bot plus its workspace deps. - market-making declares `vite` (its vitest.config.ts imports loadEnv) instead of relying on root hoisting, matching the two liquidation bots. - Registered scripts/bundle-failed.error.ts in check-jsdoc.ts, typedoc.json, and the build-jsdoc skill inventory. - .claude/commands: babysit-pr's validate block runs `pnpm test`, review.md's stack list names vitest/esbuild/Node. (CLAUDE.md's remaining "bun" is a verbatim past commit title used as a format example, so it stays.) - Declaring vite in market-making shifted knip's peer attribution and exposed root `tsx` as unused. It is not: `node --import tsx` and the CLI subprocess tests resolve it from the root, and dropping it fails two market-making tests. knip now ignores it explicitly, with the reason recorded at the usage. Verified: 1417 pass / 1 skip / 136 files (fork suites included, RPC_URL_8453 supplied); the two new tryCatch tests reproduce `fn is not a function` against the old implementation; a real execa call through tryCatch now succeeds for a present binary and still errors for a missing one; all four bots build from wiped dists via prestart and reach their fail-loud config check; all three images build, run as uid=1000(node), carry no bun binary, and reach that same check; pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main gained #121/#124/#125 after this branch's merge-base, adding test files that still import bun:test and call bun's mock(). Linearizing the stack dropped these conversions along with the merge commit that carried them, so they are restored here as their own commit: 7 files moved to vitest imports and mock() -> vi.fn(). Tree is byte-identical to the verified pre-rebase state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e0783c9 to
cfd1133
Compare
Part 2 of 2 for CRTR-2822. Stacked on #129 — review that first; this PR's base is
chore/migrate-to-pnpm, so the diff shown here is only the bun removal.Completes the toolchain consolidation: 136 test files move to vitest, bots bundle with esbuild and
run on Node, images carry no bun binary, CI drops
setup-bun.What moved
bun:testfilesvitest;mock()→vi.fn(),spyOn→vi.spyOn,mock.restore()→vi.restoreAllMocks(),.toInclude→.toContainbunfigpreloadssoltag/vitein tests, an esbuildonLoadplugin in each bot'sscripts/build.tsbun src/index.tsnode --env-file-if-exists=.env dist/src/index.jsBun.env(59)process.envBun.spawn(10)node:child_process, with the deterministic SIGKILL + exit-promise semantics preservedBun.serve(3)node:http, behind an adapter that keeps the Web-standard(Request) => ResponsehandlersBun.file,Bun.sleep,Bun.which,Bun.argv,import.meta.dir,import.meta.mainreadFile,node:timers/promises, resolved binaries,process.argv,fileURLToPath, anargv[1]comparison$shell (3 deploy scripts)execa; the secret still goes via stdin ($({ input })), never argvconfirm()node:readline/promises; a non-TTY declines rather than hanging CIBun.build(@repo/contracts)Four latent bugs this surfaced
expect(...).rejectsassertions. bun's typings hid them, so they never assertedanything. oxlint's
no-floating-promisescatches them once vitest typings apply.within a bot —
blueandmidnight-liquidationboth wanted 8545,midnight-queueandmarket-makingboth wanted 8546. vitest runs files in parallel, so every fork suite now claims adistinct port, with the registry documented in the harnesses.
fetch.preconnectinhttp-json.utils.tswas a bun-only extension tofetchin productioncode.
tryCatchmishandled every execa call (found in review). It branched onfn instanceof Promise, and execa's subprocess is a thenable that is not a native Promise whileits typings declare it
extends Promise<Result>— so the check type-checked and then took thesync path, calling the subprocess object.
assertCli()reported "Railway CLI not found" for arailwaythat exists, before Railway was ever invoked. It now branches on callable and assimilatesthenables via
Promise.resolve; the ad-hocPromise.resolve(...)wrappers that a few call sitesalready carried are removed, so all of them read the same.
prestartstays, for the local path onlystartrunsdist/src/index.js, which a clean checkout does not have. The images build atimage-build time and their
CMDnever fires a pre-script, so each bot keepsprestart: pnpm --filter "{.}..." --if-present run buildpurely for the documentedpnpm --filter <bot> run startpath.{.}...scopes it to that bot plus its workspace dependencies,so one bot's start does not build the other three.
Two bounds deliberately raised
A tsx cold start costs ~1.3s against bun's ~0.1s. The market-making subprocess tests get a 30s
ceiling, and the FIFO fail-closed probe a 10s bound (a FIFO read blocks forever, so any finite
completion still proves the loader fails closed). Both still prove what they were written to prove;
the interpreter genuinely got slower, so the ceilings moved rather than the assertions.
Evidence
*.test.ts:141 vs 141, zero delta — including
scripts/check-jsdoc.test.ts, the one test outside atest/tree, which vitest's default glob would silently have dropped.project actually executed and failed it; all files restored,
grepclean.@repo/contractsoutput byte-identical acrossBun.build→esbuild:abis/Midnight.json,abis/Morpho.json,dist/index.d.tsall match sha256. Thesolcpin from chore(repo): migrate from bun to pnpm #129 is what makes thishold.
bots' bundles contain zero residual
sol`templates with 32 inlined bytecode blobs each.RPC_URL_8453supplied, the midnight fork suites spawn anvil,deploy the soltag-compiled Executor and run a real liquidation — end-to-end proof of the
soltag/vitetransform.loadEnvwas verified not to clobber an already-exported value (so CIcannot silently skip) and to omit the key rather than set an empty string.
secret appears in argv? false/secret arrived on stdin? true.packages/utils/test/helpers/tryCatch.test.tsreproducefn is not a functionagainst the oldimplementation; a real execa call through the real
tryCatchthen returnserror: nullfor apresent binary and still errors for a missing one.
prestartverified from wiped dists for all four bots — each builds its workspace dependenciesand its own bundle, then reaches its own fail-loud config check.
pnpm lint0/0 ·pnpm knipclean ·pnpm -r run typecheck12/12 ·jsdoc:buildexits 0.main is merged in
maingained #121/#124/#125 after this branch's merge-base. Because a stacked PR's mergeabilityis computed against
mainrather than the immediate parent, that left GitHub reporting the PR asconflicting and suppressing its checks entirely. Merging main in fixes both and lands the anticipated
follow-up early: 8 more test files ported off
bun:test, 2 more where main addedmock(sites tofiles already converted,
Bun.file/Bun.serve/Bun.sleepreplaced in the setup-state test, and3 more floating
expect(...).rejectsawaited — the same latent bug class, still hidden by bun'stypings on main.
Docker is verified
All three images build, run as
uid=1000(node), carry no bun binary (node v24.14.1,pnpm 11.1.1only), and reach their own fail-loud config check fromnode dist/src/index.jswith thebundles built at image-build time. The non-root switch itself comes from #129.
Not verified
Nothing outstanding.
Follow-ups
bun:testfiles andBun.*calls, docs(market-making): separate architecture documentation #121, feat(market-making): support router ratifiers #124 and fix(market-making): batch invalidation with Midnight multicall #125 havemerged into main and are already absorbed here (see "main is merged in" above). test(market-making): add MKT-1491 workflow e2e coverage #112, fix(bot-kit): stamp pending txs with the broadcast-time head #116 and
feat(market-making): add docker image, compose, and docker hub publish #123 still need the mechanical fix on rebase — feat(market-making): add docker image, compose, and docker hub publish #123 is the sharpest, adding a bun-based
market-making/Dockerfileplus a Docker Hub publish workflow that must be ported to node-slim.bots/market-making/src/infrastructure/invalidation/batch-offer-invalidation.utils.ts(from fix(market-making): batch invalidation with Midnight multicall #125,via main) is in neither
check-jsdoc.tsnortypedoc.json. Pre-existing on main, not introducedhere, so it is left alone — but it is the same registration gap the review caught on
bundle-failed.error.ts.oxlintwithoxlint-tsgolint >= 0.24(pin comment left in the catalog).🤖 Generated with Claude Code