@ykdz/template 0.0.35 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/application.d.ts +2 -1
  2. package/dist/application.js +64 -75
  3. package/dist/main.js +6 -2
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +968 -364
  6. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
  7. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
  8. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
  9. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
  10. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
  11. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
  12. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
  15. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +0 -1
  16. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
  17. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
  18. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
  19. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
  20. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
  21. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +53 -25
  22. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
  23. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -7
  24. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
  25. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -20
  26. package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
  27. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
  28. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
  29. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
  30. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
  31. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
  32. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
  33. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
  34. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
  37. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
  38. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
  39. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
  40. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
  41. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
  42. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
  43. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
  44. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
  45. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
  46. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
  47. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
  48. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
  49. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
  50. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
  51. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
  52. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
  53. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
  54. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
  55. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
  56. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
  57. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
  58. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
  59. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
  60. package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
  61. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
  62. package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
  63. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
  64. package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
  65. package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
  66. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
  67. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
  68. package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
  69. package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
  70. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
  71. package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
  72. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
  73. package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
  74. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
  75. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
  76. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
  77. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
  78. package/package.json +5 -5
  79. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
  80. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
@@ -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,15 +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
18
  import { githubCliDevelopmentContainerToolLayer } from "./shared/development-container.js";
16
- import { typescriptConfigContribution, typescriptConfigPackageDefinition, typescriptConfigPackageName, } from "./shared/typescript.js";
19
+ import { typescriptConfigContribution, typescriptConfigPackageDefinition, typescriptConfigReplayAdapter, } from "./shared/typescript.js";
17
20
  import { vuePnpmDependencyOverrides } from "./shared/vue.js";
18
21
  import { templateSources } from "./template-sources.js";
19
22
  import { tsCliDefinition } from "./ts-cli/definition.js";
@@ -21,6 +24,7 @@ import { tsLibDefinition } from "./ts-lib/definition.js";
21
24
  import { vikeAppDefinition } from "./vike-app/definition.js";
22
25
  import { vueAppDefinition } from "./vue-app/definition.js";
23
26
  import { vueHonoAppDefinition } from "./vue-hono-app/definition.js";
27
+ const localTemplateMetadataStateKey = Symbol("localTemplateMetadataState");
24
28
  const environmentNeedsPath = ".template/environment-needs.json";
25
29
  const packageManifestKeyOrder = [
26
30
  "name",
@@ -53,8 +57,8 @@ const packageConditionKeyOrder = ["source", "types", "default"];
53
57
  export function resolveBuiltInTemplateSource(source, relativePath) {
54
58
  return resolveTemplateSource(source, relativePath);
55
59
  }
56
- export function validateProjectBlueprintV2(value) {
57
- return validateCoreProjectBlueprintV2(value);
60
+ export function validateProjectBlueprint(value) {
61
+ return validateCoreProjectBlueprint(value);
58
62
  }
59
63
  class PresetRegistry {
60
64
  #definitions;
@@ -64,6 +68,14 @@ class PresetRegistry {
64
68
  new Set(names).size !== names.length) {
65
69
  throw new Error("Preset Registry requires unique non-empty Definition names");
66
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
+ }
67
79
  this.#definitions = [...definitions].toSorted((left, right) => left.metadata.name.localeCompare(right.metadata.name));
68
80
  }
69
81
  all() {
@@ -84,245 +96,304 @@ export const builtInPresetRegistry = new PresetRegistry([
84
96
  vueHonoAppDefinition,
85
97
  vikeAppDefinition,
86
98
  ]);
87
- /** Registry-derived Template Source roots checked independently of render plans. */
88
- export function builtInPresetTemplateSourceContexts() {
89
- return [
90
- ...builtInPresetRegistry.all().map((definition) => ({
91
- name: definition.metadata.name,
92
- root: resolveBuiltInTemplateSource(definition.source, "."),
93
- })),
94
- {
95
- name: "foundation",
96
- root: resolveBuiltInTemplateSource(templateSources.foundation, "."),
97
- },
98
- {
99
- name: "shared-devcontainer",
100
- root: resolveBuiltInTemplateSource(templateSources.sharedDevcontainer, "."),
101
- },
102
- {
103
- name: "shared-oxc",
104
- root: resolveBuiltInTemplateSource(templateSources.sharedOxc, "."),
105
- },
106
- {
107
- name: "shared-vue",
108
- root: resolveBuiltInTemplateSource(templateSources.vue, "."),
109
- },
110
- ];
111
- }
112
- /**
113
- * Derives direct Template Source checks from every registered Definition's
114
- * actual initial contributions, without maintaining a second Preset catalog.
115
- */
116
- export function builtInPresetTemplateSourceCheckContexts() {
117
- return builtInPresetRegistry.all().flatMap((definition) => {
118
- const context = createGenerationContext({
119
- targetDir: path.join("generated-repository", "template-source", definition.metadata.name),
120
- toolchain: { nodeLtsMajor: "24", packageManagerPin: "pnpm@11.11.0" },
121
- });
122
- const plan = planGeneratedRepositoryInitialization({ definition, context });
123
- const contributions = definition.planInitializationContributions?.(context) ?? [definition.planInitialization(context)];
124
- return contributions.map((contribution) => ({
125
- definition,
126
- contribution,
127
- plan,
128
- }));
129
- });
130
- }
131
99
  export function createGenerationContext(options) {
132
- const projectName = path.basename(path.resolve(options.targetDir));
100
+ const repositoryName = path.basename(path.resolve(options.targetDir));
101
+ const defaultPackageScope = options.defaultPackageScope ?? repositoryName;
133
102
  return {
134
103
  targetDir: options.targetDir,
135
- projectName,
136
- scope: options.scope ?? projectName,
104
+ repositoryName,
105
+ defaultPackageScope,
106
+ foundationPackages: {
107
+ typescriptConfiguration: {
108
+ name: `@${defaultPackageScope}/typescript-config`,
109
+ },
110
+ },
137
111
  toolchain: options.toolchain,
138
112
  };
139
113
  }
140
114
  function isRecord(value) {
141
115
  return typeof value === "object" && value !== null && !Array.isArray(value);
142
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
+ }
143
156
  function readGenerationRecord(options) {
144
- const generationPath = path.join(options.context.targetDir, ".template/generation.json");
145
- 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) {
146
161
  try {
147
- value = JSON.parse(readFileSync(generationPath, "utf8"));
162
+ return JSON.parse(readFileSync(filePath, "utf8"));
148
163
  }
149
164
  catch (error) {
150
- throw new Error(`Package Addition requires valid Generation Record facts: ${error instanceof Error ? error.message : String(error)}`);
151
- }
152
- if (isRecord(value)) {
153
- for (const key of Object.keys(value)) {
154
- if (![
155
- "schemaVersion",
156
- "preset",
157
- "templateVersion",
158
- "toolchain",
159
- "packages",
160
- ].includes(key)) {
161
- throw new Error(`Package Addition Generation Record contains unknown field: ${key}`);
162
- }
163
- }
164
- if (isRecord(value.toolchain)) {
165
- for (const key of Object.keys(value.toolchain)) {
166
- if (!["nodeLtsMajor", "packageManagerPin"].includes(key)) {
167
- throw new Error(`Package Addition Generation Record toolchain contains unknown field: ${key}`);
168
- }
169
- }
170
- }
165
+ throw new Error(`Package Addition requires valid ${label}: ${error instanceof Error ? error.message : String(error)}`);
171
166
  }
172
- if (!isRecord(value) ||
173
- value.schemaVersion !== 1 ||
174
- typeof value.preset !== "string" ||
175
- value.templateVersion !== "0.0.0" ||
176
- !isRecord(value.toolchain) ||
177
- typeof value.toolchain.nodeLtsMajor !== "string" ||
178
- typeof value.toolchain.packageManagerPin !== "string" ||
179
- !Array.isArray(value.packages)) {
180
- throw new Error("Package Addition requires a supported Generation Record in .template/generation.json");
181
- }
182
- const packages = [];
183
- for (const [index, item] of value.packages.entries()) {
184
- if (isRecord(item)) {
185
- for (const key of Object.keys(item)) {
186
- if (!["path", "definitionName", "planningContribution"].includes(key)) {
187
- throw new Error(`Package Addition Generation Record packages[${index}] contains unknown field: ${key}`);
188
- }
189
- }
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`);
190
184
  }
191
- if (!isRecord(item) ||
192
- typeof item.path !== "string" ||
193
- typeof item.definitionName !== "string" ||
194
- !["foundationPlan", "planInitialization", "planPackageAddition"].includes(String(item.planningContribution))) {
195
- 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}`);
196
187
  }
197
- 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`);
198
194
  }
199
- const blueprintPaths = options.blueprint.packages
200
- .map((definition) => definition.path)
201
- .toSorted();
202
- const recordedPaths = packages.map((item) => item.path).toSorted();
203
- if (JSON.stringify(blueprintPaths) !== JSON.stringify(recordedPaths) ||
204
- new Set(recordedPaths).size !== recordedPaths.length) {
205
- 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}`);
206
198
  }
207
- const generationRecord = {
208
- schemaVersion: 1,
209
- preset: value.preset,
210
- templateVersion: value.templateVersion,
211
- toolchain: {
212
- nodeLtsMajor: value.toolchain.nodeLtsMajor,
213
- packageManagerPin: value.toolchain.packageManagerPin,
214
- },
215
- packages,
216
- };
217
- assertGenerationRecordFoundationConsistency({
218
- context: options.context,
219
- blueprint: options.blueprint,
220
- generationRecord,
221
- });
222
- return generationRecord;
223
- }
224
- function assertGenerationRecordFoundationConsistency(options) {
225
- const configDefinition = typescriptConfigPackageDefinition(options.context);
226
- const foundationRecords = options.generationRecord.packages.filter((record) => record.planningContribution === "foundationPlan");
227
- if (foundationRecords.length !== 1 ||
228
- foundationRecords[0].definitionName !== "foundation" ||
229
- foundationRecords[0].path !== configDefinition.path) {
230
- 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);
231
210
  }
232
- 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");
233
215
  if (initialRecords.length === 0) {
234
216
  throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} has no initial Package provenance`);
235
217
  }
236
- const conflictingRecord = initialRecords.find((record) => record.definitionName !== options.generationRecord.preset);
218
+ const conflictingRecord = initialRecords.find((candidate) => candidate.definitionName !== options.generationRecord.preset);
237
219
  if (conflictingRecord !== undefined) {
238
220
  throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} conflicts with initial Package provenance ${conflictingRecord.definitionName} for Blueprint package ${conflictingRecord.path}`);
239
221
  }
240
- let rootDefinition;
241
- try {
242
- rootDefinition = builtInPresetRegistry.require(options.generationRecord.preset);
243
- }
244
- catch {
245
- 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");
246
256
  }
247
- const initialBlueprint = rootDefinition.blueprint(options.context);
248
- assertProjectBlueprintV2(initialBlueprint);
249
- const expectedPaths = initialBlueprint.packages
250
- .map((definition) => definition.path)
251
- .toSorted();
252
- const recordedInitialPaths = initialRecords
253
- .map((record) => record.path)
254
- .toSorted();
255
- if (JSON.stringify(recordedInitialPaths) !== JSON.stringify(expectedPaths)) {
256
- throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} expects initial Blueprint packages ${expectedPaths.join(", ")}, but initial provenance records ${recordedInitialPaths.join(", ")}`);
257
- }
258
- for (const expectedDefinition of initialBlueprint.packages) {
259
- const currentDefinition = options.blueprint.packages.find((definition) => definition.path === expectedDefinition.path);
260
- if (currentDefinition === undefined ||
261
- !packageDefinitionsEqual(currentDefinition, expectedDefinition)) {
262
- 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`);
263
260
  }
264
261
  }
265
- for (const expectedIntent of initialBlueprint.packageLinkIntents ?? []) {
266
- if (!(options.blueprint.packageLinkIntents ?? []).some((currentIntent) => packageLinkIntentsEqual(currentIntent, expectedIntent))) {
267
- 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
+ }
268
268
  }
269
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;
270
339
  }
271
- function environmentNeedOwner(value) {
272
- if (!isRecord(value))
273
- return undefined;
274
- if (value.kind === "workspace-orchestration" && value.path === ".") {
275
- 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
+ });
276
347
  }
277
- if (value.kind === "package-boundary" && typeof value.path === "string") {
278
- 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 });
279
350
  }
280
- return undefined;
281
351
  }
282
- function assertEnvironmentNeedFactKeys(value, allowedKeys, context) {
283
- for (const key of Object.keys(value)) {
284
- if (!allowedKeys.includes(key)) {
285
- throw new Error(`Package Addition Environment Need ${context} contains unknown field: ${key}`);
286
- }
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}`);
287
371
  }
372
+ return options.adapter.identify(contribution);
288
373
  }
289
- function persistedCheckEnvironmentNeedFact(value, index) {
290
- if (!isRecord(value))
291
- return undefined;
292
- const context = `check[${index}]`;
293
- switch (value.kind) {
294
- case "playwright-browser-assets":
295
- assertEnvironmentNeedFactKeys(value, ["kind", "browser", "owner"], context);
296
- break;
297
- case "shellcheck-command":
298
- assertEnvironmentNeedFactKeys(value, ["kind", "owner"], context);
299
- break;
300
- case "rust-toolchain":
301
- assertEnvironmentNeedFactKeys(value, ["kind", "owner", "toolchain"], context);
302
- break;
303
- default:
304
- return undefined;
305
- }
306
- if (isRecord(value.owner)) {
307
- assertEnvironmentNeedFactKeys(value.owner, ["kind", "path"], `${context}.owner`);
308
- }
309
- const owner = environmentNeedOwner(value.owner);
310
- if (owner === undefined)
311
- return undefined;
312
- switch (value.kind) {
313
- case "playwright-browser-assets":
314
- return value.browser === "chromium"
315
- ? { kind: value.kind, browser: value.browser, owner }
316
- : undefined;
317
- case "shellcheck-command":
318
- return { kind: value.kind, owner };
319
- case "rust-toolchain":
320
- return value.toolchain === "stable"
321
- ? { kind: value.kind, toolchain: value.toolchain, owner }
322
- : undefined;
323
- default:
324
- 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
+ }
325
392
  }
393
+ if (foundationContribution === undefined) {
394
+ throw new Error("Package Addition Generation Record is missing its validated Foundation contribution");
395
+ }
396
+ return { foundationContribution, packageContributions };
326
397
  }
327
398
  function readPersistedEnvironmentNeeds(targetDir) {
328
399
  const filePath = path.join(targetDir, environmentNeedsPath);
@@ -336,88 +407,31 @@ function readPersistedEnvironmentNeeds(targetDir) {
336
407
  catch (error) {
337
408
  throw new Error(`Package Addition requires valid Check Environment Need facts: ${error instanceof Error ? error.message : String(error)}`);
338
409
  }
339
- if (isRecord(value)) {
340
- for (const key of Object.keys(value)) {
341
- if (!["schemaVersion", "check", "deployment"].includes(key)) {
342
- throw new Error(`Package Addition Environment Need facts contain unknown field: ${key}`);
343
- }
344
- }
345
- }
346
- if (!isRecord(value) ||
347
- value.schemaVersion !== 1 ||
348
- !Array.isArray(value.check) ||
349
- !Array.isArray(value.deployment)) {
350
- throw new Error(`Package Addition requires valid Check Environment Need facts in ${environmentNeedsPath}`);
351
- }
352
- const check = value.check.map(persistedCheckEnvironmentNeedFact);
353
- if (check.some((fact) => fact === undefined)) {
354
- throw new Error(`Package Addition requires supported Check Environment Need facts in ${environmentNeedsPath}`);
410
+ try {
411
+ return parseEnvironmentNeedsMetadata(value);
355
412
  }
356
- if (value.deployment.some((fact, index) => {
357
- if (!isRecord(fact) || fact.kind !== "docker-engine")
358
- return true;
359
- assertEnvironmentNeedFactKeys(fact, ["kind"], `deployment[${index}]`);
360
- return false;
361
- })) {
362
- 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 });
363
415
  }
364
- return {
365
- check: check.map(checkEnvironmentNeedFromFact),
366
- deployment: value.deployment.map((fact) => deploymentEnvironmentNeedFromFact(fact)),
367
- };
368
- }
369
- function uniqueEnvironmentNeeds(needs) {
370
- const seen = new Set();
371
- return needs.filter((need) => {
372
- const key = JSON.stringify(need);
373
- if (seen.has(key))
374
- return false;
375
- seen.add(key);
376
- return true;
377
- });
378
416
  }
379
417
  function readExistingPackageAdditionState(options) {
380
- const generationRecord = readGenerationRecord(options);
381
- 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);
382
422
  const manifestTruthByPackagePath = new Map();
383
- const contributions = generationRecord.packages.flatMap((record) => {
384
- if (record.planningContribution === "foundationPlan") {
385
- return [];
386
- }
387
- const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === record.path);
388
- const definition = builtInPresetRegistry.require(record.definitionName);
389
- let candidates;
390
- if (record.planningContribution === "planInitialization") {
391
- candidates = definition.planInitializationContributions?.(options.context) ?? [definition.planInitialization(options.context)];
392
- }
393
- else {
394
- if (definition.planPackageAddition === undefined) {
395
- throw new Error(`Generation Record Definition ${record.definitionName} no longer supports Package Addition`);
396
- }
397
- const packageLeafName = expectedDefinition.name.split("/")[1];
398
- if (!packageLeafName) {
399
- throw new Error(`Generation Record package has an invalid name: ${expectedDefinition.name}`);
400
- }
401
- candidates = [
402
- definition.planPackageAddition({
403
- context: options.context,
404
- packageLeafName,
405
- packagePath: expectedDefinition.path,
406
- }),
407
- ];
408
- }
409
- const contribution = candidates.find((candidate) => packageDefinitionsEqual(candidate.definition, expectedDefinition));
410
- if (contribution === undefined) {
411
- throw new Error(`Generation Record cannot reproduce Package Definition ${expectedDefinition.name} at ${expectedDefinition.path}`);
412
- }
413
- return [contribution];
414
- });
415
- for (const packagePath of new Set(options.manifestTruthPackagePaths ?? [])) {
416
- const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === packagePath);
417
- 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)) {
418
426
  throw new Error(`Package Addition requires manifest truth for unknown Package Path ${packagePath}`);
419
427
  }
420
- 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
+ }
421
435
  let manifest;
422
436
  try {
423
437
  manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -430,17 +444,16 @@ function readExistingPackageAdditionState(options) {
430
444
  }
431
445
  manifestTruthByPackagePath.set(expectedDefinition.path, manifest);
432
446
  }
433
- const reconstructedCheckFacts = uniqueEnvironmentNeeds(contributions.flatMap((contribution) => contribution.environmentNeeds)).map(checkEnvironmentNeedFact);
434
- const reconstructedDeploymentFacts = uniqueEnvironmentNeeds(contributions.flatMap((contribution) => contribution.deploymentEnvironmentNeeds ?? [])).map(deploymentEnvironmentNeedFact);
435
- const persistedCheckFacts = persistedEnvironmentNeeds.check.map(checkEnvironmentNeedFact);
436
- const persistedDeploymentFacts = persistedEnvironmentNeeds.deployment.map(deploymentEnvironmentNeedFact);
437
- if (JSON.stringify(reconstructedCheckFacts) !==
438
- JSON.stringify(persistedCheckFacts) ||
439
- JSON.stringify(reconstructedDeploymentFacts) !==
440
- JSON.stringify(persistedDeploymentFacts)) {
441
- 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");
442
454
  }
443
455
  return {
456
+ foundationContribution,
444
457
  contributions,
445
458
  manifestTruthByPackagePath,
446
459
  deploymentEnvironmentNeeds: persistedEnvironmentNeeds.deployment,
@@ -452,6 +465,13 @@ function packageDefinitionsEqual(left, right) {
452
465
  left.path === right.path &&
453
466
  left.role === right.role);
454
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
+ }
455
475
  function packageLinkIntentsEqual(left, right) {
456
476
  return (left.consumerPackagePath === right.consumerPackagePath &&
457
477
  left.providerPackagePath === right.providerPackagePath);
@@ -510,7 +530,7 @@ function contributedDevcontainerComposition(options) {
510
530
  from: "devcontainer.json",
511
531
  to: ".devcontainer/devcontainer.json",
512
532
  replacements: {
513
- PROJECT_NAME: options.context.projectName,
533
+ PROJECT_NAME: options.context.repositoryName,
514
534
  NODE_LTS_MAJOR: options.context.toolchain.nodeLtsMajor,
515
535
  PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin,
516
536
  },
@@ -642,13 +662,36 @@ function composeDependencyMaintenancePolicy(contributions) {
642
662
  interval: "weekly",
643
663
  };
644
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
+ }
645
690
  function foundationPlan(options) {
646
- assertProjectBlueprintV2(options.blueprint);
647
- const configDefinition = typescriptConfigPackageDefinition(options.context);
648
- const persistedConfigDefinition = options.blueprint.packages.find((definition) => definition.name === configDefinition.name ||
649
- definition.path === configDefinition.path);
650
- if (persistedConfigDefinition === undefined ||
651
- !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))) {
652
695
  throw new Error("Project Blueprint must contain the Foundation TypeScript configuration Package Definition");
653
696
  }
654
697
  const initialProjectLinkPlan = planExplicitProjectLinks({
@@ -663,15 +706,26 @@ function foundationPlan(options) {
663
706
  const injectedProviderNames = new Set([...initialProjectLinkPlan.manifestPatchesByPackagePath.values()].flatMap((patch) => Object.entries(patch.dependenciesMeta)
664
707
  .filter(([, metadata]) => metadata.injected)
665
708
  .map(([name]) => name)));
666
- 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);
667
714
  const packageContributions = options.contributions.map((contribution) => {
668
- const ownsTypeScriptConfig = contribution.operations.some((operation) => "to" in operation &&
669
- operation.to === `${contribution.definition.path}/tsconfig.json`);
670
- if (!ownsTypeScriptConfig)
715
+ if (contribution.foundation.typescriptConfigurationPackage === undefined) {
671
716
  return contribution;
717
+ }
672
718
  const dependencyField = injectedProviderNames.has(contribution.definition.name)
673
719
  ? "dependencies"
674
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:*";
675
729
  const existingDependencies = contribution.manifest[dependencyField];
676
730
  return {
677
731
  ...contribution,
@@ -679,43 +733,67 @@ function foundationPlan(options) {
679
733
  ...contribution.manifest,
680
734
  [dependencyField]: {
681
735
  ...(isRecord(existingDependencies) ? existingDependencies : {}),
682
- [configPackageName]: "workspace:*",
736
+ [configPackageName]: dependencySpecifier,
683
737
  },
684
738
  },
685
739
  };
686
740
  });
687
- 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
+ });
688
747
  const contributions = [configContribution, ...packageContributions];
689
- const generationRecord = options.generationRecord ?? {
690
- 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,
691
758
  preset: options.definition.metadata.name,
692
759
  templateVersion: "0.0.0",
693
760
  toolchain: options.context.toolchain,
694
761
  packages: [
695
- ...options.contributions.map((contribution) => ({
696
- path: contribution.definition.path,
697
- definitionName: options.definition.metadata.name,
698
- planningContribution: options.mode === "initialization"
762
+ ...options.contributions.map((contribution) => {
763
+ const planningContribution = options.mode === "initialization"
699
764
  ? "planInitialization"
700
- : "planPackageAddition",
701
- })),
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
+ }),
702
778
  {
779
+ packageDefinitionId: requirePersistedPackageDefinition(options.blueprint, configDefinition).packageDefinitionId,
703
780
  path: configDefinition.path,
704
781
  definitionName: "foundation",
705
782
  planningContribution: "foundationPlan",
783
+ contributionIdentity: typescriptConfigReplayAdapter.identity,
706
784
  },
707
785
  ],
708
786
  };
709
- const environmentNeeds = uniqueEnvironmentNeeds(contributions.flatMap((item) => item.environmentNeeds));
710
- const deploymentEnvironmentNeeds = uniqueEnvironmentNeeds([
711
- ...(options.existingDeploymentEnvironmentNeeds ?? []),
712
- ...contributions.flatMap((item) => item.deploymentEnvironmentNeeds ?? []),
713
- ]);
714
- const persistedEnvironmentNeeds = {
715
- schemaVersion: 1,
716
- check: environmentNeeds.map(checkEnvironmentNeedFact),
717
- deployment: deploymentEnvironmentNeeds.map(deploymentEnvironmentNeedFact),
718
- };
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;
719
797
  const hasDeploymentTask = contributions.some((contribution) => {
720
798
  const scripts = contribution.manifest.scripts;
721
799
  return (typeof scripts === "object" &&
@@ -759,13 +837,27 @@ function foundationPlan(options) {
759
837
  const editorCustomization = editorCustomizationForCapabilities(contributions.flatMap((contribution) => contribution.foundation.editorCapabilities), loadEditorCustomizationDeclarations(resolveTemplateSource(templateSources.editorCustomization, "capabilities.json")));
760
838
  const dependencyMaintenancePolicy = composeDependencyMaintenancePolicy(contributions);
761
839
  const rootManifest = {
762
- name: options.context.projectName,
763
- version: "0.0.0",
840
+ name: options.context.repositoryName,
764
841
  private: true,
765
842
  type: "module",
843
+ ...(publicationCandidate === undefined
844
+ ? {}
845
+ : {
846
+ imports: {
847
+ "#npm-publication/*": "./scripts/npm-publication/*.ts",
848
+ },
849
+ }),
766
850
  scripts: {
767
- check: renderRootCheckCommand(),
851
+ check: publicationCandidate === undefined
852
+ ? renderRootCheckCommand()
853
+ : renderPublicationRootCheckCommand(publicationCandidate.definition.path),
768
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
+ }),
769
861
  ...(hasDeploymentTask
770
862
  ? { "check:deployment": renderDeploymentCheckCommand() }
771
863
  : {}),
@@ -778,9 +870,23 @@ function foundationPlan(options) {
778
870
  },
779
871
  devDependencies: {
780
872
  "@types/node": "catalog:",
873
+ ...(publicationCandidate === undefined
874
+ ? {}
875
+ : {
876
+ "@types/semver": "catalog:",
877
+ "@types/spdx-expression-parse": "catalog:",
878
+ }),
781
879
  oxfmt: "catalog:",
782
880
  oxlint: "catalog:",
783
881
  "oxlint-tsgolint": "catalog:",
882
+ ...(publicationCandidate === undefined
883
+ ? {}
884
+ : {
885
+ semver: "catalog:",
886
+ "spdx-expression-parse": "catalog:",
887
+ npm: "catalog:",
888
+ tar: "catalog:",
889
+ }),
784
890
  turbo: "catalog:",
785
891
  "typescript-7": "catalog:",
786
892
  },
@@ -847,6 +953,16 @@ function foundationPlan(options) {
847
953
  };
848
954
  const workflowOperations = [
849
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
+ ]),
850
966
  {
851
967
  kind: "writeTextTemplate",
852
968
  source: templateSources.foundation,
@@ -866,6 +982,16 @@ function foundationPlan(options) {
866
982
  nestedKeyOrder: packageConditionKeyOrder,
867
983
  },
868
984
  workspaceOperation,
985
+ ...(requiresPackingHook
986
+ ? [
987
+ {
988
+ kind: "copyFile",
989
+ source: templateSources.foundation,
990
+ from: ".pnpmfile.mjs",
991
+ to: ".pnpmfile.mjs",
992
+ },
993
+ ]
994
+ : []),
869
995
  {
870
996
  kind: "copyFile",
871
997
  source: templateSources.foundation,
@@ -878,6 +1004,16 @@ function foundationPlan(options) {
878
1004
  from: "turbo.json",
879
1005
  to: "turbo.json",
880
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
+ ]),
881
1017
  ...turboBoundaryTags.map((tag) => ({
882
1018
  kind: "mergeJsonTemplate",
883
1019
  source: templateSources.foundation,
@@ -902,6 +1038,27 @@ function foundationPlan(options) {
902
1038
  from: "tsconfig.json",
903
1039
  to: "tsconfig.json",
904
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
+ : []),
905
1062
  {
906
1063
  kind: "copyFile",
907
1064
  source: templateSources.sharedOxc,
@@ -920,6 +1077,135 @@ function foundationPlan(options) {
920
1077
  from: "oxfmt.config.ts",
921
1078
  to: "oxfmt.config.ts",
922
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
+ ]),
923
1209
  {
924
1210
  kind: "writeJson",
925
1211
  to: ".vscode/extensions.json",
@@ -1008,6 +1294,7 @@ function foundationPlan(options) {
1008
1294
  ...linkOperations.map((operation) => withProvenance(operation, foundationProvenance)),
1009
1295
  ];
1010
1296
  const reconciliation = [
1297
+ { path: "tsconfig.json", driver: "structured" },
1011
1298
  { path: "turbo.json", driver: "structured" },
1012
1299
  {
1013
1300
  path: ".devcontainer/devcontainer.json",
@@ -1036,6 +1323,7 @@ function foundationPlan(options) {
1036
1323
  : "planInitialization",
1037
1324
  blueprint: options.blueprint,
1038
1325
  generationRecord,
1326
+ packageContributions,
1039
1327
  operations,
1040
1328
  reconciliation,
1041
1329
  developmentContainer: {
@@ -1057,40 +1345,342 @@ function foundationPlan(options) {
1057
1345
  ],
1058
1346
  };
1059
1347
  }
1060
- export function planGeneratedRepositoryInitialization(options) {
1061
- 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;
1062
1422
  const configDefinition = typescriptConfigPackageDefinition(options.context);
1063
- if (presetBlueprint.packages.some((definition) => definition.name === configDefinition.name ||
1064
- definition.path === configDefinition.path)) {
1065
- 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"));
1066
1437
  }
1438
+ const occupiedIds = new Set();
1067
1439
  const blueprint = {
1068
1440
  ...presetBlueprint,
1069
- 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
+ ],
1070
1471
  };
1071
- const contributions = options.definition.planInitializationContributions?.(options.context) ?? [options.definition.planInitialization(options.context)];
1072
1472
  return foundationPlan({
1073
1473
  definition: options.definition,
1074
1474
  context: options.context,
1075
1475
  blueprint,
1476
+ foundationContribution: typescriptConfigReplayAdapter.identify(typescriptConfigContribution(options.context, configDefinition)),
1076
1477
  contributions,
1077
1478
  mode: "initialization",
1078
1479
  });
1079
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
+ }
1080
1669
  export function planGeneratedRepositoryPackageAddition(options) {
1081
- assertProjectBlueprintV2(options.blueprint);
1670
+ const { blueprint: persistedBlueprint, context } = options.localTemplateMetadata;
1671
+ assertProjectBlueprint(persistedBlueprint);
1082
1672
  if (!options.definition.planPackageAddition)
1083
1673
  throw new Error(`Built-in Preset ${options.definition.metadata.name} does not support Package Addition`);
1084
1674
  const packagePath = options.packagePath ??
1085
1675
  options.definition.defaultPackagePath?.({
1086
- context: options.context,
1676
+ context,
1087
1677
  packageLeafName: options.packageLeafName,
1088
1678
  });
1089
1679
  if (packagePath === undefined) {
1090
1680
  throw new Error(`Built-in Preset ${options.definition.metadata.name} must own a default Package Path or receive an explicit Package Path`);
1091
1681
  }
1092
1682
  const contribution = options.definition.planPackageAddition({
1093
- context: options.context,
1683
+ context,
1094
1684
  packageLeafName: options.packageLeafName,
1095
1685
  packagePath,
1096
1686
  });
@@ -1098,28 +1688,31 @@ export function planGeneratedRepositoryPackageAddition(options) {
1098
1688
  consumerPackagePath,
1099
1689
  providerPackagePath: contribution.definition.path,
1100
1690
  }));
1101
- const conflictingPackage = options.blueprint.packages.find((existing) => existing.name === contribution.definition.name ||
1691
+ const conflictingPackage = persistedBlueprint.packages.find((existing) => existing.name === contribution.definition.name ||
1102
1692
  existing.path === contribution.definition.path);
1103
1693
  if (conflictingPackage !== undefined) {
1104
1694
  const isExactPackageDefinition = packageDefinitionsEqual(conflictingPackage, contribution.definition);
1105
- const existingPackageLinkIntents = options.blueprint.packageLinkIntents ?? [];
1695
+ const existingPackageLinkIntents = persistedBlueprint.packageLinkIntents ?? [];
1106
1696
  const missingPackageLinkIntent = requestedPackageLinkIntents.find((requested) => !existingPackageLinkIntents.some((existing) => packageLinkIntentsEqual(existing, requested)));
1107
1697
  if (isExactPackageDefinition && missingPackageLinkIntent === undefined) {
1108
1698
  const existing = readExistingPackageAdditionState(options);
1109
1699
  const plan = foundationPlan({
1110
1700
  definition: options.definition,
1111
- context: options.context,
1112
- blueprint: options.blueprint,
1701
+ context,
1702
+ blueprint: persistedBlueprint,
1703
+ foundationContribution: existing.foundationContribution,
1113
1704
  contributions: existing.contributions,
1114
1705
  renderContributions: [],
1115
1706
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1707
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1116
1708
  generationRecord: existing.generationRecord,
1117
1709
  mode: "addition",
1118
1710
  });
1119
1711
  const currentProjection = foundationPlan({
1120
1712
  definition: options.definition,
1121
- context: options.context,
1122
- blueprint: options.blueprint,
1713
+ context,
1714
+ blueprint: persistedBlueprint,
1715
+ foundationContribution: existing.foundationContribution,
1123
1716
  contributions: existing.contributions,
1124
1717
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1125
1718
  generationRecord: existing.generationRecord,
@@ -1146,47 +1739,64 @@ export function planGeneratedRepositoryPackageAddition(options) {
1146
1739
  }
1147
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})`);
1148
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
+ });
1149
1756
  const blueprint = {
1150
- ...options.blueprint,
1151
- packages: [...options.blueprint.packages, contribution.definition],
1757
+ ...persistedBlueprint,
1758
+ packages: [...persistedBlueprint.packages, persistedContributionDefinition],
1152
1759
  ...(requestedPackageLinkIntents.length > 0
1153
1760
  ? {
1154
1761
  packageLinkIntents: [
1155
- ...(options.blueprint.packageLinkIntents ?? []),
1762
+ ...(persistedBlueprint.packageLinkIntents ?? []),
1156
1763
  ...requestedPackageLinkIntents,
1157
1764
  ],
1158
1765
  }
1159
1766
  : {}),
1160
1767
  };
1161
- assertProjectBlueprintV2(blueprint);
1162
- const manifestTruthPackagePaths = requestedPackageLinkIntents.length === 0
1768
+ assertProjectBlueprint(blueprint);
1769
+ const requiredManifestTruthPackagePaths = requestedPackageLinkIntents.length === 0
1163
1770
  ? []
1164
1771
  : [
1165
- ...(options.blueprint.packageLinkIntents ?? []).flatMap((intent) => [
1772
+ ...(persistedBlueprint.packageLinkIntents ?? []).flatMap((intent) => [
1166
1773
  intent.consumerPackagePath,
1167
1774
  intent.providerPackagePath,
1168
1775
  ]),
1169
1776
  ...requestedPackageLinkIntents.map((intent) => intent.consumerPackagePath),
1170
1777
  ];
1171
1778
  const existing = readExistingPackageAdditionState({
1172
- ...options,
1173
- manifestTruthPackagePaths,
1779
+ localTemplateMetadata: options.localTemplateMetadata,
1780
+ requiredManifestTruthPackagePaths,
1174
1781
  });
1175
1782
  const generationRecord = {
1176
1783
  ...existing.generationRecord,
1177
1784
  packages: [
1178
1785
  ...existing.generationRecord.packages,
1179
1786
  {
1787
+ packageDefinitionId: persistedContributionDefinition.packageDefinitionId,
1180
1788
  path: contribution.definition.path,
1181
1789
  definitionName: options.definition.metadata.name,
1182
1790
  planningContribution: "planPackageAddition",
1791
+ contributionIdentity,
1183
1792
  },
1184
1793
  ],
1185
1794
  };
1186
1795
  const beforeProjection = foundationPlan({
1187
1796
  definition: options.definition,
1188
- context: options.context,
1189
- blueprint: options.blueprint,
1797
+ context,
1798
+ blueprint: persistedBlueprint,
1799
+ foundationContribution: existing.foundationContribution,
1190
1800
  contributions: existing.contributions,
1191
1801
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1192
1802
  generationRecord: existing.generationRecord,
@@ -1194,30 +1804,24 @@ export function planGeneratedRepositoryPackageAddition(options) {
1194
1804
  });
1195
1805
  const afterProjection = foundationPlan({
1196
1806
  definition: options.definition,
1197
- context: options.context,
1807
+ context,
1198
1808
  blueprint,
1809
+ foundationContribution: existing.foundationContribution,
1199
1810
  contributions: [...existing.contributions, contribution],
1200
1811
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1201
- ...(requestedPackageLinkIntents.length === 0
1202
- ? {}
1203
- : {
1204
- manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1205
- }),
1812
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1206
1813
  generationRecord,
1207
1814
  mode: "initialization",
1208
1815
  });
1209
1816
  const plan = foundationPlan({
1210
1817
  definition: options.definition,
1211
- context: options.context,
1818
+ context,
1212
1819
  blueprint,
1820
+ foundationContribution: existing.foundationContribution,
1213
1821
  contributions: [...existing.contributions, contribution],
1214
1822
  renderContributions: [contribution],
1215
1823
  existingDeploymentEnvironmentNeeds: existing.deploymentEnvironmentNeeds,
1216
- ...(requestedPackageLinkIntents.length === 0
1217
- ? {}
1218
- : {
1219
- manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1220
- }),
1824
+ manifestTruthByPackagePath: existing.manifestTruthByPackagePath,
1221
1825
  generationRecord,
1222
1826
  mode: "addition",
1223
1827
  });