@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.
- 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 +9 -4
- 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 +7 -7
- package/node_modules/@ykdz/template-builtin-source/package.json +4 -3
- package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +39 -11
- package/node_modules/@ykdz/template-builtin-source/templates/vue-app/scripts/run-playwright.ts +25 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vue-app/src/App.vue +18 -4
- package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/scripts/run-playwright.ts +25 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/src/App.vue +18 -4
- 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 +5 -0
- package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +42 -24
- 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 +4 -3
- 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/pnpm-workspace.yaml +3 -5
- 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 +108 -507
- package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +1 -1
- package/node_modules/@ykdz/template-core/package.json +4 -3
- 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),
|
|
@@ -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.
|
|
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
|
|
569
|
-
return
|
|
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
|
|
653
|
-
const
|
|
654
|
-
if (
|
|
655
|
-
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
693
|
-
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
757
|
-
|
|
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
|
|
817
|
-
|
|
818
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
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
|
-
|
|
2212
|
-
|
|
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
|
{
|