@ykdz/template 0.0.31 → 0.0.33

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 (70) hide show
  1. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +10 -1
  2. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +320 -161
  3. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +66 -9
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.d.ts +2 -0
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +35 -0
  6. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +13 -0
  7. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +70 -0
  8. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +2 -1
  9. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +22 -7
  10. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +4 -3
  11. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +4 -3
  12. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +82 -11
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +9 -2
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +27 -10
  15. package/node_modules/@ykdz/template-builtin-presets/package.json +1 -1
  16. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +81 -0
  17. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +55 -0
  18. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-diagnostics.yml +64 -0
  19. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.yml +24 -5
  20. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/devcontainer.json +3 -3
  21. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
  22. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/turbo.json +1 -0
  23. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/typescript-config/base.json +15 -0
  24. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/typescript-config/turbo.json +4 -0
  25. package/node_modules/@ykdz/template-builtin-presets/templates/rust-bin/devcontainer/rust.Dockerfile +3 -3
  26. package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/browser-test.Dockerfile +1 -1
  27. package/node_modules/@ykdz/template-builtin-presets/templates/shared/oxc/tsconfig.config.json +2 -12
  28. package/node_modules/@ykdz/template-builtin-presets/templates/shared/vue/tsconfig.app.json +6 -13
  29. package/node_modules/@ykdz/template-builtin-presets/templates/shared/vue/tsconfig.node.json +2 -12
  30. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.json +2 -6
  31. package/node_modules/@ykdz/template-builtin-presets/templates/ts-lib/tsconfig.json +3 -3
  32. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +2 -11
  33. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/tsconfig.json +2 -11
  34. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/devcontainer/docker-client.Dockerfile +3 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +1 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/check-standalone-deployment.ts +67 -27
  37. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.app.json +6 -12
  38. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.node.json +2 -11
  39. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/tsconfig.test.json +0 -1
  40. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +2 -1
  41. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/api/tsconfig.json +2 -12
  42. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +2 -0
  43. package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.d.ts +19 -0
  44. package/node_modules/@ykdz/template-core/dist/ci-diagnostic-artifact.js +68 -0
  45. package/node_modules/@ykdz/template-core/dist/development-container-tool-layer.d.ts +56 -0
  46. package/node_modules/@ykdz/template-core/dist/development-container-tool-layer.js +361 -0
  47. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +15 -1
  48. package/node_modules/@ykdz/template-core/dist/package-contribution.js +7 -0
  49. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +1 -0
  50. package/node_modules/@ykdz/template-core/dist/project-github.d.ts +51 -15
  51. package/node_modules/@ykdz/template-core/dist/project-github.js +83 -96
  52. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +9 -0
  53. package/node_modules/@ykdz/template-core/dist/project-projection.js +84 -5
  54. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +1 -0
  55. package/node_modules/@ykdz/template-core/dist/renderer.js +47 -7
  56. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +15 -1
  57. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +183 -1
  58. package/package.json +1 -1
  59. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-playwright.yml +0 -20
  60. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-rust.yml +0 -23
  61. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check.dynamic.template +0 -19
  62. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/.vscode/extensions.json +0 -3
  63. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/.vscode/settings.json +0 -5
  64. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/devcontainer/devcontainer.json +0 -21
  65. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/devcontainer/rust.Dockerfile +0 -18
  66. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/rust/rust-toolchain.toml +0 -3
  67. package/node_modules/@ykdz/template-builtin-presets/templates/rust-bin/devcontainer/devcontainer.json +0 -21
  68. package/node_modules/@ykdz/template-core/dist/devcontainer.d.ts +0 -83
  69. package/node_modules/@ykdz/template-core/dist/devcontainer.js +0 -217
  70. /package/node_modules/@ykdz/template-builtin-presets/templates/{shared → vike-app}/devcontainer/shellcheck.Dockerfile +0 -0
@@ -1,3 +1,5 @@
1
+ import type { CiDiagnosticArtifactDeclaration } from "#template-core/ci-diagnostic-artifact";
2
+ import { type DevelopmentContainerToolLayerBuildArgument, type DevelopmentContainerToolLayerMount, type DevelopmentContainerToolLayerProbe, type PlannedDevelopmentContainerToolLayer } from "#template-core/development-container-tool-layer";
1
3
  import type { CheckEnvironmentNeed, DeploymentEnvironmentNeed } from "#template-core/module-graph";
2
4
  import { type PackageContribution } from "#template-core/package-contribution";
3
5
  import type { BuiltInPresetDefinition, GenerationContext } from "#template-core/preset-definition";
@@ -16,7 +18,7 @@ export type NextStepInstruction = {
16
18
  type GeneratedPackagePlanningRecord = {
17
19
  readonly path: string;
18
20
  readonly definitionName: string;
19
- readonly planningContribution: "planInitialization" | "planPackageAddition";
21
+ readonly planningContribution: "foundationPlan" | "planInitialization" | "planPackageAddition";
20
22
  };
21
23
  type GenerationRecord = {
22
24
  readonly schemaVersion: 1;
@@ -33,8 +35,15 @@ export type GeneratedRepositoryPlan = {
33
35
  readonly generationRecord: GenerationRecord;
34
36
  readonly operations: readonly RenderOperation[];
35
37
  readonly reconciliation: readonly ProjectProjectionReconciliation[];
38
+ readonly developmentContainer: {
39
+ readonly toolLayers: readonly PlannedDevelopmentContainerToolLayer[];
40
+ readonly buildArguments: readonly DevelopmentContainerToolLayerBuildArgument[];
41
+ readonly mounts: readonly DevelopmentContainerToolLayerMount[];
42
+ readonly probes: readonly DevelopmentContainerToolLayerProbe[];
43
+ };
36
44
  readonly environmentNeeds: readonly CheckEnvironmentNeed[];
37
45
  readonly deploymentEnvironmentNeeds: readonly DeploymentEnvironmentNeed[];
46
+ readonly ciDiagnosticArtifacts: readonly CiDiagnosticArtifactDeclaration[];
38
47
  /** Structured manifests used to derive the generated Dependency Catalog. */
39
48
  readonly manifests: readonly Readonly<Record<string, unknown>>[];
40
49
  readonly dependencyCatalog: Readonly<Record<string, string>>;
@@ -1,16 +1,18 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { composeCiDiagnosticArtifacts } from "#template-core/ci-diagnostic-artifact";
4
5
  import { collectGeneratedManifestCatalogReferences, selectTemplateDependencyCatalogEntries, } from "#template-core/dependency-catalog";
5
- import { browserTestToolLayer } from "#template-core/devcontainer";
6
+ import { planDevelopmentContainerToolLayersSync, } from "#template-core/development-container-tool-layer";
6
7
  import { editorCustomizationForCapabilities, loadEditorCustomizationDeclarations, } from "#template-core/editor-customization";
7
8
  import { checkEnvironmentNeedFact, checkEnvironmentNeedFromFact, deploymentEnvironmentNeedFact, deploymentEnvironmentNeedFromFact, renderDeploymentCheckCommand, renderFixCommand, renderRootCheckCommand, } from "#template-core/module-graph";
8
9
  import { assertPackageContribution, } from "#template-core/package-contribution";
9
10
  import { assertProjectBlueprintV2, validateProjectBlueprintV2 as validateCoreProjectBlueprintV2, } from "#template-core/project-blueprint-v2";
10
- import { projectCheckWorkflowTemplateReplacements, projectDependabotTemplateReplacements, } from "#template-core/project-github";
11
+ import { projectCheckWorkflowTemplateSource, projectCheckWorkflowTemplateReplacements, projectDependabotTemplateReplacements, } from "#template-core/project-github";
11
12
  import { planExplicitProjectLinks } from "#template-core/project-linking-v2";
12
13
  import { resolveTemplateSource, } from "#template-core/renderer";
13
14
  import { rustBinDefinition } from "./rust-bin/definition.js";
15
+ import { typescriptConfigContribution, typescriptConfigPackageDefinition, typescriptConfigPackageName, } from "./shared/typescript.js";
14
16
  import { vuePnpmDependencyOverrides } from "./shared/vue.js";
15
17
  import { templateSources } from "./template-sources.js";
16
18
  import { tsCliDefinition } from "./ts-cli/definition.js";
@@ -188,7 +190,7 @@ function readGenerationRecord(options) {
188
190
  if (!isRecord(item) ||
189
191
  typeof item.path !== "string" ||
190
192
  typeof item.definitionName !== "string" ||
191
- !["planInitialization", "planPackageAddition"].includes(String(item.planningContribution))) {
193
+ !["foundationPlan", "planInitialization", "planPackageAddition"].includes(String(item.planningContribution))) {
192
194
  throw new Error(`Package Addition requires valid package planning facts at .template/generation.json packages[${index}]`);
193
195
  }
194
196
  packages.push(item);
@@ -219,6 +221,13 @@ function readGenerationRecord(options) {
219
221
  return generationRecord;
220
222
  }
221
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");
230
+ }
222
231
  const initialRecords = options.generationRecord.packages.filter((record) => record.planningContribution === "planInitialization");
223
232
  if (initialRecords.length === 0) {
224
233
  throw new Error(`Package Addition Generation Record preset ${options.generationRecord.preset} has no initial Package provenance`);
@@ -370,7 +379,10 @@ function readExistingPackageAdditionState(options) {
370
379
  const generationRecord = readGenerationRecord(options);
371
380
  const persistedEnvironmentNeeds = readPersistedEnvironmentNeeds(options.context.targetDir);
372
381
  const manifestTruthByPackagePath = new Map();
373
- const contributions = generationRecord.packages.map((record) => {
382
+ const contributions = generationRecord.packages.flatMap((record) => {
383
+ if (record.planningContribution === "foundationPlan") {
384
+ return [];
385
+ }
374
386
  const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === record.path);
375
387
  const definition = builtInPresetRegistry.require(record.definitionName);
376
388
  let candidates;
@@ -397,7 +409,7 @@ function readExistingPackageAdditionState(options) {
397
409
  if (contribution === undefined) {
398
410
  throw new Error(`Generation Record cannot reproduce Package Definition ${expectedDefinition.name} at ${expectedDefinition.path}`);
399
411
  }
400
- return contribution;
412
+ return [contribution];
401
413
  });
402
414
  for (const packagePath of new Set(options.manifestTruthPackagePaths ?? [])) {
403
415
  const expectedDefinition = options.blueprint.packages.find((definition) => definition.path === packagePath);
@@ -460,95 +472,277 @@ function turboBoundaryTagsForContributions(contributions) {
460
472
  }
461
473
  return [...selectedTags];
462
474
  }
463
- function devcontainerDockerfileOperations(options) {
464
- const hasBrowserTests = options.environmentNeeds.some((need) => need.kind === "playwright-browser-assets");
465
- const hasShellChecks = options.environmentNeeds.some((need) => need.kind === "shellcheck-command");
466
- const browserLayer = hasBrowserTests ? browserTestToolLayer() : undefined;
467
- return [
468
- {
469
- kind: "writeTextTemplate",
470
- source: templateSources.foundation,
471
- from: "devcontainer.json",
472
- to: ".devcontainer/devcontainer.json",
473
- replacements: {
474
- PROJECT_NAME: options.context.projectName,
475
- NODE_LTS_MAJOR: options.context.toolchain.nodeLtsMajor,
476
- PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin,
477
- PLAYWRIGHT_CLI_PACKAGE: browserLayer?.playwrightCliPackage ?? "unused",
475
+ function contributedDevcontainerComposition(options) {
476
+ const layerPlan = planDevelopmentContainerToolLayersSync({
477
+ baseLayer: {
478
+ identity: "node-pnpm",
479
+ dockerfile: {
480
+ source: templateSources.sharedDevcontainer,
481
+ from: "node-pnpm.Dockerfile",
478
482
  },
479
- },
480
- {
481
- kind: "writeTextFromFragments",
482
- to: ".devcontainer/Dockerfile",
483
- fragments: [
483
+ buildArguments: [
484
484
  {
485
- source: templateSources.sharedDevcontainer,
486
- from: "node-pnpm.Dockerfile",
485
+ name: "NODE_VERSION",
486
+ value: options.context.toolchain.nodeLtsMajor,
487
+ },
488
+ {
489
+ name: "PACKAGE_MANAGER_PIN",
490
+ value: options.context.toolchain.packageManagerPin,
491
+ },
492
+ ],
493
+ mounts: [
494
+ {
495
+ identity: "pnpm-store",
496
+ type: "volume",
497
+ source: "${devcontainerId}-pnpm-store",
498
+ target: "/pnpm/store",
487
499
  },
488
- ...(browserLayer === undefined
489
- ? []
490
- : [
491
- {
492
- source: templateSources.sharedDevcontainer,
493
- from: "browser-test.Dockerfile",
494
- },
495
- ]),
496
- ...(hasShellChecks
497
- ? [
498
- {
499
- source: templateSources.sharedDevcontainer,
500
- from: "shellcheck.Dockerfile",
501
- },
502
- ]
503
- : []),
504
500
  ],
505
501
  },
502
+ layers: options.layers,
503
+ });
504
+ return {
505
+ operations: [
506
+ {
507
+ kind: "writeTextTemplate",
508
+ source: templateSources.foundation,
509
+ from: "devcontainer.json",
510
+ to: ".devcontainer/devcontainer.json",
511
+ replacements: {
512
+ PROJECT_NAME: options.context.projectName,
513
+ NODE_LTS_MAJOR: options.context.toolchain.nodeLtsMajor,
514
+ PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin,
515
+ },
516
+ },
517
+ {
518
+ kind: "mergeJson",
519
+ to: ".devcontainer/devcontainer.json",
520
+ value: {
521
+ build: {
522
+ args: Object.fromEntries(layerPlan.buildArguments.map((argument) => [
523
+ argument.name,
524
+ argument.value,
525
+ ])),
526
+ },
527
+ ...(layerPlan.mounts.length === 0
528
+ ? {}
529
+ : {
530
+ mounts: layerPlan.mounts.map(({ identity: _identity, ...mount }) => mount),
531
+ }),
532
+ },
533
+ },
534
+ {
535
+ kind: "writeTextFromFragments",
536
+ to: ".devcontainer/Dockerfile",
537
+ validation: "development-container-dockerfile",
538
+ fragments: layerPlan.layers.map((layer) => layer.dockerfile),
539
+ },
540
+ ],
541
+ mountIdentitySet: {
542
+ location: "/mounts",
543
+ identity: {
544
+ kind: "projection",
545
+ members: layerPlan.mounts.map(({ identity, target, }) => ({
546
+ identity,
547
+ match: { target },
548
+ })),
549
+ fallback: { fields: ["target"] },
550
+ },
551
+ },
552
+ toolLayers: layerPlan.layers,
553
+ buildArguments: layerPlan.buildArguments,
554
+ mounts: layerPlan.mounts,
555
+ probes: layerPlan.probes,
556
+ };
557
+ }
558
+ function contributedFoundationTemplateFileOperations(contributions) {
559
+ const byIdentity = new Map();
560
+ const outputOwners = new Map();
561
+ for (const file of contributions.flatMap((contribution) => contribution.foundation.templateFiles ?? [])) {
562
+ const previous = byIdentity.get(file.identity);
563
+ if (previous !== undefined) {
564
+ const previousFingerprint = JSON.stringify({
565
+ source: resolveTemplateSource(previous.source, previous.from),
566
+ from: previous.from,
567
+ to: previous.to,
568
+ replacements: previous.replacements ?? {},
569
+ });
570
+ const fingerprint = JSON.stringify({
571
+ source: resolveTemplateSource(file.source, file.from),
572
+ from: file.from,
573
+ to: file.to,
574
+ replacements: file.replacements ?? {},
575
+ });
576
+ if (fingerprint !== previousFingerprint) {
577
+ throw new Error(`Foundation Template File identity ${file.identity} has conflicting descriptors`);
578
+ }
579
+ continue;
580
+ }
581
+ const outputOwner = outputOwners.get(file.to);
582
+ if (outputOwner !== undefined) {
583
+ throw new Error(`Foundation Template File output ${file.to} is declared by both ${outputOwner} and ${file.identity}`);
584
+ }
585
+ byIdentity.set(file.identity, file);
586
+ outputOwners.set(file.to, file.identity);
587
+ }
588
+ return [...byIdentity.values()]
589
+ .toSorted((left, right) => left.identity.localeCompare(right.identity))
590
+ .map((file) => ({
591
+ kind: "writeTextTemplate",
592
+ source: file.source,
593
+ from: file.from,
594
+ to: file.to,
595
+ replacements: { ...file.replacements },
596
+ }));
597
+ }
598
+ function assertCompatibleRustToolchainFacts(contributions) {
599
+ const facts = contributions.flatMap((contribution) => {
600
+ const rust = contribution.foundation.toolchains.rust;
601
+ return rust === undefined
602
+ ? []
603
+ : [
604
+ JSON.stringify({
605
+ toolchain: rust.toolchain,
606
+ components: [...new Set(rust.components)].toSorted(),
607
+ }),
608
+ ];
609
+ });
610
+ if (new Set(facts).size > 1) {
611
+ throw new Error("Foundation requires compatible Rust toolchain facts");
612
+ }
613
+ }
614
+ function composeDependencyMaintenancePolicy(contributions) {
615
+ const ecosystems = [
616
+ ...new Set(contributions.flatMap((contribution) => contribution.foundation.dependencyMaintenance.ecosystems)),
506
617
  ];
618
+ const directories = {};
619
+ const extraDirectories = {};
620
+ for (const ecosystem of ecosystems) {
621
+ const candidates = [
622
+ ...new Set(contributions.flatMap((contribution) => {
623
+ const policy = contribution.foundation.dependencyMaintenance;
624
+ const primary = policy.directories?.[ecosystem];
625
+ return [
626
+ ...(primary === undefined ? [] : [primary]),
627
+ ...(policy.extraDirectories?.[ecosystem] ?? []),
628
+ ];
629
+ })),
630
+ ];
631
+ const [primary, ...extra] = candidates;
632
+ if (primary !== undefined)
633
+ directories[ecosystem] = primary;
634
+ if (extra.length > 0)
635
+ extraDirectories[ecosystem] = extra;
636
+ }
637
+ return {
638
+ ecosystems,
639
+ ...(Object.keys(directories).length === 0 ? {} : { directories }),
640
+ ...(Object.keys(extraDirectories).length === 0 ? {} : { extraDirectories }),
641
+ interval: "weekly",
642
+ };
507
643
  }
508
644
  function foundationPlan(options) {
509
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)) {
651
+ throw new Error("Project Blueprint must contain the Foundation TypeScript configuration Package Definition");
652
+ }
653
+ const initialProjectLinkPlan = planExplicitProjectLinks({
654
+ blueprint: options.blueprint,
655
+ contributions: options.contributions,
656
+ ...(options.manifestTruthByPackagePath === undefined
657
+ ? {}
658
+ : {
659
+ manifestTruthByPackagePath: options.manifestTruthByPackagePath,
660
+ }),
661
+ });
662
+ const injectedProviderNames = new Set([...initialProjectLinkPlan.manifestPatchesByPackagePath.values()].flatMap((patch) => Object.entries(patch.dependenciesMeta)
663
+ .filter(([, metadata]) => metadata.injected)
664
+ .map(([name]) => name)));
665
+ const configPackageName = typescriptConfigPackageName(options.context);
666
+ 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)
670
+ return contribution;
671
+ const dependencyField = injectedProviderNames.has(contribution.definition.name)
672
+ ? "dependencies"
673
+ : "devDependencies";
674
+ const existingDependencies = contribution.manifest[dependencyField];
675
+ return {
676
+ ...contribution,
677
+ manifest: {
678
+ ...contribution.manifest,
679
+ [dependencyField]: {
680
+ ...(isRecord(existingDependencies) ? existingDependencies : {}),
681
+ [configPackageName]: "workspace:*",
682
+ },
683
+ },
684
+ };
685
+ });
686
+ const configContribution = typescriptConfigContribution(options.context);
687
+ const contributions = [configContribution, ...packageContributions];
510
688
  const generationRecord = options.generationRecord ?? {
511
689
  schemaVersion: 1,
512
690
  preset: options.definition.metadata.name,
513
691
  templateVersion: "0.0.0",
514
692
  toolchain: options.context.toolchain,
515
- packages: options.contributions.map((contribution) => ({
516
- path: contribution.definition.path,
517
- definitionName: options.definition.metadata.name,
518
- planningContribution: options.mode === "initialization"
519
- ? "planInitialization"
520
- : "planPackageAddition",
521
- })),
693
+ packages: [
694
+ ...options.contributions.map((contribution) => ({
695
+ path: contribution.definition.path,
696
+ definitionName: options.definition.metadata.name,
697
+ planningContribution: options.mode === "initialization"
698
+ ? "planInitialization"
699
+ : "planPackageAddition",
700
+ })),
701
+ {
702
+ path: configDefinition.path,
703
+ definitionName: "foundation",
704
+ planningContribution: "foundationPlan",
705
+ },
706
+ ],
522
707
  };
523
- const environmentNeeds = uniqueEnvironmentNeeds(options.contributions.flatMap((item) => item.environmentNeeds));
708
+ const environmentNeeds = uniqueEnvironmentNeeds(contributions.flatMap((item) => item.environmentNeeds));
524
709
  const deploymentEnvironmentNeeds = uniqueEnvironmentNeeds([
525
710
  ...(options.existingDeploymentEnvironmentNeeds ?? []),
526
- ...options.contributions.flatMap((item) => item.deploymentEnvironmentNeeds ?? []),
711
+ ...contributions.flatMap((item) => item.deploymentEnvironmentNeeds ?? []),
527
712
  ]);
528
713
  const persistedEnvironmentNeeds = {
529
714
  schemaVersion: 1,
530
715
  check: environmentNeeds.map(checkEnvironmentNeedFact),
531
716
  deployment: deploymentEnvironmentNeeds.map(deploymentEnvironmentNeedFact),
532
717
  };
533
- const hasDeploymentTask = options.contributions.some((contribution) => {
718
+ const hasDeploymentTask = contributions.some((contribution) => {
534
719
  const scripts = contribution.manifest.scripts;
535
720
  return (typeof scripts === "object" &&
536
721
  scripts !== null &&
537
722
  typeof scripts.deployment === "string");
538
723
  });
539
- const packagePaths = options.contributions.map((contribution) => contribution.definition.path);
540
- if (new Set(packagePaths).size !== packagePaths.length)
724
+ const contributionPackagePaths = contributions.map((contribution) => contribution.definition.path);
725
+ if (new Set(contributionPackagePaths).size !== contributionPackagePaths.length)
541
726
  throw new Error("Package Contributions must have unique Package Paths");
542
- const packageNames = options.contributions.map((contribution) => contribution.definition.name);
727
+ const blueprintPackagePaths = options.blueprint.packages.map((definition) => definition.path);
728
+ if (contributionPackagePaths.length !== blueprintPackagePaths.length ||
729
+ contributionPackagePaths.some((packagePath) => !blueprintPackagePaths.includes(packagePath))) {
730
+ throw new Error("Package Contributions must exactly match Project Blueprint Package Paths");
731
+ }
732
+ const packageNames = contributions.map((contribution) => contribution.definition.name);
543
733
  if (new Set(packageNames).size !== packageNames.length)
544
734
  throw new Error("Package Contributions must have unique package names");
545
- const rustToolchain = options.contributions
546
- .map((contribution) => contribution.foundation.toolchains.rust)
547
- .find((toolchain) => toolchain !== undefined);
548
- if (options.contributions.some((contribution) => contribution.foundation.toolchains.rust !== undefined &&
549
- contribution.foundation.toolchains.rust !== rustToolchain)) {
550
- throw new Error("Foundation requires one coordinated Rust toolchain");
551
- }
735
+ const ciDiagnosticArtifactDeclarations = contributions.flatMap((contribution) => contribution.ciDiagnosticArtifacts ?? []);
736
+ const ciDiagnosticArtifacts = composeCiDiagnosticArtifacts({
737
+ packagePaths: blueprintPackagePaths,
738
+ declarations: ciDiagnosticArtifactDeclarations,
739
+ });
740
+ assertCompatibleRustToolchainFacts(contributions);
741
+ const contributedToolLayers = contributions.flatMap((contribution) => contribution.foundation.developmentContainerToolLayers ?? []);
742
+ const developmentContainer = contributedDevcontainerComposition({
743
+ context: options.context,
744
+ layers: contributedToolLayers,
745
+ });
552
746
  const workspacePackageGlobs = [
553
747
  "apps/*",
554
748
  "packages/*",
@@ -556,20 +750,13 @@ function foundationPlan(options) {
556
750
  ...options.blueprint.packages
557
751
  .map((definition) => `${definition.path.split("/")[0]}/*`)
558
752
  .filter((glob) => glob !== "apps/*" && glob !== "packages/*"),
559
- ...options.contributions
753
+ ...contributions
560
754
  .flatMap((contribution) => contribution.foundation.workspacePackageGlobs ?? [])
561
755
  .filter((glob) => glob !== "apps/*" && glob !== "packages/*"),
562
756
  ]),
563
757
  ];
564
- const editorCustomization = editorCustomizationForCapabilities(options.contributions.flatMap((contribution) => contribution.foundation.editorCapabilities), loadEditorCustomizationDeclarations(resolveTemplateSource(templateSources.editorCustomization, "capabilities.json")));
565
- const dependencyMaintenancePolicy = {
566
- ecosystems: [
567
- ...new Set(options.contributions.flatMap((contribution) => contribution.foundation.dependencyMaintenance.ecosystems)),
568
- ],
569
- directories: Object.assign({}, ...options.contributions.map((contribution) => contribution.foundation.dependencyMaintenance.directories ?? {})),
570
- extraDirectories: Object.assign({}, ...options.contributions.map((contribution) => contribution.foundation.dependencyMaintenance.extraDirectories ?? {})),
571
- interval: "weekly",
572
- };
758
+ const editorCustomization = editorCustomizationForCapabilities(contributions.flatMap((contribution) => contribution.foundation.editorCapabilities), loadEditorCustomizationDeclarations(resolveTemplateSource(templateSources.editorCustomization, "capabilities.json")));
759
+ const dependencyMaintenancePolicy = composeDependencyMaintenancePolicy(contributions);
573
760
  const rootManifest = {
574
761
  name: options.context.projectName,
575
762
  version: "0.0.0",
@@ -600,7 +787,7 @@ function foundationPlan(options) {
600
787
  packageManager: options.context.toolchain.packageManagerPin,
601
788
  };
602
789
  const dependencyCatalog = selectTemplateDependencyCatalogEntries(collectGeneratedManifestCatalogReferences([
603
- ...options.contributions.map((contribution) => contribution.manifest),
790
+ ...contributions.map((contribution) => contribution.manifest),
604
791
  rootManifest,
605
792
  ]));
606
793
  const dependencyOverrides = {
@@ -634,17 +821,29 @@ function foundationPlan(options) {
634
821
  ].join("\n"),
635
822
  },
636
823
  };
637
- const workflowOperation = {
638
- kind: "writeTextTemplate",
639
- source: templateSources.foundation,
640
- from: ".github/workflows/check.dynamic.template",
641
- to: ".github/workflows/check.yml",
642
- replacements: projectCheckWorkflowTemplateReplacements({
643
- environment: { needs: environmentNeeds },
644
- deploymentEnvironmentNeeds,
645
- hasDeploymentTask,
646
- }),
647
- };
824
+ const workflowTemplateSource = projectCheckWorkflowTemplateSource({
825
+ packagePaths: blueprintPackagePaths,
826
+ deploymentEnvironmentNeeds,
827
+ hasDeploymentTask,
828
+ diagnosticArtifacts: ciDiagnosticArtifacts,
829
+ });
830
+ const workflowOperation = ciDiagnosticArtifacts.length === 0
831
+ ? {
832
+ kind: "copyFile",
833
+ source: templateSources.foundation,
834
+ from: workflowTemplateSource,
835
+ to: ".github/workflows/check.yml",
836
+ }
837
+ : {
838
+ kind: "writeTextTemplate",
839
+ source: templateSources.foundation,
840
+ from: workflowTemplateSource,
841
+ to: ".github/workflows/check.yml",
842
+ replacements: projectCheckWorkflowTemplateReplacements({
843
+ packagePaths: blueprintPackagePaths,
844
+ diagnosticArtifacts: ciDiagnosticArtifacts,
845
+ }),
846
+ };
648
847
  const workflowOperations = [
649
848
  workflowOperation,
650
849
  {
@@ -655,16 +854,8 @@ function foundationPlan(options) {
655
854
  replacements: projectDependabotTemplateReplacements(dependencyMaintenancePolicy),
656
855
  },
657
856
  ];
658
- const projectLinkPlan = planExplicitProjectLinks({
659
- blueprint: options.blueprint,
660
- contributions: options.contributions,
661
- ...(options.manifestTruthByPackagePath === undefined
662
- ? {}
663
- : {
664
- manifestTruthByPackagePath: options.manifestTruthByPackagePath,
665
- }),
666
- });
667
- const turboBoundaryTags = turboBoundaryTagsForContributions(options.contributions);
857
+ const projectLinkPlan = initialProjectLinkPlan;
858
+ const turboBoundaryTags = turboBoundaryTagsForContributions(contributions);
668
859
  const initializationFoundationOperations = [
669
860
  {
670
861
  kind: "writeJson",
@@ -741,64 +932,8 @@ function foundationPlan(options) {
741
932
  to: ".vscode/settings.json",
742
933
  value: editorCustomization.settings,
743
934
  },
744
- ...(rustToolchain === undefined
745
- ? [
746
- ...devcontainerDockerfileOperations({
747
- context: options.context,
748
- environmentNeeds,
749
- }),
750
- ]
751
- : [
752
- {
753
- kind: "writeTextTemplate",
754
- source: templateSources.foundation,
755
- from: "rust/rust-toolchain.toml",
756
- to: "rust-toolchain.toml",
757
- replacements: { RUST_TOOLCHAIN: rustToolchain.toolchain },
758
- },
759
- {
760
- kind: "writeTextTemplate",
761
- source: templateSources.foundation,
762
- from: "rust/devcontainer/devcontainer.json",
763
- to: ".devcontainer/devcontainer.json",
764
- replacements: {
765
- PROJECT_NAME: options.context.projectName,
766
- NODE_LTS_MAJOR: options.context.toolchain.nodeLtsMajor,
767
- PACKAGE_MANAGER_PIN: options.context.toolchain.packageManagerPin,
768
- RUST_TOOLCHAIN: rustToolchain.toolchain,
769
- },
770
- },
771
- {
772
- kind: "writeTextFromFragments",
773
- to: ".devcontainer/Dockerfile",
774
- fragments: [
775
- {
776
- source: templateSources.sharedDevcontainer,
777
- from: "node-pnpm.Dockerfile",
778
- },
779
- {
780
- source: templateSources.foundation,
781
- from: "rust/devcontainer/rust.Dockerfile",
782
- },
783
- ...(environmentNeeds.some((need) => need.kind === "playwright-browser-assets")
784
- ? [
785
- {
786
- source: templateSources.sharedDevcontainer,
787
- from: "browser-test.Dockerfile",
788
- },
789
- ]
790
- : []),
791
- ...(environmentNeeds.some((need) => need.kind === "shellcheck-command")
792
- ? [
793
- {
794
- source: templateSources.sharedDevcontainer,
795
- from: "shellcheck.Dockerfile",
796
- },
797
- ]
798
- : []),
799
- ],
800
- },
801
- ]),
935
+ ...developmentContainer.operations,
936
+ ...contributedFoundationTemplateFileOperations(contributions),
802
937
  ...workflowOperations,
803
938
  {
804
939
  kind: "writeJson",
@@ -843,7 +978,15 @@ function foundationPlan(options) {
843
978
  };
844
979
  const withProvenance = (operation, provenance) => ({ ...operation, provenance });
845
980
  const operations = [
846
- ...(options.renderContributions ?? options.contributions)
981
+ ...(options.renderContributions === undefined
982
+ ? contributions
983
+ : options.renderContributions.map((renderContribution) => {
984
+ const enriched = packageContributions.find((candidate) => candidate.definition.path === renderContribution.definition.path);
985
+ if (enriched === undefined) {
986
+ throw new Error(`Foundation cannot render unknown Package Contribution ${renderContribution.definition.path}`);
987
+ }
988
+ return enriched;
989
+ }))
847
990
  .map((item) => assertPackageContribution(item, {
848
991
  definitionName: options.definition.metadata.name,
849
992
  planner: options.mode === "addition"
@@ -865,7 +1008,11 @@ function foundationPlan(options) {
865
1008
  ];
866
1009
  const reconciliation = [
867
1010
  { path: "turbo.json", driver: "structured" },
868
- { path: ".devcontainer/devcontainer.json", driver: "structured" },
1011
+ {
1012
+ path: ".devcontainer/devcontainer.json",
1013
+ driver: "structured",
1014
+ identitySets: [developmentContainer.mountIdentitySet],
1015
+ },
869
1016
  {
870
1017
  path: ".vscode/extensions.json",
871
1018
  driver: "structured",
@@ -890,24 +1037,36 @@ function foundationPlan(options) {
890
1037
  generationRecord,
891
1038
  operations,
892
1039
  reconciliation,
1040
+ developmentContainer: {
1041
+ toolLayers: developmentContainer.toolLayers,
1042
+ buildArguments: developmentContainer.buildArguments,
1043
+ mounts: developmentContainer.mounts,
1044
+ probes: developmentContainer.probes,
1045
+ },
893
1046
  environmentNeeds,
894
1047
  deploymentEnvironmentNeeds,
895
- manifests: [
896
- ...options.contributions.map((item) => item.manifest),
897
- rootManifest,
898
- ],
1048
+ ciDiagnosticArtifacts,
1049
+ manifests: [...contributions.map((item) => item.manifest), rootManifest],
899
1050
  dependencyCatalog,
900
1051
  dependencyMaintenancePolicy,
901
1052
  nextStepInstructions: [
902
1053
  { display: "pnpm install" },
903
- ...environmentNeeds.map((need) => ({ display: need.nextStep.display })),
904
1054
  { display: "pnpm run fix" },
905
1055
  { display: "pnpm run check" },
906
1056
  ],
907
1057
  };
908
1058
  }
909
1059
  export function planGeneratedRepositoryInitialization(options) {
910
- const blueprint = options.definition.blueprint(options.context);
1060
+ const presetBlueprint = options.definition.blueprint(options.context);
1061
+ 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");
1065
+ }
1066
+ const blueprint = {
1067
+ ...presetBlueprint,
1068
+ packages: [...presetBlueprint.packages, configDefinition],
1069
+ };
911
1070
  const contributions = options.definition.planInitializationContributions?.(options.context) ?? [options.definition.planInitialization(options.context)];
912
1071
  return foundationPlan({
913
1072
  definition: options.definition,