@ykdz/template 0.0.35 → 0.0.36

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 (80) hide show
  1. package/dist/application.d.ts +2 -1
  2. package/dist/application.js +64 -75
  3. package/dist/main.js +6 -2
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +968 -364
  6. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
  7. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
  8. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
  9. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
  10. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
  11. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
  12. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
  15. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +0 -1
  16. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
  17. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
  18. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
  19. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
  20. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
  21. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +53 -25
  22. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
  23. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -7
  24. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
  25. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -20
  26. package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
  27. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
  28. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
  29. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
  30. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
  31. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
  32. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
  33. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
  34. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
  37. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
  38. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
  39. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
  40. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
  41. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
  42. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
  43. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
  44. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
  45. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
  46. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
  47. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
  48. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
  49. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
  50. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
  51. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
  52. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
  53. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
  54. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
  55. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
  56. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
  57. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
  58. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
  59. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
  60. package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
  61. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
  62. package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
  63. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
  64. package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
  65. package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
  66. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
  67. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
  68. package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
  69. package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
  70. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
  71. package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
  72. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
  73. package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
  74. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
  75. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
  76. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
  77. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
  78. package/package.json +5 -5
  79. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
  80. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
@@ -24,18 +24,6 @@ export function resolveTemplateSource(source, relativePath) {
24
24
  }
25
25
  return resolveContainedPath(root, relativePath);
26
26
  }
27
- function expandTemplatePath(templatePath, variables) {
28
- return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, name) => {
29
- const value = variables[name];
30
- if (!value) {
31
- throw new Error(`Missing renderer variable: ${name}`);
32
- }
33
- if (!/^[A-Za-z0-9._-]+$/.test(value)) {
34
- throw new Error(`Renderer variable ${name} is not safe for a path segment`);
35
- }
36
- return value;
37
- });
38
- }
39
27
  function resolveContainedPath(root, relativePath) {
40
28
  if (path.isAbsolute(relativePath)) {
41
29
  throw new Error(`Renderer paths must be relative: ${relativePath}`);
@@ -49,13 +37,9 @@ function resolveContainedPath(root, relativePath) {
49
37
  }
50
38
  return resolvedPath;
51
39
  }
52
- function expandOperationPath(relativePath, options) {
53
- return expandTemplatePath(relativePath, options.variables ?? {});
54
- }
55
40
  async function renderCopyFile(operation, options) {
56
- const variables = options.variables ?? {};
57
- const from = resolveTemplateSource(operation.source, expandTemplatePath(operation.from, variables));
58
- const to = resolveContainedPath(options.targetRoot, expandTemplatePath(operation.to, variables));
41
+ const from = resolveTemplateSource(operation.source, operation.from);
42
+ const to = resolveContainedPath(options.targetRoot, operation.to);
59
43
  const sourceMode = (await stat(from)).mode;
60
44
  await mkdir(path.dirname(to), { recursive: true });
61
45
  await copyGeneratedFile(from, to, operation.overwrite ?? false);
@@ -126,21 +110,20 @@ async function writeJsonFile(targetRoot, toPath, value, multilineArrays, keyOrde
126
110
  : new Map(nestedKeyOrder.map((key, index) => [key, index]))), overwrite);
127
111
  }
128
112
  async function renderWriteJson(operation, options) {
129
- await writeJsonFile(options.targetRoot, expandOperationPath(operation.to, options), operation.value, operation.multilineArrays, operation.keyOrder, operation.nestedKeyOrder, operation.overwrite);
113
+ await writeJsonFile(options.targetRoot, operation.to, operation.value, operation.multilineArrays, operation.keyOrder, operation.nestedKeyOrder, operation.overwrite);
130
114
  }
131
115
  async function renderMergeJson(operation, options) {
132
116
  await mergeJsonIntoFile({
133
117
  options,
134
118
  patch: operation.value,
135
- toPath: expandOperationPath(operation.to, options),
119
+ toPath: operation.to,
136
120
  multilineArrays: operation.multilineArrays,
137
121
  keyOrder: operation.keyOrder,
138
122
  nestedKeyOrder: operation.nestedKeyOrder,
139
123
  });
140
124
  }
141
125
  async function renderMergeJsonTemplate(operation, options) {
142
- const variables = options.variables ?? {};
143
- const sourcePath = resolveTemplateSource(operation.source, expandTemplatePath(operation.from, variables));
126
+ const sourcePath = resolveTemplateSource(operation.source, operation.from);
144
127
  let patch;
145
128
  try {
146
129
  patch = JSON.parse(await readFile(sourcePath, "utf8"));
@@ -151,7 +134,7 @@ async function renderMergeJsonTemplate(operation, options) {
151
134
  await mergeJsonIntoFile({
152
135
  options,
153
136
  patch,
154
- toPath: expandOperationPath(operation.to, options),
137
+ toPath: operation.to,
155
138
  multilineArrays: operation.multilineArrays,
156
139
  keyOrder: operation.keyOrder,
157
140
  nestedKeyOrder: operation.nestedKeyOrder,
@@ -171,16 +154,14 @@ async function mergeJsonIntoFile(options) {
171
154
  await writeJsonFile(options.options.targetRoot, options.toPath, mergeJsonValue(existing, options.patch), options.multilineArrays, options.keyOrder, options.nestedKeyOrder, true);
172
155
  }
173
156
  async function renderWriteText(operation, options) {
174
- const toPath = expandOperationPath(operation.to, options);
175
- const to = resolveContainedPath(options.targetRoot, toPath);
157
+ const to = resolveContainedPath(options.targetRoot, operation.to);
176
158
  await mkdir(path.dirname(to), { recursive: true });
177
159
  await writeGeneratedFile(to, operation.text);
178
160
  }
179
- async function loadTextFragments(operation, options) {
161
+ async function loadTextFragments(operation) {
180
162
  return Promise.all(operation.fragments.map(async (fragment) => {
181
- const fromPath = expandTemplatePath(fragment.from, options.variables ?? {});
182
- const from = resolveTemplateSource(fragment.source, fromPath);
183
- return { from: fromPath, text: await readFile(from, "utf8") };
163
+ const from = resolveTemplateSource(fragment.source, fragment.from);
164
+ return { from: fragment.from, text: await readFile(from, "utf8") };
184
165
  }));
185
166
  }
186
167
  async function validateDevelopmentContainerDockerfileOperation(operation, fragments) {
@@ -212,16 +193,15 @@ async function preflightDevelopmentContainerDockerfiles(options) {
212
193
  operation.validation !== "development-container-dockerfile") {
213
194
  continue;
214
195
  }
215
- const fragments = await loadTextFragments(operation, options);
196
+ const fragments = await loadTextFragments(operation);
216
197
  await validateDevelopmentContainerDockerfileOperation(operation, fragments);
217
198
  loadedFragments.set(operation, fragments);
218
199
  }
219
200
  return loadedFragments;
220
201
  }
221
202
  async function renderWriteTextFromFragments(operation, options, preloadedFragments) {
222
- const toPath = expandOperationPath(operation.to, options);
223
- const to = resolveContainedPath(options.targetRoot, toPath);
224
- const fragments = preloadedFragments ?? (await loadTextFragments(operation, options));
203
+ const to = resolveContainedPath(options.targetRoot, operation.to);
204
+ const fragments = preloadedFragments ?? (await loadTextFragments(operation));
225
205
  await validateDevelopmentContainerDockerfileOperation(operation, fragments);
226
206
  await mkdir(path.dirname(to), { recursive: true });
227
207
  await writeGeneratedFile(to, fragments.map(({ text }) => text.trimEnd()).join("\n\n") + "\n", operation.overwrite ?? false);
@@ -244,15 +224,14 @@ function replaceTextTemplateVariables(sourceText, replacements) {
244
224
  return rendered;
245
225
  }
246
226
  async function renderWriteTextTemplate(operation, options) {
247
- const toPath = expandOperationPath(operation.to, options);
248
- const from = resolveTemplateSource(operation.source, expandTemplatePath(operation.from, options.variables ?? {}));
249
- const to = resolveContainedPath(options.targetRoot, toPath);
227
+ const from = resolveTemplateSource(operation.source, operation.from);
228
+ const to = resolveContainedPath(options.targetRoot, operation.to);
250
229
  const sourceText = await readFile(from, "utf8");
251
230
  await mkdir(path.dirname(to), { recursive: true });
252
231
  await writeGeneratedFile(to, replaceTextTemplateVariables(sourceText, operation.replacements), operation.overwrite);
253
232
  }
254
233
  async function renderSetExecutable(operation, options) {
255
- const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
234
+ const filePath = resolveContainedPath(options.targetRoot, operation.path);
256
235
  const currentMode = (await stat(filePath)).mode;
257
236
  const executeBits = 0o111;
258
237
  const mode = operation.executable
@@ -314,7 +293,7 @@ async function renderReplaceAnchors(operation, options) {
314
293
  if (operation.language !== "typescript") {
315
294
  throw new Error("Checked Transform Anchor only supports TypeScript");
316
295
  }
317
- const filePath = resolveContainedPath(options.targetRoot, expandOperationPath(operation.path, options));
296
+ const filePath = resolveContainedPath(options.targetRoot, operation.path);
318
297
  const sourceText = await readFile(filePath, "utf8");
319
298
  const ranges = await findTypeScriptAnchorRanges(sourceText);
320
299
  for (const anchorName of Object.keys(operation.replacements)) {
@@ -1,4 +1,4 @@
1
- import type { ProjectBlueprintV2 } from "./project-blueprint-v2.ts";
1
+ import type { ProjectBlueprint } from "./project-blueprint.ts";
2
2
  import { type RenderOperation } from "./renderer.ts";
3
3
  export type TemplateBoundaryViolation = {
4
4
  readonly preset: string;
@@ -43,7 +43,7 @@ export type CheckTemplateSourceBoundaryOptions = {
43
43
  readonly planningContribution?: string;
44
44
  readonly generatedPath: ".github/workflows/check.yml";
45
45
  /** The original Blueprint, before Foundation composes workflow facts. */
46
- readonly blueprint: ProjectBlueprintV2;
46
+ readonly blueprint: ProjectBlueprint;
47
47
  /** Raw Package Contribution declarations, not the composed plan field. */
48
48
  readonly diagnosticArtifactDeclarations: readonly unknown[];
49
49
  readonly operations: readonly RenderOperation[];
@@ -70,6 +70,12 @@ export function isProtectedGeneratedPath(generatedPath) {
70
70
  normalizedPath === ".github/dependabot.yml" ||
71
71
  normalizedPath.startsWith(".github/workflows/") ||
72
72
  normalizedPath.startsWith(".vscode/") ||
73
+ normalizedPath.startsWith("scripts/npm-publication/") ||
74
+ normalizedPath.startsWith("scripts/npm-publication-setup/") ||
75
+ fileName === "CHANGELOG.md" ||
76
+ fileName === "LICENSE" ||
77
+ fileName === "README.md" ||
78
+ fileName === "RELEASING.md" ||
73
79
  fileName === "oxfmt.config.ts" ||
74
80
  fileName === "oxlint.config.ts" ||
75
81
  fileName === "playwright.config.ts" ||
@@ -32,4 +32,6 @@ export type OnlineToolchainResolutionContractResult = {
32
32
  };
33
33
  export declare function resolveToolchainVersions(options?: ResolveToolchainVersionsOptions): Promise<ResolvedToolchainVersions>;
34
34
  export declare function checkOnlineToolchainResolutionContract(options?: OnlineToolchainResolutionContractOptions): Promise<OnlineToolchainResolutionContractResult>;
35
+ export declare function isValidNodeLtsMajor(value: string): boolean;
36
+ export declare function isValidPackageManagerPin(value: string): boolean;
35
37
  export {};
@@ -2,7 +2,7 @@ export const nodeReleaseIndexUrl = "https://nodejs.org/dist/index.json";
2
2
  export const pnpmRegistryUrl = "https://registry.npmjs.org/pnpm";
3
3
  const bundledFallbackToolchain = {
4
4
  nodeLtsMajor: nodeLtsMajor("24"),
5
- packageManagerPin: packageManagerPin("11.11.0"),
5
+ packageManagerPin: packageManagerPin("11.21.0"),
6
6
  };
7
7
  export async function resolveToolchainVersions(options = {}) {
8
8
  const source = options.source ?? "online";
@@ -66,14 +66,20 @@ function fallbackResult(diagnostics) {
66
66
  diagnostics,
67
67
  };
68
68
  }
69
+ export function isValidNodeLtsMajor(value) {
70
+ return /^\d+$/.test(value);
71
+ }
72
+ export function isValidPackageManagerPin(value) {
73
+ return /^pnpm@\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
74
+ }
69
75
  function nodeLtsMajor(value) {
70
- if (!/^\d+$/.test(value)) {
76
+ if (!isValidNodeLtsMajor(value)) {
71
77
  throw new Error(`Invalid Node LTS major: ${value}`);
72
78
  }
73
79
  return { kind: "NodeLtsMajor", value };
74
80
  }
75
81
  function packageManagerPin(version) {
76
- if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version)) {
82
+ if (!isValidPackageManagerPin(`pnpm@${version}`)) {
77
83
  throw new Error(`Invalid pnpm version: ${version}`);
78
84
  }
79
85
  return { kind: "PackageManagerPin", value: `pnpm@${version}` };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykdz/template",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "private": false,
5
5
  "description": "Project template generator for YKDZ starter projects.",
6
6
  "keywords": [
@@ -60,16 +60,16 @@
60
60
  "semver": "^7.8.5",
61
61
  "typescript": "npm:typescript@^6.0.3",
62
62
  "valibot": "^1.4.2",
63
- "@ykdz/template-core": "0.0.0",
64
- "@ykdz/template-builtin-presets": "0.0.0"
63
+ "@ykdz/template-builtin-presets": "0.0.0",
64
+ "@ykdz/template-core": "0.0.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^24.13.3",
68
68
  "@ykdz/template-builtin-presets-source": "link:../builtin-presets",
69
69
  "@ykdz/template-core-source": "link:../core",
70
- "execa": "^10.0.0",
70
+ "execa": "^10.0.1",
71
71
  "oxfmt": "^0.58.0",
72
- "oxlint": "^1.73.0",
72
+ "oxlint": "^1.77.0",
73
73
  "oxlint-tsgolint": "^7.0.2001",
74
74
  "typescript-7": "npm:typescript@^7.0.2",
75
75
  "vitest": "^4.1.10"
@@ -1,33 +0,0 @@
1
- /**
2
- * The durable, preset-agnostic topology used by local template follow-up
3
- * operations. Preset provenance deliberately belongs in Generation Record.
4
- */
5
- export type PackageRole = "cli-tool" | "runtime-service" | "shared-library" | "native-package";
6
- export type PackageDefinition = {
7
- readonly name: string;
8
- readonly path: string;
9
- readonly role: PackageRole;
10
- };
11
- export type PackageLinkIntent = {
12
- readonly consumerPackagePath: string;
13
- readonly providerPackagePath: string;
14
- };
15
- export type ProjectBlueprintV2 = {
16
- readonly schemaVersion: 2;
17
- readonly packages: readonly PackageDefinition[];
18
- readonly packageLinkIntents?: readonly PackageLinkIntent[];
19
- };
20
- export type BlueprintV2ValidationIssue = {
21
- readonly path: string;
22
- readonly message: string;
23
- };
24
- export type BlueprintV2ValidationResult = {
25
- readonly ok: true;
26
- readonly value: ProjectBlueprintV2;
27
- } | {
28
- readonly ok: false;
29
- readonly issues: readonly BlueprintV2ValidationIssue[];
30
- };
31
- /** Validates persisted metadata before it is used to plan or render changes. */
32
- export declare function validateProjectBlueprintV2(value: unknown): BlueprintV2ValidationResult;
33
- export declare function assertProjectBlueprintV2(value: unknown): ProjectBlueprintV2;
@@ -1,182 +0,0 @@
1
- const packageName = /^@[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
2
- const packagePath = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
3
- const reservedWorkspaceCollections = new Set([
4
- ".git",
5
- ".github",
6
- ".devcontainer",
7
- ".template",
8
- "node_modules",
9
- "dist",
10
- "target",
11
- ]);
12
- const roles = new Set([
13
- "cli-tool",
14
- "runtime-service",
15
- "shared-library",
16
- "native-package",
17
- ]);
18
- function isRecord(value) {
19
- return typeof value === "object" && value !== null && !Array.isArray(value);
20
- }
21
- function reportUnknownKeys(value, allowed, path, issues) {
22
- for (const key of Object.keys(value)) {
23
- if (!allowed.includes(key)) {
24
- issues.push({
25
- path: `${path}.${key}`,
26
- message: "Unknown Blueprint v2 field",
27
- });
28
- }
29
- }
30
- }
31
- /** Validates persisted metadata before it is used to plan or render changes. */
32
- export function validateProjectBlueprintV2(value) {
33
- const issues = [];
34
- if (!isRecord(value)) {
35
- return {
36
- ok: false,
37
- issues: [
38
- { path: ".", message: "Local Template Metadata must be an object" },
39
- ],
40
- };
41
- }
42
- reportUnknownKeys(value, ["schemaVersion", "packages", "packageLinkIntents"], ".", issues);
43
- if (value.schemaVersion !== 2) {
44
- issues.push({
45
- path: ".schemaVersion",
46
- message: `Unsupported Local Template Metadata schema version ${String(value.schemaVersion)}; expected 2`,
47
- });
48
- }
49
- if (!Array.isArray(value.packages)) {
50
- issues.push({
51
- path: ".packages",
52
- message: "Package Definitions must be an array",
53
- });
54
- }
55
- const definitions = [];
56
- if (Array.isArray(value.packages)) {
57
- for (const [index, item] of value.packages.entries()) {
58
- const itemPath = `.packages[${index}]`;
59
- if (!isRecord(item)) {
60
- issues.push({
61
- path: itemPath,
62
- message: "Package Definition must be an object",
63
- });
64
- continue;
65
- }
66
- reportUnknownKeys(item, ["name", "path", "role"], itemPath, issues);
67
- const workspaceCollection = typeof item.path === "string" ? item.path.split("/", 1)[0] : undefined;
68
- const usesReservedWorkspaceCollection = workspaceCollection !== undefined &&
69
- reservedWorkspaceCollections.has(workspaceCollection);
70
- if (typeof item.name !== "string" || !packageName.test(item.name)) {
71
- issues.push({
72
- path: `${itemPath}.name`,
73
- message: "Package name must be a scoped lowercase package name",
74
- });
75
- }
76
- if (usesReservedWorkspaceCollection) {
77
- issues.push({
78
- path: `${itemPath}.path`,
79
- message: `Package Path ${item.path} uses reserved workspace collection ${workspaceCollection}`,
80
- });
81
- }
82
- else if (typeof item.path !== "string" ||
83
- !packagePath.test(item.path)) {
84
- issues.push({
85
- path: `${itemPath}.path`,
86
- message: "Package Path must be exactly two safe path segments",
87
- });
88
- }
89
- if (typeof item.role !== "string" ||
90
- !roles.has(item.role)) {
91
- issues.push({
92
- path: `${itemPath}.role`,
93
- message: "Package Role must be cli-tool, runtime-service, shared-library, or native-package",
94
- });
95
- }
96
- if (typeof item.name === "string" &&
97
- typeof item.path === "string" &&
98
- typeof item.role === "string" &&
99
- packageName.test(item.name) &&
100
- packagePath.test(item.path) &&
101
- !usesReservedWorkspaceCollection &&
102
- roles.has(item.role)) {
103
- definitions.push(item);
104
- }
105
- }
106
- }
107
- for (const [property, label] of [
108
- ["name", "Package name"],
109
- ["path", "Package Path"],
110
- ]) {
111
- const seen = new Set();
112
- for (const definition of definitions) {
113
- const member = definition[property];
114
- if (seen.has(member))
115
- issues.push({
116
- path: ".packages",
117
- message: `${label} must be unique: ${member}`,
118
- });
119
- seen.add(member);
120
- }
121
- }
122
- if (value.packageLinkIntents !== undefined &&
123
- !Array.isArray(value.packageLinkIntents)) {
124
- issues.push({
125
- path: ".packageLinkIntents",
126
- message: "Package Link Intents must be an array",
127
- });
128
- }
129
- const paths = new Set(definitions.map((definition) => definition.path));
130
- const links = new Set();
131
- if (Array.isArray(value.packageLinkIntents)) {
132
- for (const [index, item] of value.packageLinkIntents.entries()) {
133
- const itemPath = `.packageLinkIntents[${index}]`;
134
- if (!isRecord(item)) {
135
- issues.push({
136
- path: itemPath,
137
- message: "Package Link Intent must be an object",
138
- });
139
- continue;
140
- }
141
- reportUnknownKeys(item, ["consumerPackagePath", "providerPackagePath"], itemPath, issues);
142
- const consumer = item.consumerPackagePath;
143
- const provider = item.providerPackagePath;
144
- if (typeof consumer !== "string" || !paths.has(consumer))
145
- issues.push({
146
- path: `${itemPath}.consumerPackagePath`,
147
- message: "Package Link Intent references an unknown consumer package",
148
- });
149
- if (typeof provider !== "string" || !paths.has(provider))
150
- issues.push({
151
- path: `${itemPath}.providerPackagePath`,
152
- message: "Package Link Intent references an unknown provider package",
153
- });
154
- if (consumer === provider && typeof consumer === "string")
155
- issues.push({
156
- path: itemPath,
157
- message: "Package Link Intent cannot link a package to itself",
158
- });
159
- if (typeof consumer === "string" && typeof provider === "string") {
160
- const key = `${consumer}\u0000${provider}`;
161
- if (links.has(key))
162
- issues.push({
163
- path: itemPath,
164
- message: "Package Link Intent must be unique",
165
- });
166
- links.add(key);
167
- }
168
- }
169
- }
170
- return issues.length === 0
171
- ? { ok: true, value: value }
172
- : { ok: false, issues };
173
- }
174
- export function assertProjectBlueprintV2(value) {
175
- const result = validateProjectBlueprintV2(value);
176
- if (!result.ok) {
177
- throw new Error(result.issues
178
- .map((issue) => `${issue.path}: ${issue.message}`)
179
- .join("\n"));
180
- }
181
- return result.value;
182
- }