Skip to content

refactor(repo): drop bun for vitest, node, and esbuild - #130

Open
haydenshively wants to merge 8 commits into
mainfrom
refactor/remove-bun
Open

refactor(repo): drop bun for vitest, node, and esbuild#130
haydenshively wants to merge 8 commits into
mainfrom
refactor/remove-bun

Conversation

@haydenshively

@haydenshively haydenshively commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

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

Surface Replacement
141 bun:test files vitest; mock()vi.fn(), spyOnvi.spyOn, mock.restore()vi.restoreAllMocks(), .toInclude.toContain
soltag bunfig preloads soltag/vite in tests, an esbuild onLoad plugin in each bot's scripts/build.ts
bun src/index.ts node --env-file-if-exists=.env dist/src/index.js
Bun.env (59) process.env
Bun.spawn (10) node:child_process, with the deterministic SIGKILL + exit-promise semantics preserved
Bun.serve (3) node:http, behind an adapter that keeps the Web-standard (Request) => Response handlers
Bun.file, Bun.sleep, Bun.which, Bun.argv, import.meta.dir, import.meta.main readFile, node:timers/promises, resolved binaries, process.argv, fileURLToPath, an argv[1] comparison
bun $ shell (3 deploy scripts) execa; the secret still goes via stdin ($({ input })), never argv
global confirm() node:readline/promises; a non-TTY declines rather than hanging CI
Bun.build (@repo/contracts) esbuild, same soltag plugin

Four latent bugs this surfaced

  1. 24 floating expect(...).rejects assertions. bun's typings hid them, so they never asserted
    anything. oxlint's no-floating-promises catches them once vitest typings apply.
  2. Anvil fork ports collided. bun's runner was serial, so fixed ports were only deconflicted
    within a bot — blue and midnight-liquidation both wanted 8545, midnight-queue and
    market-making both wanted 8546. vitest runs files in parallel, so every fork suite now claims a
    distinct port, with the registry documented in the harnesses.
  3. fetch.preconnect in http-json.utils.ts was a bun-only extension to fetch in production
    code.
  4. tryCatch mishandled every execa call (found in review). It branched on
    fn instanceof Promise, and execa's subprocess is a thenable that is not a native Promise while
    its typings declare it extends Promise<Result> — so the check type-checked and then took the
    sync path, calling the subprocess object. assertCli() reported "Railway CLI not found" for a
    railway that exists, before Railway was ever invoked. It now branches on callable and assimilates
    thenables via Promise.resolve; the ad-hoc Promise.resolve(...) wrappers that a few call sites
    already carried are removed, so all of them read the same.

prestart stays, for the local path only

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 each bot keeps
prestart: pnpm --filter "{.}..." --if-present run build purely for the documented
pnpm --filter <bot> run start path. {.}... 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

  • Same test files run. Diffed vitest's collected file list against every tracked *.test.ts:
    141 vs 141, zero delta — including scripts/check-jsdoc.test.ts, the one test outside a
    test/ tree, which vitest's default glob would silently have dropped.
  • Non-vacuity, all 11 projects. Added a failing probe to one file per project and confirmed each
    project actually executed and failed it; all files restored, grep clean.
  • @repo/contracts output byte-identical across Bun.build→esbuild: abis/Midnight.json,
    abis/Morpho.json, dist/index.d.ts all match sha256. The solc pin from chore(repo): migrate from bun to pnpm #129 is what makes this
    hold.
  • Bundles actually run on Node. All four reach their own fail-loud config check, and the soltag
    bots' bundles contain zero residual sol` templates with 32 inlined bytecode blobs each.
  • Fork suites genuinely run. With RPC_URL_8453 supplied, the midnight fork suites spawn anvil,
    deploy the soltag-compiled Executor and run a real liquidation — end-to-end proof of the
    soltag/vite transform. loadEnv was verified not to clobber an already-exported value (so CI
    cannot silently skip) and to omit the key rather than set an empty string.
  • Secret path probed at runtime: secret appears in argv? false / secret arrived on stdin? true.
  • The tryCatch fix is pinned by tests that fail without it. Two new cases in
    packages/utils/test/helpers/tryCatch.test.ts reproduce fn is not a function against the old
    implementation; a real execa call through the real tryCatch then returns error: null for a
    present binary and still errors for a missing one.
  • prestart verified from wiped dists for all four bots — each builds its workspace dependencies
    and its own bundle, then reaches its own fail-loud config check.
  • Full suite green: 1459 pass, 1 skip, 141 files, fork suites included.
    pnpm lint 0/0 · pnpm knip clean · pnpm -r run typecheck 12/12 · jsdoc:build exits 0.

main is merged in

main gained #121/#124/#125 after this branch's merge-base. Because a stacked PR's mergeability
is computed against main rather than the immediate parent, that left GitHub reporting the PR as
conflicting 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 added mock( sites to
files already converted, Bun.file/Bun.serve/Bun.sleep replaced in the setup-state test, and
3 more floating expect(...).rejects awaited — the same latent bug class, still hidden by bun's
typings 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.1 only), and reach their own fail-loud config check from node dist/src/index.js with the
bundles built at image-build time. The non-root switch itself comes from #129.

Not verified

Nothing outstanding.

Follow-ups

🤖 Generated with Claude Code

@haydenshively haydenshively self-assigned this Aug 5, 2026
@haydenshively
haydenshively requested a review from cashd August 5, 2026 13:41
@haydenshively
haydenshively marked this pull request as ready for review August 5, 2026 13:41

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread bots/market-making/vitest.config.ts
Comment thread .claude/commands/review.md
Comment thread bots/market-making/scripts/bundle-failed.error.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread bots/blue-liquidation/scripts/deploy-railway.ts
Comment thread bots/blue-liquidation/package.json
haydenshively added a commit that referenced this pull request Aug 5, 2026
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>
haydenshively added a commit that referenced this pull request Aug 5, 2026
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>
@haydenshively

Copy link
Copy Markdown
Collaborator Author

Heads-up: GitHub reports this stacked PR as conflicting, and git disagrees

GitHub shows mergeable: CONFLICTING / mergeStateStatus: DIRTY, which is also why no Checks run has fired on this branch since 13:27Z — the workflow only triggers on pull_request opened/synchronize, and GitHub skips that when it cannot compute the merge.

There was a real conflict, and it is now fixed. main gained #121/#124/#125 after this branch's merge-base, and a stacked PR's mergeability is computed against main, not against the immediate parent — which is why #129 stayed green while this one went dirty. main is merged in as of ddacbad, along with the mechanical port of what it brought (8 more files off bun:test, Bun.file/Bun.serve/Bun.sleep, and 3 more floating expect(...).rejects).

After that merge, git reports no conflict from any angle:

conflicts vs main:                 0
behind main by:                    0
base (chore/migrate-to-pnpm) is an ancestor of head:  yes
compare base...head:               status "ahead", behind_by 0

GitHub has not recomputed. The usual escape hatches are both closed for a stack:

  • PUT /pulls/130/update-branch403 Updating a stacked PR's branch via this endpoint is not supported.
  • gh pr edit --base …Cannot change the base branch because the pull request is part of 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 *.test.ts with zero delta, pnpm lint 0/0, knip clean, 12/12 typecheck, jsdoc:build exits 0, and all three images build, run as uid=1000(node), and reach their fail-loud config check.

@haydenshively

Copy link
Copy Markdown
Collaborator Author

Resolved — the stack is rebased and checks are running

My previous comment said this needed the Restack control in the browser. That was wrong: the fix is gh stack (the official github/gh-stack extension), which the repo's stack was created with.

gh stack checkout 131   # stack #131: main <- #129 <- #130 <- #134
gh stack rebase
gh stack push

All three PRs now report mergeable: true (blocked is just the pending required review), and Checks fired on all three for the first time since 13:27Z.

Two notes on what the rebase needed by hand:

  • Linearizing dropped the merge commit, and with it the bun:test → vitest ports for the seven files main had added. They are restored as their own commit (refactor(market-making): port main's new test files off bun:test), and the resulting tree is byte-identical to the merge-based state that was verified.
  • The rebase tried to replay main's own 14 commits on top of #129 — they were only reachable through that merge commit and are already in the new base's ancestry, so they were dropped from the todo rather than re-applied.

Verified after the restack, per branch:

#129 #130 #134
own contribution vs. pre-rebase identical tree byte-identical identical (same 25 files)
tests 1459 pass / 1 skip / 141 files 1522 pass / 1 skip / 142 files
lint · knip · typecheck 0/0 · clean · 12/12 0/0 · clean · 12/12

haydenshively added a commit that referenced this pull request Aug 7, 2026
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>
haydenshively added a commit that referenced this pull request Aug 7, 2026
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>
haydenshively added a commit that referenced this pull request Aug 7, 2026
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>
Base automatically changed from chore/migrate-to-pnpm to main August 7, 2026 18:52
haydenshively and others added 5 commits August 7, 2026 20:52
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant