feat(core): stabilize programmatic JS API with createRstest + /api surface#1372
feat(core): stabilize programmatic JS API with createRstest + /api surface#1372fi3ework wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4086a9295
ℹ️ 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".
| fileFilters: _fileFilters, | ||
| rstestPath, | ||
| command = 'run', | ||
| command: _command = 'run', |
There was a problem hiding this comment.
Preserve the watch command for continuous runs
When the VS Code extension starts a continuous run it sends command: continuous ? 'watch' : 'run' from packages/vscode/src/master.ts, but this destructuring drops that value and runTest() then always calls rstest.run(...), which builds a normal run-mode context. In the continuous profile this means the worker performs one run and never enters Rstest watch mode, so file changes are no longer rerun.
Useful? React with 👍 / 👎.
| // Match the CLI's environment setup so workers (spawned per run) observe | ||
| // `NODE_ENV=test` / `RSTEST=true`. | ||
| initRstestEnv(); |
There was a problem hiding this comment.
Restore the host environment after initialization
In embedded programmatic use, calling createRstest() now mutates process.env immediately via initRstestEnv(), before any snapshot/restore is taken; run() snapshots after this mutation and close() is a no-op. For hosts that did not already set NODE_ENV or RSTEST, simply creating and closing an instance leaves the process permanently in test mode, whereas the previous runRstest() path snapshotted the environment before initializing it.
Useful? React with 👍 / 👎.
| const listTests = async ( | ||
| listOptions: ListCommandOptions & RunOptions = {}, | ||
| ): Promise<ListCommandResult[]> => { | ||
| const runner = await build('list', listOptions); |
There was a problem hiding this comment.
Enable location collection before listing
When callers use the programmatic listTests({ printLocation: true }) path, the runner is built before printLocation is considered, so normalizedConfig.includeTaskLocation remains false and the runtime skips collecting locations. The CLI path explicitly sets config.includeTaskLocation = true before constructing the runner; without the same wiring here, printLocation is ineffective unless users also know to set inlineConfig.includeTaskLocation manually.
Useful? React with 👍 / 👎.
Rsdoctor Bundle Diff AnalysisFound 13 projects in monorepo, 2 projects with changes. 📊 Quick Summary
📋 Detailed Reports (Click to expand)📁 core/mainPath:
📦 Download Diff Report: core/main Bundle Diff 📁 vscode/workerPath:
📦 Download Diff Report: vscode/worker Bundle Diff Generated by Rsdoctor GitHub Action |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9cff4afde
ℹ️ 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".
| export { loadConfig, mergeProjectConfig, mergeRstestConfig } from './config'; | ||
| export { createRstest } from './core'; | ||
| export * from './runtime/api/public'; |
There was a problem hiding this comment.
Restore the benchmark-facing core exports
This removes initCli and the internal createRstest from the package root without updating the benchmark scripts that still do const { initCli, createRstest } = await import('@rstest/core') in benchmarks/suiteRun.mjs and benchmarks/memorySuiteRun.mjs. Those destructured values become undefined after this change, so the CodSpeed benchmark commands fail before they can run any fixture; either keep these exports or migrate the benchmarks to the new entrypoint/internal factory.
Useful? React with 👍 / 👎.
| const runner = await build('list', listOptions); | ||
| return runner.listTests({ |
There was a problem hiding this comment.
Contain process state during programmatic listing
Unlike run(), this programmatic listTests() path calls the core lister without snapshotProcessGuards(). When listing hits collection/parse errors, packages/core/src/core/listTests.ts sets process.exitCode = 1, and this method returns without restoring it, so an embedded host can be left marked as failed just for calling createRstest().listTests() on a bad test file.
Useful? React with 👍 / 👎.
9994853 to
ea98fcd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea98fcd645
ℹ️ 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".
| const content = produced.extends | ||
| ? await resolveExtends(produced) | ||
| : produced; |
There was a problem hiding this comment.
Avoid resolving disk extends twice
When a disk config uses extends and the new function-form config returns or spreads the loadedConfig (the documented pattern in the added fixture), this branch resolves extends a second time: loadConfig() has already called resolveExtends() for the disk config (packages/core/src/config.ts:98), but the resolved object still carries the original extends field. That can re-run extend callbacks and duplicate/merge array-like settings such as setup files or plugins before the runner is built, so callbacks should not re-resolve the inherited disk extends unless the callback actually introduced a new one.
Useful? React with 👍 / 👎.
…rface
Move all programmatic entrypoints to `@rstest/core/api`; the main
`@rstest/core` entry now exposes only in-test runtime globals, config
helpers (defineConfig/loadConfig/merge*), and types.
- Replace the standalone `runRstest` with an async `createRstest()`
factory (rsbuild-`createRsbuild`-style) returning an `RstestInstance`
with `run`/`listTests`/`mergeReports`/`close`. Construction carries
static host wiring + config: `configFile` (a path to load) and `config`
(an inline override). `config` accepts either an object (deep-merged
over the disk config) or a `(loadedConfig) => RstestConfig` callback
that receives the resolved disk config and returns the final config —
letting callers transform it directly instead of merging.
Per-invocation selection/control lives in `RunOptions`
(filters/related/changed/shard/bail/...).
- Add a jest-`runCLI`-aligned public `runCli(argv, { cwd })` that takes a
parsed-object argv (positionals in `argv._`), stays host-safe (never
calls `process.exit`), and returns a structured `TestRunResult`.
- Keep the raw-argv CLI router (`startCli`) off the public surface: build
it as its own internal `dist/cli.js` chunk (not a `package.json`
export) and have the `rstest` bin import that artifact directly,
mirroring how vitest builds `src/node/cli.ts` -> `dist/cli.js`.
- Rename the internal sync factory `createRstest` -> `createRstestContext`
and the internal type `RstestInstance` -> `RstestRunner`; share public
result types + assembly helpers via `src/api/result.ts`, and dedupe the
filter-resolve/context-build tail into `buildResolvedRunner`.
- Migrate the vscode worker and the programmatic e2e fixtures.
BREAKING CHANGE: programmatic APIs are no longer exported from
`@rstest/core` (`createRstest`, `initCli`, `runCLI` removed from the main
entry). Import them from `@rstest/core/api`. `runRstest(opts)` is removed
-- use `const r = await createRstest(opts); await r.run()`.
ea98fcd to
83916c1
Compare
There was a problem hiding this comment.
💡 Codex Review
rstest/packages/core/src/cli/commands.ts
Lines 651 to 652 in 83916c1
For programmatic runCli({ related: true }) / changed runs, this temporary context is built without the caller's embedded: true, so constructor-time config validation still uses CLI behavior and can call process.exit(1). One concrete case is a project-level shard mismatch: Rstest calls failConfig(embedded, ...), and because this pre-resolution context defaults to embedded: false, the host process exits before executeHostSafeRun can return an unhandledError like non-related programmatic runs do.
ℹ️ 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".
| rstestApiUrl | ||
| )) as typeof import('@rstest/core/api'); | ||
| logger.debug('Loaded Rstest API module'); | ||
| const { createRstest } = rstestApi; |
There was a problem hiding this comment.
Bump the VS Code core-version guard
When a workspace has an older but still accepted local @rstest/core (for example any version >= the current MIN_CORE_VERSION 0.6.0 but before this new @rstest/core/api.createRstest export), this destructuring yields undefined and the worker fails as soon as it calls createRstest(...). I checked packages/vscode/src/versionCheck.ts, and the guard only warns below 0.6.0, so users with previously supported cores are not prompted to upgrade before the extension switches to the new API.
Useful? React with 👍 / 👎.
Summary
Ahead of
@rstest/core1.0, the programmatic Node API needed a stable, predictable shape. Previously a single standalonerunRstest, plus host/CLI orchestration symbols (createRstest,initCli,runCLI), leaked from the main@rstest/coreentry — mixing in-test runtime globals with programmatic runners. This PR stabilizes that surface (relates to #1294).Breaking — public API:
@rstest/core/api. The main@rstest/coreentry now exposes only in-test runtime globals, config helpers (defineConfig/loadConfig/merge*), and types. No deprecated re-export shims.runRstestis replaced by an asynccreateRstest()factory (modeled on rsbuild'screateRsbuild) returning anRstestInstancewithrun/listTests/mergeReports/addReporter/close. Construction options carry static config + host wiring (cwd/config/inlineConfig/embedded); per-invocation selection & control (filters/related/changed/shard/bail/ …) live inRunOptions.runCli({ argv?, cwd? })replaces the internalrunCLIand returns a structuredTestRunResultfor test-running commands. Therstestbin now calls it.Migration: import programmatic APIs from
@rstest/core/api; replacerunRstest(opts)withconst r = await createRstest(opts); await r.run().Implementation notes: shared public result types + assembly helpers are extracted to
src/api/result.ts(consumed by bothinstance.run()andrunCli); the internal sync factorycreateRstestis renamedcreateRstestContextand the internal typeRstestInstance→RstestRunner; the asynccreateRstestre-resolves config + projects per build and reuses the CLI's filter-resolution helpers. The vscode worker and the programmatic e2e fixtures are migrated to the new surface.Related Links
Checklist