Skip to content

♻️ refactor: own cross-runtime test infrastructure as @executablemd/test-support - #188

Merged
taras merged 2 commits into
mainfrom
refactor/test-support-identity
Jul 28, 2026
Merged

♻️ refactor: own cross-runtime test infrastructure as @executablemd/test-support#188
taras merged 2 commits into
mainfrom
refactor/test-support-identity

Conversation

@taras

@taras taras commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Why

packages/test-support was still published to the workspace as
@effectionx/bdd, but it has not been that package for a while. Upstream
@effectionx/bdd covers Node and Deno, and owns neither assertions nor process
launching. The local package drives Deno, Node, and Bun, ships a
cross-runtime expect, and is about to own the executable.md CLI launcher
arriving in #186. The borrowed name misrepresented what the code is and where
it comes from.

This separates repository-owned cross-runtime test infrastructure from the
upstream EffectionX BDD package, and gives the launcher in #186 a correctly
named home to land in. #186 is paused on this merging.

What changes

Before: tests imported @effectionx/bdd/node and @effectionx/bdd/expect,
resolving to a private local package that claimed the upstream identity and
declared only expect as a dependency.

After: tests import @executablemd/test-support/bdd and
@executablemd/test-support/expect. The package declares its real direct
dependencies — @effectionx/test-adapter, effection, and expect — at the
versions the root workspace already pins.

@effectionx/test-adapter is unchanged and still supplies the Effection scope
lifecycle that bdd.ts builds on. Only the BDD package identity is dropped.

How it works

Each runtime resolves the two subpaths by a different mechanism, so all three
had to move together:

Deno  → deno.json imports map        → ./packages/test-support/{bdd,expect}.ts
Node  → tsconfig.node.json paths     → ./packages/test-support/{bdd,expect}.ts
Bun   → workspace:* dep in node_modules → package.json "exports" {"./bdd","./expect"}

Bun resolves the package as a real workspace:* dependency and reads the
subpaths from the package's own exports map — bunfig.toml's
tsconfig-override does not provide module resolution.

What must stay true

  • The package stays out of the lockstep release. It is private: true and
    carries no deno.json; scripts/build-npm.ts, the Generate block of
    scripts/gen-publish-workflow.md, and scripts/lib/bump-version.ts all skip
    workspace dirs without one. Verified: deno task gen:publish-workflow
    regenerates publish-packages.yml byte-identically (still 8 packages), and
    deno task check:jsr succeeds.
  • Test semantics are untouched. bdd.ts and expect.ts have no changes —
    no edits to BDD lifecycle, runtime detection, assertions, sanitizers, or the
    public test API. Verified by the full Deno repository suite passing, and by
    the currently configured Node and Bun suites passing.

How to verify it

All checks run against the CI-pinned toolchains (Deno 2.9.1, Bun 1.3.14):

  • deno task lint — 0 errors
  • deno task check — 0 errors
  • deno task test145 passed, 0 failed
  • deno task check:jsrSuccess Dry run complete
  • pnpm install --frozen-lockfile — clean
  • pnpm exec tsc --project tsconfig.node.json --noEmit — 0 errors
  • pnpm test:node305 passed, 0 failed
  • bun install — reports no changes; bun run test:bun272 passed, 0 failed
  • deno task gen:publish-workflow — no drift

deno task test is the full repository suite. test:node and test:bun name
their test files explicitly, so they run a subset — deriving that scope is #144
and is not attempted here. What each proves is its own resolution path: a
missed deno.json, tsconfig.node.json, or exports entry fails that
runtime's tests with an unresolved-module error and nothing else. Every
migrated import is type-checked repository-wide by deno task check and
tsc --project tsconfig.node.json --noEmit.

git grep "@effectionx/bdd" returns nothing repository-wide.

Scope

Included

  • Package rename, accurate dependency declarations, and ./bdd / ./expect
    export keys.
  • 212 import lines across 106 files, including the lint-rule fixtures in
    scripts/tests/fixtures/ (excluded from lint and typecheck, but linted by the
    rule tests through the repository config).
  • Resolution config: deno.json, tsconfig.node.json, root devDependency.
  • Lockfiles: pnpm-lock.yaml, deno.lock, bun.lock.
  • AGENTS.md rule 12 — records packages/test-support as the deliberate
    host-adapter boundary for tests, exempt from the no-runtime-detection rule,
    and notes that the Oxlint rule tracked by Route production host access through Env #156 carries the same path
    exemption. The module detects the runtime on purpose; that is how one BDD
    surface drives @std/testing/bdd, node:test, and bun:test.
  • specs/executable-mdx-spec.md — the spec described bdd.ts as a
    "Deno-native BDD shim", which is wrong for a module driving three runtimes.
    Now "Cross-runtime Effection BDD adapter". Contract maintenance the rename
    forces.
  • packages/durable-streams/specs/DECISIONS.md — DEC-004 named the old package.

Intentionally unchanged

  • bdd.ts and expect.ts implementations.
  • @effectionx/test-adapter — still an upstream dependency, unchanged.
  • No launch.ts. ✅ test: launch the CLI through the host runtime #186 adds it after rebasing onto this.
  • No root tsconfig.json — tested previously and it breaks deno check.

Generated or mechanical changes

  • The 106 test and fixture files contain one mechanical substitution each and no
    intended behavior change. Reviewers can skim them.
  • pnpm-lock.yaml, deno.lock, and bun.lock come from their own package
    managers. Each diff is confined to the rename and the newly declared
    dependencies — no unrelated re-resolution. bun.lock was generated with the
    CI-pinned Bun 1.3.14 rather than the local 1.3.10 so it converges with CI.

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.

packages/test-support kept the upstream @effectionx/bdd name long after it
stopped being that package. The local code drives Deno, Node, and Bun, ships
a cross-runtime expect, and is about to own the executable.md CLI launcher —
none of which upstream does.

Rename the package, declare its real dependencies, and migrate every import.
@effectionx/test-adapter stays; it still supplies the Effection scope
lifecycle. Test behavior is unchanged.
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

PR #188: ♻️ refactor: own cross-runtime test infrastructure as @executablemd/test-support

119 files, +250 / -234

Scope

🟡 484 lines changed. PRs under 400 receive more thorough review.

🟡 119 files changed. Are all changes related?

🟡 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.

DEC-004's update note named Node as the test runner. Tests run under Deno,
Node, and Bun, with @executablemd/test-support binding each runner to one
test surface.
@taras
taras merged commit d2d2a08 into main Jul 28, 2026
9 checks passed
taras added a commit that referenced this pull request Jul 28, 2026
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 added a commit that referenced this pull request Jul 28, 2026
test-bun failed while test-node and test-deno passed, on one root cause: the
Bun job runs `bun install` alone, and that links only the workspace packages
the root manifest depends on. `@executablemd/test-support` was the sole entry,
added by #188 for exactly this reason, so nothing else appeared under
node_modules/@executablemd.

Every failure followed from it. Eval blocks compile to `.xmd-eval/<uuid>.ts`
beside node_modules and could not resolve `@executablemd/core`, which is why
the provider, sample and agent suites reported ERROR in their rendered output
rather than failing an assertion outright.

It passed locally because pnpm links all nine, so the pnpm and Bun layouts
disagreed and only CI ran the Bun one. Declaring the packages at the root makes
`bun install` produce the same layout: verified by moving node_modules aside,
running `bun install` alone, and getting all nine links and 1131 passing tests
where the same layout previously failed 45.

The old hand-picked `test:bun` list never reached these suites, so the gap
existed before this branch and only became visible once the scope was derived.
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