@ykdz/template 0.0.9 → 0.0.11

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 (51) hide show
  1. package/dist/cli.js +2 -2
  2. package/node_modules/@ykdz/template-builtin-source/dist/src/index.d.ts +4 -4
  3. package/node_modules/@ykdz/template-builtin-source/dist/src/index.js +12 -5
  4. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.d.ts +1 -1
  5. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.js +1 -1
  6. package/node_modules/@ykdz/template-builtin-source/dist/templates/projection-plans.js +9 -6
  7. package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.d.ts +1 -1
  8. package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.js +1 -1
  9. package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.d.ts +1 -1
  10. package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.js +1 -1
  11. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.d.ts +1 -1
  12. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.js +9 -4
  13. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.d.ts +1 -1
  14. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.js +7 -7
  15. package/node_modules/@ykdz/template-builtin-source/package.json +4 -3
  16. package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +39 -11
  17. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/scripts/run-playwright.ts +25 -0
  18. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/src/App.vue +18 -4
  19. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/scripts/run-playwright.ts +25 -0
  20. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/src/App.vue +18 -4
  21. package/node_modules/@ykdz/template-core/dist/declarations.d.ts +2 -203
  22. package/node_modules/@ykdz/template-core/dist/declarations.js +1 -303
  23. package/node_modules/@ykdz/template-core/dist/devcontainer.d.ts +17 -0
  24. package/node_modules/@ykdz/template-core/dist/devcontainer.js +22 -21
  25. package/node_modules/@ykdz/template-core/dist/editor-customization.d.ts +10 -1
  26. package/node_modules/@ykdz/template-core/dist/editor-customization.js +36 -26
  27. package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +5 -0
  28. package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +42 -24
  29. package/node_modules/@ykdz/template-core/dist/generation-context.d.ts +1 -1
  30. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +19 -1
  31. package/node_modules/@ykdz/template-core/dist/module-graph.js +42 -3
  32. package/node_modules/@ykdz/template-core/dist/next-step-instructions.d.ts +2 -0
  33. package/node_modules/@ykdz/template-core/dist/next-step-instructions.js +11 -22
  34. package/node_modules/@ykdz/template-core/dist/package-addition-support.d.ts +2 -1
  35. package/node_modules/@ykdz/template-core/dist/package-addition-support.js +2 -4
  36. package/node_modules/@ykdz/template-core/dist/package-addition.js +4 -3
  37. package/node_modules/@ykdz/template-core/dist/package-linking.d.ts +2 -6
  38. package/node_modules/@ykdz/template-core/dist/package-linking.js +1 -1
  39. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +3 -5
  40. package/node_modules/@ykdz/template-core/dist/preset-projection.d.ts +1 -2
  41. package/node_modules/@ykdz/template-core/dist/preset-source.d.ts +4 -418
  42. package/node_modules/@ykdz/template-core/dist/preset-source.js +81 -726
  43. package/node_modules/@ykdz/template-core/dist/project-github.js +2 -6
  44. package/node_modules/@ykdz/template-core/dist/projection-capabilities.d.ts +5 -53
  45. package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +108 -507
  46. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +1 -1
  47. package/node_modules/@ykdz/template-core/package.json +4 -3
  48. package/node_modules/@ykdz/template-shared/dist/index.d.ts +794 -0
  49. package/node_modules/@ykdz/template-shared/dist/index.js +1637 -0
  50. package/node_modules/@ykdz/template-shared/package.json +32 -0
  51. package/package.json +9 -6
@@ -1,22 +1,13 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
4
+ import { normalizePresetProjectionDeclaration, projectionCapabilityIssues, validatePresetProjectionDeclaration as validateProjectionCapabilities, } from "@ykdz/template-shared";
3
5
  import { collectGeneratedManifestCatalogDependencies, collectGeneratedManifestCatalogReferences, renderGeneratedPnpmWorkspaceYaml, } from "./dependency-catalog.js";
4
6
  import { browserTestToolLayer, checkedDockerfileFirstNodePnpmDevcontainer, dockerfileFirstRustPnpmDevcontainer, nodePnpmToolLayer, rustToolLayer, } from "./devcontainer.js";
5
- import { editorCustomizationForCapabilities, } from "./editor-customization.js";
6
- import { renderFixCommand, renderRootCheckCommand, } from "./module-graph.js";
7
+ import { editorCustomizationForCapabilities, loadEditorCustomizationDeclarations, } from "./editor-customization.js";
8
+ import { playwrightBrowserAssetsEnvironmentNeed, renderFixCommand, renderRootCheckCommand, } from "./module-graph.js";
7
9
  import { packageManifestExposureFields, planPackageLinks, } from "./package-linking.js";
8
- const projectionCapabilityKinds = [
9
- "workspace-library-package",
10
- "workspace-node-packages",
11
- "rust-binary-workspace",
12
- "strict-typescript-root",
13
- "oxc-format-lint",
14
- "node-pnpm-devcontainer",
15
- "github-maintenance",
16
- ];
17
- const projectionCapabilityKindSet = new Set(projectionCapabilityKinds);
18
- const workspaceNodePackageKindSet = new Set(["hono-api", "vue-app"]);
19
- const workspaceNodePackagePathSet = new Set(["apps/api", "apps/web"]);
10
+ export { normalizePresetProjectionDeclaration, projectionCapabilityIssues, validateProjectionCapabilities, };
20
11
  const strictTypescriptRootBoundary = {
21
12
  kind: "workspace-orchestration",
22
13
  path: ".",
@@ -52,26 +43,6 @@ const requiredPlanCapabilityProviders = [
52
43
  label: "development container support",
53
44
  },
54
45
  ];
55
- const exactCapabilityKeys = {
56
- "workspace-library-package": [
57
- "kind",
58
- "workspacePackageGlob",
59
- "packageRole",
60
- "packageSourcePreset",
61
- "sourceFiles",
62
- ],
63
- "workspace-node-packages": [
64
- "kind",
65
- "workspacePackageGlob",
66
- "packages",
67
- "packageLinks",
68
- ],
69
- "rust-binary-workspace": ["kind", "workspacePackageGlob", "sourceFiles"],
70
- "strict-typescript-root": ["kind"],
71
- "oxc-format-lint": ["kind"],
72
- "node-pnpm-devcontainer": ["kind"],
73
- "github-maintenance": ["kind"],
74
- };
75
46
  const dependencyMaintenanceEcosystems = ["npm", "github-actions", "docker"];
76
47
  function strictTypeScriptCompilerOptions(options) {
77
48
  return {
@@ -110,12 +81,12 @@ const capabilityInterpreters = {
110
81
  if (capability.packages.length > 1) {
111
82
  state.rootScriptFragments.dev = "turbo run dev --parallel";
112
83
  }
113
- if (hasVuePackage(capability)) {
114
- state.rootCheckEnvironmentNeeds.push({
115
- kind: "playwright-browser-assets",
84
+ const vuePackage = findVuePackage(capability);
85
+ if (vuePackage !== undefined) {
86
+ state.rootCheckEnvironmentNeeds.push(playwrightBrowserAssetsEnvironmentNeed({
116
87
  browser: "chromium",
117
- owner: { kind: "package-boundary", path: "apps/web" },
118
- });
88
+ owner: { kind: "package-boundary", path: vuePackage.path },
89
+ }));
119
90
  state.editorCustomizationCapabilities.push("vue", "tailwind");
120
91
  }
121
92
  state.editorCustomizationCapabilities.push("vitest");
@@ -126,8 +97,8 @@ const capabilityInterpreters = {
126
97
  contribute({ capability, state }) {
127
98
  state.rustWorkspace = capability;
128
99
  state.sourceRoot = templateSourceRootForPreset(state, "rust-bin");
129
- state.sourceRoots.sharedDevcontainer =
130
- state.projectionSourceRoots.sharedDevcontainer();
100
+ setDevelopmentContainerResource(state, capability.devcontainerResourceId, "rust-binary-workspace");
101
+ setEditorCustomizationResource(state, capability.editorCustomizationResourceId, "rust-binary-workspace");
131
102
  state.rootCheckComponents.push({
132
103
  kind: "turbo-package-check",
133
104
  owner: workspaceGlobBoundary(capability.workspacePackageGlob),
@@ -172,12 +143,6 @@ const capabilityInterpreters = {
172
143
  kind: "turbo-package-test",
173
144
  owner: workspaceBoundary,
174
145
  });
175
- if (hasVuePackage(state.nodeWorkspace)) {
176
- state.rootCheckComponents.push({
177
- kind: "turbo-package-e2e-test",
178
- owner: workspaceBoundary,
179
- });
180
- }
181
146
  }
182
147
  state.rootCheckComponents.push({
183
148
  kind: "turbo-package-check",
@@ -199,8 +164,9 @@ const capabilityInterpreters = {
199
164
  },
200
165
  "oxc-format-lint": {
201
166
  kind: "oxc-format-lint",
202
- contribute({ state }) {
167
+ contribute({ capability, state }) {
203
168
  state.sourceRoots.sharedOxc = state.projectionSourceRoots.sharedOxc();
169
+ setEditorCustomizationResource(state, capability.editorCustomizationResourceId, "oxc-format-lint");
204
170
  state.rootCheckComponents.unshift({
205
171
  kind: "oxc-format-check",
206
172
  owner: strictTypescriptRootBoundary,
@@ -249,9 +215,8 @@ const capabilityInterpreters = {
249
215
  },
250
216
  "node-pnpm-devcontainer": {
251
217
  kind: "node-pnpm-devcontainer",
252
- contribute({ state }) {
253
- state.sourceRoots.sharedDevcontainer =
254
- state.projectionSourceRoots.sharedDevcontainer();
218
+ contribute({ capability, state }) {
219
+ setDevelopmentContainerResource(state, capability.devcontainerResourceId, "node-pnpm-devcontainer");
255
220
  state.flags.devcontainer = true;
256
221
  state.operationFactories.push(nodePnpmDevcontainerOperations);
257
222
  },
@@ -312,111 +277,6 @@ function contributeProjectionCapability(capability, state) {
312
277
  return;
313
278
  }
314
279
  }
315
- export function validateProjectionCapabilities(input) {
316
- if (!isRecord(input) || !Array.isArray(input.capabilities)) {
317
- return {
318
- ok: false,
319
- issues: [
320
- {
321
- path: "$.capabilities",
322
- message: "Projection Declaration must select Projection Capabilities",
323
- },
324
- ],
325
- };
326
- }
327
- const issues = [];
328
- const capabilities = [];
329
- input.capabilities.forEach((capability, index) => {
330
- const pathPrefix = `$.capabilities[${index}]`;
331
- if (!isRecord(capability)) {
332
- issues.push({
333
- path: pathPrefix,
334
- message: "Projection Capability must be an object",
335
- });
336
- return;
337
- }
338
- if (typeof capability.kind !== "string") {
339
- issues.push({
340
- path: `${pathPrefix}.kind`,
341
- message: "Projection Capability kind is required",
342
- });
343
- return;
344
- }
345
- if (!isProjectionCapabilityKind(capability.kind)) {
346
- issues.push({
347
- path: `${pathPrefix}.kind`,
348
- message: `Unknown Projection Capability kind: ${capability.kind}`,
349
- });
350
- return;
351
- }
352
- const kind = capability.kind;
353
- issues.push(...unknownCapabilityPropertyIssues(kind, capability, pathPrefix));
354
- if (kind === "workspace-library-package") {
355
- const workspaceCapability = parseWorkspaceLibraryPackageCapability(capability, pathPrefix);
356
- if (Array.isArray(workspaceCapability)) {
357
- issues.push(...workspaceCapability);
358
- return;
359
- }
360
- capabilities.push(workspaceCapability);
361
- return;
362
- }
363
- if (kind === "workspace-node-packages") {
364
- const workspaceCapability = parseWorkspaceNodePackagesCapability(capability, pathPrefix);
365
- if (Array.isArray(workspaceCapability)) {
366
- issues.push(...workspaceCapability);
367
- return;
368
- }
369
- capabilities.push(workspaceCapability);
370
- return;
371
- }
372
- if (kind === "rust-binary-workspace") {
373
- const workspaceCapability = parseRustBinaryWorkspaceCapability(capability, pathPrefix);
374
- if (Array.isArray(workspaceCapability)) {
375
- issues.push(...workspaceCapability);
376
- return;
377
- }
378
- capabilities.push(workspaceCapability);
379
- return;
380
- }
381
- capabilities.push({ kind });
382
- });
383
- if (issues.length === 0) {
384
- issues.push(...duplicateCapabilityIssues(capabilities));
385
- issues.push(...capabilityCompositionIssues(capabilities));
386
- }
387
- if (issues.length > 0) {
388
- return { ok: false, issues };
389
- }
390
- return {
391
- ok: true,
392
- value: {
393
- capabilities,
394
- },
395
- };
396
- }
397
- export function projectionCapabilityIssues(presets) {
398
- return presets.flatMap((preset, presetIndex) => {
399
- if (preset.projection === undefined) {
400
- return [];
401
- }
402
- const result = validateProjectionCapabilities(preset.projection);
403
- return result.ok
404
- ? []
405
- : result.issues.map((issue) => ({
406
- path: `$.presets[${presetIndex}].projection${issue.path.slice(1)}`,
407
- message: issue.message,
408
- }));
409
- });
410
- }
411
- export function normalizePresetProjectionDeclaration(declaration) {
412
- const result = validateProjectionCapabilities(declaration);
413
- if (!result.ok) {
414
- throw new Error(`Projection Declaration is invalid:\n${result.issues
415
- .map((issue) => ` - ${issue.path}: ${issue.message}`)
416
- .join("\n")}`);
417
- }
418
- return result.value;
419
- }
420
280
  export function interpretPresetProjectionDeclaration(options) {
421
281
  const validation = validateProjectionCapabilities(options.declaration);
422
282
  if (!validation.ok) {
@@ -565,354 +425,37 @@ function createProjectionCompositionState(sourceRoots) {
565
425
  flags: {},
566
426
  };
567
427
  }
568
- function isRecord(value) {
569
- return typeof value === "object" && value !== null && !Array.isArray(value);
570
- }
571
- function isProjectionCapabilityKind(value) {
572
- return projectionCapabilityKindSet.has(value);
573
- }
574
- function isWorkspaceNodePackageKind(value) {
575
- return typeof value === "string" && workspaceNodePackageKindSet.has(value);
576
- }
577
- function isWorkspaceNodePackagePath(value) {
578
- return typeof value === "string" && workspaceNodePackagePathSet.has(value);
579
- }
580
- function isNonEmptyString(value) {
581
- return typeof value === "string" && value.length > 0;
582
- }
583
- function nonEmptyStringArray(value) {
584
- const strings = [];
585
- for (const entry of value) {
586
- if (!isNonEmptyString(entry)) {
587
- return undefined;
588
- }
589
- strings.push(entry);
590
- }
591
- return strings.length === 0 ? undefined : strings;
592
- }
593
- function unknownCapabilityPropertyIssues(kind, capability, pathPrefix) {
594
- const allowedKeys = new Set(exactCapabilityKeys[kind]);
595
- return Object.keys(capability)
596
- .filter((key) => !allowedKeys.has(key))
597
- .map((key) => ({
598
- path: `${pathPrefix}.${key}`,
599
- message: `Projection Capability ${kind} does not support property: ${key}`,
600
- }));
601
- }
602
- function parseWorkspaceLibraryPackageCapability(capability, pathPrefix) {
603
- const issues = [];
604
- let sourceFiles = [];
605
- if (capability.workspacePackageGlob !== "packages/*") {
606
- issues.push({
607
- path: `${pathPrefix}.workspacePackageGlob`,
608
- message: "workspace-library-package currently supports workspacePackageGlob: packages/*",
609
- });
610
- }
611
- if (capability.packageRole !== "shared-library") {
612
- issues.push({
613
- path: `${pathPrefix}.packageRole`,
614
- message: "workspace-library-package currently supports packageRole: shared-library",
615
- });
616
- }
617
- if (capability.packageSourcePreset !== "ts-lib") {
618
- issues.push({
619
- path: `${pathPrefix}.packageSourcePreset`,
620
- message: "workspace-library-package currently supports packageSourcePreset: ts-lib",
621
- });
622
- }
623
- if (!Array.isArray(capability.sourceFiles)) {
624
- issues.push({
625
- path: `${pathPrefix}.sourceFiles`,
626
- message: "workspace-library-package sourceFiles must be a non-empty array",
627
- });
628
- }
629
- else {
630
- const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
631
- if (parsedSourceFiles === undefined) {
632
- issues.push({
633
- path: `${pathPrefix}.sourceFiles`,
634
- message: "workspace-library-package sourceFiles must be a non-empty array of paths",
635
- });
636
- }
637
- else {
638
- sourceFiles = parsedSourceFiles;
639
- }
640
- }
641
- if (issues.length > 0) {
642
- return issues;
643
- }
644
- return {
645
- kind: "workspace-library-package",
646
- workspacePackageGlob: "packages/*",
647
- packageRole: "shared-library",
648
- packageSourcePreset: "ts-lib",
649
- sourceFiles,
650
- };
428
+ function uniqueValues(values) {
429
+ return [...new Set(values)];
651
430
  }
652
- function parseWorkspaceNodePackagesCapability(capability, pathPrefix) {
653
- const issues = [];
654
- if (capability.workspacePackageGlob !== "apps/*") {
655
- issues.push({
656
- path: `${pathPrefix}.workspacePackageGlob`,
657
- message: "workspace-node-packages currently supports workspacePackageGlob: apps/*",
658
- });
431
+ function setDevelopmentContainerResource(state, resourceId, capabilityKind) {
432
+ const root = state.projectionSourceRoots.sharedResource(resourceId);
433
+ if (root === undefined) {
434
+ throw new Error(`Projection Capability ${capabilityKind} references unresolved Development Container Shared Resource: ${resourceId}`);
659
435
  }
660
- if (!Array.isArray(capability.packages) || capability.packages.length === 0) {
661
- issues.push({
662
- path: `${pathPrefix}.packages`,
663
- message: "workspace-node-packages packages must be a non-empty array",
664
- });
665
- }
666
- const packages = Array.isArray(capability.packages)
667
- ? capability.packages.flatMap((nodePackage, packageIndex) => {
668
- const packagePath = `${pathPrefix}.packages[${packageIndex}]`;
669
- const parsed = parseWorkspaceNodePackage(nodePackage, packagePath);
670
- if (Array.isArray(parsed)) {
671
- issues.push(...parsed);
672
- return [];
673
- }
674
- return [parsed];
675
- })
676
- : [];
677
- const packageLinks = capability.packageLinks === undefined
678
- ? undefined
679
- : parseWorkspaceNodePackageLinks(capability.packageLinks, `${pathPrefix}.packageLinks`, new Set(packages.map((nodePackage) => nodePackage.path)), issues);
680
- if (issues.length > 0) {
681
- return issues;
682
- }
683
- return {
684
- kind: "workspace-node-packages",
685
- workspacePackageGlob: "apps/*",
686
- packages,
687
- ...(packageLinks === undefined ? {} : { packageLinks }),
688
- };
436
+ const sourceRootKey = developmentContainerResourceSourceRootKey(resourceId);
437
+ state.devcontainerResource = { id: resourceId, root, sourceRootKey };
438
+ state.sourceRoots[sourceRootKey] = root;
689
439
  }
690
- function parseWorkspaceNodePackage(value, pathPrefix) {
691
- const issues = [];
692
- if (!isRecord(value)) {
693
- return [
694
- {
695
- path: pathPrefix,
696
- message: "workspace-node-packages package must be an object",
697
- },
698
- ];
699
- }
700
- const kind = isWorkspaceNodePackageKind(value.kind) ? value.kind : undefined;
701
- const packagePath = isWorkspaceNodePackagePath(value.path)
702
- ? value.path
703
- : undefined;
704
- let sourceFiles = [];
705
- if (kind === undefined) {
706
- issues.push({
707
- path: `${pathPrefix}.kind`,
708
- message: "workspace-node-packages package kind must be hono-api or vue-app",
709
- });
710
- }
711
- if (packagePath === undefined) {
712
- issues.push({
713
- path: `${pathPrefix}.path`,
714
- message: "workspace-node-packages package path must be apps/api or apps/web",
715
- });
716
- }
717
- if (!Array.isArray(value.sourceFiles)) {
718
- issues.push({
719
- path: `${pathPrefix}.sourceFiles`,
720
- message: "workspace-node-packages package sourceFiles must be a non-empty array",
721
- });
722
- }
723
- else {
724
- const parsedSourceFiles = nonEmptyStringArray(value.sourceFiles);
725
- if (parsedSourceFiles === undefined) {
726
- issues.push({
727
- path: `${pathPrefix}.sourceFiles`,
728
- message: "workspace-node-packages package sourceFiles must be a non-empty array of paths",
729
- });
730
- }
731
- else {
732
- sourceFiles = parsedSourceFiles;
733
- }
440
+ function setEditorCustomizationResource(state, resourceId, capabilityKind) {
441
+ const root = state.projectionSourceRoots.sharedResource(resourceId);
442
+ if (root === undefined) {
443
+ throw new Error(`Projection Capability ${capabilityKind} references unresolved Editor Customization Shared Resource: ${resourceId}`);
734
444
  }
735
- const allowedKeys = new Set(["kind", "path", "sourceFiles"]);
736
- for (const key of Object.keys(value)) {
737
- if (!allowedKeys.has(key)) {
738
- issues.push({
739
- path: `${pathPrefix}.${key}`,
740
- message: `workspace-node-packages package does not support property: ${key}`,
741
- });
742
- }
743
- }
744
- if (issues.length > 0) {
745
- return issues;
746
- }
747
- if (kind === undefined || packagePath === undefined) {
748
- throw new Error(`workspace-node-packages package validation failed without diagnostics at ${pathPrefix}`);
749
- }
750
- return {
751
- kind,
752
- path: packagePath,
753
- sourceFiles,
445
+ state.editorCustomizationResource = {
446
+ id: resourceId,
447
+ root,
448
+ declarations: loadEditorCustomizationDeclarations(root),
754
449
  };
755
450
  }
756
- function parseWorkspaceNodePackageLinks(value, pathPrefix, declaredPackagePaths, issues) {
757
- if (!Array.isArray(value)) {
758
- issues.push({
759
- path: pathPrefix,
760
- message: "workspace-node-packages packageLinks must be an array",
761
- });
762
- return undefined;
763
- }
764
- return value.flatMap((link, linkIndex) => {
765
- const linkPath = `${pathPrefix}[${linkIndex}]`;
766
- const linkIssues = [];
767
- if (!isRecord(link)) {
768
- issues.push({
769
- path: linkPath,
770
- message: "workspace-node-packages packageLink must be an object",
771
- });
772
- return [];
773
- }
774
- const allowedKeys = new Set(["consumerPackagePath", "providerPackagePath"]);
775
- for (const key of Object.keys(link)) {
776
- if (!allowedKeys.has(key)) {
777
- linkIssues.push({
778
- path: `${linkPath}.${key}`,
779
- message: `workspace-node-packages packageLink does not support property: ${key}`,
780
- });
781
- }
782
- }
783
- if (link.consumerPackagePath !== "apps/web" ||
784
- link.providerPackagePath !== "apps/api") {
785
- linkIssues.push({
786
- path: linkPath,
787
- message: "workspace-node-packages currently supports links from apps/web to apps/api",
788
- });
789
- }
790
- else {
791
- if (!declaredPackagePaths.has(link.consumerPackagePath)) {
792
- linkIssues.push({
793
- path: `${linkPath}.consumerPackagePath`,
794
- message: "workspace-node-packages packageLink consumerPackagePath must reference a package declared in the same packages array: apps/web",
795
- });
796
- }
797
- if (!declaredPackagePaths.has(link.providerPackagePath)) {
798
- linkIssues.push({
799
- path: `${linkPath}.providerPackagePath`,
800
- message: "workspace-node-packages packageLink providerPackagePath must reference a package declared in the same packages array: apps/api",
801
- });
802
- }
803
- }
804
- if (linkIssues.length > 0) {
805
- issues.push(...linkIssues);
806
- return [];
807
- }
808
- return [
809
- {
810
- consumerPackagePath: "apps/web",
811
- providerPackagePath: "apps/api",
812
- },
813
- ];
814
- });
451
+ function developmentContainerResourceSourceRootKey(resourceId) {
452
+ return `devcontainer:${resourceId}`;
815
453
  }
816
- function parseRustBinaryWorkspaceCapability(capability, pathPrefix) {
817
- const issues = [];
818
- let sourceFiles = [];
819
- if (capability.workspacePackageGlob !== "packages/*") {
820
- issues.push({
821
- path: `${pathPrefix}.workspacePackageGlob`,
822
- message: "rust-binary-workspace currently supports workspacePackageGlob: packages/*",
823
- });
454
+ function editorCustomizationDeclarationsForState(state) {
455
+ if (state.editorCustomizationResource === undefined) {
456
+ throw new Error("Projection Capability composition did not provide Editor Customization Shared Resource");
824
457
  }
825
- if (!Array.isArray(capability.sourceFiles)) {
826
- issues.push({
827
- path: `${pathPrefix}.sourceFiles`,
828
- message: "rust-binary-workspace sourceFiles must be a non-empty array",
829
- });
830
- }
831
- else {
832
- const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
833
- if (parsedSourceFiles === undefined) {
834
- issues.push({
835
- path: `${pathPrefix}.sourceFiles`,
836
- message: "rust-binary-workspace sourceFiles must be a non-empty array of paths",
837
- });
838
- }
839
- else {
840
- sourceFiles = parsedSourceFiles;
841
- }
842
- }
843
- if (issues.length > 0) {
844
- return issues;
845
- }
846
- return {
847
- kind: "rust-binary-workspace",
848
- workspacePackageGlob: "packages/*",
849
- sourceFiles,
850
- };
851
- }
852
- function duplicateCapabilityIssues(capabilities) {
853
- const firstSeen = new Map();
854
- const issues = [];
855
- capabilities.forEach((capability, index) => {
856
- const firstIndex = firstSeen.get(capability.kind);
857
- if (firstIndex === undefined) {
858
- firstSeen.set(capability.kind, index);
859
- return;
860
- }
861
- issues.push({
862
- path: `$.capabilities[${index}].kind`,
863
- message: `Duplicate Projection Capability kind: ${capability.kind}`,
864
- });
865
- });
866
- return issues;
867
- }
868
- function capabilityCompositionIssues(capabilities) {
869
- const kinds = new Set(capabilities.map((capability) => capability.kind));
870
- const issues = [];
871
- const companionKinds = uniqueValues(capabilities
872
- .map((capability) => capability.kind)
873
- .filter((kind) => kind !== "rust-binary-workspace"));
874
- if (kinds.has("rust-binary-workspace") && companionKinds.length > 0) {
875
- issues.push({
876
- path: "$.capabilities",
877
- message: "rust-binary-workspace is a complete domain capability and must be selected by itself; remove companion Projection Capabilities: " +
878
- companionKinds.join(", "),
879
- });
880
- return issues;
881
- }
882
- if (!kinds.has("workspace-library-package") &&
883
- !kinds.has("workspace-node-packages") &&
884
- !kinds.has("rust-binary-workspace")) {
885
- issues.push({
886
- path: "$.capabilities",
887
- message: "Projection Capability composition must include a workspace package layout capability",
888
- });
889
- }
890
- if (kinds.has("strict-typescript-root") &&
891
- !kinds.has("workspace-library-package") &&
892
- !kinds.has("workspace-node-packages")) {
893
- issues.push({
894
- path: "$.capabilities",
895
- message: "strict-typescript-root requires a workspace package layout so package typecheck tasks have a workspace target",
896
- });
897
- }
898
- if (kinds.has("node-pnpm-devcontainer") && !kinds.has("oxc-format-lint")) {
899
- issues.push({
900
- path: "$.capabilities",
901
- message: "node-pnpm-devcontainer requires oxc-format-lint so editor customization is derived from declared tooling",
902
- });
903
- }
904
- for (const requirement of requiredPlanCapabilityProviders) {
905
- if (!kinds.has(requirement.kind) && !kinds.has("rust-binary-workspace")) {
906
- issues.push({
907
- path: "$.capabilities",
908
- message: `Projection Capability composition must include ${requirement.kind} to provide ${requirement.label}`,
909
- });
910
- }
911
- }
912
- return issues;
913
- }
914
- function uniqueValues(values) {
915
- return [...new Set(values)];
458
+ return state.editorCustomizationResource.declarations;
916
459
  }
917
460
  function projectNameFromDir(targetDir) {
918
461
  return path.basename(path.resolve(targetDir));
@@ -1032,7 +575,7 @@ function generationContextForPackageAddition(options) {
1032
575
  },
1033
576
  toolchain: {
1034
577
  nodeLtsMajor: { kind: "NodeLtsMajor", value: options.nodeVersion },
1035
- packageManagerPin: { kind: "PackageManagerPin", value: "pnpm@10.0.0" },
578
+ packageManagerPin: { kind: "PackageManagerPin", value: "pnpm@10.34.4" },
1036
579
  source: "bundled-fallback",
1037
580
  diagnostics: [],
1038
581
  },
@@ -1248,7 +791,10 @@ function workspaceGlobBoundary(workspacePackageGlob) {
1248
791
  };
1249
792
  }
1250
793
  function hasVuePackage(capability) {
1251
- return capability.packages.some((nodePackage) => nodePackage.kind === "vue-app");
794
+ return findVuePackage(capability) !== undefined;
795
+ }
796
+ function findVuePackage(capability) {
797
+ return capability.packages.find((nodePackage) => nodePackage.kind === "vue-app");
1252
798
  }
1253
799
  function sourceRootPreset(capability) {
1254
800
  if (capability.packages.length === 1) {
@@ -1432,7 +978,6 @@ function vuePackageJson(context, packageName, scripts, packageLinkDependencies =
1432
978
  scripts,
1433
979
  dependencies: {
1434
980
  ...packageLinkDependencies,
1435
- "@vueuse/core": "catalog:",
1436
981
  ...(Object.keys(packageLinkDependencies).length === 0
1437
982
  ? {}
1438
983
  : { hono: "catalog:" }),
@@ -1636,7 +1181,8 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
1636
1181
  const rootManifest = rustRootPackageJson(context, packageScripts, state);
1637
1182
  const packageManifest = rustWorkspacePackageJson(context, workspacePackageScripts);
1638
1183
  const rustLayer = rustToolLayer();
1639
- const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
1184
+ const dockerfileFragments = readDevelopmentContainerDockerfileFragments(state, ["rust"]);
1185
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
1640
1186
  const developmentContainer = dockerfileFirstRustPnpmDevcontainer({
1641
1187
  name: context.projectName.value,
1642
1188
  nodeLayer: nodePnpmToolLayer({
@@ -1644,6 +1190,7 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
1644
1190
  packageManagerPin: context.toolchain.packageManagerPin.value,
1645
1191
  }),
1646
1192
  rustLayer,
1193
+ dockerfileFragments,
1647
1194
  extensions: editorCustomization.extensions,
1648
1195
  settings: editorCustomization.settings,
1649
1196
  });
@@ -1931,7 +1478,7 @@ function nodePackageScripts(nodePackage, workspace) {
1931
1478
  ...oxcScripts,
1932
1479
  preview: "vite preview",
1933
1480
  test: "vitest run",
1934
- "test:e2e": "pnpm run build && playwright test",
1481
+ "test:e2e": "pnpm run build && node --experimental-strip-types scripts/run-playwright.ts",
1935
1482
  typecheck: workspace.packages.length > 1
1936
1483
  ? "vue-tsc --build"
1937
1484
  : "vue-tsc --build --noEmit",
@@ -2015,6 +1562,7 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
2015
1562
  {
2016
1563
  kind: "writeJson",
2017
1564
  to: `${nodePackage.path}/tsconfig.node.json`,
1565
+ multilineArrays: ["include"],
2018
1566
  value: {
2019
1567
  compilerOptions: strictTypeScriptCompilerOptions({
2020
1568
  composite: true,
@@ -2027,7 +1575,12 @@ function nodePackageTsconfigOperations(nodePackage, workspace) {
2027
1575
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
2028
1576
  types: ["node"],
2029
1577
  }),
2030
- include: ["playwright.config.ts", "vite.config.ts", "vitest.config.ts"],
1578
+ include: [
1579
+ "playwright.config.ts",
1580
+ "scripts/**/*.ts",
1581
+ "vite.config.ts",
1582
+ "vitest.config.ts",
1583
+ ],
2031
1584
  },
2032
1585
  },
2033
1586
  ];
@@ -2169,7 +1722,7 @@ function rootTsconfigReferences(workspace) {
2169
1722
  });
2170
1723
  }
2171
1724
  function oxcFormatLintOperations({ state, }) {
2172
- const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
1725
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
2173
1726
  return [
2174
1727
  {
2175
1728
  kind: "copyFile",
@@ -2201,16 +1754,19 @@ function oxcFormatLintOperations({ state, }) {
2201
1754
  ];
2202
1755
  }
2203
1756
  function nodePnpmDevcontainerOperations({ context, state, }) {
2204
- const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
1757
+ const additionalLayers = state.nodeWorkspace && hasVuePackage(state.nodeWorkspace)
1758
+ ? [browserTestToolLayer()]
1759
+ : [];
1760
+ const dockerfileFragments = readDevelopmentContainerDockerfileFragments(state, additionalLayers.length === 0 ? [] : ["browserTest"]);
1761
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
2205
1762
  const developmentContainer = checkedDockerfileFirstNodePnpmDevcontainer({
2206
1763
  name: context.projectName.value,
2207
1764
  layer: nodePnpmToolLayer({
2208
1765
  nodeVersion: context.toolchain.nodeLtsMajor.value,
2209
1766
  packageManagerPin: context.toolchain.packageManagerPin.value,
2210
1767
  }),
2211
- additionalLayers: state.nodeWorkspace && hasVuePackage(state.nodeWorkspace)
2212
- ? [browserTestToolLayer()]
2213
- : [],
1768
+ dockerfileFragments,
1769
+ additionalLayers,
2214
1770
  extensions: editorCustomization.extensions,
2215
1771
  settings: editorCustomization.settings,
2216
1772
  });
@@ -2225,6 +1781,51 @@ function nodePnpmDevcontainerOperations({ context, state, }) {
2225
1781
  },
2226
1782
  ];
2227
1783
  }
1784
+ function readDevelopmentContainerDockerfileFragments(state, optionalFragments) {
1785
+ if (state.devcontainerResource === undefined) {
1786
+ throw new Error("Projection Capability composition did not provide a Development Container Shared Resource");
1787
+ }
1788
+ const fragmentNames = {
1789
+ nodePnpm: "node-pnpm.Dockerfile",
1790
+ browserTest: "browser-test.Dockerfile",
1791
+ rust: "rust.Dockerfile",
1792
+ };
1793
+ const selected = new Set(optionalFragments);
1794
+ return {
1795
+ sourceRoot: state.devcontainerResource.sourceRootKey,
1796
+ nodePnpm: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.nodePnpm),
1797
+ ...(selected.has("browserTest")
1798
+ ? {
1799
+ browserTest: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.browserTest),
1800
+ }
1801
+ : {}),
1802
+ ...(selected.has("rust")
1803
+ ? {
1804
+ rust: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.rust),
1805
+ }
1806
+ : {}),
1807
+ };
1808
+ }
1809
+ function readDevelopmentContainerDockerfileFragment(resource, fragmentName) {
1810
+ const filePath = path.join(resource.root, fragmentName);
1811
+ try {
1812
+ return {
1813
+ from: fragmentName,
1814
+ text: readFileSync(filePath, "utf8"),
1815
+ };
1816
+ }
1817
+ catch (error) {
1818
+ if (isMissingDevelopmentContainerFragmentError(error)) {
1819
+ throw new Error(`Development Container Shared Resource ${resource.id} is missing Dockerfile fragment: ${fragmentName}`, { cause: error });
1820
+ }
1821
+ throw error;
1822
+ }
1823
+ }
1824
+ function isMissingDevelopmentContainerFragmentError(error) {
1825
+ return (error instanceof Error &&
1826
+ "code" in error &&
1827
+ (error.code === "ENOENT" || error.code === "ENOTDIR"));
1828
+ }
2228
1829
  function githubMaintenanceOperations() {
2229
1830
  return [
2230
1831
  {