@ykdz/template 0.0.10 → 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.
- package/dist/cli.js +2 -2
- package/node_modules/@ykdz/template-builtin-source/dist/src/index.d.ts +4 -4
- package/node_modules/@ykdz/template-builtin-source/dist/src/index.js +12 -5
- package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.js +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/projection-plans.js +9 -6
- package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.js +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.js +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.js +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.d.ts +1 -1
- package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.js +6 -5
- package/node_modules/@ykdz/template-builtin-source/package.json +3 -2
- package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +35 -9
- package/node_modules/@ykdz/template-core/dist/declarations.d.ts +2 -203
- package/node_modules/@ykdz/template-core/dist/declarations.js +1 -303
- package/node_modules/@ykdz/template-core/dist/devcontainer.d.ts +17 -0
- package/node_modules/@ykdz/template-core/dist/devcontainer.js +22 -21
- package/node_modules/@ykdz/template-core/dist/editor-customization.d.ts +10 -1
- package/node_modules/@ykdz/template-core/dist/editor-customization.js +36 -26
- package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +4 -0
- package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +32 -22
- package/node_modules/@ykdz/template-core/dist/generation-context.d.ts +1 -1
- package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +19 -1
- package/node_modules/@ykdz/template-core/dist/module-graph.js +42 -3
- package/node_modules/@ykdz/template-core/dist/next-step-instructions.d.ts +2 -0
- package/node_modules/@ykdz/template-core/dist/next-step-instructions.js +11 -22
- package/node_modules/@ykdz/template-core/dist/package-addition-support.d.ts +2 -1
- package/node_modules/@ykdz/template-core/dist/package-addition-support.js +2 -4
- package/node_modules/@ykdz/template-core/dist/package-addition.js +2 -2
- package/node_modules/@ykdz/template-core/dist/package-linking.d.ts +2 -6
- package/node_modules/@ykdz/template-core/dist/package-linking.js +1 -1
- package/node_modules/@ykdz/template-core/dist/preset-projection.d.ts +1 -2
- package/node_modules/@ykdz/template-core/dist/preset-source.d.ts +4 -418
- package/node_modules/@ykdz/template-core/dist/preset-source.js +81 -726
- package/node_modules/@ykdz/template-core/dist/project-github.js +2 -6
- package/node_modules/@ykdz/template-core/dist/projection-capabilities.d.ts +5 -53
- package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +99 -497
- package/node_modules/@ykdz/template-core/package.json +3 -2
- package/node_modules/@ykdz/template-shared/dist/index.d.ts +794 -0
- package/node_modules/@ykdz/template-shared/dist/index.js +1637 -0
- package/node_modules/@ykdz/template-shared/package.json +32 -0
- 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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
84
|
+
const vuePackage = findVuePackage(capability);
|
|
85
|
+
if (vuePackage !== undefined) {
|
|
86
|
+
state.rootCheckEnvironmentNeeds.push(playwrightBrowserAssetsEnvironmentNeed({
|
|
116
87
|
browser: "chromium",
|
|
117
|
-
owner: { kind: "package-boundary", path:
|
|
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.
|
|
130
|
-
|
|
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),
|
|
@@ -193,8 +164,9 @@ const capabilityInterpreters = {
|
|
|
193
164
|
},
|
|
194
165
|
"oxc-format-lint": {
|
|
195
166
|
kind: "oxc-format-lint",
|
|
196
|
-
contribute({ state }) {
|
|
167
|
+
contribute({ capability, state }) {
|
|
197
168
|
state.sourceRoots.sharedOxc = state.projectionSourceRoots.sharedOxc();
|
|
169
|
+
setEditorCustomizationResource(state, capability.editorCustomizationResourceId, "oxc-format-lint");
|
|
198
170
|
state.rootCheckComponents.unshift({
|
|
199
171
|
kind: "oxc-format-check",
|
|
200
172
|
owner: strictTypescriptRootBoundary,
|
|
@@ -243,9 +215,8 @@ const capabilityInterpreters = {
|
|
|
243
215
|
},
|
|
244
216
|
"node-pnpm-devcontainer": {
|
|
245
217
|
kind: "node-pnpm-devcontainer",
|
|
246
|
-
contribute({ state }) {
|
|
247
|
-
state.
|
|
248
|
-
state.projectionSourceRoots.sharedDevcontainer();
|
|
218
|
+
contribute({ capability, state }) {
|
|
219
|
+
setDevelopmentContainerResource(state, capability.devcontainerResourceId, "node-pnpm-devcontainer");
|
|
249
220
|
state.flags.devcontainer = true;
|
|
250
221
|
state.operationFactories.push(nodePnpmDevcontainerOperations);
|
|
251
222
|
},
|
|
@@ -306,111 +277,6 @@ function contributeProjectionCapability(capability, state) {
|
|
|
306
277
|
return;
|
|
307
278
|
}
|
|
308
279
|
}
|
|
309
|
-
export function validateProjectionCapabilities(input) {
|
|
310
|
-
if (!isRecord(input) || !Array.isArray(input.capabilities)) {
|
|
311
|
-
return {
|
|
312
|
-
ok: false,
|
|
313
|
-
issues: [
|
|
314
|
-
{
|
|
315
|
-
path: "$.capabilities",
|
|
316
|
-
message: "Projection Declaration must select Projection Capabilities",
|
|
317
|
-
},
|
|
318
|
-
],
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
const issues = [];
|
|
322
|
-
const capabilities = [];
|
|
323
|
-
input.capabilities.forEach((capability, index) => {
|
|
324
|
-
const pathPrefix = `$.capabilities[${index}]`;
|
|
325
|
-
if (!isRecord(capability)) {
|
|
326
|
-
issues.push({
|
|
327
|
-
path: pathPrefix,
|
|
328
|
-
message: "Projection Capability must be an object",
|
|
329
|
-
});
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (typeof capability.kind !== "string") {
|
|
333
|
-
issues.push({
|
|
334
|
-
path: `${pathPrefix}.kind`,
|
|
335
|
-
message: "Projection Capability kind is required",
|
|
336
|
-
});
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
if (!isProjectionCapabilityKind(capability.kind)) {
|
|
340
|
-
issues.push({
|
|
341
|
-
path: `${pathPrefix}.kind`,
|
|
342
|
-
message: `Unknown Projection Capability kind: ${capability.kind}`,
|
|
343
|
-
});
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const kind = capability.kind;
|
|
347
|
-
issues.push(...unknownCapabilityPropertyIssues(kind, capability, pathPrefix));
|
|
348
|
-
if (kind === "workspace-library-package") {
|
|
349
|
-
const workspaceCapability = parseWorkspaceLibraryPackageCapability(capability, pathPrefix);
|
|
350
|
-
if (Array.isArray(workspaceCapability)) {
|
|
351
|
-
issues.push(...workspaceCapability);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
capabilities.push(workspaceCapability);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (kind === "workspace-node-packages") {
|
|
358
|
-
const workspaceCapability = parseWorkspaceNodePackagesCapability(capability, pathPrefix);
|
|
359
|
-
if (Array.isArray(workspaceCapability)) {
|
|
360
|
-
issues.push(...workspaceCapability);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
capabilities.push(workspaceCapability);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (kind === "rust-binary-workspace") {
|
|
367
|
-
const workspaceCapability = parseRustBinaryWorkspaceCapability(capability, pathPrefix);
|
|
368
|
-
if (Array.isArray(workspaceCapability)) {
|
|
369
|
-
issues.push(...workspaceCapability);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
capabilities.push(workspaceCapability);
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
capabilities.push({ kind });
|
|
376
|
-
});
|
|
377
|
-
if (issues.length === 0) {
|
|
378
|
-
issues.push(...duplicateCapabilityIssues(capabilities));
|
|
379
|
-
issues.push(...capabilityCompositionIssues(capabilities));
|
|
380
|
-
}
|
|
381
|
-
if (issues.length > 0) {
|
|
382
|
-
return { ok: false, issues };
|
|
383
|
-
}
|
|
384
|
-
return {
|
|
385
|
-
ok: true,
|
|
386
|
-
value: {
|
|
387
|
-
capabilities,
|
|
388
|
-
},
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
export function projectionCapabilityIssues(presets) {
|
|
392
|
-
return presets.flatMap((preset, presetIndex) => {
|
|
393
|
-
if (preset.projection === undefined) {
|
|
394
|
-
return [];
|
|
395
|
-
}
|
|
396
|
-
const result = validateProjectionCapabilities(preset.projection);
|
|
397
|
-
return result.ok
|
|
398
|
-
? []
|
|
399
|
-
: result.issues.map((issue) => ({
|
|
400
|
-
path: `$.presets[${presetIndex}].projection${issue.path.slice(1)}`,
|
|
401
|
-
message: issue.message,
|
|
402
|
-
}));
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
export function normalizePresetProjectionDeclaration(declaration) {
|
|
406
|
-
const result = validateProjectionCapabilities(declaration);
|
|
407
|
-
if (!result.ok) {
|
|
408
|
-
throw new Error(`Projection Declaration is invalid:\n${result.issues
|
|
409
|
-
.map((issue) => ` - ${issue.path}: ${issue.message}`)
|
|
410
|
-
.join("\n")}`);
|
|
411
|
-
}
|
|
412
|
-
return result.value;
|
|
413
|
-
}
|
|
414
280
|
export function interpretPresetProjectionDeclaration(options) {
|
|
415
281
|
const validation = validateProjectionCapabilities(options.declaration);
|
|
416
282
|
if (!validation.ok) {
|
|
@@ -559,354 +425,37 @@ function createProjectionCompositionState(sourceRoots) {
|
|
|
559
425
|
flags: {},
|
|
560
426
|
};
|
|
561
427
|
}
|
|
562
|
-
function
|
|
563
|
-
return
|
|
564
|
-
}
|
|
565
|
-
function isProjectionCapabilityKind(value) {
|
|
566
|
-
return projectionCapabilityKindSet.has(value);
|
|
567
|
-
}
|
|
568
|
-
function isWorkspaceNodePackageKind(value) {
|
|
569
|
-
return typeof value === "string" && workspaceNodePackageKindSet.has(value);
|
|
570
|
-
}
|
|
571
|
-
function isWorkspaceNodePackagePath(value) {
|
|
572
|
-
return typeof value === "string" && workspaceNodePackagePathSet.has(value);
|
|
573
|
-
}
|
|
574
|
-
function isNonEmptyString(value) {
|
|
575
|
-
return typeof value === "string" && value.length > 0;
|
|
428
|
+
function uniqueValues(values) {
|
|
429
|
+
return [...new Set(values)];
|
|
576
430
|
}
|
|
577
|
-
function
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
return undefined;
|
|
582
|
-
}
|
|
583
|
-
strings.push(entry);
|
|
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}`);
|
|
584
435
|
}
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const allowedKeys = new Set(exactCapabilityKeys[kind]);
|
|
589
|
-
return Object.keys(capability)
|
|
590
|
-
.filter((key) => !allowedKeys.has(key))
|
|
591
|
-
.map((key) => ({
|
|
592
|
-
path: `${pathPrefix}.${key}`,
|
|
593
|
-
message: `Projection Capability ${kind} does not support property: ${key}`,
|
|
594
|
-
}));
|
|
436
|
+
const sourceRootKey = developmentContainerResourceSourceRootKey(resourceId);
|
|
437
|
+
state.devcontainerResource = { id: resourceId, root, sourceRootKey };
|
|
438
|
+
state.sourceRoots[sourceRootKey] = root;
|
|
595
439
|
}
|
|
596
|
-
function
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
issues.push({
|
|
601
|
-
path: `${pathPrefix}.workspacePackageGlob`,
|
|
602
|
-
message: "workspace-library-package currently supports workspacePackageGlob: packages/*",
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
if (capability.packageRole !== "shared-library") {
|
|
606
|
-
issues.push({
|
|
607
|
-
path: `${pathPrefix}.packageRole`,
|
|
608
|
-
message: "workspace-library-package currently supports packageRole: shared-library",
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
if (capability.packageSourcePreset !== "ts-lib") {
|
|
612
|
-
issues.push({
|
|
613
|
-
path: `${pathPrefix}.packageSourcePreset`,
|
|
614
|
-
message: "workspace-library-package currently supports packageSourcePreset: ts-lib",
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
if (!Array.isArray(capability.sourceFiles)) {
|
|
618
|
-
issues.push({
|
|
619
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
620
|
-
message: "workspace-library-package sourceFiles must be a non-empty array",
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
|
|
625
|
-
if (parsedSourceFiles === undefined) {
|
|
626
|
-
issues.push({
|
|
627
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
628
|
-
message: "workspace-library-package sourceFiles must be a non-empty array of paths",
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
else {
|
|
632
|
-
sourceFiles = parsedSourceFiles;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (issues.length > 0) {
|
|
636
|
-
return issues;
|
|
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}`);
|
|
637
444
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
packageSourcePreset: "ts-lib",
|
|
643
|
-
sourceFiles,
|
|
445
|
+
state.editorCustomizationResource = {
|
|
446
|
+
id: resourceId,
|
|
447
|
+
root,
|
|
448
|
+
declarations: loadEditorCustomizationDeclarations(root),
|
|
644
449
|
};
|
|
645
450
|
}
|
|
646
|
-
function
|
|
647
|
-
|
|
648
|
-
if (capability.workspacePackageGlob !== "apps/*") {
|
|
649
|
-
issues.push({
|
|
650
|
-
path: `${pathPrefix}.workspacePackageGlob`,
|
|
651
|
-
message: "workspace-node-packages currently supports workspacePackageGlob: apps/*",
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
if (!Array.isArray(capability.packages) || capability.packages.length === 0) {
|
|
655
|
-
issues.push({
|
|
656
|
-
path: `${pathPrefix}.packages`,
|
|
657
|
-
message: "workspace-node-packages packages must be a non-empty array",
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
const packages = Array.isArray(capability.packages)
|
|
661
|
-
? capability.packages.flatMap((nodePackage, packageIndex) => {
|
|
662
|
-
const packagePath = `${pathPrefix}.packages[${packageIndex}]`;
|
|
663
|
-
const parsed = parseWorkspaceNodePackage(nodePackage, packagePath);
|
|
664
|
-
if (Array.isArray(parsed)) {
|
|
665
|
-
issues.push(...parsed);
|
|
666
|
-
return [];
|
|
667
|
-
}
|
|
668
|
-
return [parsed];
|
|
669
|
-
})
|
|
670
|
-
: [];
|
|
671
|
-
const packageLinks = capability.packageLinks === undefined
|
|
672
|
-
? undefined
|
|
673
|
-
: parseWorkspaceNodePackageLinks(capability.packageLinks, `${pathPrefix}.packageLinks`, new Set(packages.map((nodePackage) => nodePackage.path)), issues);
|
|
674
|
-
if (issues.length > 0) {
|
|
675
|
-
return issues;
|
|
676
|
-
}
|
|
677
|
-
return {
|
|
678
|
-
kind: "workspace-node-packages",
|
|
679
|
-
workspacePackageGlob: "apps/*",
|
|
680
|
-
packages,
|
|
681
|
-
...(packageLinks === undefined ? {} : { packageLinks }),
|
|
682
|
-
};
|
|
451
|
+
function developmentContainerResourceSourceRootKey(resourceId) {
|
|
452
|
+
return `devcontainer:${resourceId}`;
|
|
683
453
|
}
|
|
684
|
-
function
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return [
|
|
688
|
-
{
|
|
689
|
-
path: pathPrefix,
|
|
690
|
-
message: "workspace-node-packages package must be an object",
|
|
691
|
-
},
|
|
692
|
-
];
|
|
693
|
-
}
|
|
694
|
-
const kind = isWorkspaceNodePackageKind(value.kind) ? value.kind : undefined;
|
|
695
|
-
const packagePath = isWorkspaceNodePackagePath(value.path)
|
|
696
|
-
? value.path
|
|
697
|
-
: undefined;
|
|
698
|
-
let sourceFiles = [];
|
|
699
|
-
if (kind === undefined) {
|
|
700
|
-
issues.push({
|
|
701
|
-
path: `${pathPrefix}.kind`,
|
|
702
|
-
message: "workspace-node-packages package kind must be hono-api or vue-app",
|
|
703
|
-
});
|
|
454
|
+
function editorCustomizationDeclarationsForState(state) {
|
|
455
|
+
if (state.editorCustomizationResource === undefined) {
|
|
456
|
+
throw new Error("Projection Capability composition did not provide Editor Customization Shared Resource");
|
|
704
457
|
}
|
|
705
|
-
|
|
706
|
-
issues.push({
|
|
707
|
-
path: `${pathPrefix}.path`,
|
|
708
|
-
message: "workspace-node-packages package path must be apps/api or apps/web",
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
if (!Array.isArray(value.sourceFiles)) {
|
|
712
|
-
issues.push({
|
|
713
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
714
|
-
message: "workspace-node-packages package sourceFiles must be a non-empty array",
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
const parsedSourceFiles = nonEmptyStringArray(value.sourceFiles);
|
|
719
|
-
if (parsedSourceFiles === undefined) {
|
|
720
|
-
issues.push({
|
|
721
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
722
|
-
message: "workspace-node-packages package sourceFiles must be a non-empty array of paths",
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
else {
|
|
726
|
-
sourceFiles = parsedSourceFiles;
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
const allowedKeys = new Set(["kind", "path", "sourceFiles"]);
|
|
730
|
-
for (const key of Object.keys(value)) {
|
|
731
|
-
if (!allowedKeys.has(key)) {
|
|
732
|
-
issues.push({
|
|
733
|
-
path: `${pathPrefix}.${key}`,
|
|
734
|
-
message: `workspace-node-packages package does not support property: ${key}`,
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (issues.length > 0) {
|
|
739
|
-
return issues;
|
|
740
|
-
}
|
|
741
|
-
if (kind === undefined || packagePath === undefined) {
|
|
742
|
-
throw new Error(`workspace-node-packages package validation failed without diagnostics at ${pathPrefix}`);
|
|
743
|
-
}
|
|
744
|
-
return {
|
|
745
|
-
kind,
|
|
746
|
-
path: packagePath,
|
|
747
|
-
sourceFiles,
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
function parseWorkspaceNodePackageLinks(value, pathPrefix, declaredPackagePaths, issues) {
|
|
751
|
-
if (!Array.isArray(value)) {
|
|
752
|
-
issues.push({
|
|
753
|
-
path: pathPrefix,
|
|
754
|
-
message: "workspace-node-packages packageLinks must be an array",
|
|
755
|
-
});
|
|
756
|
-
return undefined;
|
|
757
|
-
}
|
|
758
|
-
return value.flatMap((link, linkIndex) => {
|
|
759
|
-
const linkPath = `${pathPrefix}[${linkIndex}]`;
|
|
760
|
-
const linkIssues = [];
|
|
761
|
-
if (!isRecord(link)) {
|
|
762
|
-
issues.push({
|
|
763
|
-
path: linkPath,
|
|
764
|
-
message: "workspace-node-packages packageLink must be an object",
|
|
765
|
-
});
|
|
766
|
-
return [];
|
|
767
|
-
}
|
|
768
|
-
const allowedKeys = new Set(["consumerPackagePath", "providerPackagePath"]);
|
|
769
|
-
for (const key of Object.keys(link)) {
|
|
770
|
-
if (!allowedKeys.has(key)) {
|
|
771
|
-
linkIssues.push({
|
|
772
|
-
path: `${linkPath}.${key}`,
|
|
773
|
-
message: `workspace-node-packages packageLink does not support property: ${key}`,
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (link.consumerPackagePath !== "apps/web" ||
|
|
778
|
-
link.providerPackagePath !== "apps/api") {
|
|
779
|
-
linkIssues.push({
|
|
780
|
-
path: linkPath,
|
|
781
|
-
message: "workspace-node-packages currently supports links from apps/web to apps/api",
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
else {
|
|
785
|
-
if (!declaredPackagePaths.has(link.consumerPackagePath)) {
|
|
786
|
-
linkIssues.push({
|
|
787
|
-
path: `${linkPath}.consumerPackagePath`,
|
|
788
|
-
message: "workspace-node-packages packageLink consumerPackagePath must reference a package declared in the same packages array: apps/web",
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
if (!declaredPackagePaths.has(link.providerPackagePath)) {
|
|
792
|
-
linkIssues.push({
|
|
793
|
-
path: `${linkPath}.providerPackagePath`,
|
|
794
|
-
message: "workspace-node-packages packageLink providerPackagePath must reference a package declared in the same packages array: apps/api",
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
if (linkIssues.length > 0) {
|
|
799
|
-
issues.push(...linkIssues);
|
|
800
|
-
return [];
|
|
801
|
-
}
|
|
802
|
-
return [
|
|
803
|
-
{
|
|
804
|
-
consumerPackagePath: "apps/web",
|
|
805
|
-
providerPackagePath: "apps/api",
|
|
806
|
-
},
|
|
807
|
-
];
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
function parseRustBinaryWorkspaceCapability(capability, pathPrefix) {
|
|
811
|
-
const issues = [];
|
|
812
|
-
let sourceFiles = [];
|
|
813
|
-
if (capability.workspacePackageGlob !== "packages/*") {
|
|
814
|
-
issues.push({
|
|
815
|
-
path: `${pathPrefix}.workspacePackageGlob`,
|
|
816
|
-
message: "rust-binary-workspace currently supports workspacePackageGlob: packages/*",
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
if (!Array.isArray(capability.sourceFiles)) {
|
|
820
|
-
issues.push({
|
|
821
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
822
|
-
message: "rust-binary-workspace sourceFiles must be a non-empty array",
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
|
|
827
|
-
if (parsedSourceFiles === undefined) {
|
|
828
|
-
issues.push({
|
|
829
|
-
path: `${pathPrefix}.sourceFiles`,
|
|
830
|
-
message: "rust-binary-workspace sourceFiles must be a non-empty array of paths",
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
else {
|
|
834
|
-
sourceFiles = parsedSourceFiles;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
if (issues.length > 0) {
|
|
838
|
-
return issues;
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
kind: "rust-binary-workspace",
|
|
842
|
-
workspacePackageGlob: "packages/*",
|
|
843
|
-
sourceFiles,
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
function duplicateCapabilityIssues(capabilities) {
|
|
847
|
-
const firstSeen = new Map();
|
|
848
|
-
const issues = [];
|
|
849
|
-
capabilities.forEach((capability, index) => {
|
|
850
|
-
const firstIndex = firstSeen.get(capability.kind);
|
|
851
|
-
if (firstIndex === undefined) {
|
|
852
|
-
firstSeen.set(capability.kind, index);
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
issues.push({
|
|
856
|
-
path: `$.capabilities[${index}].kind`,
|
|
857
|
-
message: `Duplicate Projection Capability kind: ${capability.kind}`,
|
|
858
|
-
});
|
|
859
|
-
});
|
|
860
|
-
return issues;
|
|
861
|
-
}
|
|
862
|
-
function capabilityCompositionIssues(capabilities) {
|
|
863
|
-
const kinds = new Set(capabilities.map((capability) => capability.kind));
|
|
864
|
-
const issues = [];
|
|
865
|
-
const companionKinds = uniqueValues(capabilities
|
|
866
|
-
.map((capability) => capability.kind)
|
|
867
|
-
.filter((kind) => kind !== "rust-binary-workspace"));
|
|
868
|
-
if (kinds.has("rust-binary-workspace") && companionKinds.length > 0) {
|
|
869
|
-
issues.push({
|
|
870
|
-
path: "$.capabilities",
|
|
871
|
-
message: "rust-binary-workspace is a complete domain capability and must be selected by itself; remove companion Projection Capabilities: " +
|
|
872
|
-
companionKinds.join(", "),
|
|
873
|
-
});
|
|
874
|
-
return issues;
|
|
875
|
-
}
|
|
876
|
-
if (!kinds.has("workspace-library-package") &&
|
|
877
|
-
!kinds.has("workspace-node-packages") &&
|
|
878
|
-
!kinds.has("rust-binary-workspace")) {
|
|
879
|
-
issues.push({
|
|
880
|
-
path: "$.capabilities",
|
|
881
|
-
message: "Projection Capability composition must include a workspace package layout capability",
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
if (kinds.has("strict-typescript-root") &&
|
|
885
|
-
!kinds.has("workspace-library-package") &&
|
|
886
|
-
!kinds.has("workspace-node-packages")) {
|
|
887
|
-
issues.push({
|
|
888
|
-
path: "$.capabilities",
|
|
889
|
-
message: "strict-typescript-root requires a workspace package layout so package typecheck tasks have a workspace target",
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
if (kinds.has("node-pnpm-devcontainer") && !kinds.has("oxc-format-lint")) {
|
|
893
|
-
issues.push({
|
|
894
|
-
path: "$.capabilities",
|
|
895
|
-
message: "node-pnpm-devcontainer requires oxc-format-lint so editor customization is derived from declared tooling",
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
for (const requirement of requiredPlanCapabilityProviders) {
|
|
899
|
-
if (!kinds.has(requirement.kind) && !kinds.has("rust-binary-workspace")) {
|
|
900
|
-
issues.push({
|
|
901
|
-
path: "$.capabilities",
|
|
902
|
-
message: `Projection Capability composition must include ${requirement.kind} to provide ${requirement.label}`,
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
return issues;
|
|
907
|
-
}
|
|
908
|
-
function uniqueValues(values) {
|
|
909
|
-
return [...new Set(values)];
|
|
458
|
+
return state.editorCustomizationResource.declarations;
|
|
910
459
|
}
|
|
911
460
|
function projectNameFromDir(targetDir) {
|
|
912
461
|
return path.basename(path.resolve(targetDir));
|
|
@@ -1242,7 +791,10 @@ function workspaceGlobBoundary(workspacePackageGlob) {
|
|
|
1242
791
|
};
|
|
1243
792
|
}
|
|
1244
793
|
function hasVuePackage(capability) {
|
|
1245
|
-
return capability
|
|
794
|
+
return findVuePackage(capability) !== undefined;
|
|
795
|
+
}
|
|
796
|
+
function findVuePackage(capability) {
|
|
797
|
+
return capability.packages.find((nodePackage) => nodePackage.kind === "vue-app");
|
|
1246
798
|
}
|
|
1247
799
|
function sourceRootPreset(capability) {
|
|
1248
800
|
if (capability.packages.length === 1) {
|
|
@@ -1629,7 +1181,8 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
|
|
|
1629
1181
|
const rootManifest = rustRootPackageJson(context, packageScripts, state);
|
|
1630
1182
|
const packageManifest = rustWorkspacePackageJson(context, workspacePackageScripts);
|
|
1631
1183
|
const rustLayer = rustToolLayer();
|
|
1632
|
-
const
|
|
1184
|
+
const dockerfileFragments = readDevelopmentContainerDockerfileFragments(state, ["rust"]);
|
|
1185
|
+
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
|
|
1633
1186
|
const developmentContainer = dockerfileFirstRustPnpmDevcontainer({
|
|
1634
1187
|
name: context.projectName.value,
|
|
1635
1188
|
nodeLayer: nodePnpmToolLayer({
|
|
@@ -1637,6 +1190,7 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
|
|
|
1637
1190
|
packageManagerPin: context.toolchain.packageManagerPin.value,
|
|
1638
1191
|
}),
|
|
1639
1192
|
rustLayer,
|
|
1193
|
+
dockerfileFragments,
|
|
1640
1194
|
extensions: editorCustomization.extensions,
|
|
1641
1195
|
settings: editorCustomization.settings,
|
|
1642
1196
|
});
|
|
@@ -2168,7 +1722,7 @@ function rootTsconfigReferences(workspace) {
|
|
|
2168
1722
|
});
|
|
2169
1723
|
}
|
|
2170
1724
|
function oxcFormatLintOperations({ state, }) {
|
|
2171
|
-
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
|
|
1725
|
+
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
|
|
2172
1726
|
return [
|
|
2173
1727
|
{
|
|
2174
1728
|
kind: "copyFile",
|
|
@@ -2200,16 +1754,19 @@ function oxcFormatLintOperations({ state, }) {
|
|
|
2200
1754
|
];
|
|
2201
1755
|
}
|
|
2202
1756
|
function nodePnpmDevcontainerOperations({ context, state, }) {
|
|
2203
|
-
const
|
|
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));
|
|
2204
1762
|
const developmentContainer = checkedDockerfileFirstNodePnpmDevcontainer({
|
|
2205
1763
|
name: context.projectName.value,
|
|
2206
1764
|
layer: nodePnpmToolLayer({
|
|
2207
1765
|
nodeVersion: context.toolchain.nodeLtsMajor.value,
|
|
2208
1766
|
packageManagerPin: context.toolchain.packageManagerPin.value,
|
|
2209
1767
|
}),
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
: [],
|
|
1768
|
+
dockerfileFragments,
|
|
1769
|
+
additionalLayers,
|
|
2213
1770
|
extensions: editorCustomization.extensions,
|
|
2214
1771
|
settings: editorCustomization.settings,
|
|
2215
1772
|
});
|
|
@@ -2224,6 +1781,51 @@ function nodePnpmDevcontainerOperations({ context, state, }) {
|
|
|
2224
1781
|
},
|
|
2225
1782
|
];
|
|
2226
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
|
+
}
|
|
2227
1829
|
function githubMaintenanceOperations() {
|
|
2228
1830
|
return [
|
|
2229
1831
|
{
|