@ykdz/template 0.0.10 → 0.0.12
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 +10 -12
- package/node_modules/@ykdz/template-builtin-source/package.json +3 -2
- package/node_modules/@ykdz/template-builtin-source/templates/hono-api/vitest.config.ts +1 -0
- package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +35 -9
- package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/api/vitest.config.ts +7 -0
- package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/playwright.config.ts +23 -7
- 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 +5 -22
- 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 +103 -515
- 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),
|
|
@@ -159,20 +130,6 @@ const capabilityInterpreters = {
|
|
|
159
130
|
owner: strictTypescriptRootBoundary,
|
|
160
131
|
});
|
|
161
132
|
const workspaceBoundary = workspaceBoundaryForState(state);
|
|
162
|
-
state.rootCheckComponents.push({
|
|
163
|
-
kind: "turbo-package-typecheck",
|
|
164
|
-
owner: workspaceBoundary,
|
|
165
|
-
});
|
|
166
|
-
if (state.nodeWorkspace !== undefined) {
|
|
167
|
-
state.rootCheckComponents.push({
|
|
168
|
-
kind: "turbo-package-build",
|
|
169
|
-
owner: workspaceBoundary,
|
|
170
|
-
});
|
|
171
|
-
state.rootCheckComponents.push({
|
|
172
|
-
kind: "turbo-package-test",
|
|
173
|
-
owner: workspaceBoundary,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
133
|
state.rootCheckComponents.push({
|
|
177
134
|
kind: "turbo-package-check",
|
|
178
135
|
owner: workspaceBoundary,
|
|
@@ -193,8 +150,9 @@ const capabilityInterpreters = {
|
|
|
193
150
|
},
|
|
194
151
|
"oxc-format-lint": {
|
|
195
152
|
kind: "oxc-format-lint",
|
|
196
|
-
contribute({ state }) {
|
|
153
|
+
contribute({ capability, state }) {
|
|
197
154
|
state.sourceRoots.sharedOxc = state.projectionSourceRoots.sharedOxc();
|
|
155
|
+
setEditorCustomizationResource(state, capability.editorCustomizationResourceId, "oxc-format-lint");
|
|
198
156
|
state.rootCheckComponents.unshift({
|
|
199
157
|
kind: "oxc-format-check",
|
|
200
158
|
owner: strictTypescriptRootBoundary,
|
|
@@ -215,13 +173,13 @@ const capabilityInterpreters = {
|
|
|
215
173
|
});
|
|
216
174
|
state.packageFixComponents.push({ kind: "oxc-format-write", owner: workspacePackageBoundary }, { kind: "oxc-lint-fix", owner: workspacePackageBoundary });
|
|
217
175
|
state.rootScriptFragments["format:check"] =
|
|
218
|
-
"oxfmt --check
|
|
176
|
+
"oxfmt --check oxlint.config.ts oxfmt.config.ts";
|
|
219
177
|
state.rootScriptFragments["format:write"] =
|
|
220
|
-
"oxfmt --write
|
|
178
|
+
"oxfmt --write oxlint.config.ts oxfmt.config.ts";
|
|
221
179
|
state.rootScriptFragments.lint =
|
|
222
|
-
"oxlint
|
|
180
|
+
"oxlint oxlint.config.ts oxfmt.config.ts";
|
|
223
181
|
state.rootScriptFragments["lint:fix"] =
|
|
224
|
-
"oxlint
|
|
182
|
+
"oxlint oxlint.config.ts oxfmt.config.ts --fix";
|
|
225
183
|
state.packageScriptFragments["format:check"] =
|
|
226
184
|
"oxfmt --check --config ../../oxfmt.config.ts .";
|
|
227
185
|
state.packageScriptFragments["format:write"] =
|
|
@@ -243,9 +201,8 @@ const capabilityInterpreters = {
|
|
|
243
201
|
},
|
|
244
202
|
"node-pnpm-devcontainer": {
|
|
245
203
|
kind: "node-pnpm-devcontainer",
|
|
246
|
-
contribute({ state }) {
|
|
247
|
-
state.
|
|
248
|
-
state.projectionSourceRoots.sharedDevcontainer();
|
|
204
|
+
contribute({ capability, state }) {
|
|
205
|
+
setDevelopmentContainerResource(state, capability.devcontainerResourceId, "node-pnpm-devcontainer");
|
|
249
206
|
state.flags.devcontainer = true;
|
|
250
207
|
state.operationFactories.push(nodePnpmDevcontainerOperations);
|
|
251
208
|
},
|
|
@@ -306,111 +263,6 @@ function contributeProjectionCapability(capability, state) {
|
|
|
306
263
|
return;
|
|
307
264
|
}
|
|
308
265
|
}
|
|
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
266
|
export function interpretPresetProjectionDeclaration(options) {
|
|
415
267
|
const validation = validateProjectionCapabilities(options.declaration);
|
|
416
268
|
if (!validation.ok) {
|
|
@@ -559,354 +411,37 @@ function createProjectionCompositionState(sourceRoots) {
|
|
|
559
411
|
flags: {},
|
|
560
412
|
};
|
|
561
413
|
}
|
|
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;
|
|
414
|
+
function uniqueValues(values) {
|
|
415
|
+
return [...new Set(values)];
|
|
576
416
|
}
|
|
577
|
-
function
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
return undefined;
|
|
582
|
-
}
|
|
583
|
-
strings.push(entry);
|
|
417
|
+
function setDevelopmentContainerResource(state, resourceId, capabilityKind) {
|
|
418
|
+
const root = state.projectionSourceRoots.sharedResource(resourceId);
|
|
419
|
+
if (root === undefined) {
|
|
420
|
+
throw new Error(`Projection Capability ${capabilityKind} references unresolved Development Container Shared Resource: ${resourceId}`);
|
|
584
421
|
}
|
|
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
|
-
}));
|
|
422
|
+
const sourceRootKey = developmentContainerResourceSourceRootKey(resourceId);
|
|
423
|
+
state.devcontainerResource = { id: resourceId, root, sourceRootKey };
|
|
424
|
+
state.sourceRoots[sourceRootKey] = root;
|
|
595
425
|
}
|
|
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
|
-
});
|
|
426
|
+
function setEditorCustomizationResource(state, resourceId, capabilityKind) {
|
|
427
|
+
const root = state.projectionSourceRoots.sharedResource(resourceId);
|
|
428
|
+
if (root === undefined) {
|
|
429
|
+
throw new Error(`Projection Capability ${capabilityKind} references unresolved Editor Customization Shared Resource: ${resourceId}`);
|
|
616
430
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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;
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
kind: "workspace-library-package",
|
|
640
|
-
workspacePackageGlob: "packages/*",
|
|
641
|
-
packageRole: "shared-library",
|
|
642
|
-
packageSourcePreset: "ts-lib",
|
|
643
|
-
sourceFiles,
|
|
431
|
+
state.editorCustomizationResource = {
|
|
432
|
+
id: resourceId,
|
|
433
|
+
root,
|
|
434
|
+
declarations: loadEditorCustomizationDeclarations(root),
|
|
644
435
|
};
|
|
645
436
|
}
|
|
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
|
-
};
|
|
437
|
+
function developmentContainerResourceSourceRootKey(resourceId) {
|
|
438
|
+
return `devcontainer:${resourceId}`;
|
|
683
439
|
}
|
|
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
|
-
});
|
|
704
|
-
}
|
|
705
|
-
if (packagePath === undefined) {
|
|
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;
|
|
440
|
+
function editorCustomizationDeclarationsForState(state) {
|
|
441
|
+
if (state.editorCustomizationResource === undefined) {
|
|
442
|
+
throw new Error("Projection Capability composition did not provide Editor Customization Shared Resource");
|
|
839
443
|
}
|
|
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)];
|
|
444
|
+
return state.editorCustomizationResource.declarations;
|
|
910
445
|
}
|
|
911
446
|
function projectNameFromDir(targetDir) {
|
|
912
447
|
return path.basename(path.resolve(targetDir));
|
|
@@ -1242,7 +777,10 @@ function workspaceGlobBoundary(workspacePackageGlob) {
|
|
|
1242
777
|
};
|
|
1243
778
|
}
|
|
1244
779
|
function hasVuePackage(capability) {
|
|
1245
|
-
return capability
|
|
780
|
+
return findVuePackage(capability) !== undefined;
|
|
781
|
+
}
|
|
782
|
+
function findVuePackage(capability) {
|
|
783
|
+
return capability.packages.find((nodePackage) => nodePackage.kind === "vue-app");
|
|
1246
784
|
}
|
|
1247
785
|
function sourceRootPreset(capability) {
|
|
1248
786
|
if (capability.packages.length === 1) {
|
|
@@ -1629,7 +1167,8 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
|
|
|
1629
1167
|
const rootManifest = rustRootPackageJson(context, packageScripts, state);
|
|
1630
1168
|
const packageManifest = rustWorkspacePackageJson(context, workspacePackageScripts);
|
|
1631
1169
|
const rustLayer = rustToolLayer();
|
|
1632
|
-
const
|
|
1170
|
+
const dockerfileFragments = readDevelopmentContainerDockerfileFragments(state, ["rust"]);
|
|
1171
|
+
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
|
|
1633
1172
|
const developmentContainer = dockerfileFirstRustPnpmDevcontainer({
|
|
1634
1173
|
name: context.projectName.value,
|
|
1635
1174
|
nodeLayer: nodePnpmToolLayer({
|
|
@@ -1637,6 +1176,7 @@ function rustBinaryWorkspaceOperations({ context, state, packageScripts, package
|
|
|
1637
1176
|
packageManagerPin: context.toolchain.packageManagerPin.value,
|
|
1638
1177
|
}),
|
|
1639
1178
|
rustLayer,
|
|
1179
|
+
dockerfileFragments,
|
|
1640
1180
|
extensions: editorCustomization.extensions,
|
|
1641
1181
|
settings: editorCustomization.settings,
|
|
1642
1182
|
});
|
|
@@ -2168,7 +1708,7 @@ function rootTsconfigReferences(workspace) {
|
|
|
2168
1708
|
});
|
|
2169
1709
|
}
|
|
2170
1710
|
function oxcFormatLintOperations({ state, }) {
|
|
2171
|
-
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
|
|
1711
|
+
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
|
|
2172
1712
|
return [
|
|
2173
1713
|
{
|
|
2174
1714
|
kind: "copyFile",
|
|
@@ -2200,16 +1740,19 @@ function oxcFormatLintOperations({ state, }) {
|
|
|
2200
1740
|
];
|
|
2201
1741
|
}
|
|
2202
1742
|
function nodePnpmDevcontainerOperations({ context, state, }) {
|
|
2203
|
-
const
|
|
1743
|
+
const additionalLayers = state.nodeWorkspace && hasVuePackage(state.nodeWorkspace)
|
|
1744
|
+
? [browserTestToolLayer()]
|
|
1745
|
+
: [];
|
|
1746
|
+
const dockerfileFragments = readDevelopmentContainerDockerfileFragments(state, additionalLayers.length === 0 ? [] : ["browserTest"]);
|
|
1747
|
+
const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities, editorCustomizationDeclarationsForState(state));
|
|
2204
1748
|
const developmentContainer = checkedDockerfileFirstNodePnpmDevcontainer({
|
|
2205
1749
|
name: context.projectName.value,
|
|
2206
1750
|
layer: nodePnpmToolLayer({
|
|
2207
1751
|
nodeVersion: context.toolchain.nodeLtsMajor.value,
|
|
2208
1752
|
packageManagerPin: context.toolchain.packageManagerPin.value,
|
|
2209
1753
|
}),
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
: [],
|
|
1754
|
+
dockerfileFragments,
|
|
1755
|
+
additionalLayers,
|
|
2213
1756
|
extensions: editorCustomization.extensions,
|
|
2214
1757
|
settings: editorCustomization.settings,
|
|
2215
1758
|
});
|
|
@@ -2224,6 +1767,51 @@ function nodePnpmDevcontainerOperations({ context, state, }) {
|
|
|
2224
1767
|
},
|
|
2225
1768
|
];
|
|
2226
1769
|
}
|
|
1770
|
+
function readDevelopmentContainerDockerfileFragments(state, optionalFragments) {
|
|
1771
|
+
if (state.devcontainerResource === undefined) {
|
|
1772
|
+
throw new Error("Projection Capability composition did not provide a Development Container Shared Resource");
|
|
1773
|
+
}
|
|
1774
|
+
const fragmentNames = {
|
|
1775
|
+
nodePnpm: "node-pnpm.Dockerfile",
|
|
1776
|
+
browserTest: "browser-test.Dockerfile",
|
|
1777
|
+
rust: "rust.Dockerfile",
|
|
1778
|
+
};
|
|
1779
|
+
const selected = new Set(optionalFragments);
|
|
1780
|
+
return {
|
|
1781
|
+
sourceRoot: state.devcontainerResource.sourceRootKey,
|
|
1782
|
+
nodePnpm: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.nodePnpm),
|
|
1783
|
+
...(selected.has("browserTest")
|
|
1784
|
+
? {
|
|
1785
|
+
browserTest: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.browserTest),
|
|
1786
|
+
}
|
|
1787
|
+
: {}),
|
|
1788
|
+
...(selected.has("rust")
|
|
1789
|
+
? {
|
|
1790
|
+
rust: readDevelopmentContainerDockerfileFragment(state.devcontainerResource, fragmentNames.rust),
|
|
1791
|
+
}
|
|
1792
|
+
: {}),
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
function readDevelopmentContainerDockerfileFragment(resource, fragmentName) {
|
|
1796
|
+
const filePath = path.join(resource.root, fragmentName);
|
|
1797
|
+
try {
|
|
1798
|
+
return {
|
|
1799
|
+
from: fragmentName,
|
|
1800
|
+
text: readFileSync(filePath, "utf8"),
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
catch (error) {
|
|
1804
|
+
if (isMissingDevelopmentContainerFragmentError(error)) {
|
|
1805
|
+
throw new Error(`Development Container Shared Resource ${resource.id} is missing Dockerfile fragment: ${fragmentName}`, { cause: error });
|
|
1806
|
+
}
|
|
1807
|
+
throw error;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
function isMissingDevelopmentContainerFragmentError(error) {
|
|
1811
|
+
return (error instanceof Error &&
|
|
1812
|
+
"code" in error &&
|
|
1813
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
1814
|
+
}
|
|
2227
1815
|
function githubMaintenanceOperations() {
|
|
2228
1816
|
return [
|
|
2229
1817
|
{
|