Skip to content

feat(core): stabilize programmatic JS API with createRstest + /api surface#1372

Open
fi3ework wants to merge 1 commit into
mainfrom
feat/stabilize-programmatic-js-api
Open

feat(core): stabilize programmatic JS API with createRstest + /api surface#1372
fi3ework wants to merge 1 commit into
mainfrom
feat/stabilize-programmatic-js-api

Conversation

@fi3ework

@fi3ework fi3ework commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

Ahead of @rstest/core 1.0, the programmatic Node API needed a stable, predictable shape. Previously a single standalone runRstest, plus host/CLI orchestration symbols (createRstest, initCli, runCLI), leaked from the main @rstest/core entry — mixing in-test runtime globals with programmatic runners. This PR stabilizes that surface (relates to #1294).

Breaking — public API:

  • All programmatic entrypoints move to @rstest/core/api. The main @rstest/core entry now exposes only in-test runtime globals, config helpers (defineConfig/loadConfig/merge*), and types. No deprecated re-export shims.
  • runRstest is replaced by an async createRstest() factory (modeled on rsbuild's createRsbuild) returning an RstestInstance with run / 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 in RunOptions.
  • A single jest-compatible runCli({ argv?, cwd? }) replaces the internal runCLI and returns a structured TestRunResult for test-running commands. The rstest bin now calls it.

Migration: import programmatic APIs from @rstest/core/api; replace runRstest(opts) with const r = await createRstest(opts); await r.run().

Implementation notes: shared public result types + assembly helpers are extracted to src/api/result.ts (consumed by both instance.run() and runCli); the internal sync factory createRstest is renamed createRstestContext and the internal type RstestInstanceRstestRunner; the async createRstest re-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

  • Tests updated (or not required).
  • Documentation updated (or not required).

@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: 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".

Comment thread packages/vscode/src/worker/index.ts Outdated
fileFilters: _fileFilters,
rstestPath,
command = 'run',
command: _command = 'run',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +282 to 284
// Match the CLI's environment setup so workers (spawned per run) observe
// `NODE_ENV=test` / `RSTEST=true`.
initRstestEnv();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Rsdoctor Bundle Diff Analysis

Found 13 projects in monorepo, 2 projects with changes.

📊 Quick Summary
Project Total Size Change
adapter-rsbuild 4.1 KB 0
adapter-rslib 24.7 KB 0
adapter-rspack 8.0 KB 0
browser 2.0 MB 0
browser-react 3.7 KB 0
browser-ui 809.6 KB 0
coverage-istanbul 14.6 KB 0
coverage-v8 16.2 KB 0
core/browser 976.5 KB 0
core/loaders 869.0 B 0
core/main 1.7 MB +1.8 KB (0.1%)
vscode/extension 27.0 MB 0
vscode/worker 14.4 KB -103.0 B (-0.7%)
📋 Detailed Reports (Click to expand)

📁 core/main

Path: packages/core/.rsdoctor/main/rsdoctor-data.json

📌 Baseline Commit: 98deedc877 | PR: #1386

Metric Current Baseline Change
📊 Total Size 1.7 MB 1.7 MB +1.8 KB (0.1%)
📄 JavaScript 1.6 MB 1.6 MB +1.8 KB (0.1%)
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 81.6 KB 81.6 KB 0

📦 Download Diff Report: core/main Bundle Diff

📁 vscode/worker

Path: packages/vscode/.rsdoctor/worker/rsdoctor-data.json

📌 Baseline Commit: 98deedc877 | PR: #1386

Metric Current Baseline Change
📊 Total Size 14.4 KB 14.5 KB -103.0 B (-0.7%)
📄 JavaScript 14.4 KB 14.5 KB -103.0 B (-0.7%)
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 0 B 0 B 0

📦 Download Diff Report: vscode/worker Bundle Diff

Generated by Rsdoctor GitHub Action

@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: 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".

Comment on lines 11 to 12
export { loadConfig, mergeProjectConfig, mergeRstestConfig } from './config';
export { createRstest } from './core';
export * from './runtime/api/public';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +400 to +401
const runner = await build('list', listOptions);
return runner.listTests({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@fi3ework fi3ework force-pushed the feat/stabilize-programmatic-js-api branch from 9994853 to ea98fcd Compare June 8, 2026 08:12

@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: 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".

Comment thread packages/core/src/api/index.ts Outdated
Comment on lines +238 to +240
const content = produced.extends
? await resolveExtends(produced)
: produced;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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()`.
@fi3ework fi3ework force-pushed the feat/stabilize-programmatic-js-api branch from ea98fcd to 83916c1 Compare June 8, 2026 11:25

@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

const { resolveRelatedTestFiles } = await import('../core/related');
const rstest = createRstest({ config, configFilePath, projects }, 'list', []);

P2 Badge Preserve embedded mode while resolving related filters

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

@fi3ework fi3ework mentioned this pull request Jun 9, 2026
36 tasks
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