@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
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -5,14 +6,17 @@ import { composeCiDiagnosticArtifacts } from "#template-core/ci-diagnostic-artif
5
6
  import { collectGeneratedManifestCatalogReferences, selectTemplateDependencyCatalogEntries, } from "#template-core/dependency-catalog";
6
7
  import { planDevelopmentContainerToolLayersSync, } from "#template-core/development-container-tool-layer";
7
8
  import { editorCustomizationForCapabilities, loadEditorCustomizationDeclarations, } from "#template-core/editor-customization";
8
- import { checkEnvironmentNeedFact, checkEnvironmentNeedFromFact, deploymentEnvironmentNeedFact, deploymentEnvironmentNeedFromFact, renderDeploymentCheckCommand, renderFixCommand, renderRootCheckCommand, } from "#template-core/module-graph";
9
- import { assertPackageContribution, } from "#template-core/package-contribution";
10
- import { assertProjectBlueprintV2, validateProjectBlueprintV2 as validateCoreProjectBlueprintV2, } from "#template-core/project-blueprint-v2";
9
+ import { normalizeEnvironmentNeeds, parseEnvironmentNeedsMetadata, renderDeploymentCheckCommand, renderFixCommand, renderRootCheckCommand, renderTurboRunCommand, } from "#template-core/module-graph";
10
+ import { assertPackageContribution, assertPackageContributionCommandNames, } from "#template-core/package-contribution";
11
+ import { assertProjectBlueprint, assertProjectBlueprintDraft, isValidNewNpmPackageName, validateNewPackagePath, validateProjectBlueprint as validateCoreProjectBlueprint, } from "#template-core/project-blueprint";
11
12
  import { projectCheckWorkflowTemplateSource, projectCheckWorkflowTemplateReplacements, projectDependabotTemplateReplacements, } from "#template-core/project-github";
12
13
  import { planExplicitProjectLinks } from "#template-core/project-linking-v2";
14
+ import { validateProjectProjectionPlan } from "#template-core/project-projection";
13
15
  import { resolveTemplateSource, } from "#template-core/renderer";
16
+ import { isValidDefaultPackageScope, parseGenerationRecord, } from "./generation-record.js";
14
17
  import { rustBinDefinition } from "./rust-bin/definition.js";
15
- import { typescriptConfigContribution, typescriptConfigPackageDefinition, typescriptConfigPackageName, } from "./shared/typescript.js";
18
+ import { githubCliDevelopmentContainerToolLayer } from "./shared/development-container.js";
19
+ import { typescriptConfigContribution, typescriptConfigPackageDefinition, typescriptConfigReplayAdapter, } from "./shared/typescript.js";
16
20
  import { vuePnpmDependencyOverrides } from "./shared/vue.js";
17
21
  import { templateSources } from "./template-sources.js";
18
22
  import { tsCliDefinition } from "./ts-cli/definition.js";
@@ -20,6 +24,7 @@ import { tsLibDefinition } from "./ts-lib/definition.js";
20
24
  import { vikeAppDefinition } from "./vike-app/definition.js";
21
25
  import { vueAppDefinition } from "./vue-app/definition.js";
22
26
  import { vueHonoAppDefinition } from "./vue-hono-app/definition.js";
27
+ const localTemplateMetadataStateKey = Symbol("localTemplateMetadataState");
23
28
  const environmentNeedsPath = ".template/environment-needs.json";
24
29
  const packageManifestKeyOrder = [
25
30
  "name",
@@ -52,8 +57,8 @@ const packageConditionKeyOrder = ["source", "types", "default"];
52
57
  export function resolveBuiltInTemplateSource(source, relativePath) {
53
58
  return resolveTemplateSource(source, relativePath);
54
59
  }
55
- export function validateProjectBlueprintV2(value) {
56
- return validateCoreProjectBlueprintV2(value);
60
+ export function validateProjectBlueprint(value) {
61
+ return validateCoreProjectBlueprint(value);
57
62
  }
58
63
  class PresetRegistry {
59
64
  #definitions;
@@ -63,6 +68,14 @@ class PresetRegistry {
63
68
  new Set(names).size !== names.length) {
64
69
  throw new Error("Preset Registry requires unique non-empty Definition names");
65
70
  }
71
+ for (const definition of definitions) {
72
+ const adapterIdentities = definition.packageContributionReplayAdapters.map((adapter) => adapter.identity);
73
+ if (adapterIdentities.length === 0 ||
74
+ adapterIdentities.some((identity) => identity.length === 0) ||
75
+ new Set(adapterIdentities).size !== adapterIdentities.length) {
76
+ throw new Error(`Built-in Preset ${definition.metadata.name} requires unique non-empty Package Contribution replay adapters`);
77
+ }
78
+ }
66
79
  this.#definitions = [...definitions].toSorted((left, right) => left.metadata.name.localeCompare(right.metadata.name));
67
80
  }
68
81
  all() {
@@ -83,245 +96,304 @@ export const builtInPresetRegistry = new PresetRegistry([
83
96
  vueHonoAppDefinition,
84
97
  vikeAppDefinition,
85
98
  ]);
86
- /** Registry-derived Template Source roots checked independently of render plans. */
87
- export function builtInPresetTemplateSourceContexts() {
88
- return [
89
- ...builtInPresetRegistry.all().map((definition) => ({
90
- name: definition.metadata.name,
91
- root: resolveBuiltInTemplateSource(definition.source, "."),
92
- })),
93
- {
94
- name: "foundation",
95
- root: resolveBuiltInTemplateSource(templateSources.foundation, "."),
96
- },
97
- {
98
- name: "shared-devcontainer",
99
- root: resolveBuiltInTemplateSource(templateSources.sharedDevcontainer, "."),
100
- },
101
- {
102
- name: "shared-oxc",
103
- root: resolveBuiltInTemplateSource(templateSources.sharedOxc, "."),
104
- },
105
- {
106
- name: "shared-vue",
107
- root: resolveBuiltInTemplateSource(templateSources.vue, "."),
108
- },
109
- ];
110
- }
111
- /**
112
- * Derives direct Template Source checks from every registered Definition's
113
- * actual initial contributions, without maintaining a second Preset catalog.
114
- */
115
- export function builtInPresetTemplateSourceCheckContexts() {
116
- return builtInPresetRegistry.all().flatMap((definition) => {
117
- const context = createGenerationContext({
118
- targetDir: path.join("generated-repository", "template-source", definition.metadata.name),
119
- toolchain: { nodeLtsMajor: "24", packageManagerPin: "pnpm@11.11.0" },
120
- });
121
- const plan = planGeneratedRepositoryInitialization({ definition, context });
122
- const contributions = definition.planInitializationContributions?.(context) ?? [definition.planInitialization(context)];
123
- return contributions.map((contribution) => ({
124
- definition,
125
- contribution,
126
- plan,
127
- }));
128
- });
129
- }
130
99
  export function createGenerationContext(options) {
131
- const projectName = path.basename(path.resolve(options.targetDir));
100
+ const repositoryName = path.basename(path.resolve(options.targetDir));
101
+ const defaultPackageScope = options.defaultPackageScope ?? repositoryName;
132
102
  return {
133
103
  targetDir: options.targetDir,
134
- projectName,
135
- scope: options.scope ?? projectName,
104
+ repositoryName,
105
+ defaultPackageScope,
106
+ foundationPackages: {
107
+ typescriptConfiguration: {
108
+ name: `@${defaultPackageScope}/typescript-config`,
109
+ },
110
+ },
136
111
  toolchain: options.toolchain,
137
112
  };
138
113
  }
139
114
  function isRecord(value) {
140
115
  return typeof value === "object" && value !== null && !Array.isArray(value);
141
116
  }
117
+ const packageDefinitionIdDomain = "ykdz.template.package-definition-id.v1";
118
+ function packageDefinitionIdDigest(value) {
119
+ return `package-${createHash("sha256")
120
+ .update(JSON.stringify(value))
121
+ .digest("hex")}`;
122
+ }
123
+ function allocatePackageDefinitionId(options) {
124
+ const creationFacts = {
125
+ domain: packageDefinitionIdDomain,
126
+ definition: {
127
+ name: options.definition.name,
128
+ path: options.definition.path,
129
+ role: options.definition.role,
130
+ },
131
+ provenance: {
132
+ definitionName: options.provenance.definitionName,
133
+ planningContribution: options.provenance.planningContribution,
134
+ contributionIdentity: options.provenance.contributionIdentity,
135
+ },
136
+ };
137
+ let nonce = 0;
138
+ while (true) {
139
+ const candidate = packageDefinitionIdDigest(nonce === 0
140
+ ? creationFacts
141
+ : {
142
+ creationFacts,
143
+ occupiedIds: [...options.occupiedIds].toSorted(),
144
+ nonce,
145
+ });
146
+ if (!options.occupiedIds.has(candidate))
147
+ return candidate;
148
+ nonce += 1;
149
+ }
150
+ }
151
+ function persistPackageDefinition(options) {
152
+ const packageDefinitionId = allocatePackageDefinitionId(options);
153
+ options.occupiedIds.add(packageDefinitionId);
154
+ return { ...options.definition, packageDefinitionId };
155
+ }
142
156
  function readGenerationRecord(options) {
143
- const generationPath = path.join(options.context.targetDir, ".template/generation.json");
144
- let value;
157
+ const generationPath = path.join(options.repositoryRoot, ".template/generation.json");
158
+ return parseGenerationRecord(readJsonFile(generationPath, "Generation Record facts"));
159
+ }
160
+ function readJsonFile(filePath, label) {
145
161
  try {
146
- value = JSON.parse(readFileSync(generationPath, "utf8"));
162
+ return JSON.parse(readFileSync(filePath, "utf8"));
147
163
  }
148
164
  catch (error) {
149
- throw new Error(`Package Addition requires valid Generation Record facts: ${error instanceof Error ? error.message : String(error)}`);
150
- }
151
- if (isRecord(value)) {
152
- for (const key of Object.keys(value)) {
153
- if (![
154
- "schemaVersion",
155
- "preset",
156
- "templateVersion",
157
- "toolchain",
158
- "packages",
159
- ].includes(key)) {
160
- throw new Error(`Package Addition Generation Record contains unknown field: ${key}`);
161
- }
162
- }
163
- if (isRecord(value.toolchain)) {
164
- for (const key of Object.keys(value.toolchain)) {
165
- if (!["nodeLtsMajor", "packageManagerPin"].includes(key)) {
166
- throw new Error(`Package Addition Generation Record toolchain contains unknown field: ${key}`);
167
- }
168
- }
169
- }
165
+ throw new Error(`Package Addition requires valid ${label}: ${error instanceof Error ? error.message : String(error)}`);
170
166
  }
171
- if (!isRecord(value) ||
172
- value.schemaVersion !== 1 ||
173
- typeof value.preset !== "string" ||
174
- value.templateVersion !== "0.0.0" ||
175
- !isRecord(value.toolchain) ||
176
- typeof value.toolchain.nodeLtsMajor !== "string" ||
177
- typeof value.toolchain.packageManagerPin !== "string" ||
178
- !Array.isArray(value.packages)) {
179
- throw new Error("Package Addition requires a supported Generation Record in .template/generation.json");
180
- }
181
- const packages = [];
182
- for (const [index, item] of value.packages.entries()) {
183
- if (isRecord(item)) {
184
- for (const key of Object.keys(item)) {
185
- if (!["path", "definitionName", "planningContribution"].includes(key)) {
186
- throw new Error(`Package Addition Generation Record packages[${index}] contains unknown field: ${key}`);
187
- }
188
- }
167
+ }
168
+ function barePackageDefinition(definition) {
169
+ return {
170
+ name: definition.name,
171
+ path: definition.path,
172
+ role: definition.role,
173
+ };
174
+ }
175
+ function preflightLocalTemplateMetadata(options) {
176
+ const definitionsById = new Map(options.blueprint.packages.map((definition) => [
177
+ definition.packageDefinitionId,
178
+ definition,
179
+ ]));
180
+ const joinedPackages = options.generationRecord.packages.map((record) => {
181
+ const definition = definitionsById.get(record.packageDefinitionId);
182
+ if (definition === undefined) {
183
+ throw new Error(`Generation Record Package Definition ID ${record.packageDefinitionId} has no matching Project Blueprint Package Definition`);
189
184
  }
190
- if (!isRecord(item) ||
191
- typeof item.path !== "string" ||
192
- typeof item.definitionName !== "string" ||
193
- !["foundationPlan", "planInitialization", "planPackageAddition"].includes(String(item.planningContribution))) {
194
- throw new Error(`Package Addition requires valid package planning facts at .template/generation.json packages[${index}]`);
185
+ if (record.path !== definition.path) {
186
+ throw new Error(`Generation Record path witness ${record.path} for Package Definition ID ${record.packageDefinitionId} conflicts with Project Blueprint path ${definition.path}`);
195
187
  }
196
- packages.push(item);
188
+ return { record, definition: barePackageDefinition(definition) };
189
+ });
190
+ const recordedIds = new Set(options.generationRecord.packages.map((record) => record.packageDefinitionId));
191
+ const unrecordedDefinition = options.blueprint.packages.find((definition) => !recordedIds.has(definition.packageDefinitionId));
192
+ if (unrecordedDefinition !== undefined) {
193
+ throw new Error(`Project Blueprint Package Definition ID ${unrecordedDefinition.packageDefinitionId} has no matching Generation Record provenance`);
197
194
  }
198
- const blueprintPaths = options.blueprint.packages
199
- .map((definition) => definition.path)
200
- .toSorted();
201
- const recordedPaths = packages.map((item) => item.path).toSorted();
202
- if (JSON.stringify(blueprintPaths) !== JSON.stringify(recordedPaths) ||
203
- new Set(recordedPaths).size !== recordedPaths.length) {
204
- throw new Error("Package Addition requires Generation Record packages to match the current Project Blueprint");
195
+ const records = options.generationRecord.packages.filter((record) => record.planningContribution === "foundationPlan");
196
+ if (records.length !== 1) {
197
+ throw new Error(`Package Addition requires exactly one Foundation Package Planning Provenance record; found ${records.length}`);
205
198
  }
206
- const generationRecord = {
207
- schemaVersion: 1,
208
- preset: value.preset,
209
- templateVersion: value.templateVersion,
210
- toolchain: {
211
- nodeLtsMajor: value.toolchain.nodeLtsMajor,
212
- packageManagerPin: value.toolchain.packageManagerPin,
213
- },
214
- packages,
215
- };
216
- assertGenerationRecordFoundationConsistency({
217
- context: options.context,
218
- blueprint: options.blueprint,
219
- generationRecord,
220
- });
221
- return generationRecord;
222
- }
223
- function assertGenerationRecordFoundationConsistency(options) {
224
- const configDefinition = typescriptConfigPackageDefinition(options.context);
225
- const foundationRecords = options.generationRecord.packages.filter((record) => record.planningContribution === "foundationPlan");
226
- if (foundationRecords.length !== 1 ||
227
- foundationRecords[0].definitionName !== "foundation" ||
228
- foundationRecords[0].path !== configDefinition.path) {
229
- throw new Error("Package Addition Generation Record must contain exactly one Foundation TypeScript configuration provenance record");
199
+ const record = records[0];
200
+ if (record.definitionName !== "foundation") {
201
+ throw new Error(`Foundation Package Planning Provenance at ${record.path} must use definitionName foundation; received ${record.definitionName}`);
202
+ }
203
+ if (record.contributionIdentity !== typescriptConfigReplayAdapter.identity) {
204
+ throw new Error(`Foundation TypeScript Configuration Package provenance must use contribution identity ${typescriptConfigReplayAdapter.identity}`);
205
+ }
206
+ const foundationPackage = joinedPackages.find((candidate) => candidate.record === record);
207
+ let initialDefinition;
208
+ try {
209
+ initialDefinition = builtInPresetRegistry.require(options.generationRecord.preset);
230
210
  }
231
- const initialRecords = options.generationRecord.packages.filter((record) => record.planningContribution === "planInitialization");
211
+ catch {
212
+ throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} is not a registered Built-in Preset`);
213
+ }
214
+ const initialRecords = options.generationRecord.packages.filter((candidate) => candidate.planningContribution === "planInitialization");
232
215
  if (initialRecords.length === 0) {
233
216
  throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} has no initial Package provenance`);
234
217
  }
235
- const conflictingRecord = initialRecords.find((record) => record.definitionName !== options.generationRecord.preset);
218
+ const conflictingRecord = initialRecords.find((candidate) => candidate.definitionName !== options.generationRecord.preset);
236
219
  if (conflictingRecord !== undefined) {
237
220
  throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} conflicts with initial Package provenance ${conflictingRecord.definitionName} for Blueprint package ${conflictingRecord.path}`);
238
221
  }
239
- let rootDefinition;
240
- try {
241
- rootDefinition = builtInPresetRegistry.require(options.generationRecord.preset);
242
- }
243
- catch {
244
- throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} is not a registered Built-in Preset`);
222
+ const preparedWithoutInitialLookup = joinedPackages.map((candidate) => {
223
+ if (candidate.record === record) {
224
+ return {
225
+ ...candidate,
226
+ owner: "foundation",
227
+ adapter: typescriptConfigReplayAdapter,
228
+ kind: "foundation",
229
+ };
230
+ }
231
+ let definition;
232
+ try {
233
+ definition = builtInPresetRegistry.require(candidate.record.definitionName);
234
+ }
235
+ catch {
236
+ throw new Error(`Package Addition Generation Record Package Planning Provenance references unknown Built-in Preset ${candidate.record.definitionName} for Blueprint package ${candidate.record.path}`);
237
+ }
238
+ if (candidate.record.planningContribution === "planPackageAddition" &&
239
+ definition.planPackageAddition === undefined) {
240
+ throw new Error(`Package Addition Generation Record Package Planning Provenance references unsupported Package Addition for Built-in Preset ${candidate.record.definitionName} at ${candidate.record.path}`);
241
+ }
242
+ return {
243
+ ...candidate,
244
+ owner: definition.metadata.name,
245
+ adapter: requireRecordReplayAdapter({
246
+ owner: definition.metadata.name,
247
+ adapters: definition.packageContributionReplayAdapters,
248
+ record: candidate.record,
249
+ }),
250
+ kind: "package",
251
+ };
252
+ });
253
+ const initialPlanningKeys = initialRecords.map((candidate) => `${candidate.definitionName}:${candidate.contributionIdentity}`);
254
+ if (new Set(initialPlanningKeys).size !== initialPlanningKeys.length) {
255
+ throw new Error("Package Addition Generation Record contains duplicate initialization contribution identity");
245
256
  }
246
- const initialBlueprint = rootDefinition.blueprint(options.context);
247
- assertProjectBlueprintV2(initialBlueprint);
248
- const expectedPaths = initialBlueprint.packages
249
- .map((definition) => definition.path)
250
- .toSorted();
251
- const recordedInitialPaths = initialRecords
252
- .map((record) => record.path)
253
- .toSorted();
254
- if (JSON.stringify(recordedInitialPaths) !== JSON.stringify(expectedPaths)) {
255
- throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} expects initial Blueprint packages ${expectedPaths.join(", ")}, but initial provenance records ${recordedInitialPaths.join(", ")}`);
256
- }
257
- for (const expectedDefinition of initialBlueprint.packages) {
258
- const currentDefinition = options.blueprint.packages.find((definition) => definition.path === expectedDefinition.path);
259
- if (currentDefinition === undefined ||
260
- !packageDefinitionsEqual(currentDefinition, expectedDefinition)) {
261
- throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} cannot reproduce initial Blueprint Package Definition ${expectedDefinition.name} at ${expectedDefinition.path} (${expectedDefinition.role})`);
257
+ if (initialDefinition.initialPrimaryPackage !== undefined) {
258
+ if (initialRecords.length !== 1) {
259
+ throw new Error(`${initialDefinition.metadata.name} requires exactly one initial Primary Package Contribution provenance record`);
262
260
  }
263
261
  }
264
- for (const expectedIntent of initialBlueprint.packageLinkIntents ?? []) {
265
- if (!(options.blueprint.packageLinkIntents ?? []).some((currentIntent) => packageLinkIntentsEqual(currentIntent, expectedIntent))) {
266
- throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} cannot reproduce initial Blueprint Package Link Intent ${expectedIntent.consumerPackagePath} -> ${expectedIntent.providerPackagePath}`);
262
+ else {
263
+ for (const adapter of initialDefinition.packageContributionReplayAdapters) {
264
+ const matchingRecords = initialRecords.filter((candidate) => candidate.contributionIdentity === adapter.identity);
265
+ if (matchingRecords.length !== 1) {
266
+ throw new Error(`${initialDefinition.metadata.name} requires exactly one initial ${adapter.identity} Package Contribution provenance record`);
267
+ }
267
268
  }
268
269
  }
270
+ const initialDefinitions = new Map(preparedWithoutInitialLookup.flatMap((candidate) => candidate.record.planningContribution === "planInitialization"
271
+ ? [[candidate.record.contributionIdentity, candidate.definition]]
272
+ : []));
273
+ const initialPackages = {
274
+ require(identity) {
275
+ const definition = initialDefinitions.get(identity);
276
+ if (definition === undefined) {
277
+ throw new Error(`${initialDefinition.metadata.name} has no preflighted initial ${identity} Package Definition`);
278
+ }
279
+ return definition;
280
+ },
281
+ };
282
+ const noInitialPackages = {
283
+ require(identity) {
284
+ throw new Error(`Foundation replay adapter does not declare initial Package ${identity}`);
285
+ },
286
+ };
287
+ return {
288
+ foundationPackage,
289
+ replays: preparedWithoutInitialLookup.map((candidate) => ({
290
+ ...candidate,
291
+ initialPackages: candidate.kind === "foundation" ? noInitialPackages : initialPackages,
292
+ })),
293
+ };
294
+ }
295
+ export function loadLocalTemplateMetadata(repositoryRoot) {
296
+ const resolvedRoot = path.resolve(repositoryRoot);
297
+ const blueprintPath = path.join(resolvedRoot, ".template/blueprint.json");
298
+ let blueprint;
299
+ try {
300
+ blueprint = assertProjectBlueprint(readJsonFile(blueprintPath, "Project Blueprint facts"));
301
+ }
302
+ catch (error) {
303
+ throw new Error(`Package Addition requires a supported Project Blueprint in .template/blueprint.json: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
304
+ }
305
+ const generationRecord = readGenerationRecord({
306
+ repositoryRoot: resolvedRoot,
307
+ });
308
+ const preflight = preflightLocalTemplateMetadata({
309
+ blueprint,
310
+ generationRecord,
311
+ });
312
+ const context = {
313
+ targetDir: resolvedRoot,
314
+ repositoryName: generationRecord.repositoryName,
315
+ defaultPackageScope: generationRecord.defaultPackageScope,
316
+ foundationPackages: {
317
+ typescriptConfiguration: {
318
+ name: preflight.foundationPackage.definition.name,
319
+ },
320
+ },
321
+ toolchain: generationRecord.toolchain,
322
+ };
323
+ const planningState = replayLocalTemplateMetadata({
324
+ context,
325
+ preflight,
326
+ });
327
+ return {
328
+ blueprint,
329
+ context,
330
+ [localTemplateMetadataStateKey]: { generationRecord, ...planningState },
331
+ };
332
+ }
333
+ function requireReplayAdapter(options) {
334
+ const adapter = options.adapters.find((candidate) => candidate.identity === options.identity);
335
+ if (adapter === undefined) {
336
+ throw new Error(`unknown Package Contribution replay adapter ${options.identity}; expected ${options.adapters.map((candidate) => candidate.identity).join(", ")} for ${options.owner}`);
337
+ }
338
+ return adapter;
269
339
  }
270
- function environmentNeedOwner(value) {
271
- if (!isRecord(value))
272
- return undefined;
273
- if (value.kind === "workspace-orchestration" && value.path === ".") {
274
- return { kind: "workspace-orchestration", path: "." };
340
+ function requireRecordReplayAdapter(options) {
341
+ try {
342
+ return requireReplayAdapter({
343
+ owner: options.owner,
344
+ adapters: options.adapters,
345
+ identity: options.record.contributionIdentity,
346
+ });
275
347
  }
276
- if (value.kind === "package-boundary" && typeof value.path === "string") {
277
- return { kind: "package-boundary", path: value.path };
348
+ catch (error) {
349
+ throw new Error(`Package Planning Provenance ${options.owner}:${options.record.contributionIdentity} (${options.record.planningContribution}) at ${options.record.path} failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
278
350
  }
279
- return undefined;
280
351
  }
281
- function assertEnvironmentNeedFactKeys(value, allowedKeys, context) {
282
- for (const key of Object.keys(value)) {
283
- if (!allowedKeys.includes(key)) {
284
- throw new Error(`Package Addition Environment Need ${context} contains unknown field: ${key}`);
285
- }
352
+ function packageLeafName(packageDefinition) {
353
+ return packageDefinition.name.slice(packageDefinition.name.lastIndexOf("/") + 1);
354
+ }
355
+ function replayPersistedPackageContribution(options) {
356
+ let contribution;
357
+ try {
358
+ contribution = options.adapter.replay({
359
+ context: options.context,
360
+ planningContribution: options.record.planningContribution,
361
+ packageDefinition: options.packageDefinition,
362
+ packageLeafName: packageLeafName(options.packageDefinition),
363
+ initialPackages: options.initialPackages,
364
+ });
365
+ }
366
+ catch (error) {
367
+ throw new Error(`Package Planning Provenance ${options.owner}:${options.record.contributionIdentity} (${options.record.planningContribution}) at ${options.record.path} failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
368
+ }
369
+ if (!packageDefinitionsEqual(contribution.definition, options.packageDefinition)) {
370
+ throw new Error(`Package Planning Provenance ${options.owner}:${options.record.contributionIdentity} at ${options.record.path} expected ${options.packageDefinition.name} / ${options.packageDefinition.path} / ${options.packageDefinition.role}, received ${contribution.definition.name} / ${contribution.definition.path} / ${contribution.definition.role}`);
286
371
  }
372
+ return options.adapter.identify(contribution);
287
373
  }
288
- function persistedCheckEnvironmentNeedFact(value, index) {
289
- if (!isRecord(value))
290
- return undefined;
291
- const context = `check[${index}]`;
292
- switch (value.kind) {
293
- case "playwright-browser-assets":
294
- assertEnvironmentNeedFactKeys(value, ["kind", "browser", "owner"], context);
295
- break;
296
- case "shellcheck-command":
297
- assertEnvironmentNeedFactKeys(value, ["kind", "owner"], context);
298
- break;
299
- case "rust-toolchain":
300
- assertEnvironmentNeedFactKeys(value, ["kind", "owner", "toolchain"], context);
301
- break;
302
- default:
303
- return undefined;
304
- }
305
- if (isRecord(value.owner)) {
306
- assertEnvironmentNeedFactKeys(value.owner, ["kind", "path"], `${context}.owner`);
307
- }
308
- const owner = environmentNeedOwner(value.owner);
309
- if (owner === undefined)
310
- return undefined;
311
- switch (value.kind) {
312
- case "playwright-browser-assets":
313
- return value.browser === "chromium"
314
- ? { kind: value.kind, browser: value.browser, owner }
315
- : undefined;
316
- case "shellcheck-command":
317
- return { kind: value.kind, owner };
318
- case "rust-toolchain":
319
- return value.toolchain === "stable"
320
- ? { kind: value.kind, toolchain: value.toolchain, owner }
321
- : undefined;
322
- default:
323
- return undefined;
374
+ function replayLocalTemplateMetadata(options) {
375
+ let foundationContribution;
376
+ const packageContributions = [];
377
+ for (const replay of options.preflight.replays) {
378
+ const contribution = replayPersistedPackageContribution({
379
+ context: options.context,
380
+ owner: replay.owner,
381
+ adapter: replay.adapter,
382
+ record: replay.record,
383
+ packageDefinition: replay.definition,
384
+ initialPackages: replay.initialPackages,
385
+ });
386
+ if (replay.kind === "foundation") {
387
+ foundationContribution = contribution;
388
+ }
389
+ else {
390
+ packageContributions.push(contribution);
391
+ }
324
392
  }
393
+ if (foundationContribution === undefined) {
394
+ throw new Error("Package Addition Generation Record is missing its validated Foundation contribution");
395
+ }
396
+ return { foundationContribution, packageContributions };
325
397
  }
326
398
  function readPersistedEnvironmentNeeds(targetDir) {
327
399
  const filePath = path.join(targetDir, environmentNeedsPath);
@@ -335,88 +407,31 @@ function readPersistedEnvironmentNeeds(targetDir) {
335
407
  catch (error) {
336
408
  throw new Error(`Package Addition requires valid Check Environment Need facts: ${error instanceof Error ? error.message : String(error)}`);
337
409
  }
338
- if (isRecord(value)) {
339
- for (const key of Object.keys(value)) {
340
- if (!["schemaVersion", "check", "deployment"].includes(key)) {
341
- throw new Error(`Package Addition Environment Need facts contain unknown field: ${key}`);
342
- }
343
- }
344
- }
345
- if (!isRecord(value) ||
346
- value.schemaVersion !== 1 ||
347
- !Array.isArray(value.check) ||
348
- !Array.isArray(value.deployment)) {
349
- throw new Error(`Package Addition requires valid Check Environment Need facts in ${environmentNeedsPath}`);
350
- }
351
- const check = value.check.map(persistedCheckEnvironmentNeedFact);
352
- if (check.some((fact) => fact === undefined)) {
353
- throw new Error(`Package Addition requires supported Check Environment Need facts in ${environmentNeedsPath}`);
410
+ try {
411
+ return parseEnvironmentNeedsMetadata(value);
354
412
  }
355
- if (value.deployment.some((fact, index) => {
356
- if (!isRecord(fact) || fact.kind !== "docker-engine")
357
- return true;
358
- assertEnvironmentNeedFactKeys(fact, ["kind"], `deployment[${index}]`);
359
- return false;
360
- })) {
361
- throw new Error(`Package Addition requires supported deployment Environment Need facts in ${environmentNeedsPath}`);
413
+ catch (error) {
414
+ throw new Error(`Package Addition requires valid Environment Need metadata in ${environmentNeedsPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
362
415
  }
363
- return {
364
- check: check.map(checkEnvironmentNeedFromFact),
365
- deployment: value.deployment.map((fact) => deploymentEnvironmentNeedFromFact(fact)),
366
- };
367
- }
368
- function uniqueEnvironmentNeeds(needs) {
369
- const seen = new Set();
370
- return needs.filter((need) => {
371
- const key = JSON.stringify(need);
372
- if (seen.has(key))
373
- return false;
374
- seen.add(key);
375
- return true;
376
- });
377
416
  }
378
417
  function readExistingPackageAdditionState(options) {
379
- const generationRecord = readGenerationRecord(options);
380
- const persistedEnvironmentNeeds = readPersistedEnvironmentNeeds(options.context.targetDir);
418
+ const { blueprint, context } = options.localTemplateMetadata;
419
+ const state = options.localTemplateMetadata[localTemplateMetadataStateKey];
420
+ const { foundationContribution, generationRecord, packageContributions: contributions, } = state;
421
+ const persistedEnvironmentNeeds = readPersistedEnvironmentNeeds(context.targetDir);
381
422
  const manifestTruthByPackagePath = new Map();
382
- const contributions = generationRecord.packages.flatMap((record) => {
383
- if (record.planningContribution === "foundationPlan") {
384
- return [];
385
- }
386
- const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === record.path);
387
- const definition = builtInPresetRegistry.require(record.definitionName);
388
- let candidates;
389
- if (record.planningContribution === "planInitialization") {
390
- candidates = definition.planInitializationContributions?.(options.context) ?? [definition.planInitialization(options.context)];
391
- }
392
- else {
393
- if (definition.planPackageAddition === undefined) {
394
- throw new Error(`Generation Record Definition ${record.definitionName} no longer supports Package Addition`);
395
- }
396
- const packageLeafName = expectedDefinition.name.split("/")[1];
397
- if (!packageLeafName) {
398
- throw new Error(`Generation Record package has an invalid name: ${expectedDefinition.name}`);
399
- }
400
- candidates = [
401
- definition.planPackageAddition({
402
- context: options.context,
403
- packageLeafName,
404
- packagePath: expectedDefinition.path,
405
- }),
406
- ];
407
- }
408
- const contribution = candidates.find((candidate) => packageDefinitionsEqual(candidate.definition, expectedDefinition));
409
- if (contribution === undefined) {
410
- throw new Error(`Generation Record cannot reproduce Package Definition ${expectedDefinition.name} at ${expectedDefinition.path}`);
411
- }
412
- return [contribution];
413
- });
414
- for (const packagePath of new Set(options.manifestTruthPackagePaths ?? [])) {
415
- const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === packagePath);
416
- if (expectedDefinition === undefined) {
423
+ const requiredManifestTruthPackagePaths = new Set(options.requiredManifestTruthPackagePaths ?? []);
424
+ for (const packagePath of requiredManifestTruthPackagePaths) {
425
+ if (!blueprint.packages.some((definition) => definition.path === packagePath)) {
417
426
  throw new Error(`Package Addition requires manifest truth for unknown Package Path ${packagePath}`);
418
427
  }
419
- const manifestPath = path.join(options.context.targetDir, packagePath, "package.json");
428
+ }
429
+ for (const expectedDefinition of blueprint.packages) {
430
+ const manifestPath = path.join(context.targetDir, expectedDefinition.path, "package.json");
431
+ if (!existsSync(manifestPath) &&
432
+ !requiredManifestTruthPackagePaths.has(expectedDefinition.path)) {
433
+ continue;
434
+ }
420
435
  let manifest;
421
436
  try {
422
437
  manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -429,17 +444,16 @@ function readExistingPackageAdditionState(options) {
429
444
  }
430
445
  manifestTruthByPackagePath.set(expectedDefinition.path, manifest);
431
446
  }
432
- const reconstructedCheckFacts = uniqueEnvironmentNeeds(contributions.flatMap((contribution) => contribution.environmentNeeds)).map(checkEnvironmentNeedFact);
433
- const reconstructedDeploymentFacts = uniqueEnvironmentNeeds(contributions.flatMap((contribution) => contribution.deploymentEnvironmentNeeds ?? [])).map(deploymentEnvironmentNeedFact);
434
- const persistedCheckFacts = persistedEnvironmentNeeds.check.map(checkEnvironmentNeedFact);
435
- const persistedDeploymentFacts = persistedEnvironmentNeeds.deployment.map(deploymentEnvironmentNeedFact);
436
- if (JSON.stringify(reconstructedCheckFacts) !==
437
- JSON.stringify(persistedCheckFacts) ||
438
- JSON.stringify(reconstructedDeploymentFacts) !==
439
- JSON.stringify(persistedDeploymentFacts)) {
440
- throw new Error("Package Addition requires persisted Environment Need facts to match the reproducible Project Projection");
447
+ const reconstructedEnvironmentNeeds = normalizeEnvironmentNeeds({
448
+ check: [foundationContribution, ...contributions].flatMap((contribution) => contribution.environmentNeeds),
449
+ deployment: [foundationContribution, ...contributions].flatMap((contribution) => contribution.deploymentEnvironmentNeeds ?? []),
450
+ });
451
+ if (JSON.stringify(reconstructedEnvironmentNeeds) !==
452
+ JSON.stringify(persistedEnvironmentNeeds)) {
453
+ throw new Error("Package Addition requires persisted Environment Needs to match the reproducible Project Projection");
441
454
  }
442
455
  return {
456
+ foundationContribution,
443
457
  contributions,
444
458
  manifestTruthByPackagePath,
445
459
  deploymentEnvironmentNeeds: persistedEnvironmentNeeds.deployment,
@@ -451,6 +465,13 @@ function packageDefinitionsEqual(left, right) {
451
465
  left.path === right.path &&
452
466
  left.role === right.role);
453
467
  }
468
+ function requirePersistedPackageDefinition(blueprint, definition) {
469
+ const persisted = blueprint.packages.find((candidate) => packageDefinitionsEqual(candidate, definition));
470
+ if (persisted === undefined) {
471
+ throw new Error(`Project Blueprint has no persisted Package Definition for ${definition.name} at ${definition.path} (${definition.role})`);
472
+ }
473
+ return persisted;
474
+ }
454
475
  function packageLinkIntentsEqual(left, right) {
455
476
  return (left.consumerPackagePath === right.consumerPackagePath &&
456
477
  left.providerPackagePath === right.providerPackagePath);
@@ -499,7 +520,7 @@ function contributedDevcontainerComposition(options) {
499
520
  },
500
521
  ],
501
522
  },
502
- layers: options.layers,
523
+ layers: [githubCliDevelopmentContainerToolLayer(), ...options.layers],
503
524
  });
504
525
  return {
505
526
  operations: [
@@ -509,7 +530,7 @@ function contributedDevcontainerComposition(options) {
509
530
  from: "devcontainer.json",
510
531
  to: ".devcontainer/devcontainer.json",
511
532
  replacements: {
512
- PROJECT_NAME: options.context.projectName,
533
+ PROJECT_NAME: options.context.repositoryName,
513
534
  NODE_LTS_MAJOR: options.context.toolchain.nodeLtsMajor,
514
535
  PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin,
515
536
  },
@@ -641,13 +662,36 @@ function composeDependencyMaintenancePolicy(contributions) {
641
662
  interval: "weekly",
642
663
  };
643
664
  }
665
+ function localTypescriptConfigurationSpecifier(options) {
666
+ const relativePackagePath = path.posix.relative(options.consumerPackagePath, options.configurationPackagePath);
667
+ if (relativePackagePath.length === 0) {
668
+ throw new Error("TypeScript Configuration Package must have a distinct Package Path");
669
+ }
670
+ return `link:${relativePackagePath}`;
671
+ }
672
+ function publicCliCandidate(contributions) {
673
+ const candidates = contributions.filter((contribution) => contribution.foundation.npmPublication?.kind === "public-cli-candidate");
674
+ if (candidates.length > 1) {
675
+ throw new Error(`Generated Repository Plan supports at most one public CLI candidate; found ${candidates.map((candidate) => `${candidate.definition.name} at ${candidate.definition.path}`).join(", ")}`);
676
+ }
677
+ const candidate = candidates[0];
678
+ if (candidate !== undefined && candidate.definition.role !== "cli-tool") {
679
+ throw new Error(`Public CLI candidate ${candidate.definition.name} must use the cli-tool Package role`);
680
+ }
681
+ return candidate;
682
+ }
683
+ function renderPublicationRootCheckCommand(candidatePackagePath) {
684
+ return [
685
+ renderTurboRunCommand(["boundaries", "format:check", "lint", "typecheck", "test"], [], { continueAfterFailure: true, taskPrefix: true }),
686
+ renderTurboRunCommand(["build", "test:e2e"], [`--filter=!./${candidatePackagePath}`], { continueAfterFailure: true, taskPrefix: true }),
687
+ "pnpm run publication:artifact",
688
+ ].join(" && ");
689
+ }
644
690
  function foundationPlan(options) {
645
- assertProjectBlueprintV2(options.blueprint);
646
- const configDefinition = typescriptConfigPackageDefinition(options.context);
647
- const persistedConfigDefinition = options.blueprint.packages.find((definition) => definition.name === configDefinition.name ||
648
- definition.path === configDefinition.path);
649
- if (persistedConfigDefinition === undefined ||
650
- !packageDefinitionsEqual(persistedConfigDefinition, configDefinition)) {
691
+ assertProjectBlueprint(options.blueprint);
692
+ const configContribution = options.foundationContribution;
693
+ const configDefinition = configContribution.definition;
694
+ if (!options.blueprint.packages.some((definition) => packageDefinitionsEqual(definition, configDefinition))) {
651
695
  throw new Error("Project Blueprint must contain the Foundation TypeScript configuration Package Definition");
652
696
  }
653
697
  const initialProjectLinkPlan = planExplicitProjectLinks({
@@ -662,15 +706,26 @@ function foundationPlan(options) {
662
706
  const injectedProviderNames = new Set([...initialProjectLinkPlan.manifestPatchesByPackagePath.values()].flatMap((patch) => Object.entries(patch.dependenciesMeta)
663
707
  .filter(([, metadata]) => metadata.injected)
664
708
  .map(([name]) => name)));
665
- const configPackageName = typescriptConfigPackageName(options.context);
709
+ const configPackageName = options.context.foundationPackages.typescriptConfiguration.name;
710
+ if (configDefinition.name !== configPackageName) {
711
+ throw new Error(`Generation Context TypeScript Configuration Package ${configPackageName} conflicts with Blueprint Package Definition ${configDefinition.name}`);
712
+ }
713
+ const publicationCandidate = publicCliCandidate(options.contributions);
666
714
  const packageContributions = options.contributions.map((contribution) => {
667
- const ownsTypeScriptConfig = contribution.operations.some((operation) => "to" in operation &&
668
- operation.to === `${contribution.definition.path}/tsconfig.json`);
669
- if (!ownsTypeScriptConfig)
715
+ if (contribution.foundation.typescriptConfigurationPackage === undefined) {
670
716
  return contribution;
717
+ }
671
718
  const dependencyField = injectedProviderNames.has(contribution.definition.name)
672
719
  ? "dependencies"
673
720
  : "devDependencies";
721
+ const dependencySpecifier = dependencyField === "devDependencies" &&
722
+ (contribution.manifest.private !== true ||
723
+ publicationCandidate?.definition.path === contribution.definition.path)
724
+ ? localTypescriptConfigurationSpecifier({
725
+ consumerPackagePath: contribution.definition.path,
726
+ configurationPackagePath: configDefinition.path,
727
+ })
728
+ : "workspace:*";
674
729
  const existingDependencies = contribution.manifest[dependencyField];
675
730
  return {
676
731
  ...contribution,
@@ -678,43 +733,67 @@ function foundationPlan(options) {
678
733
  ...contribution.manifest,
679
734
  [dependencyField]: {
680
735
  ...(isRecord(existingDependencies) ? existingDependencies : {}),
681
- [configPackageName]: "workspace:*",
736
+ [configPackageName]: dependencySpecifier,
682
737
  },
683
738
  },
684
739
  };
685
740
  });
686
- const configContribution = typescriptConfigContribution(options.context);
741
+ const requiresPackingHook = packageContributions.some((contribution) => {
742
+ const developmentDependencies = contribution.manifest.devDependencies;
743
+ return (isRecord(developmentDependencies) &&
744
+ typeof developmentDependencies[configPackageName] === "string" &&
745
+ developmentDependencies[configPackageName].startsWith("link:"));
746
+ });
687
747
  const contributions = [configContribution, ...packageContributions];
688
- const generationRecord = options.generationRecord ?? {
689
- schemaVersion: 1,
748
+ assertPackageContributionCommandNames(contributions.map((contribution) => {
749
+ const manifest = options.manifestTruthByPackagePath?.get(contribution.definition.path);
750
+ return manifest === undefined
751
+ ? contribution
752
+ : { ...contribution, manifest };
753
+ }));
754
+ const candidateRecord = options.generationRecord ?? {
755
+ schemaVersion: 2,
756
+ repositoryName: options.context.repositoryName,
757
+ defaultPackageScope: options.context.defaultPackageScope,
690
758
  preset: options.definition.metadata.name,
691
759
  templateVersion: "0.0.0",
692
760
  toolchain: options.context.toolchain,
693
761
  packages: [
694
- ...options.contributions.map((contribution) => ({
695
- path: contribution.definition.path,
696
- definitionName: options.definition.metadata.name,
697
- planningContribution: options.mode === "initialization"
762
+ ...options.contributions.map((contribution) => {
763
+ const planningContribution = options.mode === "initialization"
698
764
  ? "planInitialization"
699
- : "planPackageAddition",
700
- })),
765
+ : "planPackageAddition";
766
+ return {
767
+ packageDefinitionId: requirePersistedPackageDefinition(options.blueprint, contribution.definition).packageDefinitionId,
768
+ path: contribution.definition.path,
769
+ definitionName: options.definition.metadata.name,
770
+ planningContribution,
771
+ contributionIdentity: requireReplayAdapter({
772
+ owner: options.definition.metadata.name,
773
+ adapters: options.definition.packageContributionReplayAdapters,
774
+ identity: contribution.planningIdentity,
775
+ }).identity,
776
+ };
777
+ }),
701
778
  {
779
+ packageDefinitionId: requirePersistedPackageDefinition(options.blueprint, configDefinition).packageDefinitionId,
702
780
  path: configDefinition.path,
703
781
  definitionName: "foundation",
704
782
  planningContribution: "foundationPlan",
783
+ contributionIdentity: typescriptConfigReplayAdapter.identity,
705
784
  },
706
785
  ],
707
786
  };
708
- const environmentNeeds = uniqueEnvironmentNeeds(contributions.flatMap((item) => item.environmentNeeds));
709
- const deploymentEnvironmentNeeds = uniqueEnvironmentNeeds([
710
- ...(options.existingDeploymentEnvironmentNeeds ?? []),
711
- ...contributions.flatMap((item) => item.deploymentEnvironmentNeeds ?? []),
712
- ]);
713
- const persistedEnvironmentNeeds = {
714
- schemaVersion: 1,
715
- check: environmentNeeds.map(checkEnvironmentNeedFact),
716
- deployment: deploymentEnvironmentNeeds.map(deploymentEnvironmentNeedFact),
717
- };
787
+ const generationRecord = parseGenerationRecord(candidateRecord);
788
+ const persistedEnvironmentNeeds = normalizeEnvironmentNeeds({
789
+ check: contributions.flatMap((item) => item.environmentNeeds),
790
+ deployment: [
791
+ ...(options.existingDeploymentEnvironmentNeeds ?? []),
792
+ ...contributions.flatMap((item) => item.deploymentEnvironmentNeeds ?? []),
793
+ ],
794
+ });
795
+ const environmentNeeds = persistedEnvironmentNeeds.check;
796
+ const deploymentEnvironmentNeeds = persistedEnvironmentNeeds.deployment;
718
797
  const hasDeploymentTask = contributions.some((contribution) => {
719
798
  const scripts = contribution.manifest.scripts;
720
799
  return (typeof scripts === "object" &&
@@ -758,13 +837,27 @@ function foundationPlan(options) {
758
837
  const editorCustomization = editorCustomizationForCapabilities(contributions.flatMap((contribution) => contribution.foundation.editorCapabilities), loadEditorCustomizationDeclarations(resolveTemplateSource(templateSources.editorCustomization, "capabilities.json")));
759
838
  const dependencyMaintenancePolicy = composeDependencyMaintenancePolicy(contributions);
760
839
  const rootManifest = {
761
- name: options.context.projectName,
762
- version: "0.0.0",
840
+ name: options.context.repositoryName,
763
841
  private: true,
764
842
  type: "module",
843
+ ...(publicationCandidate === undefined
844
+ ? {}
845
+ : {
846
+ imports: {
847
+ "#npm-publication/*": "./scripts/npm-publication/*.ts",
848
+ },
849
+ }),
765
850
  scripts: {
766
- check: renderRootCheckCommand(),
851
+ check: publicationCandidate === undefined
852
+ ? renderRootCheckCommand()
853
+ : renderPublicationRootCheckCommand(publicationCandidate.definition.path),
767
854
  boundaries: "node --conditions=source scripts/check-boundaries.ts",
855
+ ...(publicationCandidate === undefined
856
+ ? {}
857
+ : {
858
+ "publication:readiness": "node --conditions=source scripts/npm-publication/check-readiness.ts",
859
+ "publication:artifact": "node --conditions=source scripts/npm-publication/check-artifact.ts",
860
+ }),
768
861
  ...(hasDeploymentTask
769
862
  ? { "check:deployment": renderDeploymentCheckCommand() }
770
863
  : {}),
@@ -777,9 +870,23 @@ function foundationPlan(options) {
777
870
  },
778
871
  devDependencies: {
779
872
  "@types/node": "catalog:",
873
+ ...(publicationCandidate === undefined
874
+ ? {}
875
+ : {
876
+ "@types/semver": "catalog:",
877
+ "@types/spdx-expression-parse": "catalog:",
878
+ }),
780
879
  oxfmt: "catalog:",
781
880
  oxlint: "catalog:",
782
881
  "oxlint-tsgolint": "catalog:",
882
+ ...(publicationCandidate === undefined
883
+ ? {}
884
+ : {
885
+ semver: "catalog:",
886
+ "spdx-expression-parse": "catalog:",
887
+ npm: "catalog:",
888
+ tar: "catalog:",
889
+ }),
783
890
  turbo: "catalog:",
784
891
  "typescript-7": "catalog:",
785
892
  },
@@ -846,6 +953,16 @@ function foundationPlan(options) {
846
953
  };
847
954
  const workflowOperations = [
848
955
  workflowOperation,
956
+ ...(publicationCandidate === undefined
957
+ ? []
958
+ : [
959
+ {
960
+ kind: "copyFile",
961
+ source: templateSources.tsCli,
962
+ from: "publication/release.yml",
963
+ to: ".github/workflows/release.yml",
964
+ },
965
+ ]),
849
966
  {
850
967
  kind: "writeTextTemplate",
851
968
  source: templateSources.foundation,
@@ -865,6 +982,16 @@ function foundationPlan(options) {
865
982
  nestedKeyOrder: packageConditionKeyOrder,
866
983
  },
867
984
  workspaceOperation,
985
+ ...(requiresPackingHook
986
+ ? [
987
+ {
988
+ kind: "copyFile",
989
+ source: templateSources.foundation,
990
+ from: ".pnpmfile.mjs",
991
+ to: ".pnpmfile.mjs",
992
+ },
993
+ ]
994
+ : []),
868
995
  {
869
996
  kind: "copyFile",
870
997
  source: templateSources.foundation,
@@ -877,6 +1004,16 @@ function foundationPlan(options) {
877
1004
  from: "turbo.json",
878
1005
  to: "turbo.json",
879
1006
  },
1007
+ ...(publicationCandidate === undefined
1008
+ ? []
1009
+ : [
1010
+ {
1011
+ kind: "mergeJsonTemplate",
1012
+ source: templateSources.tsCli,
1013
+ from: "publication/turbo.json",
1014
+ to: "turbo.json",
1015
+ },
1016
+ ]),
880
1017
  ...turboBoundaryTags.map((tag) => ({
881
1018
  kind: "mergeJsonTemplate",
882
1019
  source: templateSources.foundation,
@@ -901,6 +1038,27 @@ function foundationPlan(options) {
901
1038
  from: "tsconfig.json",
902
1039
  to: "tsconfig.json",
903
1040
  },
1041
+ ...(publicationCandidate === undefined
1042
+ ? []
1043
+ : [
1044
+ {
1045
+ kind: "mergeJsonTemplate",
1046
+ source: templateSources.tsCli,
1047
+ from: "publication/root-tsconfig.json",
1048
+ to: "tsconfig.json",
1049
+ multilineArrays: ["include"],
1050
+ },
1051
+ ]),
1052
+ ...(requiresPackingHook && publicationCandidate === undefined
1053
+ ? [
1054
+ {
1055
+ kind: "mergeJsonTemplate",
1056
+ source: templateSources.foundation,
1057
+ from: "tsconfig.packing-hook.json",
1058
+ to: "tsconfig.json",
1059
+ },
1060
+ ]
1061
+ : []),
904
1062
  {
905
1063
  kind: "copyFile",
906
1064
  source: templateSources.sharedOxc,
@@ -919,6 +1077,135 @@ function foundationPlan(options) {
919
1077
  from: "oxfmt.config.ts",
920
1078
  to: "oxfmt.config.ts",
921
1079
  },
1080
+ ...(publicationCandidate === undefined
1081
+ ? []
1082
+ : [
1083
+ {
1084
+ kind: "copyFile",
1085
+ source: templateSources.tsCli,
1086
+ from: "publication/readiness.ts",
1087
+ to: "scripts/npm-publication/readiness.ts",
1088
+ },
1089
+ {
1090
+ kind: "copyFile",
1091
+ source: templateSources.tsCli,
1092
+ from: "publication/artifact.ts",
1093
+ to: "scripts/npm-publication/artifact.ts",
1094
+ },
1095
+ {
1096
+ kind: "copyFile",
1097
+ source: templateSources.tsCli,
1098
+ from: "publication/handoff.ts",
1099
+ to: "scripts/npm-publication/handoff.ts",
1100
+ },
1101
+ {
1102
+ kind: "writeTextTemplate",
1103
+ source: templateSources.tsCli,
1104
+ from: "publication/check-artifact.ts",
1105
+ to: "scripts/npm-publication/check-artifact.ts",
1106
+ replacements: {
1107
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1108
+ },
1109
+ },
1110
+ {
1111
+ kind: "writeTextTemplate",
1112
+ source: templateSources.tsCli,
1113
+ from: "publication/publish.ts",
1114
+ to: "scripts/npm-publication/publish.ts",
1115
+ replacements: {
1116
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1117
+ },
1118
+ },
1119
+ {
1120
+ kind: "writeTextTemplate",
1121
+ source: templateSources.tsCli,
1122
+ from: "publication/release.ts",
1123
+ to: "scripts/npm-publication/release.ts",
1124
+ replacements: {
1125
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1126
+ },
1127
+ },
1128
+ {
1129
+ kind: "writeTextTemplate",
1130
+ source: templateSources.tsCli,
1131
+ from: "publication/check-readiness.ts",
1132
+ to: "scripts/npm-publication/check-readiness.ts",
1133
+ replacements: {
1134
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1135
+ },
1136
+ },
1137
+ {
1138
+ kind: "copyFile",
1139
+ source: templateSources.tsCli,
1140
+ from: "publication/changelog.ts",
1141
+ to: "scripts/npm-publication/changelog.ts",
1142
+ },
1143
+ {
1144
+ kind: "copyFile",
1145
+ source: templateSources.tsCli,
1146
+ from: "publication/RELEASING.md",
1147
+ to: "RELEASING.md",
1148
+ },
1149
+ {
1150
+ kind: "copyFile",
1151
+ source: templateSources.tsCli,
1152
+ from: "src/cli-command-identity.ts",
1153
+ to: "scripts/npm-publication/cli-command-identity.ts",
1154
+ },
1155
+ {
1156
+ kind: "writeTextTemplate",
1157
+ source: templateSources.tsCli,
1158
+ from: "publication-setup/setup.sh",
1159
+ to: "scripts/npm-publication-setup/setup.sh",
1160
+ replacements: {
1161
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1162
+ },
1163
+ },
1164
+ {
1165
+ kind: "setExecutable",
1166
+ path: "scripts/npm-publication-setup/setup.sh",
1167
+ executable: true,
1168
+ },
1169
+ {
1170
+ kind: "copyFile",
1171
+ source: templateSources.tsCli,
1172
+ from: "publication-setup/README.md",
1173
+ to: "scripts/npm-publication-setup/README.md",
1174
+ },
1175
+ {
1176
+ kind: "writeTextTemplate",
1177
+ source: templateSources.tsCli,
1178
+ from: "publication-setup/bridge.ts",
1179
+ to: "scripts/npm-publication-setup/bridge.ts",
1180
+ replacements: {
1181
+ PUBLIC_CLI_PACKAGE_PATH: publicationCandidate.definition.path,
1182
+ },
1183
+ },
1184
+ {
1185
+ kind: "copyFile",
1186
+ source: templateSources.tsCli,
1187
+ from: "publication-setup/assets/LICENSE-MIT.txt",
1188
+ to: "scripts/npm-publication-setup/assets/LICENSE-MIT.txt",
1189
+ },
1190
+ {
1191
+ kind: "copyFile",
1192
+ source: templateSources.tsCli,
1193
+ from: "publication-setup/assets/LICENSE-APACHE-2.0.txt",
1194
+ to: "scripts/npm-publication-setup/assets/LICENSE-APACHE-2.0.txt",
1195
+ },
1196
+ {
1197
+ kind: "copyFile",
1198
+ source: templateSources.tsCli,
1199
+ from: "publication-setup/assets/README.md.template",
1200
+ to: "scripts/npm-publication-setup/assets/README.md.template",
1201
+ },
1202
+ {
1203
+ kind: "copyFile",
1204
+ source: templateSources.tsCli,
1205
+ from: "publication-setup/assets/CHANGELOG.md.template",
1206
+ to: "scripts/npm-publication-setup/assets/CHANGELOG.md.template",
1207
+ },
1208
+ ]),
922
1209
  {
923
1210
  kind: "writeJson",
924
1211
  to: ".vscode/extensions.json",
@@ -1007,6 +1294,7 @@ function foundationPlan(options) {
1007
1294
  ...linkOperations.map((operation) => withProvenance(operation, foundationProvenance)),
1008
1295
  ];
1009
1296
  const reconciliation = [
1297
+ { path: "tsconfig.json", driver: "structured" },
1010
1298
  { path: "turbo.json", driver: "structured" },
1011
1299
  {
1012
1300
  path: ".devcontainer/devcontainer.json",
@@ -1035,6 +1323,7 @@ function foundationPlan(options) {
1035
1323
  : "planInitialization",
1036
1324
  blueprint: options.blueprint,
1037
1325
  generationRecord,
1326
+ packageContributions,
1038
1327
  operations,
1039
1328
  reconciliation,
1040
1329
  developmentContainer: {
@@ -1056,40 +1345,342 @@ function foundationPlan(options) {
1056
1345
  ],
1057
1346
  };
1058
1347
  }
1059
- export function planGeneratedRepositoryInitialization(options) {
1060
- const presetBlueprint = options.definition.blueprint(options.context);
1348
+ function hasConfigurablePrimaryPackage(definition) {
1349
+ return definition.initialPrimaryPackage !== undefined;
1350
+ }
1351
+ function errorDiagnostics(error) {
1352
+ return (error instanceof Error ? error.message : String(error))
1353
+ .split("\n")
1354
+ .filter((diagnostic) => diagnostic.length > 0);
1355
+ }
1356
+ function preparePresetInitialization(options) {
1357
+ if (!hasConfigurablePrimaryPackage(options.definition)) {
1358
+ const diagnostics = [];
1359
+ let presetBlueprint;
1360
+ let contributions;
1361
+ try {
1362
+ presetBlueprint = assertProjectBlueprintDraft(options.definition.blueprint(options.context));
1363
+ }
1364
+ catch (error) {
1365
+ diagnostics.push(...errorDiagnostics(error));
1366
+ }
1367
+ try {
1368
+ contributions = options.definition.planInitializationContributions?.(options.context) ?? [options.definition.planInitialization(options.context)];
1369
+ }
1370
+ catch (error) {
1371
+ diagnostics.push(...errorDiagnostics(error));
1372
+ }
1373
+ if (diagnostics.length > 0)
1374
+ throw new Error(diagnostics.join("\n"));
1375
+ return {
1376
+ presetBlueprint: presetBlueprint,
1377
+ contributions: contributions,
1378
+ };
1379
+ }
1380
+ const capability = options.definition.initialPrimaryPackage;
1381
+ const leafName = options.overrides?.name ?? capability.defaultLeafName;
1382
+ const resolvedPackageIdentity = {
1383
+ leafName,
1384
+ definition: {
1385
+ name: `@${options.context.defaultPackageScope}/${leafName}`,
1386
+ path: options.overrides?.path ??
1387
+ capability.defaultPackagePath({ packageLeafName: leafName }),
1388
+ role: capability.role,
1389
+ },
1390
+ };
1391
+ const diagnostics = [];
1392
+ let presetBlueprint;
1393
+ let contribution;
1394
+ try {
1395
+ presetBlueprint = assertProjectBlueprintDraft({
1396
+ schemaVersion: 3,
1397
+ packages: [resolvedPackageIdentity.definition],
1398
+ });
1399
+ }
1400
+ catch (error) {
1401
+ diagnostics.push(...errorDiagnostics(error));
1402
+ }
1403
+ try {
1404
+ contribution = capability.planInitialContribution({
1405
+ context: options.context,
1406
+ resolvedPackageIdentity,
1407
+ });
1408
+ }
1409
+ catch (error) {
1410
+ diagnostics.push(...errorDiagnostics(error));
1411
+ }
1412
+ if (diagnostics.length > 0)
1413
+ throw new Error(diagnostics.join("\n"));
1414
+ return {
1415
+ presetBlueprint: presetBlueprint,
1416
+ contributions: [contribution],
1417
+ resolvedPackageIdentity,
1418
+ };
1419
+ }
1420
+ function planPreparedRepositoryInitialization(options) {
1421
+ const { presetBlueprint, contributions } = options;
1061
1422
  const configDefinition = typescriptConfigPackageDefinition(options.context);
1062
- if (presetBlueprint.packages.some((definition) => definition.name === configDefinition.name ||
1063
- definition.path === configDefinition.path)) {
1064
- throw new Error("Preset Blueprint must not redefine the Foundation TypeScript configuration Package Definition");
1423
+ const foundationIdentityDiagnostics = presetBlueprint.packages.flatMap((definition) => [
1424
+ ...(definition.name === configDefinition.name
1425
+ ? [
1426
+ `Initial package name ${definition.name} conflicts with Foundation package ${configDefinition.name}`,
1427
+ ]
1428
+ : []),
1429
+ ...(definition.path === configDefinition.path
1430
+ ? [
1431
+ `Initial Package Path ${definition.path} conflicts with Foundation Package Path ${configDefinition.path}`,
1432
+ ]
1433
+ : []),
1434
+ ]);
1435
+ if (foundationIdentityDiagnostics.length > 0) {
1436
+ throw new Error(foundationIdentityDiagnostics.join("\n"));
1065
1437
  }
1438
+ const occupiedIds = new Set();
1066
1439
  const blueprint = {
1067
1440
  ...presetBlueprint,
1068
- packages: [...presetBlueprint.packages, configDefinition],
1441
+ packages: [
1442
+ ...presetBlueprint.packages.map((definition) => {
1443
+ const contribution = contributions.find((candidate) => packageDefinitionsEqual(candidate.definition, definition));
1444
+ if (contribution === undefined) {
1445
+ throw new Error(`Preset Blueprint Package Definition ${definition.path} has no initialization Package Contribution`);
1446
+ }
1447
+ return persistPackageDefinition({
1448
+ definition,
1449
+ provenance: {
1450
+ definitionName: options.definition.metadata.name,
1451
+ planningContribution: "planInitialization",
1452
+ contributionIdentity: requireReplayAdapter({
1453
+ owner: options.definition.metadata.name,
1454
+ adapters: options.definition.packageContributionReplayAdapters,
1455
+ identity: contribution.planningIdentity,
1456
+ }).identity,
1457
+ },
1458
+ occupiedIds,
1459
+ });
1460
+ }),
1461
+ persistPackageDefinition({
1462
+ definition: configDefinition,
1463
+ provenance: {
1464
+ definitionName: "foundation",
1465
+ planningContribution: "foundationPlan",
1466
+ contributionIdentity: typescriptConfigReplayAdapter.identity,
1467
+ },
1468
+ occupiedIds,
1469
+ }),
1470
+ ],
1069
1471
  };
1070
- const contributions = options.definition.planInitializationContributions?.(options.context) ?? [options.definition.planInitialization(options.context)];
1071
1472
  return foundationPlan({
1072
1473
  definition: options.definition,
1073
1474
  context: options.context,
1074
1475
  blueprint,
1476
+ foundationContribution: typescriptConfigReplayAdapter.identify(typescriptConfigContribution(options.context, configDefinition)),
1075
1477
  contributions,
1076
1478
  mode: "initialization",
1077
1479
  });
1078
1480
  }
1481
+ function assertPreflightedInitializationPlan(plan) {
1482
+ const projectionDiagnostics = validateProjectProjectionPlan({
1483
+ operations: plan.operations,
1484
+ reconciliation: plan.reconciliation,
1485
+ });
1486
+ if (projectionDiagnostics.length > 0) {
1487
+ throw new Error(projectionDiagnostics.join("\n"));
1488
+ }
1489
+ return plan;
1490
+ }
1491
+ export function planGeneratedRepositoryInitialization(options) {
1492
+ const prepared = preparePresetInitialization(options);
1493
+ return assertPreflightedInitializationPlan(planPreparedRepositoryInitialization({
1494
+ definition: options.definition,
1495
+ context: options.context,
1496
+ presetBlueprint: prepared.presetBlueprint,
1497
+ contributions: prepared.contributions,
1498
+ }));
1499
+ }
1500
+ export function prepareGeneratedRepositoryInitialization(options) {
1501
+ const diagnostics = [];
1502
+ const appendDiagnostics = (error) => {
1503
+ diagnostics.push(...errorDiagnostics(error));
1504
+ };
1505
+ const scopeOverride = options.overrides?.scope;
1506
+ const normalizedScope = scopeOverride?.startsWith("@") === true
1507
+ ? scopeOverride.slice(1)
1508
+ : scopeOverride;
1509
+ const nameOverride = options.overrides?.name;
1510
+ const pathOverride = options.overrides?.path;
1511
+ const repositoryName = path.basename(path.resolve(options.targetDir));
1512
+ const configurablePrimaryPackage = options.definition.initialPrimaryPackage !== undefined;
1513
+ if (!configurablePrimaryPackage &&
1514
+ (nameOverride !== undefined || pathOverride !== undefined)) {
1515
+ diagnostics.push(`Built-in Preset ${options.definition.metadata.name} has fixed initial package topology and does not accept --name or --path`);
1516
+ }
1517
+ let validNameOverride = nameOverride !== undefined &&
1518
+ isValidNewNpmPackageName(`@x/${nameOverride}`);
1519
+ if (nameOverride !== undefined && !validNameOverride) {
1520
+ diagnostics.push("--name must be an unscoped package leaf name");
1521
+ }
1522
+ let validPathOverride = pathOverride !== undefined;
1523
+ if (pathOverride !== undefined) {
1524
+ const pathValidation = validateNewPackagePath(pathOverride);
1525
+ if (!pathValidation.hasValidShape) {
1526
+ diagnostics.push("--path must be exactly two safe path segments");
1527
+ validPathOverride = false;
1528
+ }
1529
+ if (pathValidation.reservedWorkspaceCollection !== undefined) {
1530
+ diagnostics.push(`--path ${pathOverride} uses reserved workspace collection ${pathValidation.reservedWorkspaceCollection}`);
1531
+ validPathOverride = false;
1532
+ }
1533
+ }
1534
+ let validScopeOverride = normalizedScope !== undefined &&
1535
+ scopeOverride === scopeOverride?.trim() &&
1536
+ isValidDefaultPackageScope(normalizedScope) &&
1537
+ isValidNewNpmPackageName(`@${normalizedScope}/typescript-config`);
1538
+ if (normalizedScope !== undefined && !validScopeOverride) {
1539
+ diagnostics.push("--scope must be a valid npm scope without whitespace");
1540
+ }
1541
+ let validRepositoryScope = isValidDefaultPackageScope(repositoryName) &&
1542
+ isValidNewNpmPackageName(`@${repositoryName}/typescript-config`);
1543
+ if (normalizedScope === undefined && !validRepositoryScope) {
1544
+ diagnostics.push(`Repository Identity ${repositoryName} is not a valid default package scope; pass --scope with a valid npm scope`);
1545
+ }
1546
+ const resolvedScope = validScopeOverride
1547
+ ? (normalizedScope ?? "repository")
1548
+ : validRepositoryScope
1549
+ ? repositoryName
1550
+ : "repository";
1551
+ const resolvedLeaf = configurablePrimaryPackage &&
1552
+ nameOverride !== undefined &&
1553
+ validNameOverride
1554
+ ? nameOverride
1555
+ : configurablePrimaryPackage
1556
+ ? options.definition.initialPrimaryPackage?.defaultLeafName
1557
+ : undefined;
1558
+ if (resolvedLeaf !== undefined &&
1559
+ !isValidNewNpmPackageName(`@${resolvedScope}/${resolvedLeaf}`)) {
1560
+ if (nameOverride !== undefined) {
1561
+ validNameOverride = false;
1562
+ diagnostics.push("--name must be an unscoped package leaf name");
1563
+ }
1564
+ else if (scopeOverride !== undefined) {
1565
+ validScopeOverride = false;
1566
+ diagnostics.push("--scope must be a valid npm scope without whitespace");
1567
+ }
1568
+ else {
1569
+ validRepositoryScope = false;
1570
+ diagnostics.push(`Repository Identity ${repositoryName} is not a valid default package scope; pass --scope with a valid npm scope`);
1571
+ }
1572
+ }
1573
+ const context = createGenerationContext({
1574
+ targetDir: options.targetDir,
1575
+ defaultPackageScope: validScopeOverride
1576
+ ? (normalizedScope ?? "repository")
1577
+ : validRepositoryScope
1578
+ ? repositoryName
1579
+ : "repository",
1580
+ toolchain: options.toolchain,
1581
+ });
1582
+ const safeNameOverride = configurablePrimaryPackage && nameOverride !== undefined
1583
+ ? validNameOverride
1584
+ ? nameOverride
1585
+ : "x"
1586
+ : undefined;
1587
+ let safeDerivedPathOverride;
1588
+ const initialPrimaryPackage = options.definition.initialPrimaryPackage;
1589
+ if (initialPrimaryPackage !== undefined && pathOverride === undefined) {
1590
+ const safeLeafName = safeNameOverride ?? initialPrimaryPackage.defaultLeafName;
1591
+ const derivedPackagePath = initialPrimaryPackage.defaultPackagePath({
1592
+ packageLeafName: safeLeafName,
1593
+ });
1594
+ const derivedPathValidation = validateNewPackagePath(derivedPackagePath);
1595
+ if (!derivedPathValidation.hasValidShape ||
1596
+ derivedPathValidation.reservedWorkspaceCollection !== undefined) {
1597
+ diagnostics.push(`Preset-derived Package Path ${derivedPackagePath} is unsafe; pass --path with exactly two safe path segments`);
1598
+ safeDerivedPathOverride = "packages/invalid";
1599
+ }
1600
+ }
1601
+ const safeIdentityOverrides = {
1602
+ ...(safeNameOverride === undefined ? {} : { name: safeNameOverride }),
1603
+ ...(configurablePrimaryPackage && pathOverride !== undefined
1604
+ ? { path: validPathOverride ? pathOverride : "packages/invalid" }
1605
+ : safeDerivedPathOverride === undefined
1606
+ ? {}
1607
+ : { path: safeDerivedPathOverride }),
1608
+ };
1609
+ let prepared;
1610
+ try {
1611
+ prepared = preparePresetInitialization({
1612
+ definition: options.definition,
1613
+ context,
1614
+ ...(Object.keys(safeIdentityOverrides).length === 0
1615
+ ? {}
1616
+ : { overrides: safeIdentityOverrides }),
1617
+ });
1618
+ }
1619
+ catch (error) {
1620
+ appendDiagnostics(error);
1621
+ }
1622
+ let plan;
1623
+ if (prepared !== undefined) {
1624
+ try {
1625
+ plan = planPreparedRepositoryInitialization({
1626
+ definition: options.definition,
1627
+ context,
1628
+ presetBlueprint: prepared.presetBlueprint,
1629
+ contributions: prepared.contributions,
1630
+ });
1631
+ }
1632
+ catch (error) {
1633
+ appendDiagnostics(error);
1634
+ }
1635
+ }
1636
+ if (plan !== undefined) {
1637
+ diagnostics.push(...validateProjectProjectionPlan({
1638
+ operations: plan.operations,
1639
+ reconciliation: plan.reconciliation,
1640
+ }));
1641
+ }
1642
+ if (diagnostics.length > 0)
1643
+ throw new Error(diagnostics.join("\n"));
1644
+ if (prepared === undefined || plan === undefined) {
1645
+ throw new Error("Initialization preparation did not produce a plan");
1646
+ }
1647
+ return {
1648
+ context,
1649
+ resolved: {
1650
+ preset: options.definition.metadata.name,
1651
+ topology: prepared.resolvedPackageIdentity === undefined
1652
+ ? "fixed"
1653
+ : "configurable-primary-package",
1654
+ packages: prepared.presetBlueprint.packages.map(({ name, path }) => ({
1655
+ name,
1656
+ path,
1657
+ })),
1658
+ scope: context.defaultPackageScope,
1659
+ },
1660
+ ...(prepared.resolvedPackageIdentity === undefined
1661
+ ? {}
1662
+ : { resolvedPackageIdentity: prepared.resolvedPackageIdentity }),
1663
+ plan,
1664
+ publicationSetup: publicCliCandidate(prepared.contributions) === undefined
1665
+ ? null
1666
+ : { command: "./scripts/npm-publication-setup/setup.sh" },
1667
+ };
1668
+ }
1079
1669
  export function planGeneratedRepositoryPackageAddition(options) {
1080
- assertProjectBlueprintV2(options.blueprint);
1670
+ const { blueprint: persistedBlueprint, context } = options.localTemplateMetadata;
1671
+ assertProjectBlueprint(persistedBlueprint);
1081
1672
  if (!options.definition.planPackageAddition)
1082
1673
  throw new Error(`Built-in Preset ${options.definition.metadata.name} does not support Package Addition`);
1083
1674
  const packagePath = options.packagePath ??
1084
1675
  options.definition.defaultPackagePath?.({
1085
- context: options.context,
1676
+ context,
1086
1677
  packageLeafName: options.packageLeafName,
1087
1678
  });
1088
1679
  if (packagePath === undefined) {
1089
1680
  throw new Error(`Built-in Preset ${options.definition.metadata.name} must own a default Package Path or receive an explicit Package Path`);
1090
1681
  }
1091
1682
  const contribution = options.definition.planPackageAddition({
1092
- context: options.context,
1683
+ context,
1093
1684
  packageLeafName: options.packageLeafName,
1094
1685
  packagePath,
1095
1686
  });
@@ -1097,28 +1688,31 @@ export function planGeneratedRepositoryPackageAddition(options) {
1097
1688
  consumerPackagePath,
1098
1689
  providerPackagePath: contribution.definition.path,
1099
1690
  }));
1100
- const conflictingPackage = options.blueprint.packages.find((existing) => existing.name === contribution.definition.name ||
1691
+ const conflictingPackage = persistedBlueprint.packages.find((existing) => existing.name === contribution.definition.name ||
1101
1692
  existing.path === contribution.definition.path);
1102
1693
  if (conflictingPackage !== undefined) {
1103
1694
  const isExactPackageDefinition = packageDefinitionsEqual(conflictingPackage, contribution.definition);
1104
- const existingPackageLinkIntents = options.blueprint.packageLinkIntents ?? [];
1695
+ const existingPackageLinkIntents = persistedBlueprint.packageLinkIntents ?? [];
1105
1696
  const missingPackageLinkIntent = requestedPackageLinkIntents.find((requested) => !existingPackageLinkIntents.some((existing) => packageLinkIntentsEqual(existing, requested)));
1106
1697
  if (isExactPackageDefinition && missingPackageLinkIntent === undefined) {
1107
1698
  const existing = readExistingPackageAdditionState(options);
1108
1699
  const plan = foundationPlan({
1109
1700
  definition: options.definition,
1110
- context: options.context,
1111
- blueprint: options.blueprint,
1701
+ context,
1702
+ blueprint: persistedBlueprint,
1703
+ foundationContribution: existing.foundationContribution,
1112
1704
  contributions: existing.contributions,
1113
1705
  renderContributions: [],
1114
1706
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1707
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1115
1708
  generationRecord: existing.generationRecord,
1116
1709
  mode: "addition",
1117
1710
  });
1118
1711
  const currentProjection = foundationPlan({
1119
1712
  definition: options.definition,
1120
- context: options.context,
1121
- blueprint: options.blueprint,
1713
+ context,
1714
+ blueprint: persistedBlueprint,
1715
+ foundationContribution: existing.foundationContribution,
1122
1716
  contributions: existing.contributions,
1123
1717
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1124
1718
  generationRecord: existing.generationRecord,
@@ -1145,47 +1739,64 @@ export function planGeneratedRepositoryPackageAddition(options) {
1145
1739
  }
1146
1740
  throw new Error(`Package Addition conflicts with existing Package Definition ${conflictingPackage.name} at ${conflictingPackage.path} (${conflictingPackage.role}); requested ${contribution.definition.name} at ${contribution.definition.path} (${contribution.definition.role})`);
1147
1741
  }
1742
+ const contributionIdentity = requireReplayAdapter({
1743
+ owner: options.definition.metadata.name,
1744
+ adapters: options.definition.packageContributionReplayAdapters,
1745
+ identity: contribution.planningIdentity,
1746
+ }).identity;
1747
+ const persistedContributionDefinition = persistPackageDefinition({
1748
+ definition: contribution.definition,
1749
+ provenance: {
1750
+ definitionName: options.definition.metadata.name,
1751
+ planningContribution: "planPackageAddition",
1752
+ contributionIdentity,
1753
+ },
1754
+ occupiedIds: new Set(persistedBlueprint.packages.map((definition) => definition.packageDefinitionId)),
1755
+ });
1148
1756
  const blueprint = {
1149
- ...options.blueprint,
1150
- packages: [...options.blueprint.packages, contribution.definition],
1757
+ ...persistedBlueprint,
1758
+ packages: [...persistedBlueprint.packages, persistedContributionDefinition],
1151
1759
  ...(requestedPackageLinkIntents.length > 0
1152
1760
  ? {
1153
1761
  packageLinkIntents: [
1154
- ...(options.blueprint.packageLinkIntents ?? []),
1762
+ ...(persistedBlueprint.packageLinkIntents ?? []),
1155
1763
  ...requestedPackageLinkIntents,
1156
1764
  ],
1157
1765
  }
1158
1766
  : {}),
1159
1767
  };
1160
- assertProjectBlueprintV2(blueprint);
1161
- const manifestTruthPackagePaths = requestedPackageLinkIntents.length === 0
1768
+ assertProjectBlueprint(blueprint);
1769
+ const requiredManifestTruthPackagePaths = requestedPackageLinkIntents.length === 0
1162
1770
  ? []
1163
1771
  : [
1164
- ...(options.blueprint.packageLinkIntents ?? []).flatMap((intent) => [
1772
+ ...(persistedBlueprint.packageLinkIntents ?? []).flatMap((intent) => [
1165
1773
  intent.consumerPackagePath,
1166
1774
  intent.providerPackagePath,
1167
1775
  ]),
1168
1776
  ...requestedPackageLinkIntents.map((intent) => intent.consumerPackagePath),
1169
1777
  ];
1170
1778
  const existing = readExistingPackageAdditionState({
1171
- ...options,
1172
- manifestTruthPackagePaths,
1779
+ localTemplateMetadata: options.localTemplateMetadata,
1780
+ requiredManifestTruthPackagePaths,
1173
1781
  });
1174
1782
  const generationRecord = {
1175
1783
  ...existing.generationRecord,
1176
1784
  packages: [
1177
1785
  ...existing.generationRecord.packages,
1178
1786
  {
1787
+ packageDefinitionId: persistedContributionDefinition.packageDefinitionId,
1179
1788
  path: contribution.definition.path,
1180
1789
  definitionName: options.definition.metadata.name,
1181
1790
  planningContribution: "planPackageAddition",
1791
+ contributionIdentity,
1182
1792
  },
1183
1793
  ],
1184
1794
  };
1185
1795
  const beforeProjection = foundationPlan({
1186
1796
  definition: options.definition,
1187
- context: options.context,
1188
- blueprint: options.blueprint,
1797
+ context,
1798
+ blueprint: persistedBlueprint,
1799
+ foundationContribution: existing.foundationContribution,
1189
1800
  contributions: existing.contributions,
1190
1801
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1191
1802
  generationRecord: existing.generationRecord,
@@ -1193,30 +1804,24 @@ export function planGeneratedRepositoryPackageAddition(options) {
1193
1804
  });
1194
1805
  const afterProjection = foundationPlan({
1195
1806
  definition: options.definition,
1196
- context: options.context,
1807
+ context,
1197
1808
  blueprint,
1809
+ foundationContribution: existing.foundationContribution,
1198
1810
  contributions: [...existing.contributions, contribution],
1199
1811
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1200
- ...(requestedPackageLinkIntents.length === 0
1201
- ? {}
1202
- : {
1203
- manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1204
- }),
1812
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1205
1813
  generationRecord,
1206
1814
  mode: "initialization",
1207
1815
  });
1208
1816
  const plan = foundationPlan({
1209
1817
  definition: options.definition,
1210
- context: options.context,
1818
+ context,
1211
1819
  blueprint,
1820
+ foundationContribution: existing.foundationContribution,
1212
1821
  contributions: [...existing.contributions, contribution],
1213
1822
  renderContributions: [contribution],
1214
1823
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1215
- ...(requestedPackageLinkIntents.length === 0
1216
- ? {}
1217
- : {
1218
- manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1219
- }),
1824
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1220
1825
  generationRecord,
1221
1826
  mode: "addition",
1222
1827
  });