@warlock.js/core 5.35.0 → 5.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/esm/cli/commands/migrate.command.mjs +10 -0
  3. package/esm/cli/commands/migrate.command.mjs.map +1 -1
  4. package/esm/cli/commands/release/release-command.action.mjs +245 -0
  5. package/esm/cli/commands/release/release-command.action.mjs.map +1 -0
  6. package/esm/cli/commands/release.command.mjs +66 -0
  7. package/esm/cli/commands/release.command.mjs.map +1 -0
  8. package/esm/cli/framework-cli-commands.mjs +2 -0
  9. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  10. package/esm/database/migrate-action.mjs +24 -2
  11. package/esm/database/migrate-action.mjs.map +1 -1
  12. package/esm/dev-server/loader/load-hook.d.mts.map +1 -1
  13. package/esm/dev-server/loader/load-hook.mjs +6 -0
  14. package/esm/dev-server/loader/load-hook.mjs.map +1 -1
  15. package/esm/dev-server/module-loader.mjs +1 -1
  16. package/esm/dev-server/parse-imports.mjs +1 -1
  17. package/esm/dev-server/type-generator.mjs +1 -1
  18. package/esm/e2e/cdp.d.mts.map +1 -1
  19. package/esm/e2e/cdp.mjs +9 -2
  20. package/esm/e2e/cdp.mjs.map +1 -1
  21. package/esm/e2e/page.d.mts +9 -1
  22. package/esm/e2e/page.d.mts.map +1 -1
  23. package/esm/e2e/page.mjs +45 -7
  24. package/esm/e2e/page.mjs.map +1 -1
  25. package/esm/e2e/types.d.mts +18 -0
  26. package/esm/e2e/types.d.mts.map +1 -1
  27. package/esm/e2e/types.mjs.map +1 -1
  28. package/esm/errors/error-codes.d.mts +24 -0
  29. package/esm/errors/error-codes.d.mts.map +1 -1
  30. package/esm/errors/error-codes.mjs +24 -0
  31. package/esm/errors/error-codes.mjs.map +1 -1
  32. package/esm/generations/features/test.feature.mjs +7 -7
  33. package/esm/generations/features/test.feature.mjs.map +1 -1
  34. package/esm/generations/features/web.feature.mjs +3 -3
  35. package/esm/generations/features/web.feature.mjs.map +1 -1
  36. package/esm/index.d.mts +6 -3
  37. package/esm/index.mjs +4 -2
  38. package/esm/localization/index.mjs +3 -1
  39. package/esm/localization/translation-usage-graph.d.mts +49 -0
  40. package/esm/localization/translation-usage-graph.d.mts.map +1 -0
  41. package/esm/localization/translation-usage-graph.mjs +128 -0
  42. package/esm/localization/translation-usage-graph.mjs.map +1 -0
  43. package/esm/localization/translation-usage.d.mts +85 -0
  44. package/esm/localization/translation-usage.d.mts.map +1 -0
  45. package/esm/localization/translation-usage.mjs +307 -0
  46. package/esm/localization/translation-usage.mjs.map +1 -0
  47. package/esm/production/build-budgets.d.mts +18 -0
  48. package/esm/production/build-budgets.d.mts.map +1 -1
  49. package/esm/production/build-budgets.mjs.map +1 -1
  50. package/esm/production/route-registration-snapshot.mjs +12 -3
  51. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  52. package/esm/release/activate.mjs +261 -0
  53. package/esm/release/activate.mjs.map +1 -0
  54. package/esm/release/candidate.mjs +107 -0
  55. package/esm/release/candidate.mjs.map +1 -0
  56. package/esm/release/cascade-migration-provider.mjs +32 -0
  57. package/esm/release/cascade-migration-provider.mjs.map +1 -0
  58. package/esm/release/config.mjs +60 -0
  59. package/esm/release/config.mjs.map +1 -0
  60. package/esm/release/gate.mjs +67 -0
  61. package/esm/release/gate.mjs.map +1 -0
  62. package/esm/release/migrations-proof.mjs +100 -0
  63. package/esm/release/migrations-proof.mjs.map +1 -0
  64. package/esm/release/prune.mjs +42 -0
  65. package/esm/release/prune.mjs.map +1 -0
  66. package/esm/release/report.mjs +25 -0
  67. package/esm/release/report.mjs.map +1 -0
  68. package/esm/release/runner.mjs +55 -0
  69. package/esm/release/runner.mjs.map +1 -0
  70. package/esm/release/semver.mjs +53 -0
  71. package/esm/release/semver.mjs.map +1 -0
  72. package/esm/release/stage.mjs +122 -0
  73. package/esm/release/stage.mjs.map +1 -0
  74. package/esm/release/store.mjs +131 -0
  75. package/esm/release/store.mjs.map +1 -0
  76. package/esm/release/types.d.mts +102 -0
  77. package/esm/release/types.d.mts.map +1 -0
  78. package/esm/release/verify.mjs +94 -0
  79. package/esm/release/verify.mjs.map +1 -0
  80. package/esm/router/index.d.mts +2 -2
  81. package/esm/router/index.mjs +2 -2
  82. package/esm/router/router.d.mts +4 -0
  83. package/esm/router/router.d.mts.map +1 -1
  84. package/esm/router/router.mjs +14 -3
  85. package/esm/router/router.mjs.map +1 -1
  86. package/esm/router/types.d.mts +45 -0
  87. package/esm/router/types.d.mts.map +1 -1
  88. package/esm/router/unguarded-route-diagnostics.d.mts +6 -0
  89. package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -1
  90. package/esm/router/unguarded-route-diagnostics.mjs +10 -2
  91. package/esm/router/unguarded-route-diagnostics.mjs.map +1 -1
  92. package/esm/tooling/check/api-route-references.d.mts +16 -0
  93. package/esm/tooling/check/api-route-references.d.mts.map +1 -0
  94. package/esm/tooling/check/api-route-references.mjs +146 -0
  95. package/esm/tooling/check/api-route-references.mjs.map +1 -0
  96. package/esm/tooling/check/conventions-step.d.mts +6 -3
  97. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  98. package/esm/tooling/check/conventions-step.mjs +19 -10
  99. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  100. package/esm/tooling/check/run-check.d.mts.map +1 -1
  101. package/esm/tooling/check/run-check.mjs +4 -2
  102. package/esm/tooling/check/run-check.mjs.map +1 -1
  103. package/esm/tooling/check/security-conventions.mjs +19 -1
  104. package/esm/tooling/check/security-conventions.mjs.map +1 -1
  105. package/esm/tooling/check/translation-conventions.mjs +32 -0
  106. package/esm/tooling/check/translation-conventions.mjs.map +1 -0
  107. package/esm/tooling/inspect/collect-files.mjs +1 -1
  108. package/esm/tooling/inspect/collect-routes.mjs +3 -2
  109. package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
  110. package/esm/upgrade/codemods/index.mjs +2 -0
  111. package/esm/upgrade/codemods/index.mjs.map +1 -1
  112. package/esm/upgrade/codemods/vite-vitest.mjs +170 -0
  113. package/esm/upgrade/codemods/vite-vitest.mjs.map +1 -0
  114. package/esm/upgrade/notes.mjs +4 -0
  115. package/esm/upgrade/notes.mjs.map +1 -1
  116. package/esm/validation/init.mjs +11 -4
  117. package/esm/validation/init.mjs.map +1 -1
  118. package/esm/validation/translate-rule.mjs +63 -0
  119. package/esm/validation/translate-rule.mjs.map +1 -0
  120. package/esm/warlock-config/index.d.mts +3 -2
  121. package/esm/warlock-config/types.d.mts +8 -0
  122. package/esm/warlock-config/types.d.mts.map +1 -1
  123. package/llms-full.txt +221 -18
  124. package/llms.txt +1 -0
  125. package/package.json +13 -13
  126. package/skills/deploy-release/SKILL.md +76 -0
  127. package/skills/index.md +1 -1
  128. package/skills/register-route/SKILL.md +25 -0
  129. package/skills/test-service/SKILL.md +8 -8
  130. package/skills/upgrade-from-5-29/SKILL.md +91 -0
  131. package/skills/use-localization/SKILL.md +4 -0
  132. package/skills/warlock-check/SKILL.md +11 -8
  133. package/skills/warlock-conventions/SKILL.md +1 -1
package/llms-full.txt CHANGED
@@ -30,7 +30,7 @@ When the framework is wrong or missing a capability, follow the `report-framewor
30
30
  - **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
31
31
  - **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
32
32
  - **Domain layers:** write-use-case, use-repository, write-seeder
33
- - **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
33
+ - **CLI & running the app:** generate-web-ui, run-app, deploy-release, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
34
34
  - **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
35
35
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
36
36
  - **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
@@ -2487,6 +2487,86 @@ export const CommentResource = defineResource({
2487
2487
  - The `create-controller` topic — calling `new XResource(record).toJSON()` from a controller.
2488
2488
 
2489
2489
 
2490
+ ## deploy-release `@warlock.js/core/skills/deploy-release/SKILL.md`
2491
+
2492
+ ---
2493
+ name: deploy-release
2494
+ description: 'Deploy a Warlock application with `warlock release` — freeze a clean commit as a candidate, run the gate, prove migrations, stage the build, switch `current` by atomic symlink, verify the health URL and roll back, configured by the `release` section of `warlock.config.ts`. Triggers: `warlock release`, `release candidate`, `release gate`, `release migrations --prove`, `release stage`, `release activate`, `release verify`, `release rollback`, `release prune`, `ReleaseConfig`, `release.restart.command`, `release.verify`, `.warlock/releases`; "deploy my app", "zero-downtime deploy", "roll back a deploy", "atomic release", "release gate", "prove migrations before deploy". Skip: the `/health` and `/ready` endpoints themselves — the `health-checks` topic; generating a PM2 ecosystem file — `warlock pm2` (the `run-app` topic); publishing the framework packages — `releasing-warlock-monorepo`; competing tools Capistrano, Deployer, hand-rolled symlink scripts.'
2495
+ ---
2496
+
2497
+ # Warlock — deploy with `warlock release`
2498
+
2499
+ A recorded, step-by-step deploy for one host. Each step is a command; each writes into a per-release `manifest.json` and a shared `release.log`.
2500
+
2501
+ ```text
2502
+ candidate -> gate -> migrations --prove -> stage -> activate -> verify
2503
+ \-> rollback
2504
+ ```
2505
+
2506
+ Warlock never calls PM2, systemd or any process manager. The only restart is the command you set in `release.restart.command`.
2507
+
2508
+ ## Configure
2509
+
2510
+ ```ts title="warlock.config.ts"
2511
+ import { defineConfig } from "@warlock.js/core";
2512
+
2513
+ export default defineConfig({
2514
+ release: {
2515
+ gate: {
2516
+ checks: [
2517
+ { name: "typecheck", command: "npx warlock typecheck" },
2518
+ { name: "test", command: "npx warlock test" },
2519
+ { name: "build", command: "NODE_ENV=production npx warlock build" },
2520
+ ],
2521
+ },
2522
+ restart: { command: "pm2 reload my-app" },
2523
+ verify: { url: "http://127.0.0.1:2030/health" },
2524
+ },
2525
+ });
2526
+ ```
2527
+
2528
+ Defaults: `directory` `.warlock`, `distDir` `dist`, gate = typecheck, check, test, build. Add `.warlock` to `.gitignore`.
2529
+
2530
+ ## Run the steps
2531
+
2532
+ ```bash
2533
+ npx warlock release candidate [--commit <sha>] [--allow-dirty]
2534
+ npx warlock release gate
2535
+ npx warlock release migrations --prove --target staging
2536
+ npx warlock release stage
2537
+ npx warlock release activate --preflight
2538
+ npx warlock release activate [--dry-run] [--allow-same]
2539
+ npx warlock release verify || npx warlock release rollback
2540
+ ```
2541
+
2542
+ Every command takes an optional release id (`release gate 1.4.0-a1b2c3d`); without one it uses the newest candidate, gated or staged release. `--json` prints the result as JSON; a failed step exits 1.
2543
+
2544
+ - **candidate** freezes the commit with `git archive` into `.warlock/releases/<version>-<shortsha>/source`. Refuses a dirty tree without `--allow-dirty` and never overwrites a release.
2545
+ - **gate** runs the checks in order in the frozen source and stops at the first failure; results go into the manifest.
2546
+ - **migrations** without `--prove` only lists the pending set. With `--prove` it snapshots the ledger, tables and indexes, runs the pending migrations, snapshots again and compares with the expected set (`release.migrations.expected`, default the pending set; `expectedTables`). The migrations really run: use `--target` (a `release.migrations.targets` entry whose `env` is applied before the database connects) on a database that is safe to migrate. A failed proof blocks activation.
2547
+ - **stage** (needs a passing gate) copies `dist`, `package.json`, the lockfile, `release.include` entries and `release.json` into `staged/`, atomically.
2548
+ - **activate** needs staged artefacts, a passed gate (and proof), and a version strictly greater than the active one (semver; `--allow-same` accepts an equal version, a lower one is refused). It switches `current -> releases/<id>` with an atomic rename, keeps the old target as `previous`, then runs the restart command with `WARLOCK_RELEASE_ID`, `WARLOCK_RELEASE_DIR` and `WARLOCK_RELEASE_CURRENT` set. If that command fails, the pointer stays switched and the exit code is 1.
2549
+ - **verify** GETs `release.verify.url` with retries and a timeout and checks the status plus the `version` and `commit` fields of the JSON body against the active release (`verify.versionPath`, `commitPath` and `match` adjust that). Serve the `release.json` values from your health endpoint.
2550
+ - **rollback** switches `current` back to `previous` (and `previous` to the release just left), reruns the restart command. It does not roll migrations back: a migration may have destroyed data no reverse step restores; use `warlock migrate --rollback` deliberately if needed.
2551
+ - **list** and **status** show releases and the active one; **prune --keep <n>** is the only command that deletes releases and always keeps `current` and `previous`.
2552
+
2553
+ ## Layout
2554
+
2555
+ ```text
2556
+ .warlock/current -> releases/<id> .warlock/previous -> releases/<id>
2557
+ .warlock/release.log
2558
+ .warlock/releases/<id>/{manifest.json, source/, staged/}
2559
+ ```
2560
+
2561
+ Run the app from `.warlock/current/staged` so a restart follows the pointer.
2562
+
2563
+ ## Pitfalls
2564
+
2565
+ - Run `activate --dry-run` or `--preflight` first in CI; both change nothing.
2566
+ - Without `restart.command` activation only moves the symlink; the running process keeps serving the old code.
2567
+ - `verify` fails when the response does not report a version; set `verify.match` to `[]` to check only the status.
2568
+
2569
+
2490
2570
  ## encrypt-data `@warlock.js/core/skills/encrypt-data/SKILL.md`
2491
2571
 
2492
2572
  ---
@@ -4014,6 +4094,31 @@ router.get("/webhooks/status", webhookStatusController, { rateLimit: false });
4014
4094
  - The `send-response` topic — picking the right `response.<helper>()`.
4015
4095
  - The `warlock-conventions` topic — module layout, the `guarded` convention, path aliases.
4016
4096
 
4097
+ ## A named route the browser must never call — `clientVisible: false`
4098
+
4099
+ A named API route is embedded in a page's browser route table only when that page's client code reaches its name. Mark an endpoint that browser code must never reach with `clientVisible: false`:
4100
+
4101
+ ```ts
4102
+ router.post("/internal/reindex", reindexController, {
4103
+ name: "internal.reindex",
4104
+ clientVisible: false,
4105
+ });
4106
+ ```
4107
+
4108
+ It is then never embedded in a page, and `warlock check` reports a browser reference to its name as `WL9020` (an error). The server still authorizes the call as for any route.
4109
+
4110
+ ### Offer a route to some sites only — `sites`
4111
+
4112
+ In a multi-site app a route answers on every host, and a page of any site may embed it in its browser table. Declare the sites that may embed it on the route or on a group; a route's own `sites` replaces its group's, and a route with none is shared:
4113
+
4114
+ ```ts
4115
+ router.group({ prefix: "/api/admin", sites: ["admin"] }, () => {
4116
+ router.post("/users", createUser, { name: "admin.users.create" });
4117
+ });
4118
+ ```
4119
+
4120
+ This limits the browser table only; keep the route's guard. A guard in the route's middleware chain (group middleware included) also keeps it out of a guest's table, and a custom guard must say so with `markAuthGuard(fn)` unless its name contains `auth` or `guard`. `warlock inspect`, `warlock agents-md` and `WL9006` read the same chain, so a guard declared once on a group guards every route inside it.
4121
+
4017
4122
 
4018
4123
  ## report-framework-issue `@warlock.js/core/skills/report-framework-issue/SKILL.md`
4019
4124
 
@@ -8505,7 +8610,7 @@ and has no Warlock setup or HTTP global setup. `integration` discovers
8505
8610
  It uses the framework setup, HTTP global setup and a generated `src/test-runner.ts`.
8506
8611
  Keep pure service specs beside their source and mock repositories at the boundary.
8507
8612
  Existing config files are preserved; copy the projects layout into them manually.
8508
- Vitest 4.1.10 or newer is required for the generated runner.
8613
+ Vitest 5 or newer is required for the generated runner (Vitest 5 removed `vitest/runners`; the runner extends `TestRunner` from `vitest`).
8509
8614
 
8510
8615
  For a unit spec that needs configuration values, use:
8511
8616
 
@@ -8553,20 +8658,20 @@ Run with `npx vitest run --project integration`. Worker lifetime refuses watch,
8553
8658
  threads, isolated module graphs, or a missing runner with a message explaining
8554
8659
  the required settings. Keep `afterAll(teardownTest)` in the setup file: in worker
8555
8660
  mode it marks the end of the file; Vitest's awaited worker cleanup closes the
8556
- runtime once. The runner uses `VitestTestRunner.onCleanupWorkerContext`, verified
8557
- in Vitest 4.1.10's `runners.d.ts`, runner implementation and worker cleanup source.
8661
+ runtime once. The runner uses `TestRunner.onCleanupWorkerContext`, verified
8662
+ in Vitest 5.0.3's `TestRunner` declaration, runner implementation and worker cleanup source.
8558
8663
  For an existing project, generate this runner without overwriting your config:
8559
8664
 
8560
8665
  ```ts title="src/test-runner.ts"
8561
8666
  import { configureTestWorker } from "@warlock.js/core/tests";
8562
- import { VitestTestRunner } from "vitest/runners";
8667
+ import { TestRunner } from "vitest";
8563
8668
 
8564
- export default class WarlockTestRunner extends VitestTestRunner {
8565
- public constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {
8669
+ export default class WarlockTestRunner extends TestRunner {
8670
+ public constructor(config: ConstructorParameters<typeof TestRunner>[0]) {
8566
8671
  super(config);
8567
8672
  configureTestWorker(
8568
8673
  { pool: this.pool, watch: config.watch, isolate: config.isolate },
8569
- (cleanup) => this.onCleanupWorkerContext(cleanup),
8674
+ (cleanup) => this.onCleanupWorkerContext?.(cleanup),
8570
8675
  );
8571
8676
  }
8572
8677
  }
@@ -8585,7 +8690,7 @@ This trades module isolation for startup cost: application singletons, config
8585
8690
  mutations, mocks and module side effects can survive between files. Reset your
8586
8691
  own state in `resetBetweenFiles`, restore mocks, and keep connector selection
8587
8692
  consistent. Give each worker a distinct database/schema using
8588
- `process.env.VITEST_POOL_ID` when preparing test connections. Never drop a shared
8693
+ `process.env.VITEST_POOL_ID` (1-based since Vitest 5) when preparing test connections. Never drop a shared
8589
8694
  database from this hook while other workers are using it. The framework does not
8590
8695
  create a memory replica set or a cross-worker lock; those belong to the application's
8591
8696
  global setup. A shared HTTP server and a single replica set can contend on I/O,
@@ -9360,6 +9465,97 @@ one `#vessel` around `{children}`. Client navigation still swaps layout/page
9360
9465
  children while preserving the document root. Remove temporary explicit form IDs
9361
9466
  only when the application does not otherwise depend on those names.
9362
9467
 
9468
+ ## 5.35 to 5.36 per-page API routes
9469
+
9470
+ A page document now carries only the named API routes its client code can reach. Before 5.36 every page carried every named route, so a browser call to a name the build cannot see worked on every page by accident. Check each `apiHref()` and `useSubmitForm({ route })` call:
9471
+
9472
+ - A name written as a string literal, a constant or a template with a static prefix is found automatically.
9473
+ - A name computed at run time, or taken from a module the build cannot follow, now throws on the page that calls it. Declare the names in that page's setup file, for example `export const config = { apiRoutes: ["orders.*"] };`, a layout or root `config` covers every page that renders it. Or call the endpoint with an explicit `path` and `method`.
9474
+ - `npx warlock check` reports the call sites that need a declaration as `WL9019`.
9475
+
9476
+ Routes registered with `clientVisible: false` are never embedded, and a browser call to one is an error (`WL9020`). Keep endpoints the browser must not reach on that option. Authorization is unchanged: the server still decides each request.
9477
+
9478
+ To measure a page's inline payload, set `build.budgets.payload` (default `64kb`). An over-budget route logs one line per process and never fails a request.
9479
+
9480
+ ## 5.35 to 5.36 translation payloads
9481
+
9482
+ A page's hydration payload now carries only the translations its client code
9483
+ can reach, in route-dictionary (`locales.json`) and global-registry (`extend()`)
9484
+ apps alike. The groups every page uses (default `app` and `common`) arrive once
9485
+ per locale in a hashed, cached file that the page preloads. Nothing in a page's
9486
+ render changes: server output is the full dictionary, as before.
9487
+
9488
+ Run `warlock build`, then `warlock check`. A `WL9104` warning marks a page that
9489
+ renders a translation key computed at runtime. Declare the keys it renders in
9490
+ the page's setup file, for example
9491
+ `export const config = { translations: ["statuses.*", "members.title"] }`, so the
9492
+ page ships only what it uses.
9493
+
9494
+ In development, a warning names a key the page reads but its payload lacks, and
9495
+ resolves it from the full table so the page keeps working. In production the
9496
+ key renders as its own text and the miss is reported once through
9497
+ `onClientError` with `kind: "translations"`. Declare the key, or set
9498
+ `build: { translations: { prune: false } }` in the web connector options while migrating to
9499
+ restore the previous per-page payload.
9500
+
9501
+ Global-registry apps are covered too. Their shared table is served at
9502
+ `/__warlock/translations/<locale>.<hash>.json`, which is cached immutable in
9503
+ production. A direct `trans()` call from `@mongez/localization` is not checked
9504
+ for a pruning miss; the calls through `useTrans()` are.
9505
+
9506
+ ## 5.35 to 5.36 Vite 8 and Vitest 5
9507
+
9508
+ `@warlock.js/web` requires Vite 8 (`^8.0.0`) and `@vitejs/plugin-react` 6, and the
9509
+ test kit requires Vitest 5. Vite 8 bundles with Rolldown and transforms
9510
+ TypeScript with Oxc. Vite 7 is not supported; plugin-react 6 needs Vite 8, so the
9511
+ family moves together. Vite 8 needs Node.js 20.19 or 22.12 and newer, Vitest 5
9512
+ needs 22.12 or newer; Warlock already requires 22.15.
9513
+
9514
+ ```bash
9515
+ npx warlock upgrade --from 5.35 --to 5.36.0 --dry-run
9516
+ npx warlock upgrade --from 5.35 --to 5.36.0
9517
+ npm install
9518
+ npx warlock check
9519
+ ```
9520
+
9521
+ The `vite-8-vitest-5` codemod:
9522
+
9523
+ - raises `vite` (8.3.3), `vitest` and `@vitest/*` (5.0.3) and `@vitejs/plugin-react`
9524
+ (6.1.2) in `dependencies`, `devDependencies`, `overrides` and `resolutions`,
9525
+ keeping `^`, `~` or exact style;
9526
+ - rewrites a generated `src/test-runner.ts` from `VitestTestRunner` in
9527
+ `vitest/runners` to `TestRunner` from `vitest`, and calls
9528
+ `this.onCleanupWorkerContext?.(cleanup)` (the hook is absent in vm pools);
9529
+ - lists, without editing, imports of the removed `vitest/coverage`,
9530
+ `vitest/reporters`, `vitest/environments`, `vitest/snapshot`, `vitest/suite`,
9531
+ `vitest/mocker` and `vitest/internal/module-runner` entry points, other names
9532
+ imported from `vitest/runners`, a top-level `esbuild` option in a Vite or
9533
+ Vitest config, and object-form `output.manualChunks`.
9534
+
9535
+ Do these by hand when the codemod lists them:
9536
+
9537
+ - Move a config's `esbuild: { jsx: "automatic" }` to
9538
+ `oxc: { jsx: { runtime: "automatic" } }`. `esbuild.target` is not converted, and
9539
+ Oxc does not lower native decorators: keep `lowerStage3Decorators()` from
9540
+ `@warlock.js/core/vite` first in `plugins` for decorated modules, as in the
9541
+ generated test config.
9542
+ - Replace object `manualChunks` with
9543
+ `build.rolldownOptions.output.codeSplitting.groups`; `build.rollupOptions` still
9544
+ works but is deprecated in favour of `build.rolldownOptions`.
9545
+ - A custom bundler `external` list that names Node builtins is still refused by the
9546
+ client build.
9547
+
9548
+ Vitest 5 changes defaults that can alter a green suite: `clearMocks` is true,
9549
+ `json` and `junit` reporters write to `.vitest/` instead of stdout, `-t` joins a
9550
+ test's names with `>`, `VITEST_POOL_ID` and `VITEST_WORKER_ID` start at 1, nested
9551
+ `vi.mock`/`vi.hoisted` calls throw, and an unawaited `resolves` or `rejects`
9552
+ assertion fails the test. Vitest also no longer searches parent directories for a
9553
+ config file; run it from the package that owns the config. Official guides:
9554
+ https://vite.dev/guide/migration and https://vitest.dev/guide/migration.
9555
+
9556
+ A project that uses `@warlock.js/ai`'s custom matchers needs Vitest 5 for their
9557
+ TypeScript types.
9558
+
9363
9559
 
9364
9560
  ## upload-file `@warlock.js/core/skills/upload-file/SKILL.md`
9365
9561
 
@@ -10347,6 +10543,10 @@ workers, and registered module cleanup hooks, including after partial startup.
10347
10543
  A Node loader registration is process-scoped; its message channel is closed at
10348
10544
  teardown. Module-owned timers/listeners must register their cleanup hooks.
10349
10545
 
10546
+ ## Translation keys in pages (5.36)
10547
+
10548
+ `warlock check` reports `WL9104` when a page renders a translation key computed at runtime (`trans(key)` with no static prefix, or a translator passed on as a value). Such a page ships every translation group. Declare its keys in the page's setup file, `export const config = { translations: ["statuses.*", "members.title"] }`, to ship only what it uses. The `web` skill `localize-pages` covers the browser side.
10549
+
10350
10550
 
10351
10551
  ## use-middleware `@warlock.js/core/skills/use-middleware/SKILL.md`
10352
10552
 
@@ -12214,14 +12414,17 @@ and work through the manual-review list. Use `--lint=oxc` for new apps.
12214
12414
  The following codes default to **warnings**, so upgrading does not fail existing projects.
12215
12415
  They parse real TypeScript/JSX syntax with the same oxc-parser adapter as the existing conventions.
12216
12416
 
12217
- | Code | Convention | Remedy |
12218
- | -------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
12219
- | `WL9013` | A component file exceeds 150 lines | Split components and extract hooks; configure `componentLineLimit`. |
12220
- | `WL9014` | More than one component in a file | Move each component into its own TSX file. |
12221
- | `WL9015` | State/effects or event-handler functions inside a component | Move `useState`, `useReducer`, `useEffect`, `useLayoutEffect` and handlers into `use-*.ts` hooks. |
12222
- | `WL9016` | Native select, checkbox, radio, date, time or datetime-local input | Use the design-system control. Native controls are allowed in `src/web/design-system/**`. |
12223
- | `WL9017` | DOM input/textarea/select native validation | Use shared schema validation instead of `required`, `pattern`, `min`, `max`, `minLength`, `maxLength` or input `type="email"`. Custom component props are allowed. |
12224
- | `WL9018` | Exported `RequestHandler` in `src/app/**/controllers` lacks `responseSchema` | Assign `handler.responseSchema` to document each response status. |
12417
+ | Code | Convention | Remedy |
12418
+ | -------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
12419
+ | `WL9013` | A component file exceeds 150 lines | Split components and extract hooks; configure `componentLineLimit`. |
12420
+ | `WL9014` | More than one component in a file | Move each component into its own TSX file. |
12421
+ | `WL9015` | State/effects or event-handler functions inside a component | Move `useState`, `useReducer`, `useEffect`, `useLayoutEffect` and handlers into `use-*.ts` hooks. |
12422
+ | `WL9016` | Native select, checkbox, radio, date, time or datetime-local input | Use the design-system control. Native controls are allowed in `src/web/design-system/**`. |
12423
+ | `WL9017` | DOM input/textarea/select native validation | Use shared schema validation instead of `required`, `pattern`, `min`, `max`, `minLength`, `maxLength` or input `type="email"`. Custom component props are allowed. |
12424
+ | `WL9018` | Exported `RequestHandler` in `src/app/**/controllers` lacks `responseSchema` | Assign `handler.responseSchema` to document each response status. |
12425
+ | `WL9019` | Browser route call (`apiHref`, `useSubmitForm({ route })`) takes a run-time name | Pass a string literal or a template with a static prefix, or declare the names with `config.apiRoutes` in a page setup file. |
12426
+ | `WL9020` | Browser code names a route registered with `clientVisible: false` (error) | Call the endpoint from the server, or remove `clientVisible: false` from the route. |
12427
+ | `WL9104` | Translation key computed at runtime in `src/web` (the page ships every group) | Declare the keys with `config.translations` in the page's setup file, for example `["statuses.*"]`. |
12225
12428
 
12226
12429
  Web rules apply to `src/web/**/*.tsx`. Components are top-level JSX-returning functions
12227
12430
  with a capitalized name (or an anonymous default export). Hook files and ordinary helpers
@@ -12471,7 +12674,7 @@ router.post("/posts", createPostController, { middleware: [authMiddleware("user"
12471
12674
  router.post("/contact", contactController, { public: true });
12472
12675
  ```
12473
12676
 
12474
- A route inside `guarded(() => …)` or `router.group({ middleware: [authMiddleware()] }, …)` is guarded. A middleware counts as a guard when its name contains `auth` or `guard`, or when it is a constant initialised with `markAuthGuard()` in the same file. `warlock dev` warns about the same routes at runtime.
12677
+ A route inside `guarded(() => …)` or `router.group({ middleware: [authMiddleware()] }, …)` is guarded. A middleware counts as a guard when its name contains `auth` or `guard`, or when it is a constant initialised with `markAuthGuard()` in the same file, or a local factory that returns `markAuthGuard(fn)` and is called in the `middleware` list. `warlock dev` warns about the same routes at runtime.
12475
12678
 
12476
12679
  ### `WL9007` — safe redirects
12477
12680
 
package/llms.txt CHANGED
@@ -16,6 +16,7 @@
16
16
  - [create-controller](@warlock.js/core/skills/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.signal`, `request.input`, `controller.validation`, `controller.responseSchema`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type", "type the API response"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — the `send-response` topic; schema authoring — the `validate-input` topic; URL wiring — the `register-route` topic; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
17
17
  - [create-module](@warlock.js/core/skills/create-module/SKILL.md): Scaffold a new feature module under `src/app/<name>/` via `warlock generate.module` and the follow-up generators for controllers, models, repositories, resources, and validation schemas. Triggers: `warlock generate.module`, `generate.controller`, `generate.service`, `generate.model`, `generate.repository`, `generate.resource`, `generate.migration`, `--minimal`, `gen.m`; "scaffold a new module", "create CRUD bootstrap", "add a controller to a module", "generate a model"; typical CLI `npx warlock generate.module <name>`. Skip: framework-wide layout rules — the `warlock-conventions` topic; routes file shape — the `register-route` topic; controller shape — the `create-controller` topic; competing tooling: `@nestjs/cli`, `hygen`, hand-rolled folder layouts.
18
18
  - [define-resource](@warlock.js/core/skills/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Also covers the typed output: `ResourceOutput<typeof R>`, `defineResource<Out>()`, `Serialized<T, W>` and `ModelResourceRegistry`. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `ResourceOutput`, `Serialized`, `ModelResourceRegistry`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "type a resource's output", "what does the client receive", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — the `use-localization` topic; URL casting — the `build-url` topic; controller side — the `create-controller` topic; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
19
+ - [deploy-release](@warlock.js/core/skills/deploy-release/SKILL.md): Deploy a Warlock application with `warlock release` — freeze a clean commit as a candidate, run the gate, prove migrations, stage the build, switch `current` by atomic symlink, verify the health URL and roll back, configured by the `release` section of `warlock.config.ts`. Triggers: `warlock release`, `release candidate`, `release gate`, `release migrations --prove`, `release stage`, `release activate`, `release verify`, `release rollback`, `release prune`, `ReleaseConfig`, `release.restart.command`, `release.verify`, `.warlock/releases`; "deploy my app", "zero-downtime deploy", "roll back a deploy", "atomic release", "release gate", "prove migrations before deploy". Skip: the `/health` and `/ready` endpoints themselves — the `health-checks` topic; generating a PM2 ecosystem file — `warlock pm2` (the `run-app` topic); publishing the framework packages — `releasing-warlock-monorepo`; competing tools Capistrano, Deployer, hand-rolled symlink scripts.
19
20
  - [encrypt-data](@warlock.js/core/skills/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — the `hash-password` topic; config wiring — the `configure-app` topic; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
20
21
  - [generate-openapi](@warlock.js/core/skills/generate-openapi/SKILL.md): Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.
21
22
  - [generate-web-ui](@warlock.js/core/skills/generate-web-ui/SKILL.md): Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites.
package/package.json CHANGED
@@ -25,12 +25,12 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^3.0.0",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.35.0",
29
- "@warlock.js/cascade": "5.35.0",
30
- "@warlock.js/context": "5.35.0",
31
- "@warlock.js/fs": "5.35.0",
32
- "@warlock.js/logger": "5.35.0",
33
- "@warlock.js/seal": "5.35.0",
28
+ "@warlock.js/cache": "5.37.0",
29
+ "@warlock.js/cascade": "5.37.0",
30
+ "@warlock.js/context": "5.37.0",
31
+ "@warlock.js/fs": "5.37.0",
32
+ "@warlock.js/logger": "5.37.0",
33
+ "@warlock.js/seal": "5.37.0",
34
34
  "bcryptjs": "^3.0.3",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
@@ -56,12 +56,12 @@
56
56
  "@aws-sdk/lib-storage": "^3.955.0",
57
57
  "@aws-sdk/s3-request-presigner": "^3.955.0",
58
58
  "@react-email/render": "^2.0.5",
59
- "@warlock.js/access": "5.35.0",
60
- "@warlock.js/ai": "5.35.0",
61
- "@warlock.js/ai-tools": "5.35.0",
62
- "@warlock.js/devtools": "5.35.0",
63
- "@warlock.js/herald": "5.35.0",
64
- "@warlock.js/notifications": "5.35.0",
59
+ "@warlock.js/access": "5.37.0",
60
+ "@warlock.js/ai": "5.37.0",
61
+ "@warlock.js/ai-tools": "5.37.0",
62
+ "@warlock.js/devtools": "5.37.0",
63
+ "@warlock.js/herald": "5.37.0",
64
+ "@warlock.js/notifications": "5.37.0",
65
65
  "nodemailer": "^8.0.5",
66
66
  "react": "^19.2.3",
67
67
  "react-dom": "^19.2.3",
@@ -146,7 +146,7 @@
146
146
  "engines": {
147
147
  "node": ">=22.15"
148
148
  },
149
- "version": "5.35.0",
149
+ "version": "5.37.0",
150
150
  "type": "module",
151
151
  "main": "./esm/index.mjs",
152
152
  "module": "./esm/index.mjs",
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: deploy-release
3
+ description: 'Deploy a Warlock application with `warlock release` — freeze a clean commit as a candidate, run the gate, prove migrations, stage the build, switch `current` by atomic symlink, verify the health URL and roll back, configured by the `release` section of `warlock.config.ts`. Triggers: `warlock release`, `release candidate`, `release gate`, `release migrations --prove`, `release stage`, `release activate`, `release verify`, `release rollback`, `release prune`, `ReleaseConfig`, `release.restart.command`, `release.verify`, `.warlock/releases`; "deploy my app", "zero-downtime deploy", "roll back a deploy", "atomic release", "release gate", "prove migrations before deploy". Skip: the `/health` and `/ready` endpoints themselves — the `health-checks` topic; generating a PM2 ecosystem file — `warlock pm2` (the `run-app` topic); publishing the framework packages — `releasing-warlock-monorepo`; competing tools Capistrano, Deployer, hand-rolled symlink scripts.'
4
+ ---
5
+
6
+ # Warlock — deploy with `warlock release`
7
+
8
+ A recorded, step-by-step deploy for one host. Each step is a command; each writes into a per-release `manifest.json` and a shared `release.log`.
9
+
10
+ ```text
11
+ candidate -> gate -> migrations --prove -> stage -> activate -> verify
12
+ \-> rollback
13
+ ```
14
+
15
+ Warlock never calls PM2, systemd or any process manager. The only restart is the command you set in `release.restart.command`.
16
+
17
+ ## Configure
18
+
19
+ ```ts title="warlock.config.ts"
20
+ import { defineConfig } from "@warlock.js/core";
21
+
22
+ export default defineConfig({
23
+ release: {
24
+ gate: {
25
+ checks: [
26
+ { name: "typecheck", command: "npx warlock typecheck" },
27
+ { name: "test", command: "npx warlock test" },
28
+ { name: "build", command: "NODE_ENV=production npx warlock build" },
29
+ ],
30
+ },
31
+ restart: { command: "pm2 reload my-app" },
32
+ verify: { url: "http://127.0.0.1:2030/health" },
33
+ },
34
+ });
35
+ ```
36
+
37
+ Defaults: `directory` `.warlock`, `distDir` `dist`, gate = typecheck, check, test, build. Add `.warlock` to `.gitignore`.
38
+
39
+ ## Run the steps
40
+
41
+ ```bash
42
+ npx warlock release candidate [--commit <sha>] [--allow-dirty]
43
+ npx warlock release gate
44
+ npx warlock release migrations --prove --target staging
45
+ npx warlock release stage
46
+ npx warlock release activate --preflight
47
+ npx warlock release activate [--dry-run] [--allow-same]
48
+ npx warlock release verify || npx warlock release rollback
49
+ ```
50
+
51
+ Every command takes an optional release id (`release gate 1.4.0-a1b2c3d`); without one it uses the newest candidate, gated or staged release. `--json` prints the result as JSON; a failed step exits 1.
52
+
53
+ - **candidate** freezes the commit with `git archive` into `.warlock/releases/<version>-<shortsha>/source`. Refuses a dirty tree without `--allow-dirty` and never overwrites a release.
54
+ - **gate** runs the checks in order in the frozen source and stops at the first failure; results go into the manifest.
55
+ - **migrations** without `--prove` only lists the pending set. With `--prove` it snapshots the ledger, tables and indexes, runs the pending migrations, snapshots again and compares with the expected set (`release.migrations.expected`, default the pending set; `expectedTables`). The migrations really run: use `--target` (a `release.migrations.targets` entry whose `env` is applied before the database connects) on a database that is safe to migrate. A failed proof blocks activation.
56
+ - **stage** (needs a passing gate) copies `dist`, `package.json`, the lockfile, `release.include` entries and `release.json` into `staged/`, atomically.
57
+ - **activate** needs staged artefacts, a passed gate (and proof), and a version strictly greater than the active one (semver; `--allow-same` accepts an equal version, a lower one is refused). It switches `current -> releases/<id>` with an atomic rename, keeps the old target as `previous`, then runs the restart command with `WARLOCK_RELEASE_ID`, `WARLOCK_RELEASE_DIR` and `WARLOCK_RELEASE_CURRENT` set. If that command fails, the pointer stays switched and the exit code is 1.
58
+ - **verify** GETs `release.verify.url` with retries and a timeout and checks the status plus the `version` and `commit` fields of the JSON body against the active release (`verify.versionPath`, `commitPath` and `match` adjust that). Serve the `release.json` values from your health endpoint.
59
+ - **rollback** switches `current` back to `previous` (and `previous` to the release just left), reruns the restart command. It does not roll migrations back: a migration may have destroyed data no reverse step restores; use `warlock migrate --rollback` deliberately if needed.
60
+ - **list** and **status** show releases and the active one; **prune --keep <n>** is the only command that deletes releases and always keeps `current` and `previous`.
61
+
62
+ ## Layout
63
+
64
+ ```text
65
+ .warlock/current -> releases/<id> .warlock/previous -> releases/<id>
66
+ .warlock/release.log
67
+ .warlock/releases/<id>/{manifest.json, source/, staged/}
68
+ ```
69
+
70
+ Run the app from `.warlock/current/staged` so a restart follows the pointer.
71
+
72
+ ## Pitfalls
73
+
74
+ - Run `activate --dry-run` or `--preflight` first in CI; both change nothing.
75
+ - Without `restart.command` activation only moves the symlink; the running process keeps serving the old code.
76
+ - `verify` fails when the response does not report a version; set `verify.match` to `[]` to check only the status.
package/skills/index.md CHANGED
@@ -22,7 +22,7 @@ When the framework is wrong or missing a capability, follow the `report-framewor
22
22
  - **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
23
23
  - **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
24
24
  - **Domain layers:** write-use-case, use-repository, write-seeder
25
- - **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
25
+ - **CLI & running the app:** generate-web-ui, run-app, deploy-release, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
26
26
  - **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
27
27
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
28
28
  - **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
@@ -279,3 +279,28 @@ router.get("/webhooks/status", webhookStatusController, { rateLimit: false });
279
279
  - The `create-controller` topic — the handler shape, validation, request/response surface.
280
280
  - The `send-response` topic — picking the right `response.<helper>()`.
281
281
  - The `warlock-conventions` topic — module layout, the `guarded` convention, path aliases.
282
+
283
+ ## A named route the browser must never call — `clientVisible: false`
284
+
285
+ A named API route is embedded in a page's browser route table only when that page's client code reaches its name. Mark an endpoint that browser code must never reach with `clientVisible: false`:
286
+
287
+ ```ts
288
+ router.post("/internal/reindex", reindexController, {
289
+ name: "internal.reindex",
290
+ clientVisible: false,
291
+ });
292
+ ```
293
+
294
+ It is then never embedded in a page, and `warlock check` reports a browser reference to its name as `WL9020` (an error). The server still authorizes the call as for any route.
295
+
296
+ ### Offer a route to some sites only — `sites`
297
+
298
+ In a multi-site app a route answers on every host, and a page of any site may embed it in its browser table. Declare the sites that may embed it on the route or on a group; a route's own `sites` replaces its group's, and a route with none is shared:
299
+
300
+ ```ts
301
+ router.group({ prefix: "/api/admin", sites: ["admin"] }, () => {
302
+ router.post("/users", createUser, { name: "admin.users.create" });
303
+ });
304
+ ```
305
+
306
+ This limits the browser table only; keep the route's guard. A guard in the route's middleware chain (group middleware included) also keeps it out of a guest's table, and a custom guard must say so with `markAuthGuard(fn)` unless its name contains `auth` or `guard`. `warlock inspect`, `warlock agents-md` and `WL9006` read the same chain, so a guard declared once on a group guards every route inside it.
@@ -454,7 +454,7 @@ and has no Warlock setup or HTTP global setup. `integration` discovers
454
454
  It uses the framework setup, HTTP global setup and a generated `src/test-runner.ts`.
455
455
  Keep pure service specs beside their source and mock repositories at the boundary.
456
456
  Existing config files are preserved; copy the projects layout into them manually.
457
- Vitest 4.1.10 or newer is required for the generated runner.
457
+ Vitest 5 or newer is required for the generated runner (Vitest 5 removed `vitest/runners`; the runner extends `TestRunner` from `vitest`).
458
458
 
459
459
  For a unit spec that needs configuration values, use:
460
460
 
@@ -502,20 +502,20 @@ Run with `npx vitest run --project integration`. Worker lifetime refuses watch,
502
502
  threads, isolated module graphs, or a missing runner with a message explaining
503
503
  the required settings. Keep `afterAll(teardownTest)` in the setup file: in worker
504
504
  mode it marks the end of the file; Vitest's awaited worker cleanup closes the
505
- runtime once. The runner uses `VitestTestRunner.onCleanupWorkerContext`, verified
506
- in Vitest 4.1.10's `runners.d.ts`, runner implementation and worker cleanup source.
505
+ runtime once. The runner uses `TestRunner.onCleanupWorkerContext`, verified
506
+ in Vitest 5.0.3's `TestRunner` declaration, runner implementation and worker cleanup source.
507
507
  For an existing project, generate this runner without overwriting your config:
508
508
 
509
509
  ```ts title="src/test-runner.ts"
510
510
  import { configureTestWorker } from "@warlock.js/core/tests";
511
- import { VitestTestRunner } from "vitest/runners";
511
+ import { TestRunner } from "vitest";
512
512
 
513
- export default class WarlockTestRunner extends VitestTestRunner {
514
- public constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {
513
+ export default class WarlockTestRunner extends TestRunner {
514
+ public constructor(config: ConstructorParameters<typeof TestRunner>[0]) {
515
515
  super(config);
516
516
  configureTestWorker(
517
517
  { pool: this.pool, watch: config.watch, isolate: config.isolate },
518
- (cleanup) => this.onCleanupWorkerContext(cleanup),
518
+ (cleanup) => this.onCleanupWorkerContext?.(cleanup),
519
519
  );
520
520
  }
521
521
  }
@@ -534,7 +534,7 @@ This trades module isolation for startup cost: application singletons, config
534
534
  mutations, mocks and module side effects can survive between files. Reset your
535
535
  own state in `resetBetweenFiles`, restore mocks, and keep connector selection
536
536
  consistent. Give each worker a distinct database/schema using
537
- `process.env.VITEST_POOL_ID` when preparing test connections. Never drop a shared
537
+ `process.env.VITEST_POOL_ID` (1-based since Vitest 5) when preparing test connections. Never drop a shared
538
538
  database from this hook while other workers are using it. The framework does not
539
539
  create a memory replica set or a cross-worker lock; those belong to the application's
540
540
  global setup. A shared HTTP server and a single replica set can contend on I/O,