Vite plugin + headless: simplified (#2) #23

Merged
sufian merged 9 commits from wip/issue-02-simplify into main 2026-10-13 13:58:51 +00:00
Member

Supersedes #14. This branch contains #14's change plus the simplification pass, so it is the one to land in main. #14 is closed in its favour.

What it does

Adds @srhazi/gooey-test/vite and declares vite an optional peer (>=4), so hosted + headless modes "just work": the plugin serves the runner at a stable URL (/@gooey-test/) and owns test discovery (no hand-wired #/... URL), and headless drives the consumer's Vite.

Simplified relative to #14

  • Headless drives the consumer's Vite programmatically (loadConfigFromFile + createServer) instead of writing a throwaway merged config to disk and spawning the CLI. Removes the mkdtemp/mergeConfig templating, the child-process lifecycle, and the read-only/PnP probes.
  • The plugin serves by URL rewrite, delegating to Vite's HTML pipeline (removes the hand-rendered runner HTML, the MIME map, the traversal guard, the sibling-asset server, and the double transformIndexHtml).
  • One discovery source (src/discovery.ts), imported by both consumers.
  • Removed: virtual:gooey-test/manifest, the entry option, the no-plugin #/... fallback, --vite-arg / --vite-startup-timeout, and forced ALWAYS_EXCLUDE (a supplied exclude is now authoritative).
  • vite peer widened to >=4, and the smoke now logs + asserts the resolved Vite.

Verification

  • tsc clean on all three configs; self-test PASS: 11.
  • Dockerized ./s/smoke: 72 passed / 0 failed against Vite 8.3.4 (unpinned).

dist/ is not committed — it is built at release.

Closes #2.

**Supersedes #14.** This branch contains #14's change *plus* the simplification pass, so it is the one to land in `main`. #14 is closed in its favour. ## What it does Adds `@srhazi/gooey-test/vite` and declares `vite` an **optional peer** (`>=4`), so hosted + headless modes "just work": the plugin serves the runner at a stable URL (`/@gooey-test/`) and owns test discovery (no hand-wired `#/...` URL), and headless drives the consumer's Vite. ## Simplified relative to #14 - **Headless drives the consumer's Vite programmatically** (`loadConfigFromFile` + `createServer`) instead of writing a throwaway merged config to disk and spawning the CLI. Removes the mkdtemp/`mergeConfig` templating, the child-process lifecycle, and the read-only/PnP probes. - **The plugin serves by URL rewrite**, delegating to Vite's HTML pipeline (removes the hand-rendered runner HTML, the MIME map, the traversal guard, the sibling-asset server, and the double `transformIndexHtml`). - **One discovery source** (`src/discovery.ts`), imported by both consumers. - **Removed:** `virtual:gooey-test/manifest`, the `entry` option, the no-plugin `#/...` fallback, `--vite-arg` / `--vite-startup-timeout`, and forced `ALWAYS_EXCLUDE` (a supplied `exclude` is now authoritative). - **`vite` peer widened to `>=4`**, and the smoke now logs + asserts the resolved Vite. ## Verification - `tsc` clean on all three configs; self-test `PASS: 11`. - Dockerized `./s/smoke`: **72 passed / 0 failed** against **Vite 8.3.4** (unpinned). `dist/` is not committed — it is built at release. Closes #2.
Reduce the PR to the smallest structure that keeps its goals (vite stays an
optional peer; hosted + headless "just work" without hand-wiring).

Headless: drive the consumer's Vite with its programmatic `createServer`
instead of writing a throwaway merged config to disk and spawning the Vite CLI.
This deletes the mkdtemp/text-template machinery, the plugin specifier and
on-disk config lifecycle, the subprocess URL-scraping (ANSI stripping, startup
timeout) and the process-group teardown. The consumer config is loaded once,
any gooey-test plugin they registered is replaced by one carrying the exact
files to run, and the server is closed on every exit path. Drops the now-unused
`viteArgs` / `viteStartupTimeoutMs` options and the no-plugin `#/...` fallback.

Plugin: serve the built runner by rewriting `/@gooey-test/*` to its real files
and letting Vite's own HTML pipeline handle them, so the client/`define`
injection for the runner and the sandbox iframe comes for free. Deletes the
hand-rendered runner HTML, content-type map, path-traversal guard and sibling
asset server. Drops the unused `virtual:gooey-test/manifest` module and the
`entry` option (YAGNI).

Discovery: one shared `src/discovery.ts` for the defaults, node_modules
pruning and glob scan, used by both the plugin and the headless runner.

smoke: assert the in-process server answers (no `vite` child to pgrep) and
that the removed flags are gone.
src/discovery.ts Outdated
@ -0,0 +11,4 @@
export const DEFAULT_EXCLUDE = '**/node_modules/**';
/** Glob(s) ignored even when the caller widens `exclude`. */
export const ALWAYS_EXCLUDE = ['**/node_modules/**', '**/.git/**'];
Owner

Let's drop ALWAYS_EXCLUDE, the user should not be prevented from making bad decisions if they choose to

Let's drop ALWAYS_EXCLUDE, the user should not be prevented from making bad decisions if they choose to
@ -665,2 +339,2 @@
])
);
const include = asArray(options.include) ?? [DEFAULT_INCLUDE];
const exclude = withAlwaysExcluded(options.exclude);
Owner

Which I think means this becomes const exclude = asArray(options.exclude) ?? [DEFAULT_EXCLUDE];

Which I think means this becomes `const exclude = asArray(options.exclude) ?? [DEFAULT_EXCLUDE];`
@ -108,4 +61,3 @@
existsSync(join(dir, 'entry', 'testrunner.js'));
const candidates = [
join(root, 'node_modules', PACKAGE_NAME, 'dist', 'testrunner'),
join(root, 'dist', 'testrunner'),
Owner

Question: is this dist/testrunner location used only for local tests within this repo?

Question: is this `dist/testrunner` location used only for local tests within this repo?
Remove ALWAYS_EXCLUDE and withAlwaysExcluded. The exclude set is now exactly
`asArray(options.exclude) ?? [DEFAULT_EXCLUDE]` in both the headless runner
and the Vite plugin: a caller who widens `exclude` gets exactly the globs
they asked for, with no forced additions. The default is unchanged
(`**/node_modules/**`).
Author
Member

Pushed 1375dfe on wip/issue-02-simplify.

#213 src/discovery.ts — "Let's drop ALWAYS_EXCLUDE": Done end to end. ALWAYS_EXCLUDE and withAlwaysExcluded are gone; src/discovery.ts now exports only the defaults, toPosix, asArray and discoverTestFiles. A caller who widens exclude now gets exactly that set — no forced node_modules/.git additions. Corrected the one option doc that claimed .git is always ignored (README/CLI already documented only the **/node_modules/** default). No smoke assertion relied on the forced additions: --exclude '**/hidden/**' and the injected-node_modules prune both still pass, via the default exclude.

#215 src/headless/index.ts — "becomes const exclude = asArray(options.exclude) ?? [DEFAULT_EXCLUDE];": Yes, exactly. Both the headless runner and the Vite plugin now use that expression, so the two stay one source of truth. Default behaviour is unchanged (**/node_modules/**); the only difference is a supplied exclude is no longer overridden.

#217 src/vite/index.ts — "is this dist/testrunner location used only for local tests within this repo?": Yes. In locateRunnerDir, <root>/node_modules/@srhazi/gooey-test/dist/testrunner is how a real consumer resolves their installed copy (checked first, so it wins when present). The second candidate, <root>/dist/testrunner, only matches this repo's self-test — run from the repo root, where the package is not in its own node_modules (e.g. node dist/headless/cli.mjs src/happypath.test.ts). It is not dead weight: removing it would break the self-test, so I left it in place.

Verification: tsc --noEmit clean on all three configs; self-test PASS: 11; ./s/smoke 69 passed / 0 failed once the known Vite-8 peer conflict is worked around in the harness (the unmodified smoke still aborts in npm install, as flagged in the PR body — untouched). dist/ was rebuilt locally for the run but is not committed.

Pushed `1375dfe` on `wip/issue-02-simplify`. **#213 `src/discovery.ts` — "Let's drop ALWAYS_EXCLUDE":** Done end to end. `ALWAYS_EXCLUDE` and `withAlwaysExcluded` are gone; `src/discovery.ts` now exports only the defaults, `toPosix`, `asArray` and `discoverTestFiles`. A caller who widens `exclude` now gets exactly that set — no forced `node_modules`/`.git` additions. Corrected the one option doc that claimed `.git` is always ignored (README/CLI already documented only the `**/node_modules/**` default). No smoke assertion relied on the forced additions: `--exclude '**/hidden/**'` and the injected-`node_modules` prune both still pass, via the *default* exclude. **#215 `src/headless/index.ts` — "becomes `const exclude = asArray(options.exclude) ?? [DEFAULT_EXCLUDE];`":** Yes, exactly. Both the headless runner and the Vite plugin now use that expression, so the two stay one source of truth. Default behaviour is unchanged (`**/node_modules/**`); the only difference is a supplied `exclude` is no longer overridden. **#217 `src/vite/index.ts` — "is this `dist/testrunner` location used only for local tests within this repo?":** Yes. In `locateRunnerDir`, `<root>/node_modules/@srhazi/gooey-test/dist/testrunner` is how a real consumer resolves their installed copy (checked first, so it wins when present). The second candidate, `<root>/dist/testrunner`, only matches this repo's *self-test* — run from the repo root, where the package is not in its own `node_modules` (e.g. `node dist/headless/cli.mjs src/happypath.test.ts`). It is not dead weight: removing it would break the self-test, so I left it in place. Verification: `tsc --noEmit` clean on all three configs; self-test `PASS: 11`; `./s/smoke` **69 passed / 0 failed** once the known Vite-8 peer conflict is worked around in the harness (the unmodified smoke still aborts in `npm install`, as flagged in the PR body — untouched). `dist/` was rebuilt locally for the run but is not committed.
Author
Member

Pushed 8563cf7 on wip/issue-02-simplify.

Peer range widened. peerDependencies.vite is now >=4.0.0 (was ^4.0.0 || ^5.0.0 || ^6.0.0); still optional via peerDependenciesMeta. devDependencies.vite is untouched at ^4.0.0.

README made accurate. Setup requirement now reads `vite` (`>=4`) and the optional-peer note ends "Tested on Vite 4 and 8." (Vite 4 from the repo's own devDependency / self-test, Vite 8 from the unpinned smoke.)

Smoke against Vite 8, unpinned — 69 passed / 0 failed. No harness pin: the consumer's npm install --save-dev @playwright/test vite resolved to Vite 8.3.4 (confirmed: an equivalent fresh install of just those two deps pins vite@^8.3.4). dist/ rebuit locally, then ./s/smoke run end-to-end; every check passed, including setup across npm/yarn/pnpm, default discovery, --include/--exclude, node_modules prune, the vite define regression guard, hosted runner, failure artifacts, --debug-failures, --remote-debugging-port, and the option surface.

No Vite-8-specific fixes were needed — the integration ran unchanged. The only change required to make a fresh install resolve was the peer range itself.

Also confirmed, out of the harness: the base: '/app/' consumer flow (gooeyTest() plugin + base in the config) runs green against Vite 8.3.4 (PASS: 1).

Other checks: tsc clean on all three configs (., tsconfig.headless.json, tsconfig.vite.json); self-test node dist/headless/cli.mjs src/happypath.test.ts → PASS: 11 (against the repo's Vite 4.5.14).

This resolves the PR's question 1; no dist/ committed.

Pushed `8563cf7` on `wip/issue-02-simplify`. **Peer range widened.** `peerDependencies.vite` is now `>=4.0.0` (was `^4.0.0 || ^5.0.0 || ^6.0.0`); still optional via `peerDependenciesMeta`. `devDependencies.vite` is untouched at `^4.0.0`. **README made accurate.** Setup requirement now reads `` `vite` (`>=4`) `` and the optional-peer note ends "Tested on Vite 4 and 8." (Vite 4 from the repo's own devDependency / self-test, Vite 8 from the unpinned smoke.) **Smoke against Vite 8, unpinned — 69 passed / 0 failed.** No harness pin: the consumer's `npm install --save-dev @playwright/test vite` resolved to **Vite 8.3.4** (confirmed: an equivalent fresh install of just those two deps pins `vite@^8.3.4`). `dist/` rebuit locally, then `./s/smoke` run end-to-end; every check passed, including setup across npm/yarn/pnpm, default discovery, `--include`/`--exclude`, node_modules prune, the `vite define` regression guard, hosted runner, failure artifacts, `--debug-failures`, `--remote-debugging-port`, and the option surface. **No Vite-8-specific fixes were needed** — the integration ran unchanged. The only change required to make a fresh install resolve was the peer range itself. Also confirmed, out of the harness: the `base: '/app/'` consumer flow (`gooeyTest()` plugin + `base` in the config) runs green against Vite 8.3.4 (`PASS: 1`). Other checks: `tsc` clean on all three configs (`.`, `tsconfig.headless.json`, `tsconfig.vite.json`); self-test `node dist/headless/cli.mjs src/happypath.test.ts` → `PASS: 11` (against the repo's Vite 4.5.14). This resolves the PR's question 1; no `dist/` committed.
Record the version of vite actually resolved in each consumer tree
(npm/yarn/pnpm) and fail loudly if a tree falls outside the >=4 peer
range, instead of testing whatever `latest` resolves to that day
silently. Small: one loop over the existing tree vars using the
existing pass/fail helpers.
Author
Member

Pushed 0aa2fcb on wip/issue-02-simplify.

Smoke now records and guards the resolved Vite. Added a resolved vite section to smoke/smoke.sh, right after the dependency-setup block and before the README flows. For each consumer tree (npm/yarn/pnpm) it reads the installed version from require('<tree>/node_modules/vite/package.json').version, logs it, and asserts the major is >=4 — matching the >=4.0.0 peer range. A tree outside the range now fails loudly with the observed version instead of passing silently against whatever latest resolved to that day.

Small and in the smoke's existing style: one loop over the existing $NPM/$YARN/$PNPM vars, reusing the existing say/pass/fail helpers; 16 lines, no new machinery.

=== resolved vite ===
vite (npm): 8.3.4
PASS: npm vite 8.3.4 satisfies the peer range (>=4)
vite (yarn): 8.3.4
PASS: yarn vite 8.3.4 satisfies the peer range (>=4)
vite (pnpm): 8.3.4
PASS: pnpm vite 8.3.4 satisfies the peer range (>=4)

./s/smoke: 72 passed / 0 failed (up from 69 — the three new checks). All three trees resolved Vite 8.3.4. dist/ was rebuilt locally for the run but is not committed.

Pushed `0aa2fcb` on `wip/issue-02-simplify`. **Smoke now records and guards the resolved Vite.** Added a `resolved vite` section to `smoke/smoke.sh`, right after the dependency-setup block and before the README flows. For each consumer tree (npm/yarn/pnpm) it reads the installed version from `require('<tree>/node_modules/vite/package.json').version`, logs it, and asserts the major is `>=4` — matching the `>=4.0.0` peer range. A tree outside the range now `fail`s loudly with the observed version instead of passing silently against whatever `latest` resolved to that day. Small and in the smoke's existing style: one loop over the existing `$NPM`/`$YARN`/`$PNPM` vars, reusing the existing `say`/`pass`/`fail` helpers; 16 lines, no new machinery. ``` === resolved vite === vite (npm): 8.3.4 PASS: npm vite 8.3.4 satisfies the peer range (>=4) vite (yarn): 8.3.4 PASS: yarn vite 8.3.4 satisfies the peer range (>=4) vite (pnpm): 8.3.4 PASS: pnpm vite 8.3.4 satisfies the peer range (>=4) ``` **`./s/smoke`: 72 passed / 0 failed** (up from 69 — the three new checks). All three trees resolved **Vite 8.3.4**. `dist/` was rebuilt locally for the run but is not committed.
simplify changed target branch from wip/issue-02 to main 2026-10-13 13:53:59 +00:00
simplify changed title from Simplify the Vite plugin + headless runner (stacked on #14) to Vite plugin + headless: simplified (#2) 2026-10-13 13:54:14 +00:00
sufian merged commit c39428b68f into main 2026-10-13 13:58:51 +00:00
sufian deleted branch wip/issue-02-simplify 2026-10-13 13:58:51 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
legend/gooey-test!23
No description provided.