Skip to content

✅ test: launch the CLI through the host runtime - #186

Merged
taras merged 2 commits into
mainfrom
test/runtime-launcher
Jul 28, 2026
Merged

✅ test: launch the CLI through the host runtime#186
taras merged 2 commits into
mainfrom
test/runtime-launcher

Conversation

@taras

@taras taras commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Why

Preparation for #144. Ten suites shell out to the CLI with a hardcoded
deno run --allow-all packages/cli/src/deno.ts. That pins them to one runtime
and hides whether the CLI works under the others — the npm package runs under
Node, and the published bin is what users get.

This PR makes those ten suites runtime-portable. Adding them to the derived
Node and Bun scope is #144 itself.

What changes

Before: every CLI-launching suite spawned Deno, whatever runtime was running
the tests.

After: each launches the entrypoint belonging to the host runtime. Verified:
73 tests, 0 failures under Deno, Node and Bun.

How it works

@executablemd/test-support/launch builds the command, using the entrypoint
that runtime installs its command and compile providers from (#184):

Runtime base
Deno [execPath, "run", "--allow-all", <root>/packages/cli/src/deno.ts]
Bun [execPath, <root>/packages/cli/src/bun.ts]
Node ["tsx", "--tsconfig", <root>/tsconfig.node.json, <root>/packages/cli/src/node.ts]

<root> comes from import.meta.url, not the working directory — props-cli
and command run the CLI from temp directories.

It returns a command, never a process. Two shapes need it. Seven suites
spawn, each consuming the process differently: .expect(), .join(), manual
each(proc.stdout) streaming under four different timeboxes, and in
worker-lifecycle a long-lived bidirectional JSON-RPC peer driven through
proc.stdin. Three never spawn at all — they hand a base array to
API.Env.command and the test-agent provider does the launching.

Node starts a fresh tsx process, not the running one. A CLI subprocess
should not inherit --test, loaders or inspectors from the test runner's
process.execArgv, and naming the tsconfig on the command line means no suite
has to alter its environment.

Environment handling is untouched. It differs on purpose: agent-cli
injects an isolated HOME so ACPX never reads the developer's config;
props-cli allowlists precisely so an inherited XMD_PROPS_* cannot corrupt
its precedence assertions; the rest inherit deliberately.

Resolution

/launch reaches each runtime the same way /bdd and /expect do after #188:
deno.json for Deno, tsconfig.node.json paths for Node, and — for Bun — the
real workspace dependency plus the package's own exports. No root
tsconfig.json, no additional workspace package.

What must stay true

How to verify it

The four repository checks with the CI-pinned Deno 2.9.1: lint 0 errors,
typecheck clean, 145 passed / 0 failed, JSR Success. Plus the ten suites
named explicitly under each runtime, so no unrelated suite changes the result:

deno test --allow-all <the ten>                                  # 10 passed, 0 failed
pnpm exec tsx --tsconfig tsconfig.node.json --test <the ten>     # 73 pass, 0 fail
bun test <the ten>                                               # 73 pass, 0 fail

Also green: pnpm install --frozen-lockfile, bun install,
pnpm exec tsc --project tsconfig.node.json --noEmit, the Bun entrypoint smoke
from #184, and deno task gen:publish-workflow with no generated drift.

Scope

Included

  • packages/test-support/launch.ts and its three mappings.
  • Routing all ten suites. Two needed more than a substitution:
    worker-lifecycle passed its whole command line as one interpolated string
    and now uses command + arguments; XC4 claimed to test "the Deno
    entrypoint" and now says whichever runtime is running.

Intentionally unchanged

  • test:node and test:bun still enumerate a file list excluding all ten.
    A green CI job here proves nothing about them, which is why they are run
    explicitly. Deriving the scope is Derive test:node and test:bun scope instead of naming test files #144.
  • bdd.ts and expect.ts are untouched.
  • cli-journal and testing/cli still pass repo-relative fixture paths with no
    cwd, so they need the repo root as working directory. Both CI jobs run
    there.

Risks and limitations

  • The Node branch depends on tsx being on PATH and on every suite's
    environment preserving PATH. Both hold today — the Node job runs through
    pnpm exec — but a suite that stopped passing PATH would break launching
    rather than fail an assertion.
  • Timeouts remain tuned to Deno's startup. All ten pass under Node and Bun now;
    if CI machines are slower than this one, agent-cli (twelve launches) and
    smoke (120 s) are where that would surface first.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

PR #186: ✅ test: launch the CLI through the host runtime

14 files, +72 / -47

Scope

🟡 PR mixes config and source changes.

Structural

✅ No structural bloat detected.

Slop

✅ Slop indicators look low.

Static Analysis

✅ Oxlint found no issues.

Correctness

No extraneous code patterns detected.

Ten suites shelled out to the CLI with a hardcoded `deno run --allow-all
packages/cli/src/deno.ts`. That pinned them to one runtime and hid whether the
CLI works under the others — the npm package runs under Node, and the published
bin is what users get.

`@executablemd/test-support/launch` builds the command for whichever runtime is
running the tests, using the entrypoint that runtime installs its `command` and
`compile` providers from. It returns a command, never a process: seven suites
spawn it and each consumes the process differently — `.expect()`, `.join()`,
manual stdout streaming under four timeboxes, and one long-lived JSON-RPC peer
— while three never spawn it at all, handing a base array to `API.Env.command`
for the test-agent provider to launch.

Detection belongs here rather than being tolerated: packages/test-support is
the host-adapter boundary for tests, which rule 12 already exempts (#188).

Node starts a fresh tsx process rather than reusing the running one, so a CLI
subprocess does not inherit `--test` or the runner's loaders, and naming the
tsconfig on the command line means no suite has to alter its environment.
Suite-specific environments, timeouts and process handling are untouched:
agent-cli isolates HOME, props-cli allowlists so an inherited XMD_PROPS_* cannot
corrupt its precedence assertions, and the rest inherit deliberately.

worker-lifecycle passed its whole command line as one interpolated string and
now uses command + arguments form. XC4 asserted "the Deno entrypoint relaunches
a worker"; it exercises whichever runtime is running, and says so.

This makes the ten suites runtime-portable. Adding them to the derived Node and
Bun scope is #144. Ran explicitly: 73 tests, 0 failures under Deno, Node and Bun.
@taras
taras force-pushed the test/runtime-launcher branch from 46d5614 to 839ca3a Compare July 28, 2026 14:14
The module JSDoc on launch.ts restated what the package name, the exported
functions, and AGENTS.md rule 12 already say. The two comments that carry
rationale a reader cannot infer stay: why paths come from import.meta.url, and
why Node starts a fresh tsx process.

command.test.ts said it inherited the environment "so the child resolves the
same Deno install", which stopped being the reason when the suite started
launching whichever runtime runs it. What the inheritance is actually for is
PATH, so the Node launcher can find tsx.
@taras
taras merged commit c42e17c into main Jul 28, 2026
9 checks passed
@taras
taras deleted the test/runtime-launcher branch July 28, 2026 14:29
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