@warlock.js/core 5.35.0 → 5.36.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 (72) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/esm/dev-server/module-loader.mjs +1 -1
  3. package/esm/dev-server/parse-imports.mjs +1 -1
  4. package/esm/dev-server/type-generator.mjs +1 -1
  5. package/esm/e2e/cdp.d.mts.map +1 -1
  6. package/esm/e2e/cdp.mjs +9 -2
  7. package/esm/e2e/cdp.mjs.map +1 -1
  8. package/esm/e2e/page.d.mts +9 -1
  9. package/esm/e2e/page.d.mts.map +1 -1
  10. package/esm/e2e/page.mjs +45 -7
  11. package/esm/e2e/page.mjs.map +1 -1
  12. package/esm/e2e/types.d.mts +18 -0
  13. package/esm/e2e/types.d.mts.map +1 -1
  14. package/esm/e2e/types.mjs.map +1 -1
  15. package/esm/errors/error-codes.d.mts +24 -0
  16. package/esm/errors/error-codes.d.mts.map +1 -1
  17. package/esm/errors/error-codes.mjs +24 -0
  18. package/esm/errors/error-codes.mjs.map +1 -1
  19. package/esm/generations/features/test.feature.mjs +7 -7
  20. package/esm/generations/features/test.feature.mjs.map +1 -1
  21. package/esm/generations/features/web.feature.mjs +3 -3
  22. package/esm/generations/features/web.feature.mjs.map +1 -1
  23. package/esm/index.d.mts +4 -2
  24. package/esm/index.mjs +3 -1
  25. package/esm/localization/index.mjs +3 -1
  26. package/esm/localization/translation-usage-graph.d.mts +49 -0
  27. package/esm/localization/translation-usage-graph.d.mts.map +1 -0
  28. package/esm/localization/translation-usage-graph.mjs +128 -0
  29. package/esm/localization/translation-usage-graph.mjs.map +1 -0
  30. package/esm/localization/translation-usage.d.mts +85 -0
  31. package/esm/localization/translation-usage.d.mts.map +1 -0
  32. package/esm/localization/translation-usage.mjs +307 -0
  33. package/esm/localization/translation-usage.mjs.map +1 -0
  34. package/esm/production/build-budgets.d.mts +18 -0
  35. package/esm/production/build-budgets.d.mts.map +1 -1
  36. package/esm/production/build-budgets.mjs.map +1 -1
  37. package/esm/production/route-registration-snapshot.mjs +5 -3
  38. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  39. package/esm/router/router.d.mts +2 -0
  40. package/esm/router/router.d.mts.map +1 -1
  41. package/esm/router/router.mjs +4 -1
  42. package/esm/router/router.mjs.map +1 -1
  43. package/esm/router/types.d.mts +19 -0
  44. package/esm/router/types.d.mts.map +1 -1
  45. package/esm/tooling/check/api-route-references.d.mts +16 -0
  46. package/esm/tooling/check/api-route-references.d.mts.map +1 -0
  47. package/esm/tooling/check/api-route-references.mjs +146 -0
  48. package/esm/tooling/check/api-route-references.mjs.map +1 -0
  49. package/esm/tooling/check/conventions-step.d.mts +6 -3
  50. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  51. package/esm/tooling/check/conventions-step.mjs +19 -10
  52. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  53. package/esm/tooling/check/run-check.d.mts.map +1 -1
  54. package/esm/tooling/check/run-check.mjs +4 -2
  55. package/esm/tooling/check/run-check.mjs.map +1 -1
  56. package/esm/tooling/check/translation-conventions.mjs +32 -0
  57. package/esm/tooling/check/translation-conventions.mjs.map +1 -0
  58. package/esm/tooling/inspect/collect-files.mjs +1 -1
  59. package/esm/upgrade/codemods/index.mjs +2 -0
  60. package/esm/upgrade/codemods/index.mjs.map +1 -1
  61. package/esm/upgrade/codemods/vite-vitest.mjs +170 -0
  62. package/esm/upgrade/codemods/vite-vitest.mjs.map +1 -0
  63. package/esm/upgrade/notes.mjs +4 -0
  64. package/esm/upgrade/notes.mjs.map +1 -1
  65. package/esm/warlock-config/index.d.mts +2 -2
  66. package/llms-full.txt +127 -16
  67. package/package.json +13 -13
  68. package/skills/register-route/SKILL.md +13 -0
  69. package/skills/test-service/SKILL.md +8 -8
  70. package/skills/upgrade-from-5-29/SKILL.md +91 -0
  71. package/skills/use-localization/SKILL.md +4 -0
  72. package/skills/warlock-check/SKILL.md +11 -8
@@ -0,0 +1,170 @@
1
+ import { manualAt, replaceNode } from "./helpers.mjs";
2
+ import { parseVersion } from "../version.mjs";
3
+
4
+ //#region ../../packages/core/src/upgrade/codemods/vite-vitest.ts
5
+ /**
6
+ * The newest releases that are at least two weeks old on the day 5.36 shipped;
7
+ * `warlock add test` and `warlock add web` install the same ones.
8
+ */
9
+ const TARGETS = {
10
+ vite: {
11
+ major: 8,
12
+ version: "8.3.3"
13
+ },
14
+ vitest: {
15
+ major: 5,
16
+ version: "5.0.3"
17
+ },
18
+ "@vitest/coverage-v8": {
19
+ major: 5,
20
+ version: "5.0.3"
21
+ },
22
+ "@vitest/coverage-istanbul": {
23
+ major: 5,
24
+ version: "5.0.3"
25
+ },
26
+ "@vitest/ui": {
27
+ major: 5,
28
+ version: "5.0.3"
29
+ },
30
+ "@vitejs/plugin-react": {
31
+ major: 6,
32
+ version: "6.1.2"
33
+ }
34
+ };
35
+ const MIGRATION_GUIDES = "https://vite.dev/guide/migration and https://vitest.dev/guide/migration";
36
+ /** Version specs that are not a plain registry range and must not be rewritten. */
37
+ const NON_REGISTRY_SPEC = /^(workspace:|file:|link:|portal:|git|github:|https?:|npm:)|^\*$|^latest$/;
38
+ /** Entry points Vitest 5 removed. `vitest/runners` is handled separately. */
39
+ const REMOVED_ENTRY_POINTS = /* @__PURE__ */ new Set([
40
+ "vitest/coverage",
41
+ "vitest/reporters",
42
+ "vitest/environments",
43
+ "vitest/snapshot",
44
+ "vitest/suite",
45
+ "vitest/mocker",
46
+ "vitest/internal/module-runner"
47
+ ]);
48
+ const VITE_CONFIG_FILE = /^vite(st)?\.config\.(ts|mts|cts|js|mjs|cjs)$/;
49
+ function serialize(original, value) {
50
+ const indent = /^[ \t]+(?=")/m.exec(original)?.[0];
51
+ return (indent ? JSON.stringify(value, null, indent) : JSON.stringify(value)) + (original.endsWith("\n") ? "\n" : "");
52
+ }
53
+ function lineOf(source, needle) {
54
+ const index = source.indexOf(needle);
55
+ return index < 0 ? 1 : source.slice(0, index).split("\n").length;
56
+ }
57
+ /** The spec moved to `target`, keeping its `^`/`~`/exact style; undefined when it already allows it. */
58
+ function upgradedSpec(spec, target) {
59
+ if (NON_REGISTRY_SPEC.test(spec)) return void 0;
60
+ const parsed = parseVersion(spec);
61
+ if (!parsed || parsed[0] >= target.major) return void 0;
62
+ return `${/^[\^~]/.exec(spec)?.[0] ?? (/^\d/.test(spec) ? "" : "^")}${target.version}`;
63
+ }
64
+ /**
65
+ * 5.36: Vite 8 (Rolldown), Vitest 5 and `@vitejs/plugin-react` 6 are one
66
+ * family and must move together.
67
+ *
68
+ * - package.json: raises `vite`, `vitest`, `@vitest/*` and
69
+ * `@vitejs/plugin-react` (dependencies, devDependencies, `overrides` and
70
+ * `resolutions`) to the supported majors.
71
+ * - `src/test-runner.ts`-style runners: Vitest 5 has no `vitest/runners`; the
72
+ * runner class is `TestRunner`, exported from `vitest`, and
73
+ * `onCleanupWorkerContext` is an optional property.
74
+ * - Lists removed Vitest entry points, a top-level `esbuild` option, and object
75
+ * `manualChunks` in Vite configs for review.
76
+ */
77
+ const viteVitest = {
78
+ id: "vite-8-vitest-5",
79
+ version: "5.36.0",
80
+ package: "@warlock.js/core",
81
+ title: "Move to Vite 8, Vitest 5 and @vitejs/plugin-react 6",
82
+ skill: "upgrade-from-5-29",
83
+ run(context) {
84
+ const { Node, SyntaxKind } = context.tsMorph;
85
+ const output = {
86
+ edits: [],
87
+ manual: []
88
+ };
89
+ const source = context.text.read("package.json");
90
+ if (source !== void 0) {
91
+ const pkg = JSON.parse(source);
92
+ const changes = [];
93
+ for (const field of [
94
+ "dependencies",
95
+ "devDependencies",
96
+ "overrides",
97
+ "resolutions"
98
+ ]) {
99
+ const entries = pkg[field];
100
+ if (!entries) continue;
101
+ for (const [name, target] of Object.entries(TARGETS)) {
102
+ const spec = entries[name];
103
+ if (typeof spec !== "string") continue;
104
+ const next = upgradedSpec(spec, target);
105
+ if (!next) continue;
106
+ entries[name] = next;
107
+ changes.push({
108
+ name: field === "dependencies" || field === "devDependencies" ? name : `${field}.${name}`,
109
+ from: spec,
110
+ to: next
111
+ });
112
+ }
113
+ }
114
+ const first = changes[0];
115
+ if (first) context.text.write("package.json", serialize(source, pkg), {
116
+ line: lineOf(source, `"${first.name.replace(/^[a-z]+\./, "")}"`),
117
+ description: changes.map((change) => `${change.name} ${change.from} → ${change.to}`).join(", ")
118
+ });
119
+ }
120
+ for (const sourceFile of context.sourceFiles) for (const declaration of sourceFile.getImportDeclarations()) {
121
+ const moduleName = declaration.getModuleSpecifierValue();
122
+ if (REMOVED_ENTRY_POINTS.has(moduleName)) {
123
+ output.manual.push(manualAt(context, declaration, `Vitest 5 removed the "${moduleName}" entry point. Import from "vitest" or "vitest/node" instead; see ${MIGRATION_GUIDES}.`));
124
+ continue;
125
+ }
126
+ if (moduleName !== "vitest/runners") continue;
127
+ const specifiers = declaration.getNamedImports();
128
+ const runner = specifiers.find((specifier) => specifier.getName() === "VitestTestRunner");
129
+ if (!runner || specifiers.length !== 1 || declaration.getDefaultImport()) {
130
+ output.manual.push(manualAt(context, declaration, "Vitest 5 removed \"vitest/runners\". Import the runner class as `TestRunner` from \"vitest\"; benchmark runners no longer exist."));
131
+ continue;
132
+ }
133
+ const alias = runner.getAliasNode()?.getText();
134
+ output.edits.push(replaceNode(context, declaration, alias ? `import { TestRunner as ${alias} } from "vitest";` : "import { TestRunner } from \"vitest\";", "imported the runner as TestRunner from \"vitest\""));
135
+ if (!alias) for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
136
+ if (identifier.getText() !== "VitestTestRunner") continue;
137
+ if (identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration)) continue;
138
+ output.edits.push(replaceNode(context, identifier, "TestRunner", "renamed VitestTestRunner to TestRunner"));
139
+ }
140
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
141
+ const callee = call.getExpression();
142
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "onCleanupWorkerContext" || call.getQuestionDotTokenNode()) continue;
143
+ output.edits.push(replaceNode(context, callee, `${callee.getText()}?.`, "onCleanupWorkerContext is optional in Vitest 5 (absent in vm pools)"));
144
+ }
145
+ }
146
+ for (const file of context.text.list()) {
147
+ if (!VITE_CONFIG_FILE.test(file)) continue;
148
+ const config = context.text.read(file);
149
+ if (config === void 0) continue;
150
+ config.split("\n").forEach((text, index) => {
151
+ const line = index + 1;
152
+ if (/^\s*esbuild\s*:/.test(text)) output.manual.push({
153
+ file,
154
+ line,
155
+ hint: "Vite 8 transforms TypeScript and JSX with Oxc: move this `esbuild` option to `oxc` (jsx, define, include/exclude convert; `target` does not). Oxc does not lower native decorators; keep `lowerStage3Decorators()` in `plugins` for decorated modules."
156
+ });
157
+ if (/\bmanualChunks\s*:\s*\{/.test(text)) output.manual.push({
158
+ file,
159
+ line,
160
+ hint: "Vite 8 (Rolldown) removed the object form of `output.manualChunks`. Use `output.codeSplitting.groups` with `name` and `test`."
161
+ });
162
+ });
163
+ }
164
+ return output;
165
+ }
166
+ };
167
+
168
+ //#endregion
169
+ export { viteVitest };
170
+ //# sourceMappingURL=vite-vitest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-vitest.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/upgrade/codemods/vite-vitest.ts"],"sourcesContent":["import type { Codemod, CodemodOutput } from \"../types\";\nimport { parseVersion } from \"../version\";\nimport { manualAt, replaceNode } from \"./helpers\";\n\n/**\n * The newest releases that are at least two weeks old on the day 5.36 shipped;\n * `warlock add test` and `warlock add web` install the same ones.\n */\nconst TARGETS: Record<string, { major: number; version: string }> = {\n vite: { major: 8, version: \"8.3.3\" },\n vitest: { major: 5, version: \"5.0.3\" },\n \"@vitest/coverage-v8\": { major: 5, version: \"5.0.3\" },\n \"@vitest/coverage-istanbul\": { major: 5, version: \"5.0.3\" },\n \"@vitest/ui\": { major: 5, version: \"5.0.3\" },\n \"@vitejs/plugin-react\": { major: 6, version: \"6.1.2\" },\n};\n\nconst MIGRATION_GUIDES = \"https://vite.dev/guide/migration and https://vitest.dev/guide/migration\";\n\n/** Version specs that are not a plain registry range and must not be rewritten. */\nconst NON_REGISTRY_SPEC =\n /^(workspace:|file:|link:|portal:|git|github:|https?:|npm:)|^\\*$|^latest$/;\n\n/** Entry points Vitest 5 removed. `vitest/runners` is handled separately. */\nconst REMOVED_ENTRY_POINTS = new Set([\n \"vitest/coverage\",\n \"vitest/reporters\",\n \"vitest/environments\",\n \"vitest/snapshot\",\n \"vitest/suite\",\n \"vitest/mocker\",\n \"vitest/internal/module-runner\",\n]);\n\nconst VITE_CONFIG_FILE = /^vite(st)?\\.config\\.(ts|mts|cts|js|mjs|cjs)$/;\n\ntype PackageJson = Record<string, unknown> & {\n dependencies?: Record<string, unknown>;\n devDependencies?: Record<string, unknown>;\n overrides?: Record<string, unknown>;\n resolutions?: Record<string, unknown>;\n};\n\nfunction serialize(original: string, value: unknown): string {\n const indent = /^[ \\t]+(?=\")/m.exec(original)?.[0];\n const json = indent ? JSON.stringify(value, null, indent) : JSON.stringify(value);\n return json + (original.endsWith(\"\\n\") ? \"\\n\" : \"\");\n}\n\nfunction lineOf(source: string, needle: string): number {\n const index = source.indexOf(needle);\n return index < 0 ? 1 : source.slice(0, index).split(\"\\n\").length;\n}\n\n/** The spec moved to `target`, keeping its `^`/`~`/exact style; undefined when it already allows it. */\nfunction upgradedSpec(spec: string, target: { major: number; version: string }) {\n if (NON_REGISTRY_SPEC.test(spec)) return undefined;\n const parsed = parseVersion(spec);\n // `>=7.3.5 <8` and ranges like it are rewritten too: the lower bound is what is read.\n if (!parsed || parsed[0] >= target.major) return undefined;\n const prefix = /^[\\^~]/.exec(spec)?.[0] ?? (/^\\d/.test(spec) ? \"\" : \"^\");\n return `${prefix}${target.version}`;\n}\n\n/**\n * 5.36: Vite 8 (Rolldown), Vitest 5 and `@vitejs/plugin-react` 6 are one\n * family and must move together.\n *\n * - package.json: raises `vite`, `vitest`, `@vitest/*` and\n * `@vitejs/plugin-react` (dependencies, devDependencies, `overrides` and\n * `resolutions`) to the supported majors.\n * - `src/test-runner.ts`-style runners: Vitest 5 has no `vitest/runners`; the\n * runner class is `TestRunner`, exported from `vitest`, and\n * `onCleanupWorkerContext` is an optional property.\n * - Lists removed Vitest entry points, a top-level `esbuild` option, and object\n * `manualChunks` in Vite configs for review.\n */\nexport const viteVitest: Codemod = {\n id: \"vite-8-vitest-5\",\n version: \"5.36.0\",\n package: \"@warlock.js/core\",\n title: \"Move to Vite 8, Vitest 5 and @vitejs/plugin-react 6\",\n skill: \"upgrade-from-5-29\",\n run(context) {\n const { Node, SyntaxKind } = context.tsMorph;\n const output: CodemodOutput = { edits: [], manual: [] };\n\n const source = context.text.read(\"package.json\");\n if (source !== undefined) {\n const pkg = JSON.parse(source) as PackageJson;\n const changes: { name: string; from: string; to: string }[] = [];\n for (const field of [\n \"dependencies\",\n \"devDependencies\",\n \"overrides\",\n \"resolutions\",\n ] as const) {\n const entries = pkg[field];\n if (!entries) continue;\n for (const [name, target] of Object.entries(TARGETS)) {\n const spec = entries[name];\n if (typeof spec !== \"string\") continue;\n const next = upgradedSpec(spec, target);\n if (!next) continue;\n entries[name] = next;\n changes.push({\n name:\n field === \"dependencies\" || field === \"devDependencies\" ? name : `${field}.${name}`,\n from: spec,\n to: next,\n });\n }\n }\n const first = changes[0];\n if (first) {\n context.text.write(\"package.json\", serialize(source, pkg), {\n line: lineOf(source, `\"${first.name.replace(/^[a-z]+\\./, \"\")}\"`),\n description: changes\n .map((change) => `${change.name} ${change.from} → ${change.to}`)\n .join(\", \"),\n });\n }\n }\n\n for (const sourceFile of context.sourceFiles) {\n for (const declaration of sourceFile.getImportDeclarations()) {\n const moduleName = declaration.getModuleSpecifierValue();\n\n if (REMOVED_ENTRY_POINTS.has(moduleName)) {\n output.manual.push(\n manualAt(\n context,\n declaration,\n `Vitest 5 removed the \"${moduleName}\" entry point. Import from \"vitest\" or \"vitest/node\" instead; see ${MIGRATION_GUIDES}.`,\n ),\n );\n continue;\n }\n if (moduleName !== \"vitest/runners\") continue;\n\n const specifiers = declaration.getNamedImports();\n const runner = specifiers.find((specifier) => specifier.getName() === \"VitestTestRunner\");\n if (!runner || specifiers.length !== 1 || declaration.getDefaultImport()) {\n output.manual.push(\n manualAt(\n context,\n declaration,\n 'Vitest 5 removed \"vitest/runners\". Import the runner class as `TestRunner` from \"vitest\"; benchmark runners no longer exist.',\n ),\n );\n continue;\n }\n\n const alias = runner.getAliasNode()?.getText();\n output.edits.push(\n replaceNode(\n context,\n declaration,\n alias\n ? `import { TestRunner as ${alias} } from \"vitest\";`\n : 'import { TestRunner } from \"vitest\";',\n 'imported the runner as TestRunner from \"vitest\"',\n ),\n );\n\n if (!alias) {\n for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {\n if (identifier.getText() !== \"VitestTestRunner\") continue;\n if (identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration)) continue;\n output.edits.push(\n replaceNode(\n context,\n identifier,\n \"TestRunner\",\n \"renamed VitestTestRunner to TestRunner\",\n ),\n );\n }\n }\n\n for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {\n const callee = call.getExpression();\n if (\n !Node.isPropertyAccessExpression(callee) ||\n callee.getName() !== \"onCleanupWorkerContext\" ||\n call.getQuestionDotTokenNode()\n ) {\n continue;\n }\n output.edits.push(\n replaceNode(\n context,\n callee,\n `${callee.getText()}?.`,\n \"onCleanupWorkerContext is optional in Vitest 5 (absent in vm pools)\",\n ),\n );\n }\n }\n }\n\n for (const file of context.text.list()) {\n if (!VITE_CONFIG_FILE.test(file)) continue;\n const config = context.text.read(file);\n if (config === undefined) continue;\n const lines = config.split(\"\\n\");\n lines.forEach((text, index) => {\n const line = index + 1;\n if (/^\\s*esbuild\\s*:/.test(text)) {\n output.manual.push({\n file,\n line,\n hint: \"Vite 8 transforms TypeScript and JSX with Oxc: move this `esbuild` option to `oxc` (jsx, define, include/exclude convert; `target` does not). Oxc does not lower native decorators; keep `lowerStage3Decorators()` in `plugins` for decorated modules.\",\n });\n }\n if (/\\bmanualChunks\\s*:\\s*\\{/.test(text)) {\n output.manual.push({\n file,\n line,\n hint: \"Vite 8 (Rolldown) removed the object form of `output.manualChunks`. Use `output.codeSplitting.groups` with `name` and `test`.\",\n });\n }\n });\n }\n\n return output;\n },\n};\n"],"mappings":"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"}
@@ -4,6 +4,10 @@
4
4
  * operations. Printed so the rollout plans for them.
5
5
  */
6
6
  const UPGRADE_NOTES = [
7
+ {
8
+ version: "5.36.0",
9
+ text: "Vitest 5 changes defaults: `clearMocks` is true, `json`/`junit` reporters write to `.vitest/` instead of stdout, `-t` joins names with ` > `, nested `vi.mock`/`vi.hoisted` calls throw and unawaited `resolves`/`rejects` assertions fail the test. Vite 8 needs Node.js 20.19 or 22.12+ and bundles with Rolldown."
10
+ },
7
11
  {
8
12
  version: "5.34.0",
9
13
  text: "ESLint and Prettier support is gone in 5.34.0. Oxlint/Oxfmt is the only toolchain. The oxc-toolchain-removal step migrates remaining legacy configs/dependencies; review its manual findings. New/migrated templates enable correctness: error, which may report additional defects."
@@ -1 +1 @@
1
- {"version":3,"file":"notes.mjs","names":[],"sources":["../../../../../../../../packages/core/src/upgrade/notes.ts"],"sourcesContent":["import type { UpgradeNote } from \"./types\";\n\n/**\n * Breaking changes with nothing to edit in source: they change data or\n * operations. Printed so the rollout plans for them.\n */\nexport const UPGRADE_NOTES: readonly UpgradeNote[] = [\n {\n version: \"5.34.0\",\n text: \"ESLint and Prettier support is gone in 5.34.0. Oxlint/Oxfmt is the only toolchain. The oxc-toolchain-removal step migrates remaining legacy configs/dependencies; review its manual findings. New/migrated templates enable correctness: error, which may report additional defects.\",\n },\n {\n version: \"5.30.0\",\n text: \"Auth login throttle keys are hashed: counters and lockouts written by 5.29 are forgotten on upgrade (locked accounts and IPs unlock).\",\n },\n {\n version: \"5.30.0\",\n text: \"Image derivatives use a new cache layout: the first request re-renders each one. Delete the old two-level cache folders once the rollout is done.\",\n },\n];\n"],"mappings":";;;;;AAMA,MAAa,gBAAwC;CACnD;EACE,SAAS;EACT,MAAM;CACR;CACA;EACE,SAAS;EACT,MAAM;CACR;CACA;EACE,SAAS;EACT,MAAM;CACR;AACF"}
1
+ {"version":3,"file":"notes.mjs","names":[],"sources":["../../../../../../../../packages/core/src/upgrade/notes.ts"],"sourcesContent":["import type { UpgradeNote } from \"./types\";\n\n/**\n * Breaking changes with nothing to edit in source: they change data or\n * operations. Printed so the rollout plans for them.\n */\nexport const UPGRADE_NOTES: readonly UpgradeNote[] = [\n {\n version: \"5.36.0\",\n text: \"Vitest 5 changes defaults: `clearMocks` is true, `json`/`junit` reporters write to `.vitest/` instead of stdout, `-t` joins names with ` > `, nested `vi.mock`/`vi.hoisted` calls throw and unawaited `resolves`/`rejects` assertions fail the test. Vite 8 needs Node.js 20.19 or 22.12+ and bundles with Rolldown.\",\n },\n {\n version: \"5.34.0\",\n text: \"ESLint and Prettier support is gone in 5.34.0. Oxlint/Oxfmt is the only toolchain. The oxc-toolchain-removal step migrates remaining legacy configs/dependencies; review its manual findings. New/migrated templates enable correctness: error, which may report additional defects.\",\n },\n {\n version: \"5.30.0\",\n text: \"Auth login throttle keys are hashed: counters and lockouts written by 5.29 are forgotten on upgrade (locked accounts and IPs unlock).\",\n },\n {\n version: \"5.30.0\",\n text: \"Image derivatives use a new cache layout: the first request re-renders each one. Delete the old two-level cache folders once the rollout is done.\",\n },\n];\n"],"mappings":";;;;;AAMA,MAAa,gBAAwC;CACnD;EACE,SAAS;EACT,MAAM;CACR;CACA;EACE,SAAS;EACT,MAAM;CACR;CACA;EACE,SAAS;EACT,MAAM;CACR;CACA;EACE,SAAS;EACT,MAAM;CACR;AACF"}
@@ -1,5 +1,5 @@
1
- import { BudgetPage, BudgetViolation, BuildBudgetReport, BuildBudgets, PageBudget } from "../production/build-budgets.mjs";
1
+ import { BudgetPage, BudgetViolation, BuildBudgetReport, BuildBudgets, PageBudget, PayloadBudget } from "../production/build-budgets.mjs";
2
2
  import { WarlockConfig } from "./types.mjs";
3
3
  import { defineConfig } from "./define-config.mjs";
4
4
  import { WarlockConfigManager, isUnknownTsExtensionError, isUnresolvedConfigImportError, isUnsupportedTypeStrippingError, warlockConfigManager } from "./warlock-config.manager.mjs";
5
- export { type BudgetPage, type BudgetViolation, type BuildBudgetReport, type BuildBudgets, type PageBudget, WarlockConfig, WarlockConfigManager, defineConfig, isUnknownTsExtensionError, isUnresolvedConfigImportError, isUnsupportedTypeStrippingError, warlockConfigManager };
5
+ export { type BudgetPage, type BudgetViolation, type BuildBudgetReport, type BuildBudgets, type PageBudget, type PayloadBudget, WarlockConfig, WarlockConfigManager, defineConfig, isUnknownTsExtensionError, isUnresolvedConfigImportError, isUnsupportedTypeStrippingError, warlockConfigManager };
package/llms-full.txt CHANGED
@@ -4014,6 +4014,19 @@ router.get("/webhooks/status", webhookStatusController, { rateLimit: false });
4014
4014
  - The `send-response` topic — picking the right `response.<helper>()`.
4015
4015
  - The `warlock-conventions` topic — module layout, the `guarded` convention, path aliases.
4016
4016
 
4017
+ ## A named route the browser must never call — `clientVisible: false`
4018
+
4019
+ 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`:
4020
+
4021
+ ```ts
4022
+ router.post("/internal/reindex", reindexController, {
4023
+ name: "internal.reindex",
4024
+ clientVisible: false,
4025
+ });
4026
+ ```
4027
+
4028
+ 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.
4029
+
4017
4030
 
4018
4031
  ## report-framework-issue `@warlock.js/core/skills/report-framework-issue/SKILL.md`
4019
4032
 
@@ -8505,7 +8518,7 @@ and has no Warlock setup or HTTP global setup. `integration` discovers
8505
8518
  It uses the framework setup, HTTP global setup and a generated `src/test-runner.ts`.
8506
8519
  Keep pure service specs beside their source and mock repositories at the boundary.
8507
8520
  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.
8521
+ Vitest 5 or newer is required for the generated runner (Vitest 5 removed `vitest/runners`; the runner extends `TestRunner` from `vitest`).
8509
8522
 
8510
8523
  For a unit spec that needs configuration values, use:
8511
8524
 
@@ -8553,20 +8566,20 @@ Run with `npx vitest run --project integration`. Worker lifetime refuses watch,
8553
8566
  threads, isolated module graphs, or a missing runner with a message explaining
8554
8567
  the required settings. Keep `afterAll(teardownTest)` in the setup file: in worker
8555
8568
  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.
8569
+ runtime once. The runner uses `TestRunner.onCleanupWorkerContext`, verified
8570
+ in Vitest 5.0.3's `TestRunner` declaration, runner implementation and worker cleanup source.
8558
8571
  For an existing project, generate this runner without overwriting your config:
8559
8572
 
8560
8573
  ```ts title="src/test-runner.ts"
8561
8574
  import { configureTestWorker } from "@warlock.js/core/tests";
8562
- import { VitestTestRunner } from "vitest/runners";
8575
+ import { TestRunner } from "vitest";
8563
8576
 
8564
- export default class WarlockTestRunner extends VitestTestRunner {
8565
- public constructor(config: ConstructorParameters<typeof VitestTestRunner>[0]) {
8577
+ export default class WarlockTestRunner extends TestRunner {
8578
+ public constructor(config: ConstructorParameters<typeof TestRunner>[0]) {
8566
8579
  super(config);
8567
8580
  configureTestWorker(
8568
8581
  { pool: this.pool, watch: config.watch, isolate: config.isolate },
8569
- (cleanup) => this.onCleanupWorkerContext(cleanup),
8582
+ (cleanup) => this.onCleanupWorkerContext?.(cleanup),
8570
8583
  );
8571
8584
  }
8572
8585
  }
@@ -8585,7 +8598,7 @@ This trades module isolation for startup cost: application singletons, config
8585
8598
  mutations, mocks and module side effects can survive between files. Reset your
8586
8599
  own state in `resetBetweenFiles`, restore mocks, and keep connector selection
8587
8600
  consistent. Give each worker a distinct database/schema using
8588
- `process.env.VITEST_POOL_ID` when preparing test connections. Never drop a shared
8601
+ `process.env.VITEST_POOL_ID` (1-based since Vitest 5) when preparing test connections. Never drop a shared
8589
8602
  database from this hook while other workers are using it. The framework does not
8590
8603
  create a memory replica set or a cross-worker lock; those belong to the application's
8591
8604
  global setup. A shared HTTP server and a single replica set can contend on I/O,
@@ -9360,6 +9373,97 @@ one `#vessel` around `{children}`. Client navigation still swaps layout/page
9360
9373
  children while preserving the document root. Remove temporary explicit form IDs
9361
9374
  only when the application does not otherwise depend on those names.
9362
9375
 
9376
+ ## 5.35 to 5.36 per-page API routes
9377
+
9378
+ 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:
9379
+
9380
+ - A name written as a string literal, a constant or a template with a static prefix is found automatically.
9381
+ - 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`.
9382
+ - `npx warlock check` reports the call sites that need a declaration as `WL9019`.
9383
+
9384
+ 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.
9385
+
9386
+ 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.
9387
+
9388
+ ## 5.35 to 5.36 translation payloads
9389
+
9390
+ A page's hydration payload now carries only the translations its client code
9391
+ can reach, in route-dictionary (`locales.json`) and global-registry (`extend()`)
9392
+ apps alike. The groups every page uses (default `app` and `common`) arrive once
9393
+ per locale in a hashed, cached file that the page preloads. Nothing in a page's
9394
+ render changes: server output is the full dictionary, as before.
9395
+
9396
+ Run `warlock build`, then `warlock check`. A `WL9104` warning marks a page that
9397
+ renders a translation key computed at runtime. Declare the keys it renders in
9398
+ the page's setup file, for example
9399
+ `export const config = { translations: ["statuses.*", "members.title"] }`, so the
9400
+ page ships only what it uses.
9401
+
9402
+ In development, a warning names a key the page reads but its payload lacks, and
9403
+ resolves it from the full table so the page keeps working. In production the
9404
+ key renders as its own text and the miss is reported once through
9405
+ `onClientError` with `kind: "translations"`. Declare the key, or set
9406
+ `build: { translations: { prune: false } }` in the web connector options while migrating to
9407
+ restore the previous per-page payload.
9408
+
9409
+ Global-registry apps are covered too. Their shared table is served at
9410
+ `/__warlock/translations/<locale>.<hash>.json`, which is cached immutable in
9411
+ production. A direct `trans()` call from `@mongez/localization` is not checked
9412
+ for a pruning miss; the calls through `useTrans()` are.
9413
+
9414
+ ## 5.35 to 5.36 Vite 8 and Vitest 5
9415
+
9416
+ `@warlock.js/web` requires Vite 8 (`^8.0.0`) and `@vitejs/plugin-react` 6, and the
9417
+ test kit requires Vitest 5. Vite 8 bundles with Rolldown and transforms
9418
+ TypeScript with Oxc. Vite 7 is not supported; plugin-react 6 needs Vite 8, so the
9419
+ family moves together. Vite 8 needs Node.js 20.19 or 22.12 and newer, Vitest 5
9420
+ needs 22.12 or newer; Warlock already requires 22.15.
9421
+
9422
+ ```bash
9423
+ npx warlock upgrade --from 5.35 --to 5.36.0 --dry-run
9424
+ npx warlock upgrade --from 5.35 --to 5.36.0
9425
+ npm install
9426
+ npx warlock check
9427
+ ```
9428
+
9429
+ The `vite-8-vitest-5` codemod:
9430
+
9431
+ - raises `vite` (8.3.3), `vitest` and `@vitest/*` (5.0.3) and `@vitejs/plugin-react`
9432
+ (6.1.2) in `dependencies`, `devDependencies`, `overrides` and `resolutions`,
9433
+ keeping `^`, `~` or exact style;
9434
+ - rewrites a generated `src/test-runner.ts` from `VitestTestRunner` in
9435
+ `vitest/runners` to `TestRunner` from `vitest`, and calls
9436
+ `this.onCleanupWorkerContext?.(cleanup)` (the hook is absent in vm pools);
9437
+ - lists, without editing, imports of the removed `vitest/coverage`,
9438
+ `vitest/reporters`, `vitest/environments`, `vitest/snapshot`, `vitest/suite`,
9439
+ `vitest/mocker` and `vitest/internal/module-runner` entry points, other names
9440
+ imported from `vitest/runners`, a top-level `esbuild` option in a Vite or
9441
+ Vitest config, and object-form `output.manualChunks`.
9442
+
9443
+ Do these by hand when the codemod lists them:
9444
+
9445
+ - Move a config's `esbuild: { jsx: "automatic" }` to
9446
+ `oxc: { jsx: { runtime: "automatic" } }`. `esbuild.target` is not converted, and
9447
+ Oxc does not lower native decorators: keep `lowerStage3Decorators()` from
9448
+ `@warlock.js/core/vite` first in `plugins` for decorated modules, as in the
9449
+ generated test config.
9450
+ - Replace object `manualChunks` with
9451
+ `build.rolldownOptions.output.codeSplitting.groups`; `build.rollupOptions` still
9452
+ works but is deprecated in favour of `build.rolldownOptions`.
9453
+ - A custom bundler `external` list that names Node builtins is still refused by the
9454
+ client build.
9455
+
9456
+ Vitest 5 changes defaults that can alter a green suite: `clearMocks` is true,
9457
+ `json` and `junit` reporters write to `.vitest/` instead of stdout, `-t` joins a
9458
+ test's names with `>`, `VITEST_POOL_ID` and `VITEST_WORKER_ID` start at 1, nested
9459
+ `vi.mock`/`vi.hoisted` calls throw, and an unawaited `resolves` or `rejects`
9460
+ assertion fails the test. Vitest also no longer searches parent directories for a
9461
+ config file; run it from the package that owns the config. Official guides:
9462
+ https://vite.dev/guide/migration and https://vitest.dev/guide/migration.
9463
+
9464
+ A project that uses `@warlock.js/ai`'s custom matchers needs Vitest 5 for their
9465
+ TypeScript types.
9466
+
9363
9467
 
9364
9468
  ## upload-file `@warlock.js/core/skills/upload-file/SKILL.md`
9365
9469
 
@@ -10347,6 +10451,10 @@ workers, and registered module cleanup hooks, including after partial startup.
10347
10451
  A Node loader registration is process-scoped; its message channel is closed at
10348
10452
  teardown. Module-owned timers/listeners must register their cleanup hooks.
10349
10453
 
10454
+ ## Translation keys in pages (5.36)
10455
+
10456
+ `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.
10457
+
10350
10458
 
10351
10459
  ## use-middleware `@warlock.js/core/skills/use-middleware/SKILL.md`
10352
10460
 
@@ -12214,14 +12322,17 @@ and work through the manual-review list. Use `--lint=oxc` for new apps.
12214
12322
  The following codes default to **warnings**, so upgrading does not fail existing projects.
12215
12323
  They parse real TypeScript/JSX syntax with the same oxc-parser adapter as the existing conventions.
12216
12324
 
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. |
12325
+ | Code | Convention | Remedy |
12326
+ | -------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
12327
+ | `WL9013` | A component file exceeds 150 lines | Split components and extract hooks; configure `componentLineLimit`. |
12328
+ | `WL9014` | More than one component in a file | Move each component into its own TSX file. |
12329
+ | `WL9015` | State/effects or event-handler functions inside a component | Move `useState`, `useReducer`, `useEffect`, `useLayoutEffect` and handlers into `use-*.ts` hooks. |
12330
+ | `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/**`. |
12331
+ | `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. |
12332
+ | `WL9018` | Exported `RequestHandler` in `src/app/**/controllers` lacks `responseSchema` | Assign `handler.responseSchema` to document each response status. |
12333
+ | `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. |
12334
+ | `WL9020` | Browser code names a route registered with `clientVisible: false` (error) | Call the endpoint from the server, or remove `clientVisible: false` from the route. |
12335
+ | `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
12336
 
12226
12337
  Web rules apply to `src/web/**/*.tsx`. Components are top-level JSX-returning functions
12227
12338
  with a capitalized name (or an anonymous default export). Hook files and ordinary helpers
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.36.0",
29
+ "@warlock.js/cascade": "5.36.0",
30
+ "@warlock.js/context": "5.36.0",
31
+ "@warlock.js/fs": "5.36.0",
32
+ "@warlock.js/logger": "5.36.0",
33
+ "@warlock.js/seal": "5.36.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.36.0",
60
+ "@warlock.js/ai": "5.36.0",
61
+ "@warlock.js/ai-tools": "5.36.0",
62
+ "@warlock.js/devtools": "5.36.0",
63
+ "@warlock.js/herald": "5.36.0",
64
+ "@warlock.js/notifications": "5.36.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.36.0",
150
150
  "type": "module",
151
151
  "main": "./esm/index.mjs",
152
152
  "module": "./esm/index.mjs",
@@ -279,3 +279,16 @@ 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.
@@ -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,
@@ -552,3 +552,94 @@ Root components and providers execute in both realms. Move server-only work into
552
552
  one `#vessel` around `{children}`. Client navigation still swaps layout/page
553
553
  children while preserving the document root. Remove temporary explicit form IDs
554
554
  only when the application does not otherwise depend on those names.
555
+
556
+ ## 5.35 to 5.36 per-page API routes
557
+
558
+ 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:
559
+
560
+ - A name written as a string literal, a constant or a template with a static prefix is found automatically.
561
+ - 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`.
562
+ - `npx warlock check` reports the call sites that need a declaration as `WL9019`.
563
+
564
+ 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.
565
+
566
+ 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.
567
+
568
+ ## 5.35 to 5.36 translation payloads
569
+
570
+ A page's hydration payload now carries only the translations its client code
571
+ can reach, in route-dictionary (`locales.json`) and global-registry (`extend()`)
572
+ apps alike. The groups every page uses (default `app` and `common`) arrive once
573
+ per locale in a hashed, cached file that the page preloads. Nothing in a page's
574
+ render changes: server output is the full dictionary, as before.
575
+
576
+ Run `warlock build`, then `warlock check`. A `WL9104` warning marks a page that
577
+ renders a translation key computed at runtime. Declare the keys it renders in
578
+ the page's setup file, for example
579
+ `export const config = { translations: ["statuses.*", "members.title"] }`, so the
580
+ page ships only what it uses.
581
+
582
+ In development, a warning names a key the page reads but its payload lacks, and
583
+ resolves it from the full table so the page keeps working. In production the
584
+ key renders as its own text and the miss is reported once through
585
+ `onClientError` with `kind: "translations"`. Declare the key, or set
586
+ `build: { translations: { prune: false } }` in the web connector options while migrating to
587
+ restore the previous per-page payload.
588
+
589
+ Global-registry apps are covered too. Their shared table is served at
590
+ `/__warlock/translations/<locale>.<hash>.json`, which is cached immutable in
591
+ production. A direct `trans()` call from `@mongez/localization` is not checked
592
+ for a pruning miss; the calls through `useTrans()` are.
593
+
594
+ ## 5.35 to 5.36 Vite 8 and Vitest 5
595
+
596
+ `@warlock.js/web` requires Vite 8 (`^8.0.0`) and `@vitejs/plugin-react` 6, and the
597
+ test kit requires Vitest 5. Vite 8 bundles with Rolldown and transforms
598
+ TypeScript with Oxc. Vite 7 is not supported; plugin-react 6 needs Vite 8, so the
599
+ family moves together. Vite 8 needs Node.js 20.19 or 22.12 and newer, Vitest 5
600
+ needs 22.12 or newer; Warlock already requires 22.15.
601
+
602
+ ```bash
603
+ npx warlock upgrade --from 5.35 --to 5.36.0 --dry-run
604
+ npx warlock upgrade --from 5.35 --to 5.36.0
605
+ npm install
606
+ npx warlock check
607
+ ```
608
+
609
+ The `vite-8-vitest-5` codemod:
610
+
611
+ - raises `vite` (8.3.3), `vitest` and `@vitest/*` (5.0.3) and `@vitejs/plugin-react`
612
+ (6.1.2) in `dependencies`, `devDependencies`, `overrides` and `resolutions`,
613
+ keeping `^`, `~` or exact style;
614
+ - rewrites a generated `src/test-runner.ts` from `VitestTestRunner` in
615
+ `vitest/runners` to `TestRunner` from `vitest`, and calls
616
+ `this.onCleanupWorkerContext?.(cleanup)` (the hook is absent in vm pools);
617
+ - lists, without editing, imports of the removed `vitest/coverage`,
618
+ `vitest/reporters`, `vitest/environments`, `vitest/snapshot`, `vitest/suite`,
619
+ `vitest/mocker` and `vitest/internal/module-runner` entry points, other names
620
+ imported from `vitest/runners`, a top-level `esbuild` option in a Vite or
621
+ Vitest config, and object-form `output.manualChunks`.
622
+
623
+ Do these by hand when the codemod lists them:
624
+
625
+ - Move a config's `esbuild: { jsx: "automatic" }` to
626
+ `oxc: { jsx: { runtime: "automatic" } }`. `esbuild.target` is not converted, and
627
+ Oxc does not lower native decorators: keep `lowerStage3Decorators()` from
628
+ `@warlock.js/core/vite` first in `plugins` for decorated modules, as in the
629
+ generated test config.
630
+ - Replace object `manualChunks` with
631
+ `build.rolldownOptions.output.codeSplitting.groups`; `build.rollupOptions` still
632
+ works but is deprecated in favour of `build.rolldownOptions`.
633
+ - A custom bundler `external` list that names Node builtins is still refused by the
634
+ client build.
635
+
636
+ Vitest 5 changes defaults that can alter a green suite: `clearMocks` is true,
637
+ `json` and `junit` reporters write to `.vitest/` instead of stdout, `-t` joins a
638
+ test's names with `>`, `VITEST_POOL_ID` and `VITEST_WORKER_ID` start at 1, nested
639
+ `vi.mock`/`vi.hoisted` calls throw, and an unawaited `resolves` or `rejects`
640
+ assertion fails the test. Vitest also no longer searches parent directories for a
641
+ config file; run it from the package that owns the config. Official guides:
642
+ https://vite.dev/guide/migration and https://vitest.dev/guide/migration.
643
+
644
+ A project that uses `@warlock.js/ai`'s custom matchers needs Vitest 5 for their
645
+ TypeScript types.