@ykdz/template 0.0.33 → 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 (98) 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 +970 -365
  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/development-container.d.ts +1 -0
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +17 -0
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
  15. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
  16. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
  17. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +1 -2
  18. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
  19. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
  20. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
  21. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
  22. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
  23. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +61 -35
  24. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
  25. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -8
  26. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
  27. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -21
  28. package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
  29. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
  30. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
  31. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
  32. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
  33. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
  34. package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/github-cli.Dockerfile +9 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
  37. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
  38. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
  39. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
  40. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
  41. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
  42. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
  43. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
  44. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
  45. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
  46. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
  47. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
  48. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
  49. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
  50. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
  51. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
  52. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
  53. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
  54. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
  55. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
  56. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
  57. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
  58. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
  59. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
  60. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
  61. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
  62. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
  63. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/src/readiness.ts +1 -1
  64. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/test/global-setup.ts +21 -0
  65. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +6 -1
  66. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/vitest.config.ts +7 -0
  67. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/drizzle.config.ts +5 -1
  68. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +102 -22
  69. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/test/playwright-teardown.ts +12 -0
  70. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/vite.config.ts +39 -1
  71. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +48 -7
  72. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +59 -8
  73. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
  74. package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
  75. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
  76. package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
  77. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
  78. package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
  79. package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
  80. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
  81. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
  82. package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
  83. package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
  84. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
  85. package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
  86. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
  87. package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
  88. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
  89. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
  90. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
  91. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
  92. package/package.json +3 -3
  93. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/scripts/prepare-database.ts +0 -45
  94. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/run-playwright.ts +0 -151
  95. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/scripts/run-playwright.ts +0 -62
  96. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/scripts/run-playwright.ts +0 -103
  97. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
  98. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
@@ -0,0 +1,271 @@
1
+ import { isBuiltin } from "node:module";
2
+ import * as v from "valibot";
3
+ /**
4
+ * The durable, preset-agnostic topology used by local template follow-up
5
+ * operations. Preset provenance deliberately belongs in Generation Record.
6
+ */
7
+ const packageRoles = [
8
+ "cli-tool",
9
+ "runtime-service",
10
+ "shared-library",
11
+ "native-package",
12
+ ];
13
+ const packagePath = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
14
+ const reservedWorkspaceCollections = new Set([
15
+ ".git",
16
+ ".github",
17
+ ".devcontainer",
18
+ ".template",
19
+ "node_modules",
20
+ "dist",
21
+ "target",
22
+ ]);
23
+ const reservedPackageNames = new Set(["node_modules", "favicon.ico"]);
24
+ const unknownBlueprintField = "Unknown Blueprint v3 field";
25
+ const packageDefinitionEntries = {
26
+ name: v.string("Package name must be a string"),
27
+ path: v.string("Package Path must be a string"),
28
+ role: v.picklist(packageRoles, "Package Role must be cli-tool, runtime-service, shared-library, or native-package"),
29
+ };
30
+ function packageDefinitionMessage(issue) {
31
+ return issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
32
+ ? unknownBlueprintField
33
+ : "Package Definition must be an object";
34
+ }
35
+ const packageDefinitionSchema = v.strictObject({
36
+ packageDefinitionId: v.string("Package Definition ID must use the package-<sha256> format"),
37
+ ...packageDefinitionEntries,
38
+ }, packageDefinitionMessage);
39
+ const packageLinkIntentSchema = v.strictObject({
40
+ consumerPackagePath: v.string("Package Link Intent consumer Package Path must be a string"),
41
+ providerPackagePath: v.string("Package Link Intent provider Package Path must be a string"),
42
+ }, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
43
+ ? unknownBlueprintField
44
+ : "Package Link Intent must be an object");
45
+ const projectBlueprintSchema = v.strictObject({
46
+ schemaVersion: v.literal(3, (issue) => `Unsupported Local Template Metadata schema version ${String(issue.input)}; expected 3`),
47
+ packages: v.array(packageDefinitionSchema, "Package Definitions must be an array"),
48
+ packageLinkIntents: v.optional(v.array(packageLinkIntentSchema, "Package Link Intents must be an array")),
49
+ }, (issue) => issue.path?.at(-1)?.origin === "key" && issue.input !== undefined
50
+ ? unknownBlueprintField
51
+ : "Local Template Metadata must be an object");
52
+ /** Canonical npm package-name predicate shared by Blueprint and input preparation. */
53
+ export function isValidNewNpmPackageName(value) {
54
+ if (value.length === 0 ||
55
+ value.length > 214 ||
56
+ value !== value.trim() ||
57
+ value !== value.toLowerCase() ||
58
+ value === "." ||
59
+ value === ".." ||
60
+ reservedPackageNames.has(value) ||
61
+ isBuiltin(value)) {
62
+ return false;
63
+ }
64
+ const scopedMatch = /^@([^/]+)\/([^/]+)$/.exec(value);
65
+ if (value.startsWith("@") && scopedMatch === null)
66
+ return false;
67
+ const scope = scopedMatch?.[1];
68
+ const leaf = scopedMatch?.[2] ?? value;
69
+ if (scope === undefined && /^[._-]/.test(leaf)) {
70
+ return false;
71
+ }
72
+ if (scope !== undefined && leaf.startsWith(".")) {
73
+ return false;
74
+ }
75
+ if (/[~'!()*]/.test(leaf))
76
+ return false;
77
+ return (encodeURIComponent(leaf) === leaf &&
78
+ (scope === undefined || encodeURIComponent(scope) === scope));
79
+ }
80
+ /** Canonical Package Path facts shared by Blueprint and input preparation. */
81
+ export function validateNewPackagePath(value) {
82
+ const workspaceCollection = value.split("/", 1)[0];
83
+ return {
84
+ hasValidShape: packagePath.test(value),
85
+ ...(reservedWorkspaceCollections.has(workspaceCollection)
86
+ ? { reservedWorkspaceCollection: workspaceCollection }
87
+ : {}),
88
+ };
89
+ }
90
+ function blueprintIssuePath(issue) {
91
+ let result = ".";
92
+ for (const item of issue.path ?? []) {
93
+ if (typeof item.key === "number") {
94
+ result += `[${item.key}]`;
95
+ }
96
+ else if (typeof item.key === "string") {
97
+ result += `${result === "." ? "" : "."}${item.key}`;
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+ function blueprintStructureMessage(issue) {
103
+ if (issue.type !== "strict_object" ||
104
+ issue.path?.at(-1)?.origin !== "key" ||
105
+ issue.input !== undefined) {
106
+ return issue.message;
107
+ }
108
+ const path = blueprintIssuePath(issue);
109
+ if (path === ".schemaVersion") {
110
+ return "Unsupported Local Template Metadata schema version undefined; expected 3";
111
+ }
112
+ if (path === ".packages")
113
+ return "Package Definitions must be an array";
114
+ if (path.endsWith(".name"))
115
+ return "Package name must be a string";
116
+ if (path.endsWith(".packageDefinitionId")) {
117
+ return "Package Definition ID must use the package-<sha256> format";
118
+ }
119
+ if (path.endsWith(".path"))
120
+ return "Package Path must be a string";
121
+ if (path.endsWith(".role")) {
122
+ return "Package Role must be cli-tool, runtime-service, shared-library, or native-package";
123
+ }
124
+ if (path === ".packageLinkIntents") {
125
+ return "Package Link Intents must be an array";
126
+ }
127
+ if (path.endsWith(".consumerPackagePath")) {
128
+ return "Package Link Intent consumer Package Path must be a string";
129
+ }
130
+ if (path.endsWith(".providerPackagePath")) {
131
+ return "Package Link Intent provider Package Path must be a string";
132
+ }
133
+ return issue.message;
134
+ }
135
+ function validateBlueprintTopologySemantics(blueprint) {
136
+ const issues = [];
137
+ for (const [index, definition] of blueprint.packages.entries()) {
138
+ const itemPath = `.packages[${index}]`;
139
+ const pathValidation = validateNewPackagePath(definition.path);
140
+ if (!isValidNewNpmPackageName(definition.name)) {
141
+ issues.push({
142
+ path: `${itemPath}.name`,
143
+ message: "Package name must be a valid npm package name for new packages",
144
+ });
145
+ }
146
+ if (pathValidation.reservedWorkspaceCollection !== undefined) {
147
+ issues.push({
148
+ path: `${itemPath}.path`,
149
+ message: `Package Path ${definition.path} uses reserved workspace collection ${pathValidation.reservedWorkspaceCollection}`,
150
+ });
151
+ }
152
+ else if (!pathValidation.hasValidShape) {
153
+ issues.push({
154
+ path: `${itemPath}.path`,
155
+ message: "Package Path must be exactly two safe path segments",
156
+ });
157
+ }
158
+ }
159
+ for (const [property, label] of [
160
+ ["name", "Package name"],
161
+ ["path", "Package Path"],
162
+ ]) {
163
+ const seen = new Set();
164
+ for (const definition of blueprint.packages) {
165
+ const member = definition[property];
166
+ if (seen.has(member)) {
167
+ issues.push({
168
+ path: ".packages",
169
+ message: `${label} must be unique: ${member}`,
170
+ });
171
+ }
172
+ seen.add(member);
173
+ }
174
+ }
175
+ const paths = new Set(blueprint.packages.map((definition) => definition.path));
176
+ const links = new Set();
177
+ for (const [index, intent] of (blueprint.packageLinkIntents ?? []).entries()) {
178
+ const itemPath = `.packageLinkIntents[${index}]`;
179
+ if (!paths.has(intent.consumerPackagePath)) {
180
+ issues.push({
181
+ path: `${itemPath}.consumerPackagePath`,
182
+ message: "Package Link Intent references an unknown consumer package",
183
+ });
184
+ }
185
+ if (!paths.has(intent.providerPackagePath)) {
186
+ issues.push({
187
+ path: `${itemPath}.providerPackagePath`,
188
+ message: "Package Link Intent references an unknown provider package",
189
+ });
190
+ }
191
+ if (intent.consumerPackagePath === intent.providerPackagePath) {
192
+ issues.push({
193
+ path: itemPath,
194
+ message: "Package Link Intent cannot link a package to itself",
195
+ });
196
+ }
197
+ const key = `${intent.consumerPackagePath}\u0000${intent.providerPackagePath}`;
198
+ if (links.has(key)) {
199
+ issues.push({
200
+ path: itemPath,
201
+ message: "Package Link Intent must be unique",
202
+ });
203
+ }
204
+ links.add(key);
205
+ }
206
+ return issues;
207
+ }
208
+ function validatePersistedPackageIds(blueprint) {
209
+ const issues = [];
210
+ const seen = new Set();
211
+ for (const [index, definition] of blueprint.packages.entries()) {
212
+ if (!/^package-[a-f0-9]{64}$/.test(definition.packageDefinitionId)) {
213
+ issues.push({
214
+ path: `.packages[${index}].packageDefinitionId`,
215
+ message: "Package Definition ID must use the package-<sha256> format",
216
+ });
217
+ }
218
+ if (seen.has(definition.packageDefinitionId)) {
219
+ issues.push({
220
+ path: ".packages",
221
+ message: `Package Definition ID must be unique: ${definition.packageDefinitionId}`,
222
+ });
223
+ }
224
+ seen.add(definition.packageDefinitionId);
225
+ }
226
+ return issues;
227
+ }
228
+ /** Validates persisted metadata before it is used to plan or render changes. */
229
+ export function validateProjectBlueprint(value) {
230
+ const parsed = v.safeParse(projectBlueprintSchema, value);
231
+ if (!parsed.success) {
232
+ return {
233
+ ok: false,
234
+ issues: parsed.issues.map((issue) => ({
235
+ path: blueprintIssuePath(issue),
236
+ message: blueprintStructureMessage(issue),
237
+ })),
238
+ };
239
+ }
240
+ const blueprint = {
241
+ schemaVersion: parsed.output.schemaVersion,
242
+ packages: parsed.output.packages,
243
+ ...(parsed.output.packageLinkIntents === undefined
244
+ ? {}
245
+ : { packageLinkIntents: parsed.output.packageLinkIntents }),
246
+ };
247
+ const issues = [
248
+ ...validateBlueprintTopologySemantics(blueprint),
249
+ ...validatePersistedPackageIds(blueprint),
250
+ ];
251
+ return issues.length === 0
252
+ ? { ok: true, value: blueprint }
253
+ : { ok: false, issues };
254
+ }
255
+ /** Validates preset-owned topology before any persisted identity is assigned. */
256
+ export function assertProjectBlueprintDraft(value) {
257
+ const issues = validateBlueprintTopologySemantics(value);
258
+ if (issues.length > 0) {
259
+ throw new Error(issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
260
+ }
261
+ return value;
262
+ }
263
+ export function assertProjectBlueprint(value) {
264
+ const result = validateProjectBlueprint(value);
265
+ if (!result.ok) {
266
+ throw new Error(result.issues
267
+ .map((issue) => `${issue.path}: ${issue.message}`)
268
+ .join("\n"));
269
+ }
270
+ return result.value;
271
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PackageContribution } from "./package-contribution.ts";
2
- import { type PackageRole, type ProjectBlueprintV2 } from "./project-blueprint-v2.ts";
2
+ import { type PackageRole, type ProjectBlueprint } from "./project-blueprint.ts";
3
3
  type PackageManifest = Readonly<Record<string, unknown>>;
4
4
  export type ExplicitProjectLinkPlan = {
5
5
  readonly manifestPatchesByPackagePath: ReadonlyMap<string, {
@@ -24,7 +24,7 @@ export declare function canProvideSourceConditionPackageNameImport(contribution:
24
24
  * string resource protocol is involved.
25
25
  */
26
26
  export declare function planExplicitProjectLinks(options: {
27
- readonly blueprint: ProjectBlueprintV2;
27
+ readonly blueprint: ProjectBlueprint;
28
28
  readonly contributions: readonly PackageContribution[];
29
29
  readonly manifestTruthByPackagePath?: ReadonlyMap<string, PackageManifest>;
30
30
  }): ExplicitProjectLinkPlan;
@@ -1,4 +1,4 @@
1
- import { assertProjectBlueprintV2, } from "./project-blueprint-v2.js";
1
+ import { assertProjectBlueprint, } from "./project-blueprint.js";
2
2
  export function canConsumeNodePackageNameImport(contribution, manifest = contribution.manifest) {
3
3
  return (contribution.definition.role !== "native-package" &&
4
4
  manifest.type === "module");
@@ -74,7 +74,7 @@ function exposesImportablePackageRoot(manifest) {
74
74
  * string resource protocol is involved.
75
75
  */
76
76
  export function planExplicitProjectLinks(options) {
77
- const blueprint = assertProjectBlueprintV2(options.blueprint);
77
+ const blueprint = assertProjectBlueprint(options.blueprint);
78
78
  const contributionsByPath = new Map(options.contributions.map((contribution) => [
79
79
  contribution.definition.path,
80
80
  contribution,
@@ -87,9 +87,10 @@ export type ProjectProjectionAction = {
87
87
  };
88
88
  export type MaterializeProjectProjectionOptions = {
89
89
  readonly operations: readonly RenderOperation[];
90
- readonly variables?: Readonly<Record<string, string>>;
91
90
  readonly reconciliation?: readonly ProjectProjectionReconciliation[];
92
91
  };
92
+ /** Pure structural preflight for a renderer-facing Project Projection plan. */
93
+ export declare function validateProjectProjectionPlan(options: MaterializeProjectProjectionOptions): readonly string[];
93
94
  export declare function reconcileProjectProjections(options: {
94
95
  readonly before: ProjectProjection;
95
96
  readonly after: ProjectProjection;
@@ -11,30 +11,18 @@ class ProjectProjectionPathPreconditionError extends Error {
11
11
  this.conflicts = conflicts;
12
12
  }
13
13
  }
14
- function expandTemplatePath(templatePath, variables) {
15
- return templatePath.replaceAll(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/gu, (_placeholder, name) => {
16
- const value = variables[name];
17
- if (!value)
18
- throw new Error(`Missing renderer variable: ${name}`);
19
- if (!/^[A-Za-z0-9._-]+$/u.test(value)) {
20
- throw new Error(`Renderer variable ${name} is not safe for a path segment`);
21
- }
22
- return value;
23
- });
24
- }
25
- function normalizedOutputPath(operation, variables) {
14
+ function normalizedOutputPath(operation) {
26
15
  const operationPath = operation.kind === "setExecutable" || operation.kind === "replaceAnchors"
27
16
  ? operation.path
28
17
  : operation.to;
29
- const expanded = expandTemplatePath(operationPath, variables);
30
- if (path.isAbsolute(expanded)) {
31
- throw new Error(`Project Projection paths must be relative: ${expanded}`);
18
+ if (path.isAbsolute(operationPath)) {
19
+ throw new Error(`Project Projection paths must be relative: ${operationPath}`);
32
20
  }
33
- const normalized = path.posix.normalize(expanded.split(path.sep).join("/"));
21
+ const normalized = path.posix.normalize(operationPath.split(path.sep).join("/"));
34
22
  if (normalized === "." ||
35
23
  normalized === ".." ||
36
24
  normalized.startsWith("../")) {
37
- throw new Error(`Project Projection path escapes its root: ${expanded}`);
25
+ throw new Error(`Project Projection path escapes its root: ${operationPath}`);
38
26
  }
39
27
  return normalized;
40
28
  }
@@ -55,6 +43,103 @@ function operationDriver(operation) {
55
43
  return undefined;
56
44
  }
57
45
  }
46
+ /** Pure structural preflight for a renderer-facing Project Projection plan. */
47
+ export function validateProjectProjectionPlan(options) {
48
+ const diagnostics = [];
49
+ const contentKindByPath = new Map();
50
+ const contentTypeByPath = new Map();
51
+ const transformationKindByPath = new Map();
52
+ const finalPaths = new Set();
53
+ for (const operation of options.operations) {
54
+ let outputPath;
55
+ try {
56
+ outputPath = normalizedOutputPath(operation);
57
+ }
58
+ catch (error) {
59
+ diagnostics.push(error instanceof Error ? error.message : String(error));
60
+ continue;
61
+ }
62
+ finalPaths.add(outputPath);
63
+ const precedingKind = contentKindByPath.get(outputPath);
64
+ if (operation.kind === "setExecutable" ||
65
+ operation.kind === "replaceAnchors") {
66
+ if (precedingKind === undefined) {
67
+ diagnostics.push(`Project Projection ${operation.kind} requires a preceding content-producing operation: ${outputPath}`);
68
+ }
69
+ const hasCompatibleContent = operation.kind !== "replaceAnchors" ||
70
+ contentTypeByPath.get(outputPath) !== "json";
71
+ if (!hasCompatibleContent) {
72
+ diagnostics.push(`Project Projection replaceAnchors requires text-compatible content: ${outputPath} follows ${precedingKind}`);
73
+ }
74
+ if (precedingKind !== undefined && hasCompatibleContent) {
75
+ if (operation.kind === "replaceAnchors") {
76
+ contentTypeByPath.set(outputPath, "text");
77
+ }
78
+ transformationKindByPath.set(outputPath, operation.kind);
79
+ }
80
+ continue;
81
+ }
82
+ if (operation.kind === "mergeJson" ||
83
+ operation.kind === "mergeJsonTemplate") {
84
+ const transformationKind = transformationKindByPath.get(outputPath);
85
+ if (transformationKind !== undefined) {
86
+ diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
87
+ }
88
+ if (contentTypeByPath.get(outputPath) === "text") {
89
+ diagnostics.push(`Project Projection ${operation.kind} requires JSON-compatible content: ${outputPath} follows ${precedingKind}`);
90
+ }
91
+ if (precedingKind === undefined) {
92
+ contentKindByPath.set(outputPath, operation.kind);
93
+ }
94
+ contentTypeByPath.set(outputPath, "json");
95
+ transformationKindByPath.delete(outputPath);
96
+ continue;
97
+ }
98
+ const overwrite = "overwrite" in operation && operation.overwrite;
99
+ const transformationKind = transformationKindByPath.get(outputPath);
100
+ if (overwrite && transformationKind !== undefined) {
101
+ diagnostics.push(`Project Projection ${operation.kind} may not overwrite content after ${transformationKind}: ${outputPath}`);
102
+ }
103
+ if (precedingKind !== undefined && !overwrite) {
104
+ diagnostics.push(`Project Projection collision at ${outputPath}: ${operation.kind} follows ${precedingKind} without overwrite`);
105
+ }
106
+ contentKindByPath.set(outputPath, operation.kind);
107
+ if (operation.kind === "writeJson") {
108
+ contentTypeByPath.set(outputPath, "json");
109
+ }
110
+ else if (operation.kind === "writeText") {
111
+ try {
112
+ JSON.parse(operation.text);
113
+ contentTypeByPath.set(outputPath, "json");
114
+ }
115
+ catch {
116
+ contentTypeByPath.set(outputPath, "text");
117
+ }
118
+ }
119
+ else {
120
+ contentTypeByPath.set(outputPath, "unknown");
121
+ }
122
+ transformationKindByPath.delete(outputPath);
123
+ }
124
+ const orderedPaths = [...finalPaths].toSorted();
125
+ for (const candidate of orderedPaths) {
126
+ const ancestor = orderedPaths.find((other) => other !== candidate && candidate.startsWith(`${other}/`));
127
+ if (ancestor !== undefined) {
128
+ diagnostics.push(`Project Projection path collision: file ${ancestor} is an ancestor of ${candidate}`);
129
+ }
130
+ }
131
+ const reconciliationPaths = new Set();
132
+ for (const reconciliation of options.reconciliation ?? []) {
133
+ if (reconciliationPaths.has(reconciliation.path)) {
134
+ diagnostics.push(`Project Projection reconciliation paths must be unique: ${reconciliation.path}`);
135
+ }
136
+ else if (!finalPaths.has(reconciliation.path)) {
137
+ diagnostics.push(`Project Projection reconciliation references an unknown path: ${reconciliation.path}`);
138
+ }
139
+ reconciliationPaths.add(reconciliation.path);
140
+ }
141
+ return diagnostics;
142
+ }
58
143
  function bytesEqual(left, right) {
59
144
  return (left.byteLength === right.byteLength &&
60
145
  left.every((value, index) => value === right[index]));
@@ -1031,12 +1116,15 @@ export function createProjectProjectionReconciler(dependencies = {}) {
1031
1116
  }
1032
1117
  export const reconcileAndApplyProjectProjections = createProjectProjectionReconciler();
1033
1118
  export async function materializeProjectProjection(options) {
1034
- const variables = options.variables ?? {};
1119
+ const planDiagnostics = validateProjectProjectionPlan(options);
1120
+ if (planDiagnostics.length > 0) {
1121
+ throw new Error(planDiagnostics.join("\n"));
1122
+ }
1035
1123
  const finalPaths = new Set();
1036
1124
  const contentPaths = new Set();
1037
1125
  const policies = new Map();
1038
1126
  for (const operation of options.operations) {
1039
- const outputPath = normalizedOutputPath(operation, variables);
1127
+ const outputPath = normalizedOutputPath(operation);
1040
1128
  finalPaths.add(outputPath);
1041
1129
  if (operation.kind === "setExecutable") {
1042
1130
  if (!contentPaths.has(outputPath)) {
@@ -1067,7 +1155,6 @@ export async function materializeProjectProjection(options) {
1067
1155
  try {
1068
1156
  await renderProject({
1069
1157
  targetRoot,
1070
- variables: { ...variables },
1071
1158
  operations: [...options.operations],
1072
1159
  });
1073
1160
  const entries = await Promise.all([...finalPaths].toSorted().map(async (relativePath) => {
@@ -1,4 +1,3 @@
1
- type RenderVariables = Record<string, string>;
2
1
  export type RenderOperationProvenance = {
3
2
  readonly definitionName: string;
4
3
  readonly plannerSourceFile: string;
@@ -96,7 +95,6 @@ export type ReplaceAnchorsOperation = {
96
95
  export type RenderOperation = CopyFileOperation | WriteJsonOperation | MergeJsonOperation | MergeJsonTemplateOperation | WriteTextOperation | WriteTextFromFragmentsOperation | WriteTextTemplateOperation | SetExecutableOperation | ReplaceAnchorsOperation;
97
96
  export type RenderProjectOptions = {
98
97
  targetRoot: string;
99
- variables?: RenderVariables | undefined;
100
98
  operations: RenderOperation[];
101
99
  };
102
100
  export declare function renderProject(options: RenderProjectOptions): Promise<void>;
@@ -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 {};