@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
@@ -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":";;;;;;;;AAQA,MAAM,UAA8D;CAClE,MAAM;EAAE,OAAO;EAAG,SAAS;CAAQ;CACnC,QAAQ;EAAE,OAAO;EAAG,SAAS;CAAQ;CACrC,uBAAuB;EAAE,OAAO;EAAG,SAAS;CAAQ;CACpD,6BAA6B;EAAE,OAAO;EAAG,SAAS;CAAQ;CAC1D,cAAc;EAAE,OAAO;EAAG,SAAS;CAAQ;CAC3C,wBAAwB;EAAE,OAAO;EAAG,SAAS;CAAQ;AACvD;AAEA,MAAM,mBAAmB;;AAGzB,MAAM,oBACJ;;AAGF,MAAM,uCAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,mBAAmB;AASzB,SAAS,UAAU,UAAkB,OAAwB;CAC3D,MAAM,SAAS,gBAAgB,KAAK,QAAQ,CAAC,GAAG;CAEhD,QADa,SAAS,KAAK,UAAU,OAAO,MAAM,MAAM,IAAI,KAAK,UAAU,KAAK,MACjE,SAAS,SAAS,IAAI,IAAI,OAAO;AAClD;AAEA,SAAS,OAAO,QAAgB,QAAwB;CACtD,MAAM,QAAQ,OAAO,QAAQ,MAAM;CACnC,OAAO,QAAQ,IAAI,IAAI,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AAC5D;;AAGA,SAAS,aAAa,MAAc,QAA4C;CAC9E,IAAI,kBAAkB,KAAK,IAAI,GAAG,OAAO;CACzC,MAAM,SAAS,aAAa,IAAI;CAEhC,IAAI,CAAC,UAAU,OAAO,MAAM,OAAO,OAAO,OAAO;CAEjD,OAAO,GADQ,SAAS,KAAK,IAAI,CAAC,GAAG,OAAO,MAAM,KAAK,IAAI,IAAI,KAAK,OACjD,OAAO;AAC5B;;;;;;;;;;;;;;AAeA,MAAa,aAAsB;CACjC,IAAI;CACJ,SAAS;CACT,SAAS;CACT,OAAO;CACP,OAAO;CACP,IAAI,SAAS;EACX,MAAM,EAAE,MAAM,eAAe,QAAQ;EACrC,MAAM,SAAwB;GAAE,OAAO,CAAC;GAAG,QAAQ,CAAC;EAAE;EAEtD,MAAM,SAAS,QAAQ,KAAK,KAAK,cAAc;EAC/C,IAAI,WAAW,QAAW;GACxB,MAAM,MAAM,KAAK,MAAM,MAAM;GAC7B,MAAM,UAAwD,CAAC;GAC/D,KAAK,MAAM,SAAS;IAClB;IACA;IACA;IACA;GACF,GAAY;IACV,MAAM,UAAU,IAAI;IACpB,IAAI,CAAC,SAAS;IACd,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,OAAO,GAAG;KACpD,MAAM,OAAO,QAAQ;KACrB,IAAI,OAAO,SAAS,UAAU;KAC9B,MAAM,OAAO,aAAa,MAAM,MAAM;KACtC,IAAI,CAAC,MAAM;KACX,QAAQ,QAAQ;KAChB,QAAQ,KAAK;MACX,MACE,UAAU,kBAAkB,UAAU,oBAAoB,OAAO,GAAG,MAAM,GAAG;MAC/E,MAAM;MACN,IAAI;KACN,CAAC;IACH;GACF;GACA,MAAM,QAAQ,QAAQ;GACtB,IAAI,OACF,QAAQ,KAAK,MAAM,gBAAgB,UAAU,QAAQ,GAAG,GAAG;IACzD,MAAM,OAAO,QAAQ,IAAI,MAAM,KAAK,QAAQ,aAAa,EAAE,EAAE,EAAE;IAC/D,aAAa,QACV,KAAK,WAAW,GAAG,OAAO,KAAK,GAAG,OAAO,KAAK,KAAK,OAAO,IAAI,CAAC,CAC/D,KAAK,IAAI;GACd,CAAC;EAEL;EAEA,KAAK,MAAM,cAAc,QAAQ,aAC/B,KAAK,MAAM,eAAe,WAAW,sBAAsB,GAAG;GAC5D,MAAM,aAAa,YAAY,wBAAwB;GAEvD,IAAI,qBAAqB,IAAI,UAAU,GAAG;IACxC,OAAO,OAAO,KACZ,SACE,SACA,aACA,yBAAyB,WAAW,oEAAoE,iBAAiB,EAC3H,CACF;IACA;GACF;GACA,IAAI,eAAe,kBAAkB;GAErC,MAAM,aAAa,YAAY,gBAAgB;GAC/C,MAAM,SAAS,WAAW,MAAM,cAAc,UAAU,QAAQ,MAAM,kBAAkB;GACxF,IAAI,CAAC,UAAU,WAAW,WAAW,KAAK,YAAY,iBAAiB,GAAG;IACxE,OAAO,OAAO,KACZ,SACE,SACA,aACA,kIACF,CACF;IACA;GACF;GAEA,MAAM,QAAQ,OAAO,aAAa,CAAC,EAAE,QAAQ;GAC7C,OAAO,MAAM,KACX,YACE,SACA,aACA,QACI,0BAA0B,MAAM,qBAChC,0CACJ,mDACF,CACF;GAEA,IAAI,CAAC,OACH,KAAK,MAAM,cAAc,WAAW,qBAAqB,WAAW,UAAU,GAAG;IAC/E,IAAI,WAAW,QAAQ,MAAM,oBAAoB;IACjD,IAAI,WAAW,uBAAuB,WAAW,iBAAiB,GAAG;IACrE,OAAO,MAAM,KACX,YACE,SACA,YACA,cACA,wCACF,CACF;GACF;GAGF,KAAK,MAAM,QAAQ,WAAW,qBAAqB,WAAW,cAAc,GAAG;IAC7E,MAAM,SAAS,KAAK,cAAc;IAClC,IACE,CAAC,KAAK,2BAA2B,MAAM,KACvC,OAAO,QAAQ,MAAM,4BACrB,KAAK,wBAAwB,GAE7B;IAEF,OAAO,MAAM,KACX,YACE,SACA,QACA,GAAG,OAAO,QAAQ,EAAE,KACpB,qEACF,CACF;GACF;EACF;EAGF,KAAK,MAAM,QAAQ,QAAQ,KAAK,KAAK,GAAG;GACtC,IAAI,CAAC,iBAAiB,KAAK,IAAI,GAAG;GAClC,MAAM,SAAS,QAAQ,KAAK,KAAK,IAAI;GACrC,IAAI,WAAW,QAAW;GAE1B,AADc,OAAO,MAAM,IACvB,CAAC,CAAC,SAAS,MAAM,UAAU;IAC7B,MAAM,OAAO,QAAQ;IACrB,IAAI,kBAAkB,KAAK,IAAI,GAC7B,OAAO,OAAO,KAAK;KACjB;KACA;KACA,MAAM;IACR,CAAC;IAEH,IAAI,0BAA0B,KAAK,IAAI,GACrC,OAAO,OAAO,KAAK;KACjB;KACA;KACA,MAAM;IACR,CAAC;GAEL,CAAC;EACH;EAEA,OAAO;CACT;AACF"}
@@ -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,6 +1,7 @@
1
1
  import { config } from "../config/config-getter.mjs";
2
2
  import "../config/index.mjs";
3
3
  import { t } from "../http/middleware/inject-request-context.mjs";
4
+ import { translateSealRule } from "./translate-rule.mjs";
4
5
  import { databasePlugin } from "./plugins/database-plugin.mjs";
5
6
  import { filePlugin } from "./plugins/file-plugin.mjs";
6
7
  import { localizedPlugin } from "./plugins/localized-plugin.mjs";
@@ -14,15 +15,21 @@ import { configureSeal, getSealConfig, registerPlugin } from "@warlock.js/seal";
14
15
  * This file configures Seal to use Warlock's localization system
15
16
  */
16
17
  configureSeal({
17
- translateRule({ rule, attributes }) {
18
+ translateRule({ rule, attributes, context }) {
18
19
  const translateRule = config.key("validation.translateRule");
19
20
  if (translateRule) return translateRule({
20
21
  rule,
21
- attributes
22
+ attributes,
23
+ context
22
24
  });
23
25
  const translationKey = `${config.key("validation.translationGroup", "validation")}.${rule.name}`;
24
- const translation = t(translationKey, attributes);
25
- return translation === translationKey ? rule.errorMessage || rule.defaultErrorMessage : translation;
26
+ return translateSealRule({
27
+ rule,
28
+ attributes,
29
+ context,
30
+ key: translationKey,
31
+ translate: t
32
+ });
26
33
  },
27
34
  translateAttribute({ attribute, context, rule }) {
28
35
  const translateAttribute = config.key("validation.translateAttribute");
@@ -1 +1 @@
1
- {"version":3,"file":"init.mjs","names":[],"sources":["../../../../../../../../packages/core/src/validation/init.ts"],"sourcesContent":["/**\n * Initialize Seal with Warlock Framework Settings\n *\n * This file configures Seal to use Warlock's localization system\n */\n\n// Auto-register framework plugins\nimport { configureSeal, getSealConfig, registerPlugin } from \"@warlock.js/seal\";\nimport { config } from \"../config\";\nimport { t } from \"../http/middleware/inject-request-context\";\nimport { databasePlugin, filePlugin, localizedPlugin } from \"./plugins\";\n\n// Configure Seal to use Warlock's localization\nconfigureSeal({\n translateRule({ rule, attributes }) {\n const translateRule = config.key(\"validation.translateRule\");\n\n if (translateRule) {\n return translateRule({ rule, attributes });\n }\n\n const translationGroup = config.key(\"validation.translationGroup\", \"validation\");\n\n const translationKey = `${translationGroup}.${rule.name}`;\n const translation = t(translationKey, attributes);\n\n return translation === translationKey\n ? rule.errorMessage || rule.defaultErrorMessage\n : translation;\n },\n\n translateAttribute({ attribute, context, rule }) {\n const translateAttribute = config.key(\"validation.translateAttribute\");\n\n if (translateAttribute) {\n return translateAttribute({ attribute, context, rule });\n }\n\n const attributeGroup = config.key(\"validation.attributeGroup\") ?? \"attributes\";\n\n const translationKey = `${attributeGroup ? attributeGroup + \".\" : \"\"}${attribute}`;\n\n const output = t(translationKey, context.allValues);\n return output === translationKey ? attribute : output;\n },\n});\n\n// App config is not loaded yet when this module is imported, so resolve\n// firstErrorOnly lazily on every read instead of capturing it once.\nObject.defineProperty(getSealConfig(), \"firstErrorOnly\", {\n configurable: true,\n enumerable: true,\n get: () => config.key(\"validation.firstErrorOnly\", true),\n});\n\n// Register plugins to inject methods\nregisterPlugin(databasePlugin);\nregisterPlugin(filePlugin);\nregisterPlugin(localizedPlugin);\n"],"mappings":";;;;;;;;;;;;;;;AAaA,cAAc;CACZ,cAAc,EAAE,MAAM,cAAc;EAClC,MAAM,gBAAgB,OAAO,IAAI,0BAA0B;EAE3D,IAAI,eACF,OAAO,cAAc;GAAE;GAAM;EAAW,CAAC;EAK3C,MAAM,iBAAiB,GAFE,OAAO,IAAI,+BAA+B,YAE1B,EAAE,GAAG,KAAK;EACnD,MAAM,cAAc,EAAE,gBAAgB,UAAU;EAEhD,OAAO,gBAAgB,iBACnB,KAAK,gBAAgB,KAAK,sBAC1B;CACN;CAEA,mBAAmB,EAAE,WAAW,SAAS,QAAQ;EAC/C,MAAM,qBAAqB,OAAO,IAAI,+BAA+B;EAErE,IAAI,oBACF,OAAO,mBAAmB;GAAE;GAAW;GAAS;EAAK,CAAC;EAGxD,MAAM,iBAAiB,OAAO,IAAI,2BAA2B,KAAK;EAElE,MAAM,iBAAiB,GAAG,iBAAiB,iBAAiB,MAAM,KAAK;EAEvE,MAAM,SAAS,EAAE,gBAAgB,QAAQ,SAAS;EAClD,OAAO,WAAW,iBAAiB,YAAY;CACjD;AACF,CAAC;AAID,OAAO,eAAe,cAAc,GAAG,kBAAkB;CACvD,cAAc;CACd,YAAY;CACZ,WAAW,OAAO,IAAI,6BAA6B,IAAI;AACzD,CAAC;AAGD,eAAe,cAAc;AAC7B,eAAe,UAAU;AACzB,eAAe,eAAe"}
1
+ {"version":3,"file":"init.mjs","names":[],"sources":["../../../../../../../../packages/core/src/validation/init.ts"],"sourcesContent":["/**\n * Initialize Seal with Warlock Framework Settings\n *\n * This file configures Seal to use Warlock's localization system\n */\n\n// Auto-register framework plugins\nimport { configureSeal, getSealConfig, registerPlugin } from \"@warlock.js/seal\";\nimport { config } from \"../config\";\nimport { t } from \"../http/middleware/inject-request-context\";\nimport { translateSealRule } from \"./translate-rule\";\nimport { databasePlugin, filePlugin, localizedPlugin } from \"./plugins\";\n\n// Configure Seal to use Warlock's localization\nconfigureSeal({\n translateRule({ rule, attributes, context }) {\n const translateRule = config.key(\"validation.translateRule\");\n\n if (translateRule) {\n return translateRule({ rule, attributes, context });\n }\n\n const translationGroup = config.key(\"validation.translationGroup\", \"validation\");\n\n const translationKey = `${translationGroup}.${rule.name}`;\n\n return translateSealRule({ rule, attributes, context, key: translationKey, translate: t });\n },\n\n translateAttribute({ attribute, context, rule }) {\n const translateAttribute = config.key(\"validation.translateAttribute\");\n\n if (translateAttribute) {\n return translateAttribute({ attribute, context, rule });\n }\n\n const attributeGroup = config.key(\"validation.attributeGroup\") ?? \"attributes\";\n\n const translationKey = `${attributeGroup ? attributeGroup + \".\" : \"\"}${attribute}`;\n\n const output = t(translationKey, context.allValues);\n return output === translationKey ? attribute : output;\n },\n});\n\n// App config is not loaded yet when this module is imported, so resolve\n// firstErrorOnly lazily on every read instead of capturing it once.\nObject.defineProperty(getSealConfig(), \"firstErrorOnly\", {\n configurable: true,\n enumerable: true,\n get: () => config.key(\"validation.firstErrorOnly\", true),\n});\n\n// Register plugins to inject methods\nregisterPlugin(databasePlugin);\nregisterPlugin(filePlugin);\nregisterPlugin(localizedPlugin);\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,cAAc;CACZ,cAAc,EAAE,MAAM,YAAY,WAAW;EAC3C,MAAM,gBAAgB,OAAO,IAAI,0BAA0B;EAE3D,IAAI,eACF,OAAO,cAAc;GAAE;GAAM;GAAY;EAAQ,CAAC;EAKpD,MAAM,iBAAiB,GAFE,OAAO,IAAI,+BAA+B,YAE1B,EAAE,GAAG,KAAK;EAEnD,OAAO,kBAAkB;GAAE;GAAM;GAAY;GAAS,KAAK;GAAgB,WAAW;EAAE,CAAC;CAC3F;CAEA,mBAAmB,EAAE,WAAW,SAAS,QAAQ;EAC/C,MAAM,qBAAqB,OAAO,IAAI,+BAA+B;EAErE,IAAI,oBACF,OAAO,mBAAmB;GAAE;GAAW;GAAS;EAAK,CAAC;EAGxD,MAAM,iBAAiB,OAAO,IAAI,2BAA2B,KAAK;EAElE,MAAM,iBAAiB,GAAG,iBAAiB,iBAAiB,MAAM,KAAK;EAEvE,MAAM,SAAS,EAAE,gBAAgB,QAAQ,SAAS;EAClD,OAAO,WAAW,iBAAiB,YAAY;CACjD;AACF,CAAC;AAID,OAAO,eAAe,cAAc,GAAG,kBAAkB;CACvD,cAAc;CACd,YAAY;CACZ,WAAW,OAAO,IAAI,6BAA6B,IAAI;AACzD,CAAC;AAGD,eAAe,cAAc;AAC7B,eAAe,UAAU;AACzB,eAAe,eAAe"}
@@ -0,0 +1,63 @@
1
+ import { environment } from "../utils/environment.mjs";
2
+ import { log } from "@warlock.js/logger";
3
+
4
+ //#region ../../packages/core/src/validation/translate-rule.ts
5
+ const PLACEHOLDER = /:([a-zA-Z_]+)/g;
6
+ /**
7
+ * Placeholder names other validation message sets use for the values seal
8
+ * exposes under a different name. Keyed by rule name; each entry maps the
9
+ * alias to the seal attribute it reads.
10
+ */
11
+ const PLACEHOLDER_ALIASES = {
12
+ minLength: { length: "minLength" },
13
+ maxLength: { length: "maxLength" },
14
+ strongMinLength: { length: "minLength" },
15
+ match: { matchingInput: "field" }
16
+ };
17
+ const warnedKeys = /* @__PURE__ */ new Set();
18
+ /** Seal attributes plus the alias names other message sets use for them. */
19
+ function withPlaceholderAliases(ruleName, attributes) {
20
+ const aliases = PLACEHOLDER_ALIASES[ruleName];
21
+ if (!aliases) return attributes;
22
+ const output = { ...attributes };
23
+ for (const [alias, source] of Object.entries(aliases)) if (!(alias in output) && source in attributes) output[alias] = attributes[source];
24
+ return output;
25
+ }
26
+ /** The `:placeholder` names in a message that `attributes` cannot fill. */
27
+ function unfilledPlaceholders(message, attributes) {
28
+ const names = /* @__PURE__ */ new Set();
29
+ for (const match of message.matchAll(PLACEHOLDER)) {
30
+ const name = match[1];
31
+ if (name !== void 0 && !(name in attributes)) names.add(name);
32
+ }
33
+ return [...names];
34
+ }
35
+ function warnUnfilled(key, message, names) {
36
+ if (environment() !== "development" || warnedKeys.has(key)) return;
37
+ warnedKeys.add(key);
38
+ log.warn("validation", "placeholder", `Validation message for "${key}" still contains ${names.map((name) => `:${name}`).join(", ")} after interpolation: "${message}". Check the translation placeholders against the rule's parameters.`);
39
+ }
40
+ /**
41
+ * Seal's default rule translation for Warlock. A translation is used only when
42
+ * every `:placeholder` in it can be filled from the rule's parameters (after
43
+ * mapping the alias names other message sets use, such as `:length` for
44
+ * `:maxLength`). Otherwise seal's own message is used, so a message written
45
+ * for a different library (for example the browser form defaults that share the
46
+ * process-wide translation store) never reaches an API response with a raw
47
+ * placeholder in it. In development, the first time a key falls back this way
48
+ * a warning names the key.
49
+ */
50
+ function translateSealRule({ rule, attributes, key, translate }) {
51
+ const own = rule.errorMessage || rule.defaultErrorMessage || key;
52
+ const aliased = withPlaceholderAliases(rule.name, attributes);
53
+ const translation = translate(key, aliased);
54
+ if (translation === key) return own;
55
+ const leftover = unfilledPlaceholders(translation, aliased);
56
+ if (leftover.length === 0) return translation;
57
+ warnUnfilled(key, translation, leftover);
58
+ return own;
59
+ }
60
+
61
+ //#endregion
62
+ export { translateSealRule, withPlaceholderAliases };
63
+ //# sourceMappingURL=translate-rule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate-rule.mjs","names":[],"sources":["../../../../../../../../packages/core/src/validation/translate-rule.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport type { RuleTranslation } from \"@warlock.js/seal\";\nimport { environment } from \"../utils/environment\";\n\nconst PLACEHOLDER = /:([a-zA-Z_]+)/g;\n\n/**\n * Placeholder names other validation message sets use for the values seal\n * exposes under a different name. Keyed by rule name; each entry maps the\n * alias to the seal attribute it reads.\n */\nconst PLACEHOLDER_ALIASES: Record<string, Record<string, string>> = {\n minLength: { length: \"minLength\" },\n maxLength: { length: \"maxLength\" },\n strongMinLength: { length: \"minLength\" },\n match: { matchingInput: \"field\" },\n};\n\nconst warnedKeys = new Set<string>();\n\n/** Test-only: forget which keys already produced a warning. */\nexport function resetUnfilledPlaceholderWarningsForTests(): void {\n warnedKeys.clear();\n}\n\n/** Seal attributes plus the alias names other message sets use for them. */\nexport function withPlaceholderAliases(\n ruleName: string,\n attributes: RuleTranslation[\"attributes\"],\n): RuleTranslation[\"attributes\"] {\n const aliases = PLACEHOLDER_ALIASES[ruleName];\n\n if (!aliases) return attributes;\n\n const output = { ...attributes };\n\n for (const [alias, source] of Object.entries(aliases)) {\n if (!(alias in output) && source in attributes) {\n output[alias] = attributes[source];\n }\n }\n\n return output;\n}\n\n/** The `:placeholder` names in a message that `attributes` cannot fill. */\nfunction unfilledPlaceholders(message: string, attributes: Record<string, unknown>): string[] {\n const names = new Set<string>();\n\n for (const match of message.matchAll(PLACEHOLDER)) {\n const name = match[1];\n\n if (name !== undefined && !(name in attributes)) names.add(name);\n }\n\n return [...names];\n}\n\nfunction warnUnfilled(key: string, message: string, names: string[]): void {\n if (environment() !== \"development\" || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n\n log.warn(\n \"validation\",\n \"placeholder\",\n `Validation message for \"${key}\" still contains ${names\n .map((name) => `:${name}`)\n .join(\", \")} after interpolation: \"${message}\". ` +\n \"Check the translation placeholders against the rule's parameters.\",\n );\n}\n\nexport type TranslateSealRuleOptions = RuleTranslation & {\n /** Translation key to look up, e.g. `validation.maxLength`. */\n key: string;\n /** Translates the key with the given placeholders (the request-aware `t`). */\n translate: (key: string, placeholders: Record<string, unknown>) => string;\n};\n\n/**\n * Seal's default rule translation for Warlock. A translation is used only when\n * every `:placeholder` in it can be filled from the rule's parameters (after\n * mapping the alias names other message sets use, such as `:length` for\n * `:maxLength`). Otherwise seal's own message is used, so a message written\n * for a different library (for example the browser form defaults that share the\n * process-wide translation store) never reaches an API response with a raw\n * placeholder in it. In development, the first time a key falls back this way\n * a warning names the key.\n */\nexport function translateSealRule({\n rule,\n attributes,\n key,\n translate,\n}: TranslateSealRuleOptions): string {\n const own = rule.errorMessage || rule.defaultErrorMessage || key;\n const aliased = withPlaceholderAliases(rule.name, attributes);\n const translation = translate(key, aliased);\n\n if (translation === key) return own;\n\n const leftover = unfilledPlaceholders(translation, aliased);\n\n if (leftover.length === 0) return translation;\n\n warnUnfilled(key, translation, leftover);\n\n return own;\n}\n"],"mappings":";;;;AAIA,MAAM,cAAc;;;;;;AAOpB,MAAM,sBAA8D;CAClE,WAAW,EAAE,QAAQ,YAAY;CACjC,WAAW,EAAE,QAAQ,YAAY;CACjC,iBAAiB,EAAE,QAAQ,YAAY;CACvC,OAAO,EAAE,eAAe,QAAQ;AAClC;AAEA,MAAM,6BAAa,IAAI,IAAY;;AAQnC,SAAgB,uBACd,UACA,YAC+B;CAC/B,MAAM,UAAU,oBAAoB;CAEpC,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,SAAS,EAAE,GAAG,WAAW;CAE/B,KAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,OAAO,GAClD,IAAI,EAAE,SAAS,WAAW,UAAU,YAClC,OAAO,SAAS,WAAW;CAI/B,OAAO;AACT;;AAGA,SAAS,qBAAqB,SAAiB,YAA+C;CAC5F,MAAM,wBAAQ,IAAI,IAAY;CAE9B,KAAK,MAAM,SAAS,QAAQ,SAAS,WAAW,GAAG;EACjD,MAAM,OAAO,MAAM;EAEnB,IAAI,SAAS,UAAa,EAAE,QAAQ,aAAa,MAAM,IAAI,IAAI;CACjE;CAEA,OAAO,CAAC,GAAG,KAAK;AAClB;AAEA,SAAS,aAAa,KAAa,SAAiB,OAAuB;CACzE,IAAI,YAAY,MAAM,iBAAiB,WAAW,IAAI,GAAG,GAAG;CAE5D,WAAW,IAAI,GAAG;CAElB,IAAI,KACF,cACA,eACA,2BAA2B,IAAI,mBAAmB,MAC/C,KAAK,SAAS,IAAI,MAAM,CAAC,CACzB,KAAK,IAAI,EAAE,yBAAyB,QAAQ,qEAEjD;AACF;;;;;;;;;;;AAmBA,SAAgB,kBAAkB,EAChC,MACA,YACA,KACA,aACmC;CACnC,MAAM,MAAM,KAAK,gBAAgB,KAAK,uBAAuB;CAC7D,MAAM,UAAU,uBAAuB,KAAK,MAAM,UAAU;CAC5D,MAAM,cAAc,UAAU,KAAK,OAAO;CAE1C,IAAI,gBAAgB,KAAK,OAAO;CAEhC,MAAM,WAAW,qBAAqB,aAAa,OAAO;CAE1D,IAAI,SAAS,WAAW,GAAG,OAAO;CAElC,aAAa,KAAK,aAAa,QAAQ;CAEvC,OAAO;AACT"}
@@ -1,5 +1,6 @@
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
+ import { ReleaseCheck, ReleaseConfig } from "../release/types.mjs";
2
3
  import { WarlockConfig } from "./types.mjs";
3
4
  import { defineConfig } from "./define-config.mjs";
4
5
  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 };
6
+ export { type BudgetPage, type BudgetViolation, type BuildBudgetReport, type BuildBudgets, type PageBudget, type PayloadBudget, type ReleaseCheck, type ReleaseConfig, WarlockConfig, WarlockConfigManager, defineConfig, isUnknownTsExtensionError, isUnresolvedConfigImportError, isUnsupportedTypeStrippingError, warlockConfigManager };
@@ -1,4 +1,5 @@
1
1
  import { BuildBudgets } from "../production/build-budgets.mjs";
2
+ import { ReleaseConfig } from "../release/types.mjs";
2
3
  import { BuiltInHealthCheckerOptions, FileHealthCheckerContract } from "../dev-server/health-checker/file-health-checker.contract.mjs";
3
4
  import { Connector } from "../connectors/types.mjs";
4
5
  import { CLICommand } from "../commands/cli-command.mjs";
@@ -270,6 +271,13 @@ export type WarlockConfig = {
270
271
  */
271
272
  migrations?: Array<MigrationConstructor>;
272
273
  };
274
+ /**
275
+ * Release pipeline settings for `warlock release`: candidate, gate,
276
+ * migration proof, stage, activate, verify and rollback.
277
+ *
278
+ * @see ReleaseConfig
279
+ */
280
+ release?: ReleaseConfig;
273
281
  /**
274
282
  * Testing configuration
275
283
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/warlock-config/types.ts"],"mappings":";;;;;;;;;;;;YAeY;;;;EAIV;;;;IAIE;;;;IAIA;;;;IAIA;;;;;;;;;;;;;EAcF,QAAQ,KAAK;;;;;;IAMX;;IAEA,UAAU;;;;;;;;;;;IAWV;;;;;;IAMA;;;;;;;IAOA;;;;;;;;;;;;;;;;;IAiBA;;;;;;;;;;;;;;;;IAgBA;;;;;;IAMA;;;;;;IAMA;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BF,aAAa;;;;EAKb;IACE,WAAW;;;;;EAMb;;;;IAIE;;;;;;MAME;;;;;;MAMA;;;;;;IAMF,iBAAiB,8BAA8B;;;;;IAK/C;;;;;;;;;IASA;;;;;;;;;IASA;;;;;;;;;;IAUA;;;;;;;;;;;;IAYA;;;;;;;;;;IAUA;;;;;;;;;IASA;;MAIM;;MAEA;;MAEA;;;;;;EAOR;;;;;;;;;;;;;;;;;;IAkBE,aAAa,MAAM;;;;;;;;EASrB;;;;;;;;;IASE;;;;;;;;IASA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/warlock-config/types.ts"],"mappings":";;;;;;;;;;;;;YAgBY;;;;EAIV;;;;IAIE;;;;IAIA;;;;IAIA;;;;;;;;;;;;;EAcF,QAAQ,KAAK;;;;;;IAMX;;IAEA,UAAU;;;;;;;;;;;IAWV;;;;;;IAMA;;;;;;;IAOA;;;;;;;;;;;;;;;;;IAiBA;;;;;;;;;;;;;;;;IAgBA;;;;;;IAMA;;;;;;IAMA;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BF,aAAa;;;;EAKb;IACE,WAAW;;;;;EAMb;;;;IAIE;;;;;;MAME;;;;;;MAMA;;;;;;IAMF,iBAAiB,8BAA8B;;;;;IAK/C;;;;;;;;;IASA;;;;;;;;;IASA;;;;;;;;;;IAUA;;;;;;;;;;;;IAYA;;;;;;;;;;IAUA;;;;;;;;;IASA;;MAIM;;MAEA;;MAEA;;;;;;EAOR;;;;;;;;;;;;;;;;;;IAkBE,aAAa,MAAM;;;;;;;;EASrB,UAAU;;;;;;;EAQV;;;;;;;;;IASE;;;;;;;;IASA"}