@ykdz/template 0.0.5 → 0.0.6

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.
@@ -0,0 +1,2207 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { collectGeneratedManifestCatalogDependencies, collectGeneratedManifestCatalogReferences, renderGeneratedPnpmWorkspaceYaml, } from "./dependency-catalog.js";
6
+ import { browserTestToolLayer, checkedDockerfileFirstNodePnpmDevcontainer, dockerfileFirstRustPnpmDevcontainer, nodePnpmToolLayer, rustToolLayer, } from "./devcontainer.js";
7
+ import { editorCustomizationForCapabilities, } from "./editor-customization.js";
8
+ import { renderFixCommand, renderRootCheckCommand, } from "./module-graph.js";
9
+ import { packageManifestExposureFields, planPackageLinks, } from "./package-linking.js";
10
+ import { packageTemplateRoot } from "./runtime-paths.js";
11
+ const projectionCapabilityKinds = [
12
+ "workspace-library-package",
13
+ "workspace-node-packages",
14
+ "rust-binary-workspace",
15
+ "strict-typescript-root",
16
+ "oxc-format-lint",
17
+ "node-pnpm-devcontainer",
18
+ "github-maintenance",
19
+ ];
20
+ const projectionCapabilityKindSet = new Set(projectionCapabilityKinds);
21
+ const strictTypescriptRootBoundary = {
22
+ kind: "workspace-orchestration",
23
+ path: ".",
24
+ };
25
+ const workspacePackageBoundary = {
26
+ kind: "package-boundary",
27
+ path: ".",
28
+ };
29
+ const workspacePackageCollectionBoundary = {
30
+ kind: "package-boundary",
31
+ path: "packages/*",
32
+ };
33
+ const requiredPlanCapabilityProviders = [
34
+ {
35
+ flag: "rootCheck",
36
+ kind: "strict-typescript-root",
37
+ label: "root check command",
38
+ },
39
+ {
40
+ flag: "fixCommand",
41
+ kind: "oxc-format-lint",
42
+ label: "fix command",
43
+ },
44
+ {
45
+ flag: "githubActions",
46
+ kind: "github-maintenance",
47
+ label: "GitHub Actions maintenance",
48
+ },
49
+ {
50
+ flag: "dependabot",
51
+ kind: "github-maintenance",
52
+ label: "Dependabot maintenance",
53
+ },
54
+ {
55
+ flag: "devcontainer",
56
+ kind: "node-pnpm-devcontainer",
57
+ label: "development container support",
58
+ },
59
+ ];
60
+ const exactCapabilityKeys = {
61
+ "workspace-library-package": [
62
+ "kind",
63
+ "workspacePackageGlob",
64
+ "packageRole",
65
+ "packageSourcePreset",
66
+ "sourceFiles",
67
+ ],
68
+ "workspace-node-packages": [
69
+ "kind",
70
+ "workspacePackageGlob",
71
+ "packages",
72
+ "packageLinks",
73
+ ],
74
+ "rust-binary-workspace": ["kind", "workspacePackageGlob", "sourceFiles"],
75
+ "strict-typescript-root": ["kind"],
76
+ "oxc-format-lint": ["kind"],
77
+ "node-pnpm-devcontainer": ["kind"],
78
+ "github-maintenance": ["kind"],
79
+ };
80
+ const dependencyMaintenanceEcosystems = ["npm", "github-actions", "docker"];
81
+ const capabilityInterpreters = {
82
+ "workspace-library-package": {
83
+ kind: "workspace-library-package",
84
+ contribute({ capability, state }) {
85
+ state.package = capability;
86
+ state.sourceRoot = templateSourceRoot(capability.packageSourcePreset);
87
+ state.packageDependencies.add("valibot");
88
+ state.operationFactories.push(workspaceLibraryPackageOperations);
89
+ },
90
+ },
91
+ "workspace-node-packages": {
92
+ kind: "workspace-node-packages",
93
+ contribute({ capability, state }) {
94
+ state.nodeWorkspace = capability;
95
+ state.sourceRoot = templateSourceRootForPreset(sourceRootPreset(capability));
96
+ state.operationFactories.push(workspaceNodePackagesOperations);
97
+ if (capability.packages.length > 1) {
98
+ state.rootScriptFragments.dev = "turbo run dev --parallel";
99
+ }
100
+ if (hasVuePackage(capability)) {
101
+ state.rootCheckEnvironmentNeeds.push({
102
+ kind: "playwright-browser-assets",
103
+ browser: "chromium",
104
+ owner: { kind: "package-boundary", path: "apps/web" },
105
+ });
106
+ state.editorCustomizationCapabilities.push("vue", "tailwind");
107
+ }
108
+ state.editorCustomizationCapabilities.push("vitest");
109
+ },
110
+ },
111
+ "rust-binary-workspace": {
112
+ kind: "rust-binary-workspace",
113
+ contribute({ capability, state }) {
114
+ state.rustWorkspace = capability;
115
+ state.sourceRoot = templateSourceRootForPreset("rust-bin");
116
+ state.sourceRoots.sharedDevcontainer = sharedDevcontainerSourceRoot();
117
+ state.rootCheckComponents.push({
118
+ kind: "turbo-package-check",
119
+ owner: workspaceGlobBoundary(capability.workspacePackageGlob),
120
+ });
121
+ state.rootFixComponents.push({
122
+ kind: "turbo-package-fix",
123
+ owner: workspaceGlobBoundary(capability.workspacePackageGlob),
124
+ });
125
+ state.packageCheckComponents.push({ kind: "rustfmt-check", owner: workspacePackageBoundary }, { kind: "cargo-clippy", owner: workspacePackageBoundary }, { kind: "cargo-test", owner: workspacePackageBoundary });
126
+ state.packageFixComponents.push({
127
+ kind: "rustfmt-write",
128
+ owner: workspacePackageBoundary,
129
+ });
130
+ state.dependencyMaintenanceEcosystems.push("npm", "cargo", "github-actions", "docker", "rust-toolchain");
131
+ state.editorCustomizationCapabilities.push("rust-tooling");
132
+ state.flags.rootCheck = true;
133
+ state.flags.fixCommand = true;
134
+ state.flags.githubActions = true;
135
+ state.flags.dependabot = true;
136
+ state.flags.devcontainer = true;
137
+ state.operationFactories.push(rustBinaryWorkspaceOperations);
138
+ },
139
+ },
140
+ "strict-typescript-root": {
141
+ kind: "strict-typescript-root",
142
+ contribute({ state }) {
143
+ state.rootCheckComponents.push({
144
+ kind: "typescript-typecheck",
145
+ owner: strictTypescriptRootBoundary,
146
+ });
147
+ const workspaceBoundary = workspaceBoundaryForState(state);
148
+ state.rootCheckComponents.push({
149
+ kind: "turbo-package-typecheck",
150
+ owner: workspaceBoundary,
151
+ });
152
+ if (state.nodeWorkspace !== undefined) {
153
+ state.rootCheckComponents.push({
154
+ kind: "turbo-package-build",
155
+ owner: workspaceBoundary,
156
+ });
157
+ state.rootCheckComponents.push({
158
+ kind: "turbo-package-test",
159
+ owner: workspaceBoundary,
160
+ });
161
+ if (hasVuePackage(state.nodeWorkspace)) {
162
+ state.rootCheckComponents.push({
163
+ kind: "turbo-package-e2e-test",
164
+ owner: workspaceBoundary,
165
+ });
166
+ }
167
+ }
168
+ state.rootCheckComponents.push({
169
+ kind: "turbo-package-check",
170
+ owner: workspaceBoundary,
171
+ });
172
+ state.packageCheckComponents.push({
173
+ kind: "typescript-typecheck",
174
+ owner: workspacePackageBoundary,
175
+ });
176
+ state.rootScriptFragments.typecheck =
177
+ "tsc -p tsconfig.config.json --noEmit";
178
+ state.packageScriptFragments.typecheck = "tsc -p tsconfig.json --noEmit";
179
+ state.rootDevDependencies.add("typescript");
180
+ state.packageDevDependencies.add("@types/node");
181
+ state.packageDevDependencies.add("typescript");
182
+ state.flags.rootCheck = true;
183
+ state.operationFactories.push(strictTypescriptOperations);
184
+ },
185
+ },
186
+ "oxc-format-lint": {
187
+ kind: "oxc-format-lint",
188
+ contribute({ state }) {
189
+ state.sourceRoots.sharedOxc = sharedOxcSourceRoot();
190
+ state.rootCheckComponents.unshift({
191
+ kind: "oxc-format-check",
192
+ owner: strictTypescriptRootBoundary,
193
+ }, {
194
+ kind: "oxc-lint",
195
+ owner: strictTypescriptRootBoundary,
196
+ });
197
+ state.packageCheckComponents.push({ kind: "oxc-lint", owner: workspacePackageBoundary }, { kind: "oxc-format-check", owner: workspacePackageBoundary });
198
+ state.rootFixComponents.push({
199
+ kind: "oxc-format-write",
200
+ owner: strictTypescriptRootBoundary,
201
+ }, {
202
+ kind: "oxc-lint-fix",
203
+ owner: strictTypescriptRootBoundary,
204
+ }, {
205
+ kind: "turbo-package-fix",
206
+ owner: workspaceBoundaryForState(state),
207
+ });
208
+ state.packageFixComponents.push({ kind: "oxc-format-write", owner: workspacePackageBoundary }, { kind: "oxc-lint-fix", owner: workspacePackageBoundary });
209
+ state.rootScriptFragments["format:check"] =
210
+ "oxfmt --check --config oxfmt.config.ts oxlint.config.ts oxfmt.config.ts";
211
+ state.rootScriptFragments["format:write"] =
212
+ "oxfmt --write --config oxfmt.config.ts oxlint.config.ts oxfmt.config.ts";
213
+ state.rootScriptFragments.lint =
214
+ "oxlint --config oxlint.config.ts oxlint.config.ts oxfmt.config.ts --deny-warnings";
215
+ state.rootScriptFragments["lint:fix"] =
216
+ "oxlint --config oxlint.config.ts oxlint.config.ts oxfmt.config.ts --fix --deny-warnings";
217
+ state.packageScriptFragments["format:check"] =
218
+ "oxfmt --check --config ../../oxfmt.config.ts .";
219
+ state.packageScriptFragments["format:write"] =
220
+ "oxfmt --write --config ../../oxfmt.config.ts .";
221
+ state.packageScriptFragments.lint =
222
+ "oxlint --config ../../oxlint.config.ts . --deny-warnings";
223
+ state.packageScriptFragments["lint:fix"] =
224
+ "oxlint --config ../../oxlint.config.ts . --fix --deny-warnings";
225
+ state.rootDevDependencies.add("oxfmt");
226
+ state.rootDevDependencies.add("oxlint");
227
+ state.packageDevDependencies.add("oxfmt");
228
+ state.packageDevDependencies.add("oxlint");
229
+ state.editorCustomizationCapabilities.push("oxc-format-lint");
230
+ state.flags.fixCommand = true;
231
+ state.operationFactories.push(oxcFormatLintOperations);
232
+ },
233
+ },
234
+ "node-pnpm-devcontainer": {
235
+ kind: "node-pnpm-devcontainer",
236
+ contribute({ state }) {
237
+ state.sourceRoots.sharedDevcontainer = sharedDevcontainerSourceRoot();
238
+ state.flags.devcontainer = true;
239
+ state.operationFactories.push(nodePnpmDevcontainerOperations);
240
+ },
241
+ },
242
+ "github-maintenance": {
243
+ kind: "github-maintenance",
244
+ contribute({ state }) {
245
+ state.dependencyMaintenanceEcosystems.push(...dependencyMaintenanceEcosystems);
246
+ state.flags.githubActions = true;
247
+ state.flags.dependabot = true;
248
+ state.operationFactories.push(githubMaintenanceOperations);
249
+ },
250
+ },
251
+ };
252
+ export function validateProjectionCapabilities(input) {
253
+ if (!isRecord(input) || !Array.isArray(input.capabilities)) {
254
+ return {
255
+ ok: false,
256
+ issues: [
257
+ {
258
+ path: "$.capabilities",
259
+ message: "Projection Declaration must select Projection Capabilities",
260
+ },
261
+ ],
262
+ };
263
+ }
264
+ const issues = [];
265
+ const capabilities = [];
266
+ input.capabilities.forEach((capability, index) => {
267
+ const pathPrefix = `$.capabilities[${index}]`;
268
+ if (!isRecord(capability)) {
269
+ issues.push({
270
+ path: pathPrefix,
271
+ message: "Projection Capability must be an object",
272
+ });
273
+ return;
274
+ }
275
+ if (typeof capability.kind !== "string") {
276
+ issues.push({
277
+ path: `${pathPrefix}.kind`,
278
+ message: "Projection Capability kind is required",
279
+ });
280
+ return;
281
+ }
282
+ if (!projectionCapabilityKindSet.has(capability.kind)) {
283
+ issues.push({
284
+ path: `${pathPrefix}.kind`,
285
+ message: `Unknown Projection Capability kind: ${capability.kind}`,
286
+ });
287
+ return;
288
+ }
289
+ const kind = capability.kind;
290
+ issues.push(...unknownCapabilityPropertyIssues(capability, pathPrefix));
291
+ if (kind === "workspace-library-package") {
292
+ const workspaceCapability = parseWorkspaceLibraryPackageCapability(capability, pathPrefix);
293
+ if (Array.isArray(workspaceCapability)) {
294
+ issues.push(...workspaceCapability);
295
+ return;
296
+ }
297
+ capabilities.push(workspaceCapability);
298
+ return;
299
+ }
300
+ if (kind === "workspace-node-packages") {
301
+ const workspaceCapability = parseWorkspaceNodePackagesCapability(capability, pathPrefix);
302
+ if (Array.isArray(workspaceCapability)) {
303
+ issues.push(...workspaceCapability);
304
+ return;
305
+ }
306
+ capabilities.push(workspaceCapability);
307
+ return;
308
+ }
309
+ if (kind === "rust-binary-workspace") {
310
+ const workspaceCapability = parseRustBinaryWorkspaceCapability(capability, pathPrefix);
311
+ if (Array.isArray(workspaceCapability)) {
312
+ issues.push(...workspaceCapability);
313
+ return;
314
+ }
315
+ capabilities.push(workspaceCapability);
316
+ return;
317
+ }
318
+ capabilities.push({ kind });
319
+ });
320
+ if (issues.length === 0) {
321
+ issues.push(...duplicateCapabilityIssues(capabilities));
322
+ issues.push(...capabilityCompositionIssues(capabilities));
323
+ }
324
+ if (issues.length > 0) {
325
+ return { ok: false, issues };
326
+ }
327
+ return {
328
+ ok: true,
329
+ value: {
330
+ capabilities,
331
+ },
332
+ };
333
+ }
334
+ export function projectionCapabilityIssues(presets) {
335
+ return presets.flatMap((preset, presetIndex) => {
336
+ if (preset.projection === undefined) {
337
+ return [];
338
+ }
339
+ const result = validateProjectionCapabilities(preset.projection);
340
+ return result.ok
341
+ ? []
342
+ : result.issues.map((issue) => ({
343
+ path: `$.presets[${presetIndex}].projection${issue.path.slice(1)}`,
344
+ message: issue.message,
345
+ }));
346
+ });
347
+ }
348
+ export function normalizePresetProjectionDeclaration(declaration) {
349
+ const result = validateProjectionCapabilities(declaration);
350
+ if (!result.ok) {
351
+ throw new Error(`Projection Declaration is invalid:\n${result.issues
352
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
353
+ .join("\n")}`);
354
+ }
355
+ return result.value;
356
+ }
357
+ export function interpretPresetProjectionDeclaration(options) {
358
+ const validation = validateProjectionCapabilities(options.declaration);
359
+ if (!validation.ok) {
360
+ throw new Error(`Projection Declaration is invalid:\n${validation.issues
361
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
362
+ .join("\n")}`);
363
+ }
364
+ const state = createProjectionCompositionState();
365
+ for (const capability of validation.value.capabilities) {
366
+ capabilityInterpreters[capability.kind].contribute({
367
+ capability: capability,
368
+ state,
369
+ });
370
+ }
371
+ const checkPlan = {
372
+ components: state.rootCheckComponents,
373
+ environmentNeeds: state.rootCheckEnvironmentNeeds,
374
+ };
375
+ const fixPlan = {
376
+ components: state.rootFixComponents,
377
+ };
378
+ const packageScripts = projectRootPackageScripts(checkPlan, fixPlan, state.rootScriptFragments);
379
+ const packageScriptsByPath = packageScriptsByWorkspacePath(state);
380
+ const dependencyMaintenancePolicy = {
381
+ ecosystems: uniqueValues(state.dependencyMaintenanceEcosystems),
382
+ ...(state.rustWorkspace === undefined
383
+ ? {}
384
+ : {
385
+ directories: {
386
+ cargo: `/${rustWorkspacePackagePath(options.context)}`,
387
+ },
388
+ }),
389
+ interval: "weekly",
390
+ };
391
+ if (state.sourceRoot === undefined) {
392
+ throw new Error("Projection Capability composition did not provide sourceRoot");
393
+ }
394
+ return {
395
+ sourceRoot: state.sourceRoot,
396
+ sourceRoots: state.sourceRoots,
397
+ operations: state.operationFactories.flatMap((factory) => factory({
398
+ context: options.context,
399
+ state,
400
+ packageScripts,
401
+ packageScriptsByPath,
402
+ })),
403
+ checkPlan,
404
+ fixPlan,
405
+ dependencyMaintenancePolicy,
406
+ packageScripts,
407
+ capabilities: completePlanCapabilityFlags(state.flags),
408
+ };
409
+ }
410
+ export function blueprintForPresetSourcePreset(preset, options = { targetDir: process.cwd() }) {
411
+ const packageScope = options.scope ?? projectNameFromDir(options.targetDir);
412
+ const projectName = projectNameFromDir(options.targetDir);
413
+ const blueprint = {
414
+ schemaVersion: 1,
415
+ preset: preset.name,
416
+ packageManager: preset.supportedPackageManagers[0],
417
+ projectKind: preset.supportedProjectKinds[0] ?? "multi-package",
418
+ features: [...preset.features],
419
+ };
420
+ if (preset.projection === undefined) {
421
+ return blueprint;
422
+ }
423
+ const validation = validateProjectionCapabilities(preset.projection);
424
+ if (!validation.ok) {
425
+ throw new Error(`Projection Declaration is invalid:\n${validation.issues
426
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
427
+ .join("\n")}`);
428
+ }
429
+ const packages = blueprintPackagesForCapabilities(validation.value.capabilities, packageScope, projectName);
430
+ return packages.length === 0 ? blueprint : { ...blueprint, packages };
431
+ }
432
+ export function projectPresetSourcePreset(options) {
433
+ if (options.preset.projection === undefined) {
434
+ throw new Error(`Preset ${options.preset.name} must declare a Projection Declaration`);
435
+ }
436
+ return interpretPresetProjectionDeclaration({
437
+ preset: options.preset,
438
+ declaration: options.preset.projection,
439
+ context: options.context,
440
+ });
441
+ }
442
+ export async function planPresetSourcePackageAddition(options) {
443
+ if (options.preset.projection === undefined) {
444
+ throw new Error(`Preset ${options.preset.name} declares Package Addition support but has no Projection Declaration`);
445
+ }
446
+ const state = stateForProjectionDeclaration(options.preset.projection);
447
+ const additionCapability = packageAdditionCapabilityForState(options.preset.name, state);
448
+ return {
449
+ packagePath: options.addition.packagePath,
450
+ workspacePackageGlob: additionCapability.workspacePackageGlob,
451
+ workspaceMembershipGlob: `${packageCollectionFromPackagePath(options.addition.packagePath)}/*`,
452
+ packageRole: additionCapability.packageRole,
453
+ packageSourcePreset: additionCapability.packageSourcePreset,
454
+ sourceRoot: templateSourceRootForPreset(additionCapability.sourcePreset),
455
+ sourceRoots: state.sourceRoots,
456
+ operations: [
457
+ ...packageAdditionOperationsForCapability({
458
+ capability: additionCapability,
459
+ packageName: options.addition.packageName,
460
+ packagePath: options.addition.packagePath,
461
+ nodeVersion: options.addition.nodeVersion,
462
+ }),
463
+ ...(additionCapability.packageSourcePreset === "vue-app"
464
+ ? [
465
+ {
466
+ kind: "writeTextTemplate",
467
+ from: "playwright.package-addition.config.ts",
468
+ to: `${options.addition.packagePath}/playwright.config.ts`,
469
+ replacements: {
470
+ VUE_PREVIEW_PORT: String(await nextVuePreviewPort(options.addition.root, options.addition.blueprint)),
471
+ },
472
+ },
473
+ ]
474
+ : []),
475
+ ],
476
+ };
477
+ }
478
+ export function defaultPackagePathForPresetSourcePackageAddition(preset, packageLeafName) {
479
+ if (preset.projection === undefined) {
480
+ throw new Error(`Preset ${preset.name} declares Package Addition support but has no Projection Declaration`);
481
+ }
482
+ const state = stateForProjectionDeclaration(preset.projection);
483
+ const additionCapability = packageAdditionCapabilityForState(preset.name, state);
484
+ return `${packageCollection(additionCapability.workspacePackageGlob)}/${packageLeafName}`;
485
+ }
486
+ export function loadBuiltInPresetProjectionDeclaration(presetName) {
487
+ const manifestPath = path.join(packageTemplateRoot(path.dirname(fileURLToPath(import.meta.url))), "preset-source.json");
488
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
489
+ if (!isRecord(manifest) || !Array.isArray(manifest.presets)) {
490
+ throw new Error("Built-in Preset Source Manifest must declare presets");
491
+ }
492
+ const preset = manifest.presets.find((candidate) => isRecord(candidate) && candidate.name === presetName);
493
+ if (!isRecord(preset) || preset.projection === undefined) {
494
+ throw new Error(`Built-in Preset ${presetName} must declare a Projection Declaration`);
495
+ }
496
+ const result = validateProjectionCapabilities(preset.projection);
497
+ if (!result.ok) {
498
+ throw new Error(`Built-in Preset ${presetName} Projection Declaration is invalid:\n${result.issues
499
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
500
+ .join("\n")}`);
501
+ }
502
+ return result.value;
503
+ }
504
+ function createProjectionCompositionState() {
505
+ return {
506
+ sourceRoots: {},
507
+ rootCheckComponents: [],
508
+ rootCheckEnvironmentNeeds: [],
509
+ packageCheckComponents: [],
510
+ rootFixComponents: [],
511
+ packageFixComponents: [],
512
+ rootScriptFragments: {},
513
+ packageScriptFragments: {},
514
+ rootDevDependencies: new Set(["turbo"]),
515
+ packageDependencies: new Set(),
516
+ packageDevDependencies: new Set(),
517
+ dependencyMaintenanceEcosystems: [],
518
+ editorCustomizationCapabilities: [],
519
+ operationFactories: [],
520
+ flags: {},
521
+ };
522
+ }
523
+ function isRecord(value) {
524
+ return typeof value === "object" && value !== null && !Array.isArray(value);
525
+ }
526
+ function isNonEmptyString(value) {
527
+ return typeof value === "string" && value.length > 0;
528
+ }
529
+ function unknownCapabilityPropertyIssues(capability, pathPrefix) {
530
+ const kind = capability.kind;
531
+ const allowedKeys = new Set(exactCapabilityKeys[kind]);
532
+ return Object.keys(capability)
533
+ .filter((key) => !allowedKeys.has(key))
534
+ .map((key) => ({
535
+ path: `${pathPrefix}.${key}`,
536
+ message: `Projection Capability ${kind} does not support property: ${key}`,
537
+ }));
538
+ }
539
+ function parseWorkspaceLibraryPackageCapability(capability, pathPrefix) {
540
+ const issues = [];
541
+ if (capability.workspacePackageGlob !== "packages/*") {
542
+ issues.push({
543
+ path: `${pathPrefix}.workspacePackageGlob`,
544
+ message: "workspace-library-package currently supports workspacePackageGlob: packages/*",
545
+ });
546
+ }
547
+ if (capability.packageRole !== "shared-library") {
548
+ issues.push({
549
+ path: `${pathPrefix}.packageRole`,
550
+ message: "workspace-library-package currently supports packageRole: shared-library",
551
+ });
552
+ }
553
+ if (capability.packageSourcePreset !== "ts-lib") {
554
+ issues.push({
555
+ path: `${pathPrefix}.packageSourcePreset`,
556
+ message: "workspace-library-package currently supports packageSourcePreset: ts-lib",
557
+ });
558
+ }
559
+ if (!Array.isArray(capability.sourceFiles)) {
560
+ issues.push({
561
+ path: `${pathPrefix}.sourceFiles`,
562
+ message: "workspace-library-package sourceFiles must be a non-empty array",
563
+ });
564
+ }
565
+ else if (capability.sourceFiles.length === 0 ||
566
+ !capability.sourceFiles.every(isNonEmptyString)) {
567
+ issues.push({
568
+ path: `${pathPrefix}.sourceFiles`,
569
+ message: "workspace-library-package sourceFiles must be a non-empty array of paths",
570
+ });
571
+ }
572
+ if (issues.length > 0) {
573
+ return issues;
574
+ }
575
+ return {
576
+ kind: "workspace-library-package",
577
+ workspacePackageGlob: "packages/*",
578
+ packageRole: "shared-library",
579
+ packageSourcePreset: "ts-lib",
580
+ sourceFiles: capability.sourceFiles,
581
+ };
582
+ }
583
+ function parseWorkspaceNodePackagesCapability(capability, pathPrefix) {
584
+ const issues = [];
585
+ if (capability.workspacePackageGlob !== "apps/*") {
586
+ issues.push({
587
+ path: `${pathPrefix}.workspacePackageGlob`,
588
+ message: "workspace-node-packages currently supports workspacePackageGlob: apps/*",
589
+ });
590
+ }
591
+ if (!Array.isArray(capability.packages) || capability.packages.length === 0) {
592
+ issues.push({
593
+ path: `${pathPrefix}.packages`,
594
+ message: "workspace-node-packages packages must be a non-empty array",
595
+ });
596
+ }
597
+ const packages = Array.isArray(capability.packages)
598
+ ? capability.packages.flatMap((nodePackage, packageIndex) => {
599
+ const packagePath = `${pathPrefix}.packages[${packageIndex}]`;
600
+ const parsed = parseWorkspaceNodePackage(nodePackage, packagePath);
601
+ if (Array.isArray(parsed)) {
602
+ issues.push(...parsed);
603
+ return [];
604
+ }
605
+ return [parsed];
606
+ })
607
+ : [];
608
+ const packageLinks = capability.packageLinks === undefined
609
+ ? undefined
610
+ : parseWorkspaceNodePackageLinks(capability.packageLinks, `${pathPrefix}.packageLinks`, new Set(packages.map((nodePackage) => nodePackage.path)), issues);
611
+ if (issues.length > 0) {
612
+ return issues;
613
+ }
614
+ return {
615
+ kind: "workspace-node-packages",
616
+ workspacePackageGlob: "apps/*",
617
+ packages,
618
+ ...(packageLinks === undefined ? {} : { packageLinks }),
619
+ };
620
+ }
621
+ function parseWorkspaceNodePackage(value, pathPrefix) {
622
+ const issues = [];
623
+ if (!isRecord(value)) {
624
+ return [
625
+ {
626
+ path: pathPrefix,
627
+ message: "workspace-node-packages package must be an object",
628
+ },
629
+ ];
630
+ }
631
+ if (value.kind !== "hono-api" && value.kind !== "vue-app") {
632
+ issues.push({
633
+ path: `${pathPrefix}.kind`,
634
+ message: "workspace-node-packages package kind must be hono-api or vue-app",
635
+ });
636
+ }
637
+ if (value.path !== "apps/api" && value.path !== "apps/web") {
638
+ issues.push({
639
+ path: `${pathPrefix}.path`,
640
+ message: "workspace-node-packages package path must be apps/api or apps/web",
641
+ });
642
+ }
643
+ if (!Array.isArray(value.sourceFiles)) {
644
+ issues.push({
645
+ path: `${pathPrefix}.sourceFiles`,
646
+ message: "workspace-node-packages package sourceFiles must be a non-empty array",
647
+ });
648
+ }
649
+ else if (value.sourceFiles.length === 0 ||
650
+ !value.sourceFiles.every(isNonEmptyString)) {
651
+ issues.push({
652
+ path: `${pathPrefix}.sourceFiles`,
653
+ message: "workspace-node-packages package sourceFiles must be a non-empty array of paths",
654
+ });
655
+ }
656
+ const allowedKeys = new Set(["kind", "path", "sourceFiles"]);
657
+ for (const key of Object.keys(value)) {
658
+ if (!allowedKeys.has(key)) {
659
+ issues.push({
660
+ path: `${pathPrefix}.${key}`,
661
+ message: `workspace-node-packages package does not support property: ${key}`,
662
+ });
663
+ }
664
+ }
665
+ if (issues.length > 0) {
666
+ return issues;
667
+ }
668
+ return {
669
+ kind: value.kind,
670
+ path: value.path,
671
+ sourceFiles: value.sourceFiles,
672
+ };
673
+ }
674
+ function parseWorkspaceNodePackageLinks(value, pathPrefix, declaredPackagePaths, issues) {
675
+ if (!Array.isArray(value)) {
676
+ issues.push({
677
+ path: pathPrefix,
678
+ message: "workspace-node-packages packageLinks must be an array",
679
+ });
680
+ return undefined;
681
+ }
682
+ return value.flatMap((link, linkIndex) => {
683
+ const linkPath = `${pathPrefix}[${linkIndex}]`;
684
+ const linkIssues = [];
685
+ if (!isRecord(link)) {
686
+ issues.push({
687
+ path: linkPath,
688
+ message: "workspace-node-packages packageLink must be an object",
689
+ });
690
+ return [];
691
+ }
692
+ const allowedKeys = new Set(["consumerPackagePath", "providerPackagePath"]);
693
+ for (const key of Object.keys(link)) {
694
+ if (!allowedKeys.has(key)) {
695
+ linkIssues.push({
696
+ path: `${linkPath}.${key}`,
697
+ message: `workspace-node-packages packageLink does not support property: ${key}`,
698
+ });
699
+ }
700
+ }
701
+ if (link.consumerPackagePath !== "apps/web" ||
702
+ link.providerPackagePath !== "apps/api") {
703
+ linkIssues.push({
704
+ path: linkPath,
705
+ message: "workspace-node-packages currently supports links from apps/web to apps/api",
706
+ });
707
+ }
708
+ else {
709
+ if (!declaredPackagePaths.has(link.consumerPackagePath)) {
710
+ linkIssues.push({
711
+ path: `${linkPath}.consumerPackagePath`,
712
+ message: "workspace-node-packages packageLink consumerPackagePath must reference a package declared in the same packages array: apps/web",
713
+ });
714
+ }
715
+ if (!declaredPackagePaths.has(link.providerPackagePath)) {
716
+ linkIssues.push({
717
+ path: `${linkPath}.providerPackagePath`,
718
+ message: "workspace-node-packages packageLink providerPackagePath must reference a package declared in the same packages array: apps/api",
719
+ });
720
+ }
721
+ }
722
+ if (linkIssues.length > 0) {
723
+ issues.push(...linkIssues);
724
+ return [];
725
+ }
726
+ return [
727
+ {
728
+ consumerPackagePath: "apps/web",
729
+ providerPackagePath: "apps/api",
730
+ },
731
+ ];
732
+ });
733
+ }
734
+ function parseRustBinaryWorkspaceCapability(capability, pathPrefix) {
735
+ const issues = [];
736
+ if (capability.workspacePackageGlob !== "packages/*") {
737
+ issues.push({
738
+ path: `${pathPrefix}.workspacePackageGlob`,
739
+ message: "rust-binary-workspace currently supports workspacePackageGlob: packages/*",
740
+ });
741
+ }
742
+ if (!Array.isArray(capability.sourceFiles)) {
743
+ issues.push({
744
+ path: `${pathPrefix}.sourceFiles`,
745
+ message: "rust-binary-workspace sourceFiles must be a non-empty array",
746
+ });
747
+ }
748
+ else if (capability.sourceFiles.length === 0 ||
749
+ !capability.sourceFiles.every(isNonEmptyString)) {
750
+ issues.push({
751
+ path: `${pathPrefix}.sourceFiles`,
752
+ message: "rust-binary-workspace sourceFiles must be a non-empty array of paths",
753
+ });
754
+ }
755
+ if (issues.length > 0) {
756
+ return issues;
757
+ }
758
+ return {
759
+ kind: "rust-binary-workspace",
760
+ workspacePackageGlob: "packages/*",
761
+ sourceFiles: capability.sourceFiles,
762
+ };
763
+ }
764
+ function duplicateCapabilityIssues(capabilities) {
765
+ const firstSeen = new Map();
766
+ const issues = [];
767
+ capabilities.forEach((capability, index) => {
768
+ const firstIndex = firstSeen.get(capability.kind);
769
+ if (firstIndex === undefined) {
770
+ firstSeen.set(capability.kind, index);
771
+ return;
772
+ }
773
+ issues.push({
774
+ path: `$.capabilities[${index}].kind`,
775
+ message: `Duplicate Projection Capability kind: ${capability.kind}`,
776
+ });
777
+ });
778
+ return issues;
779
+ }
780
+ function capabilityCompositionIssues(capabilities) {
781
+ const kinds = new Set(capabilities.map((capability) => capability.kind));
782
+ const issues = [];
783
+ const companionKinds = uniqueValues(capabilities
784
+ .map((capability) => capability.kind)
785
+ .filter((kind) => kind !== "rust-binary-workspace"));
786
+ if (kinds.has("rust-binary-workspace") && companionKinds.length > 0) {
787
+ issues.push({
788
+ path: "$.capabilities",
789
+ message: "rust-binary-workspace is a complete domain capability and must be selected by itself; remove companion Projection Capabilities: " +
790
+ companionKinds.join(", "),
791
+ });
792
+ return issues;
793
+ }
794
+ if (!kinds.has("workspace-library-package") &&
795
+ !kinds.has("workspace-node-packages") &&
796
+ !kinds.has("rust-binary-workspace")) {
797
+ issues.push({
798
+ path: "$.capabilities",
799
+ message: "Projection Capability composition must include a workspace package layout capability",
800
+ });
801
+ }
802
+ if (kinds.has("strict-typescript-root") &&
803
+ !kinds.has("workspace-library-package") &&
804
+ !kinds.has("workspace-node-packages")) {
805
+ issues.push({
806
+ path: "$.capabilities",
807
+ message: "strict-typescript-root requires a workspace package layout so package typecheck tasks have a workspace target",
808
+ });
809
+ }
810
+ if (kinds.has("node-pnpm-devcontainer") && !kinds.has("oxc-format-lint")) {
811
+ issues.push({
812
+ path: "$.capabilities",
813
+ message: "node-pnpm-devcontainer requires oxc-format-lint so editor customization is derived from declared tooling",
814
+ });
815
+ }
816
+ for (const requirement of requiredPlanCapabilityProviders) {
817
+ if (!kinds.has(requirement.kind) && !kinds.has("rust-binary-workspace")) {
818
+ issues.push({
819
+ path: "$.capabilities",
820
+ message: `Projection Capability composition must include ${requirement.kind} to provide ${requirement.label}`,
821
+ });
822
+ }
823
+ }
824
+ return issues;
825
+ }
826
+ function uniqueValues(values) {
827
+ return [...new Set(values)];
828
+ }
829
+ function projectNameFromDir(targetDir) {
830
+ return path.basename(path.resolve(targetDir));
831
+ }
832
+ function blueprintPackagesForCapabilities(capabilities, packageScope, projectName) {
833
+ return capabilities.flatMap((capability) => {
834
+ if (capability.kind === "workspace-library-package") {
835
+ return [
836
+ {
837
+ name: `@${packageScope}/${projectName}`,
838
+ path: `${packageCollection(capability.workspacePackageGlob)}/${projectName}`,
839
+ },
840
+ ];
841
+ }
842
+ if (capability.kind === "workspace-node-packages") {
843
+ return capability.packages.map((nodePackage) => {
844
+ const leaf = nodePackage.path.split("/").at(-1) ?? nodePackage.path;
845
+ return {
846
+ name: `@${packageScope}/${leaf}`,
847
+ path: nodePackage.path,
848
+ };
849
+ });
850
+ }
851
+ if (capability.kind === "rust-binary-workspace") {
852
+ const rustPackageName = cargoPackageNameFromProjectName(projectName);
853
+ return [
854
+ {
855
+ name: `${rustPackageName}-native`,
856
+ path: `packages/${rustPackageName}`,
857
+ },
858
+ ];
859
+ }
860
+ return [];
861
+ });
862
+ }
863
+ function stateForProjectionDeclaration(declaration) {
864
+ const validation = validateProjectionCapabilities(declaration);
865
+ if (!validation.ok) {
866
+ throw new Error(`Projection Declaration is invalid:\n${validation.issues
867
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
868
+ .join("\n")}`);
869
+ }
870
+ const state = createProjectionCompositionState();
871
+ for (const capability of validation.value.capabilities) {
872
+ capabilityInterpreters[capability.kind].contribute({
873
+ capability: capability,
874
+ state,
875
+ });
876
+ }
877
+ return state;
878
+ }
879
+ function packageAdditionCapabilityForState(presetName, state) {
880
+ if (state.package !== undefined) {
881
+ const scripts = packageScriptsByWorkspacePath(state).get(state.package.workspacePackageGlob);
882
+ if (scripts === undefined) {
883
+ throw new Error(`Missing package scripts for ${state.package.workspacePackageGlob}`);
884
+ }
885
+ return {
886
+ sourcePreset: state.package.packageSourcePreset,
887
+ workspacePackageGlob: state.package.workspacePackageGlob,
888
+ packageRole: state.package.packageRole,
889
+ packageSourcePreset: state.package.packageSourcePreset,
890
+ sourceFiles: state.package.sourceFiles,
891
+ packageScripts: scripts,
892
+ };
893
+ }
894
+ if (state.nodeWorkspace !== undefined) {
895
+ if (state.nodeWorkspace.packages.length !== 1) {
896
+ throw new Error(`Preset ${presetName} is an initialization-only workspace and cannot be used for Package Addition`);
897
+ }
898
+ const nodePackage = state.nodeWorkspace.packages[0];
899
+ return {
900
+ sourcePreset: nodePackage.kind,
901
+ workspacePackageGlob: state.nodeWorkspace.workspacePackageGlob,
902
+ packageRole: "runtime-service",
903
+ packageSourcePreset: nodePackage.kind,
904
+ sourceFiles: nodePackage.sourceFiles,
905
+ packageScripts: nodePackageScripts(nodePackage, state.nodeWorkspace),
906
+ nodePackage,
907
+ nodeWorkspace: state.nodeWorkspace,
908
+ };
909
+ }
910
+ throw new Error(`Preset ${presetName} is an initialization-only workspace and cannot be used for Package Addition`);
911
+ }
912
+ function packageCollectionFromPackagePath(packagePath) {
913
+ const [workspaceCollection] = packagePath.split("/");
914
+ if (!workspaceCollection) {
915
+ throw new Error(`Invalid Package Path for Package Addition: ${packagePath}`);
916
+ }
917
+ return workspaceCollection;
918
+ }
919
+ function packageAdditionOperationsForCapability(options) {
920
+ switch (options.capability.packageSourcePreset) {
921
+ case "ts-lib":
922
+ return libraryPackageAdditionOperations(options);
923
+ case "hono-api":
924
+ return honoApiPackageAdditionOperations(options);
925
+ case "vue-app":
926
+ return vueAppPackageAdditionOperations(options);
927
+ }
928
+ }
929
+ function generationContextForPackageAddition(options) {
930
+ return {
931
+ projectName: {
932
+ kind: "ProjectName",
933
+ value: options.packageName.split("/").at(0)?.replace(/^@/, "") ?? "app",
934
+ },
935
+ preset: options.preset,
936
+ packageManager: { kind: "PackageManager", value: "pnpm" },
937
+ blueprint: {
938
+ schemaVersion: 1,
939
+ preset: options.preset,
940
+ packageManager: "pnpm",
941
+ projectKind: "multi-package",
942
+ features: [],
943
+ packages: [{ name: options.packageName, path: options.packagePath }],
944
+ },
945
+ toolchain: {
946
+ nodeLtsMajor: { kind: "NodeLtsMajor", value: options.nodeVersion },
947
+ packageManagerPin: { kind: "PackageManagerPin", value: "pnpm@10.0.0" },
948
+ source: "bundled-fallback",
949
+ diagnostics: [],
950
+ },
951
+ };
952
+ }
953
+ function libraryPackageAdditionOperations(options) {
954
+ const packageExposure = planPackageLinks([
955
+ {
956
+ name: options.packageName,
957
+ path: options.packagePath,
958
+ role: "shared-library",
959
+ sourcePreset: "ts-lib",
960
+ },
961
+ ]).exposuresByPackagePath.get(options.packagePath);
962
+ if (packageExposure === undefined) {
963
+ throw new Error(`Missing Package Exposure for ${options.packagePath}`);
964
+ }
965
+ const exposureFields = packageManifestExposureFields(packageExposure);
966
+ return [
967
+ {
968
+ kind: "writeJson",
969
+ to: `${options.packagePath}/package.json`,
970
+ value: {
971
+ name: options.packageName,
972
+ version: "0.0.0",
973
+ private: true,
974
+ type: "module",
975
+ imports: exposureFields.imports,
976
+ exports: exposureFields.exports,
977
+ dependencies: {
978
+ valibot: "catalog:",
979
+ },
980
+ scripts: options.capability.packageScripts,
981
+ devDependencies: {
982
+ "@types/node": "catalog:",
983
+ oxfmt: "catalog:",
984
+ oxlint: "catalog:",
985
+ typescript: "catalog:",
986
+ },
987
+ engines: {
988
+ node: options.nodeVersion,
989
+ },
990
+ },
991
+ multilineArrays: ["files"],
992
+ },
993
+ {
994
+ kind: "writeJson",
995
+ to: `${options.packagePath}/tsconfig.json`,
996
+ value: libraryTsconfigJson(),
997
+ },
998
+ ...options.capability.sourceFiles.map((sourceFile) => ({
999
+ kind: "copyFile",
1000
+ from: sourceFile,
1001
+ to: `${options.packagePath}/${sourceFile}`,
1002
+ })),
1003
+ ];
1004
+ }
1005
+ function libraryTsconfigJson() {
1006
+ return {
1007
+ compilerOptions: {
1008
+ composite: true,
1009
+ declaration: true,
1010
+ declarationMap: true,
1011
+ module: "NodeNext",
1012
+ moduleResolution: "NodeNext",
1013
+ noEmitOnError: true,
1014
+ rootDir: "src",
1015
+ skipLibCheck: false,
1016
+ strict: true,
1017
+ target: "ES2022",
1018
+ types: ["node"],
1019
+ },
1020
+ include: ["src/**/*.ts"],
1021
+ };
1022
+ }
1023
+ function honoApiPackageAdditionOperations(options) {
1024
+ const packageExposure = planPackageLinks([
1025
+ {
1026
+ name: options.packageName,
1027
+ path: options.packagePath,
1028
+ role: "runtime-service",
1029
+ sourcePreset: "hono-api",
1030
+ },
1031
+ ]).exposuresByPackagePath.get(options.packagePath);
1032
+ if (packageExposure === undefined) {
1033
+ throw new Error(`Missing Package Exposure for ${options.packagePath}`);
1034
+ }
1035
+ return [
1036
+ {
1037
+ kind: "writeJson",
1038
+ to: `${options.packagePath}/package.json`,
1039
+ value: honoApiPackageJson(generationContextForPackageAddition({
1040
+ preset: "hono-api",
1041
+ packageName: options.packageName,
1042
+ packagePath: options.packagePath,
1043
+ nodeVersion: options.nodeVersion,
1044
+ }), options.packageName, options.capability.packageScripts, packageManifestExposureFields(packageExposure)),
1045
+ },
1046
+ ...honoApiTsconfigOperations(options.packagePath),
1047
+ ...options.capability.sourceFiles.map((sourceFile) => ({
1048
+ kind: "copyFile",
1049
+ from: sourceFile,
1050
+ to: `${options.packagePath}/${sourceFile}`,
1051
+ })),
1052
+ ];
1053
+ }
1054
+ function vueAppPackageAdditionOperations(options) {
1055
+ return [
1056
+ {
1057
+ kind: "writeJson",
1058
+ to: `${options.packagePath}/package.json`,
1059
+ value: vuePackageJson(generationContextForPackageAddition({
1060
+ preset: "vue-app",
1061
+ packageName: options.packageName,
1062
+ packagePath: options.packagePath,
1063
+ nodeVersion: options.nodeVersion,
1064
+ }), options.packageName, options.capability.packageScripts),
1065
+ },
1066
+ ...vueAppTsconfigOperations(options.packagePath),
1067
+ ...options.capability.sourceFiles
1068
+ .filter((sourceFile) => path.basename(sourceFile) !== "playwright.config.ts")
1069
+ .map((sourceFile) => ({
1070
+ kind: "copyFile",
1071
+ from: sourceFile,
1072
+ to: `${options.packagePath}/${sourceFile}`,
1073
+ })),
1074
+ ];
1075
+ }
1076
+ function localPortsFromText(text) {
1077
+ return [
1078
+ ...text.matchAll(/--port\s+(\d+)/g),
1079
+ ...text.matchAll(/https?:\/\/(?:localhost|127\.0\.0\.1):(\d+)/g),
1080
+ ].map((match) => Number(match[1]));
1081
+ }
1082
+ async function usedPlaywrightPorts(root, blueprint) {
1083
+ const ports = new Set();
1084
+ for (const projectPackage of blueprint.packages ?? []) {
1085
+ try {
1086
+ const configText = await readFile(path.join(root, projectPackage.path, "playwright.config.ts"), "utf8");
1087
+ for (const port of localPortsFromText(configText)) {
1088
+ ports.add(port);
1089
+ }
1090
+ }
1091
+ catch (error) {
1092
+ if (!isNodeError(error) || error.code !== "ENOENT") {
1093
+ throw error;
1094
+ }
1095
+ }
1096
+ }
1097
+ return ports;
1098
+ }
1099
+ async function nextVuePreviewPort(root, blueprint) {
1100
+ const usedPorts = await usedPlaywrightPorts(root, blueprint);
1101
+ let port = 4173;
1102
+ while (usedPorts.has(port)) {
1103
+ port += 1;
1104
+ }
1105
+ return port;
1106
+ }
1107
+ function isNodeError(error) {
1108
+ return error instanceof Error && "code" in error;
1109
+ }
1110
+ function completePlanCapabilityFlags(flags) {
1111
+ const missing = requiredPlanCapabilityProviders.filter((requirement) => flags[requirement.flag] !== true);
1112
+ if (missing.length > 0) {
1113
+ throw new Error(`Projection Capability composition did not provide required plan capabilities: ${missing
1114
+ .map((requirement) => requirement.flag)
1115
+ .join(", ")}`);
1116
+ }
1117
+ return {
1118
+ rootCheck: true,
1119
+ fixCommand: true,
1120
+ githubActions: true,
1121
+ dependabot: true,
1122
+ devcontainer: true,
1123
+ };
1124
+ }
1125
+ function packageScriptsByWorkspacePath(state) {
1126
+ if (state.package === undefined && state.rustWorkspace === undefined) {
1127
+ return new Map();
1128
+ }
1129
+ if (state.rustWorkspace !== undefined) {
1130
+ return new Map([
1131
+ [
1132
+ state.rustWorkspace.workspacePackageGlob,
1133
+ projectPackageScripts({
1134
+ components: state.packageCheckComponents,
1135
+ environmentNeeds: [],
1136
+ }, {
1137
+ components: state.packageFixComponents,
1138
+ }, state.packageScriptFragments),
1139
+ ],
1140
+ ]);
1141
+ }
1142
+ return new Map([
1143
+ [
1144
+ state.package.workspacePackageGlob,
1145
+ projectPackageScripts({
1146
+ components: state.packageCheckComponents,
1147
+ environmentNeeds: [],
1148
+ }, {
1149
+ components: state.packageFixComponents,
1150
+ }, state.packageScriptFragments),
1151
+ ],
1152
+ ]);
1153
+ }
1154
+ function workspaceBoundaryForState(state) {
1155
+ return workspaceGlobBoundary(state.nodeWorkspace?.workspacePackageGlob ??
1156
+ state.package?.workspacePackageGlob ??
1157
+ state.rustWorkspace?.workspacePackageGlob ??
1158
+ "packages/*");
1159
+ }
1160
+ function workspaceGlobBoundary(workspacePackageGlob) {
1161
+ return {
1162
+ kind: "package-boundary",
1163
+ path: workspacePackageGlob,
1164
+ };
1165
+ }
1166
+ function hasVuePackage(capability) {
1167
+ return capability.packages.some((nodePackage) => nodePackage.kind === "vue-app");
1168
+ }
1169
+ function sourceRootPreset(capability) {
1170
+ if (capability.packages.length === 1) {
1171
+ return capability.packages[0].kind;
1172
+ }
1173
+ return "vue-hono-app";
1174
+ }
1175
+ function projectRootPackageScripts(checkPlan, fixPlan, fragments) {
1176
+ return {
1177
+ check: renderRootCheckCommand(checkPlan),
1178
+ fix: renderFixCommand(fixPlan),
1179
+ ...fragments,
1180
+ };
1181
+ }
1182
+ function projectPackageScripts(checkPlan, fixPlan, fragments) {
1183
+ return {
1184
+ check: renderRootCheckCommand(checkPlan),
1185
+ fix: renderFixCommand(fixPlan),
1186
+ ...fragments,
1187
+ };
1188
+ }
1189
+ function packageCollection(workspacePackageGlob) {
1190
+ return workspacePackageGlob.slice(0, -"/*".length);
1191
+ }
1192
+ function workspacePackagePath(context, capability) {
1193
+ return `${packageCollection(capability.workspacePackageGlob)}/${context.projectName.value}`;
1194
+ }
1195
+ function workspacePackageName(context, capability) {
1196
+ const packageDefinition = context.blueprint.packages?.find((pkg) => pkg.path === workspacePackagePath(context, capability));
1197
+ return (packageDefinition?.name ??
1198
+ `@${context.projectName.value}/${context.projectName.value}`);
1199
+ }
1200
+ function cargoPackageNameFromProjectName(projectName) {
1201
+ const slug = projectName
1202
+ .normalize("NFKD")
1203
+ .replace(/[\u0300-\u036f]/g, "")
1204
+ .toLowerCase()
1205
+ .replace(/[^a-z0-9]+/g, "-")
1206
+ .replace(/^-+|-+$/g, "");
1207
+ return slug || "rust-bin";
1208
+ }
1209
+ function rustProjectName(context) {
1210
+ return cargoPackageNameFromProjectName(context.projectName.value);
1211
+ }
1212
+ function rustWorkspacePackagePath(context) {
1213
+ return `packages/${rustProjectName(context)}`;
1214
+ }
1215
+ function rustWorkspacePackageName(context) {
1216
+ return `${rustProjectName(context)}-native`;
1217
+ }
1218
+ function packageLinkPlanFor(context, capability) {
1219
+ return planPackageLinks([
1220
+ {
1221
+ name: workspacePackageName(context, capability),
1222
+ path: workspacePackagePath(context, capability),
1223
+ role: capability.packageRole,
1224
+ sourcePreset: capability.packageSourcePreset,
1225
+ },
1226
+ ]);
1227
+ }
1228
+ function nodePackageDefinitions(context, capability) {
1229
+ return capability.packages
1230
+ .filter((nodePackage) => nodePackage.kind === "hono-api")
1231
+ .map((nodePackage) => ({
1232
+ name: nodePackageName(context, nodePackage),
1233
+ path: nodePackage.path,
1234
+ role: "runtime-service",
1235
+ sourcePreset: "hono-api",
1236
+ }));
1237
+ }
1238
+ function nodePackageName(context, nodePackage) {
1239
+ const packageDefinition = context.blueprint.packages?.find((pkg) => pkg.path === nodePackage.path);
1240
+ if (packageDefinition !== undefined) {
1241
+ return packageDefinition.name;
1242
+ }
1243
+ const leaf = nodePackage.path.split("/").at(-1) ?? nodePackage.path;
1244
+ return `@${context.projectName.value}/${leaf}`;
1245
+ }
1246
+ function packageScopeFromNodeWorkspace(context) {
1247
+ const apiPackage = context.blueprint.packages?.find((pkg) => pkg.path === "apps/api");
1248
+ if (apiPackage?.name.startsWith("@") && apiPackage.name.endsWith("/api")) {
1249
+ return apiPackage.name.slice(1, -"/api".length);
1250
+ }
1251
+ return context.projectName.value;
1252
+ }
1253
+ function nodePackageLinkPlan(context, capability) {
1254
+ return planPackageLinks(nodePackageDefinitions(context, capability), capability.packageLinks ?? []);
1255
+ }
1256
+ function rootPackageJson(context, packageScripts, state) {
1257
+ return {
1258
+ name: context.projectName.value,
1259
+ version: "0.0.0",
1260
+ private: true,
1261
+ type: "module",
1262
+ scripts: packageScripts,
1263
+ devDependencies: catalogDependencies(state.rootDevDependencies),
1264
+ engines: {
1265
+ node: context.toolchain.nodeLtsMajor.value,
1266
+ },
1267
+ packageManager: context.toolchain.packageManagerPin.value,
1268
+ };
1269
+ }
1270
+ function libraryPackageJson(context, state, capability, scripts) {
1271
+ const packagePath = workspacePackagePath(context, capability);
1272
+ const packageExposure = packageLinkPlanFor(context, capability).exposuresByPackagePath.get(packagePath);
1273
+ if (packageExposure === undefined) {
1274
+ throw new Error(`Missing Package Exposure for ${packagePath}`);
1275
+ }
1276
+ const exposureFields = packageManifestExposureFields(packageExposure);
1277
+ return {
1278
+ name: workspacePackageName(context, capability),
1279
+ version: "0.0.0",
1280
+ private: true,
1281
+ type: "module",
1282
+ imports: exposureFields.imports,
1283
+ exports: exposureFields.exports,
1284
+ dependencies: catalogDependencies(state.packageDependencies),
1285
+ scripts,
1286
+ devDependencies: catalogDependencies(state.packageDevDependencies),
1287
+ engines: {
1288
+ node: context.toolchain.nodeLtsMajor.value,
1289
+ },
1290
+ };
1291
+ }
1292
+ function honoApiPackageJson(context, packageName, scripts, exposureFields) {
1293
+ return {
1294
+ name: packageName,
1295
+ version: "0.0.0",
1296
+ private: true,
1297
+ type: "module",
1298
+ ...(exposureFields === undefined
1299
+ ? {
1300
+ imports: {
1301
+ "#/*": {
1302
+ default: "./dist/*.js",
1303
+ types: "./src/*.ts",
1304
+ },
1305
+ },
1306
+ }
1307
+ : {
1308
+ types: exposureFields.types,
1309
+ exports: exposureFields.exports,
1310
+ imports: exposureFields.imports,
1311
+ }),
1312
+ scripts,
1313
+ dependencies: {
1314
+ "@hono/node-server": "catalog:",
1315
+ hono: "catalog:",
1316
+ },
1317
+ devDependencies: {
1318
+ "@types/node": "catalog:",
1319
+ oxfmt: "catalog:",
1320
+ oxlint: "catalog:",
1321
+ "tsc-alias": "catalog:",
1322
+ ...(scripts.dev === undefined ? {} : { tsx: "catalog:" }),
1323
+ typescript: "catalog:",
1324
+ vitest: "catalog:",
1325
+ },
1326
+ engines: {
1327
+ node: context.toolchain.nodeLtsMajor.value,
1328
+ },
1329
+ };
1330
+ }
1331
+ function vuePackageJson(context, packageName, scripts, packageLinkDependencies = {}) {
1332
+ return {
1333
+ name: packageName,
1334
+ version: "0.0.0",
1335
+ private: true,
1336
+ type: "module",
1337
+ imports: {
1338
+ "#/*": {
1339
+ default: "./src/*.ts",
1340
+ types: "./src/*.ts",
1341
+ },
1342
+ },
1343
+ scripts,
1344
+ dependencies: {
1345
+ ...packageLinkDependencies,
1346
+ "@vueuse/core": "catalog:",
1347
+ ...(Object.keys(packageLinkDependencies).length === 0
1348
+ ? {}
1349
+ : { hono: "catalog:" }),
1350
+ pinia: "catalog:",
1351
+ vue: "catalog:",
1352
+ },
1353
+ devDependencies: {
1354
+ "@playwright/test": "catalog:",
1355
+ "@tailwindcss/vite": "catalog:",
1356
+ "@types/node": "catalog:",
1357
+ "@types/web-bluetooth": "catalog:",
1358
+ "@vitejs/plugin-vue": "catalog:",
1359
+ "@vue/tsconfig": "catalog:",
1360
+ oxfmt: "catalog:",
1361
+ oxlint: "catalog:",
1362
+ tailwindcss: "catalog:",
1363
+ typescript: "catalog:",
1364
+ vite: "catalog:",
1365
+ vitest: "catalog:",
1366
+ "vue-tsc": "catalog:",
1367
+ },
1368
+ engines: {
1369
+ node: context.toolchain.nodeLtsMajor.value,
1370
+ },
1371
+ };
1372
+ }
1373
+ function catalogDependencies(dependencies) {
1374
+ return Object.fromEntries([...dependencies].sort().map((dependency) => [dependency, "catalog:"]));
1375
+ }
1376
+ function generationRecord(context) {
1377
+ return {
1378
+ packageName: "@ykdz/template",
1379
+ version: "0.0.0",
1380
+ command: `template init --preset ${context.blueprint.preset}`,
1381
+ toolchain: {
1382
+ nodeLtsMajor: context.toolchain.nodeLtsMajor.value,
1383
+ packageManagerPin: context.toolchain.packageManagerPin.value,
1384
+ source: context.toolchain.source,
1385
+ },
1386
+ };
1387
+ }
1388
+ function rustCargoToml(projectName) {
1389
+ return [
1390
+ "[package]",
1391
+ `name = "${projectName}"`,
1392
+ 'version = "0.1.0"',
1393
+ 'edition = "2024"',
1394
+ "",
1395
+ "[dependencies]",
1396
+ "",
1397
+ "[lints]",
1398
+ "workspace = true",
1399
+ "",
1400
+ "[workspace]",
1401
+ 'members = ["."]',
1402
+ 'resolver = "3"',
1403
+ "",
1404
+ "[workspace.lints.rust]",
1405
+ 'unsafe_code = "forbid"',
1406
+ "",
1407
+ "[workspace.lints.clippy]",
1408
+ 'all = "deny"',
1409
+ 'pedantic = "deny"',
1410
+ 'nursery = "deny"',
1411
+ "",
1412
+ "[profile.release]",
1413
+ 'strip = "symbols"',
1414
+ 'lto = "thin"',
1415
+ "codegen-units = 1",
1416
+ "",
1417
+ ].join("\n");
1418
+ }
1419
+ function rustCargoLock(projectName) {
1420
+ return [
1421
+ "# This file is automatically @generated by Cargo.",
1422
+ "# It is not intended for manual editing.",
1423
+ "version = 4",
1424
+ "",
1425
+ "[[package]]",
1426
+ `name = "${projectName}"`,
1427
+ 'version = "0.1.0"',
1428
+ "",
1429
+ ].join("\n");
1430
+ }
1431
+ function rustRootPackageJson(context, packageScripts, state) {
1432
+ return {
1433
+ name: rustProjectName(context),
1434
+ version: "0.0.0",
1435
+ private: true,
1436
+ scripts: packageScripts,
1437
+ devDependencies: catalogDependencies(state.rootDevDependencies),
1438
+ engines: {
1439
+ node: context.toolchain.nodeLtsMajor.value,
1440
+ },
1441
+ packageManager: context.toolchain.packageManagerPin.value,
1442
+ };
1443
+ }
1444
+ function rustWorkspacePackageJson(context, scripts) {
1445
+ return {
1446
+ name: rustWorkspacePackageName(context),
1447
+ version: "0.0.0",
1448
+ private: true,
1449
+ scripts,
1450
+ engines: {
1451
+ node: context.toolchain.nodeLtsMajor.value,
1452
+ },
1453
+ };
1454
+ }
1455
+ function workspaceLibraryPackageOperations({ context, state, packageScripts, packageScriptsByPath, }) {
1456
+ if (state.package === undefined) {
1457
+ throw new Error("workspace-library-package capability state is missing");
1458
+ }
1459
+ const capability = state.package;
1460
+ const packagePath = workspacePackagePath(context, capability);
1461
+ const workspacePackageScripts = packageScriptsByPath.get(capability.workspacePackageGlob);
1462
+ if (workspacePackageScripts === undefined) {
1463
+ throw new Error(`Missing package scripts for ${capability.workspacePackageGlob}`);
1464
+ }
1465
+ const rootManifest = rootPackageJson(context, packageScripts, state);
1466
+ const packageManifest = libraryPackageJson(context, state, capability, workspacePackageScripts);
1467
+ const packageLinkPlan = packageLinkPlanFor(context, capability);
1468
+ return [
1469
+ {
1470
+ kind: "writeJson",
1471
+ to: "package.json",
1472
+ value: rootManifest,
1473
+ },
1474
+ {
1475
+ kind: "writeText",
1476
+ to: "pnpm-workspace.yaml",
1477
+ text: renderGeneratedPnpmWorkspaceYaml({
1478
+ packages: [capability.workspacePackageGlob],
1479
+ dependencies: collectGeneratedManifestCatalogDependencies([
1480
+ rootManifest,
1481
+ packageManifest,
1482
+ ]),
1483
+ }),
1484
+ },
1485
+ {
1486
+ kind: "writeJson",
1487
+ to: "turbo.json",
1488
+ value: {
1489
+ tasks: {
1490
+ build: packageLinkPlan.turboTasks.build,
1491
+ check: packageLinkPlan.turboTasks.check,
1492
+ typecheck: packageLinkPlan.turboTasks.typecheck,
1493
+ test: packageLinkPlan.turboTasks.test,
1494
+ "test:e2e": packageLinkPlan.turboTasks["test:e2e"],
1495
+ fix: {
1496
+ cache: false,
1497
+ },
1498
+ },
1499
+ },
1500
+ },
1501
+ {
1502
+ kind: "writeJson",
1503
+ to: `${packagePath}/package.json`,
1504
+ value: packageManifest,
1505
+ multilineArrays: ["files"],
1506
+ },
1507
+ {
1508
+ kind: "writeText",
1509
+ to: ".gitignore",
1510
+ text: [
1511
+ "node_modules",
1512
+ "dist",
1513
+ ".env",
1514
+ ".template/",
1515
+ ".pnpm-store/",
1516
+ "",
1517
+ ].join("\n"),
1518
+ },
1519
+ ...capability.sourceFiles.map((sourceFile) => ({
1520
+ kind: "copyFile",
1521
+ from: sourceFile,
1522
+ to: `${packagePath}/${sourceFile}`,
1523
+ })),
1524
+ {
1525
+ kind: "writeJson",
1526
+ to: ".template/blueprint.json",
1527
+ value: context.blueprint,
1528
+ },
1529
+ {
1530
+ kind: "writeJson",
1531
+ to: ".template/generated-by.json",
1532
+ value: generationRecord(context),
1533
+ },
1534
+ ];
1535
+ }
1536
+ function rustBinaryWorkspaceOperations({ context, state, packageScripts, packageScriptsByPath, }) {
1537
+ if (state.rustWorkspace === undefined) {
1538
+ throw new Error("rust-binary-workspace capability state is missing");
1539
+ }
1540
+ const capability = state.rustWorkspace;
1541
+ const packagePath = rustWorkspacePackagePath(context);
1542
+ const workspacePackageScripts = packageScriptsByPath.get(capability.workspacePackageGlob);
1543
+ if (workspacePackageScripts === undefined) {
1544
+ throw new Error(`Missing package scripts for ${capability.workspacePackageGlob}`);
1545
+ }
1546
+ const rootManifest = rustRootPackageJson(context, packageScripts, state);
1547
+ const packageManifest = rustWorkspacePackageJson(context, workspacePackageScripts);
1548
+ const rustLayer = rustToolLayer();
1549
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
1550
+ const developmentContainer = dockerfileFirstRustPnpmDevcontainer({
1551
+ name: context.projectName.value,
1552
+ nodeLayer: nodePnpmToolLayer({
1553
+ nodeVersion: context.toolchain.nodeLtsMajor.value,
1554
+ packageManagerPin: context.toolchain.packageManagerPin.value,
1555
+ }),
1556
+ rustLayer,
1557
+ extensions: editorCustomization.extensions,
1558
+ settings: editorCustomization.settings,
1559
+ });
1560
+ return [
1561
+ {
1562
+ kind: "writeJson",
1563
+ to: "package.json",
1564
+ value: rootManifest,
1565
+ },
1566
+ {
1567
+ kind: "writeText",
1568
+ to: "pnpm-workspace.yaml",
1569
+ text: renderGeneratedPnpmWorkspaceYaml({
1570
+ packages: [capability.workspacePackageGlob],
1571
+ dependencies: collectGeneratedManifestCatalogDependencies([
1572
+ rootManifest,
1573
+ packageManifest,
1574
+ ]),
1575
+ }),
1576
+ },
1577
+ {
1578
+ kind: "writeJson",
1579
+ to: "turbo.json",
1580
+ value: {
1581
+ tasks: {
1582
+ check: {},
1583
+ fix: {
1584
+ cache: false,
1585
+ },
1586
+ },
1587
+ },
1588
+ },
1589
+ {
1590
+ kind: "writeJson",
1591
+ to: `${packagePath}/package.json`,
1592
+ value: packageManifest,
1593
+ },
1594
+ {
1595
+ kind: "writeText",
1596
+ to: `${packagePath}/Cargo.toml`,
1597
+ text: rustCargoToml(rustProjectName(context)),
1598
+ },
1599
+ {
1600
+ kind: "writeText",
1601
+ to: `${packagePath}/Cargo.lock`,
1602
+ text: rustCargoLock(rustProjectName(context)),
1603
+ },
1604
+ {
1605
+ kind: "copyFile",
1606
+ from: "rustfmt.toml",
1607
+ to: `${packagePath}/rustfmt.toml`,
1608
+ },
1609
+ {
1610
+ kind: "writeTextTemplate",
1611
+ from: "rust-toolchain.toml",
1612
+ to: "rust-toolchain.toml",
1613
+ replacements: {
1614
+ RUST_TOOLCHAIN: rustLayer.toolchain,
1615
+ },
1616
+ },
1617
+ {
1618
+ kind: "writeText",
1619
+ to: ".gitignore",
1620
+ text: [
1621
+ "target",
1622
+ "node_modules",
1623
+ "dist",
1624
+ ".env",
1625
+ ".template/",
1626
+ ".pnpm-store/",
1627
+ "",
1628
+ ].join("\n"),
1629
+ },
1630
+ ...capability.sourceFiles.map((sourceFile) => ({
1631
+ kind: "copyFile",
1632
+ from: sourceFile,
1633
+ to: `${packagePath}/${sourceFile}`,
1634
+ })),
1635
+ {
1636
+ kind: "writeJson",
1637
+ to: ".template/blueprint.json",
1638
+ value: context.blueprint,
1639
+ },
1640
+ {
1641
+ kind: "writeJson",
1642
+ to: ".template/generated-by.json",
1643
+ value: generationRecord(context),
1644
+ },
1645
+ {
1646
+ kind: "writeJson",
1647
+ to: ".devcontainer/devcontainer.json",
1648
+ value: developmentContainer.devcontainer,
1649
+ },
1650
+ {
1651
+ ...developmentContainer.dockerfileOperation,
1652
+ },
1653
+ {
1654
+ kind: "writeJson",
1655
+ to: ".vscode/extensions.json",
1656
+ value: {
1657
+ recommendations: editorCustomization.extensions,
1658
+ },
1659
+ },
1660
+ {
1661
+ kind: "writeJson",
1662
+ to: ".vscode/settings.json",
1663
+ value: editorCustomization.settings,
1664
+ },
1665
+ {
1666
+ kind: "copyFile",
1667
+ from: ".github/workflows/check.yml",
1668
+ to: ".github/workflows/check.yml",
1669
+ },
1670
+ {
1671
+ kind: "writeTextTemplate",
1672
+ from: ".github/dependabot.yml",
1673
+ to: ".github/dependabot.yml",
1674
+ replacements: {
1675
+ CARGO_PACKAGE_DIRECTORY: `/${packagePath}`,
1676
+ },
1677
+ },
1678
+ ];
1679
+ }
1680
+ function workspaceNodePackagesOperations({ context, state, packageScripts, }) {
1681
+ if (state.nodeWorkspace === undefined) {
1682
+ throw new Error("workspace-node-packages capability state is missing");
1683
+ }
1684
+ const capability = state.nodeWorkspace;
1685
+ const packageLinkPlan = nodePackageLinkPlan(context, capability);
1686
+ const packageManifests = capability.packages.map((nodePackage) => {
1687
+ const scripts = nodePackageScripts(nodePackage, capability);
1688
+ const packageName = nodePackageName(context, nodePackage);
1689
+ if (nodePackage.kind === "hono-api") {
1690
+ const exposure = packageLinkPlan.exposuresByPackagePath.get(nodePackage.path);
1691
+ return honoApiPackageJson(context, packageName, scripts, exposure === undefined
1692
+ ? undefined
1693
+ : packageManifestExposureFields(exposure));
1694
+ }
1695
+ return vuePackageJson(context, packageName, scripts, packageLinkPlan.manifestDependenciesByPackagePath.get(nodePackage.path));
1696
+ });
1697
+ const rootManifest = rootPackageJson(context, packageScripts, state);
1698
+ return [
1699
+ {
1700
+ kind: "writeJson",
1701
+ to: "package.json",
1702
+ value: rootManifest,
1703
+ },
1704
+ {
1705
+ kind: "writeText",
1706
+ to: "pnpm-workspace.yaml",
1707
+ text: renderGeneratedPnpmWorkspaceYaml({
1708
+ packages: [capability.workspacePackageGlob],
1709
+ dependencies: capability.packageLinks === undefined
1710
+ ? collectGeneratedManifestCatalogDependencies([
1711
+ rootManifest,
1712
+ ...packageManifests,
1713
+ ])
1714
+ : collectGeneratedManifestCatalogReferences([
1715
+ rootManifest,
1716
+ ...packageManifests,
1717
+ ]),
1718
+ ...(hasVuePackage(capability)
1719
+ ? {
1720
+ allowBuilds: {
1721
+ esbuild: true,
1722
+ },
1723
+ }
1724
+ : {}),
1725
+ }),
1726
+ },
1727
+ {
1728
+ kind: "writeJson",
1729
+ to: "turbo.json",
1730
+ value: {
1731
+ tasks: {
1732
+ build: packageLinkPlan.turboTasks.build,
1733
+ check: packageLinkPlan.turboTasks.check,
1734
+ typecheck: packageLinkPlan.turboTasks.typecheck,
1735
+ test: packageLinkPlan.turboTasks.test,
1736
+ "test:e2e": packageLinkPlan.turboTasks["test:e2e"],
1737
+ ...(hasVuePackage(capability)
1738
+ ? {
1739
+ dev: {
1740
+ cache: false,
1741
+ persistent: true,
1742
+ },
1743
+ }
1744
+ : {}),
1745
+ fix: {
1746
+ cache: false,
1747
+ },
1748
+ },
1749
+ },
1750
+ },
1751
+ {
1752
+ kind: "writeText",
1753
+ to: ".gitignore",
1754
+ text: gitignoreForNodeWorkspace(capability),
1755
+ },
1756
+ ...capability.packages.flatMap((nodePackage, index) => [
1757
+ {
1758
+ kind: "writeJson",
1759
+ to: `${nodePackage.path}/package.json`,
1760
+ value: packageManifests[index],
1761
+ },
1762
+ ...nodePackageTsconfigOperations(nodePackage, capability),
1763
+ ...nodePackage.sourceFiles.map((sourceFile) => ({
1764
+ kind: "copyFile",
1765
+ from: sourceFile,
1766
+ to: `${nodePackage.path}/${sourcePathWithinNodePackage(sourceFile, capability)}`,
1767
+ })),
1768
+ ]),
1769
+ ...fullStackApiAnchorOperations(context, capability),
1770
+ {
1771
+ kind: "writeJson",
1772
+ to: ".template/blueprint.json",
1773
+ value: context.blueprint,
1774
+ },
1775
+ {
1776
+ kind: "writeJson",
1777
+ to: ".template/generated-by.json",
1778
+ value: generationRecord(context),
1779
+ },
1780
+ ];
1781
+ }
1782
+ function nodePackageScripts(nodePackage, workspace) {
1783
+ const oxcScripts = {
1784
+ "format:check": "oxfmt --check --config ../../oxfmt.config.ts .",
1785
+ "format:write": "oxfmt --write --config ../../oxfmt.config.ts .",
1786
+ lint: "oxlint --config ../../oxlint.config.ts . --deny-warnings",
1787
+ "lint:fix": "oxlint --config ../../oxlint.config.ts . --fix --deny-warnings",
1788
+ };
1789
+ if (nodePackage.kind === "hono-api") {
1790
+ const checks = {
1791
+ components: [
1792
+ { kind: "oxc-format-check", owner: workspacePackageBoundary },
1793
+ { kind: "oxc-lint", owner: workspacePackageBoundary },
1794
+ { kind: "typescript-typecheck", owner: workspacePackageBoundary },
1795
+ { kind: "build", owner: workspacePackageBoundary },
1796
+ { kind: "unit-test", owner: workspacePackageBoundary },
1797
+ ],
1798
+ environmentNeeds: [],
1799
+ };
1800
+ const fixes = {
1801
+ components: [
1802
+ { kind: "oxc-format-write", owner: workspacePackageBoundary },
1803
+ { kind: "oxc-lint-fix", owner: workspacePackageBoundary },
1804
+ ],
1805
+ };
1806
+ return {
1807
+ build: "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
1808
+ check: renderRootCheckCommand(checks),
1809
+ ...(workspace.packages.length > 1
1810
+ ? { dev: "tsx watch src/server.ts" }
1811
+ : {}),
1812
+ fix: renderFixCommand(fixes),
1813
+ ...oxcScripts,
1814
+ start: "node dist/server.js",
1815
+ test: "vitest run",
1816
+ typecheck: "tsc -p tsconfig.json --noEmit",
1817
+ };
1818
+ }
1819
+ const checks = {
1820
+ components: [
1821
+ { kind: "oxc-format-check", owner: workspacePackageBoundary },
1822
+ { kind: "oxc-lint", owner: workspacePackageBoundary },
1823
+ { kind: "typescript-typecheck", owner: workspacePackageBoundary },
1824
+ { kind: "build", owner: workspacePackageBoundary },
1825
+ { kind: "unit-test", owner: workspacePackageBoundary },
1826
+ { kind: "e2e-test", owner: workspacePackageBoundary },
1827
+ ],
1828
+ environmentNeeds: [],
1829
+ };
1830
+ const fixes = {
1831
+ components: [
1832
+ { kind: "oxc-format-write", owner: workspacePackageBoundary },
1833
+ { kind: "oxc-lint-fix", owner: workspacePackageBoundary },
1834
+ ],
1835
+ };
1836
+ return {
1837
+ build: "vite build",
1838
+ check: renderRootCheckCommand(checks),
1839
+ dev: "vite",
1840
+ fix: renderFixCommand(fixes),
1841
+ ...oxcScripts,
1842
+ preview: "vite preview",
1843
+ test: "vitest run",
1844
+ "test:e2e": "pnpm run build && playwright test",
1845
+ typecheck: workspace.packages.length > 1
1846
+ ? "vue-tsc --build"
1847
+ : "vue-tsc --build --noEmit",
1848
+ };
1849
+ }
1850
+ function nodePackageTsconfigOperations(nodePackage, workspace) {
1851
+ if (nodePackage.kind === "hono-api") {
1852
+ return [
1853
+ {
1854
+ kind: "writeJson",
1855
+ to: `${nodePackage.path}/tsconfig.json`,
1856
+ value: {
1857
+ compilerOptions: {
1858
+ composite: true,
1859
+ ...(workspace.packages.length > 1
1860
+ ? { declaration: true, declarationMap: true }
1861
+ : {}),
1862
+ module: "NodeNext",
1863
+ moduleResolution: "NodeNext",
1864
+ noEmitOnError: true,
1865
+ skipLibCheck: false,
1866
+ strict: true,
1867
+ target: "ES2022",
1868
+ types: ["node", "vitest/globals"],
1869
+ },
1870
+ include: ["src/**/*.ts", "test/**/*.ts", "vitest.config.ts"],
1871
+ },
1872
+ },
1873
+ {
1874
+ kind: "writeJson",
1875
+ to: `${nodePackage.path}/tsconfig.build.json`,
1876
+ value: {
1877
+ extends: "./tsconfig.json",
1878
+ compilerOptions: {
1879
+ outDir: "dist",
1880
+ rootDir: "src",
1881
+ types: ["node"],
1882
+ },
1883
+ include: ["src/**/*.ts"],
1884
+ },
1885
+ },
1886
+ ];
1887
+ }
1888
+ return [
1889
+ {
1890
+ kind: "writeJson",
1891
+ to: `${nodePackage.path}/tsconfig.json`,
1892
+ value: {
1893
+ files: [],
1894
+ references: [
1895
+ { path: "./tsconfig.app.json" },
1896
+ { path: "./tsconfig.test.json" },
1897
+ { path: "./tsconfig.node.json" },
1898
+ ],
1899
+ },
1900
+ },
1901
+ {
1902
+ kind: "writeJson",
1903
+ to: `${nodePackage.path}/tsconfig.app.json`,
1904
+ value: {
1905
+ extends: "@vue/tsconfig/tsconfig.dom.json",
1906
+ compilerOptions: {
1907
+ composite: true,
1908
+ module: "ESNext",
1909
+ moduleResolution: "Bundler",
1910
+ noEmitOnError: true,
1911
+ skipLibCheck: false,
1912
+ strict: true,
1913
+ target: "ES2022",
1914
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
1915
+ types: ["web-bluetooth"],
1916
+ },
1917
+ include: ["env.d.ts", "src/**/*.ts", "src/**/*.vue"],
1918
+ },
1919
+ },
1920
+ {
1921
+ kind: "writeJson",
1922
+ to: `${nodePackage.path}/tsconfig.test.json`,
1923
+ value: {
1924
+ extends: "./tsconfig.app.json",
1925
+ compilerOptions: {
1926
+ lib: ["ESNext", "DOM", "DOM.Iterable"],
1927
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.test.tsbuildinfo",
1928
+ types: ["node", "vitest/globals", "web-bluetooth"],
1929
+ },
1930
+ include: ["env.d.ts", "src/**/*.ts", "src/**/*.vue", "test/**/*.ts"],
1931
+ },
1932
+ },
1933
+ {
1934
+ kind: "writeJson",
1935
+ to: `${nodePackage.path}/tsconfig.node.json`,
1936
+ value: {
1937
+ compilerOptions: {
1938
+ composite: true,
1939
+ module: "ESNext",
1940
+ moduleResolution: "Bundler",
1941
+ noEmitOnError: true,
1942
+ lib: ["ESNext", "DOM", "DOM.Iterable"],
1943
+ ...(workspace.packages.length > 1
1944
+ ? { outDir: "./node_modules/.tmp/tsconfig.node" }
1945
+ : {}),
1946
+ skipLibCheck: false,
1947
+ strict: true,
1948
+ target: "ES2022",
1949
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
1950
+ types: ["node"],
1951
+ },
1952
+ include: ["playwright.config.ts", "vite.config.ts", "vitest.config.ts"],
1953
+ },
1954
+ },
1955
+ ];
1956
+ }
1957
+ function honoApiTsconfigOperations(packagePath) {
1958
+ const operations = nodePackageTsconfigOperations({
1959
+ kind: "hono-api",
1960
+ path: "apps/api",
1961
+ sourceFiles: [],
1962
+ }, {
1963
+ kind: "workspace-node-packages",
1964
+ workspacePackageGlob: "apps/*",
1965
+ packages: [
1966
+ {
1967
+ kind: "hono-api",
1968
+ path: "apps/api",
1969
+ sourceFiles: [],
1970
+ },
1971
+ ],
1972
+ });
1973
+ return operations.map((operation) => ({
1974
+ ...operation,
1975
+ to: operation.to.replace(/^apps\/api/, packagePath),
1976
+ }));
1977
+ }
1978
+ function vueAppTsconfigOperations(packagePath) {
1979
+ const operations = nodePackageTsconfigOperations({
1980
+ kind: "vue-app",
1981
+ path: "apps/web",
1982
+ sourceFiles: [],
1983
+ }, {
1984
+ kind: "workspace-node-packages",
1985
+ workspacePackageGlob: "apps/*",
1986
+ packages: [
1987
+ {
1988
+ kind: "vue-app",
1989
+ path: "apps/web",
1990
+ sourceFiles: [],
1991
+ },
1992
+ ],
1993
+ });
1994
+ return operations.map((operation) => ({
1995
+ ...operation,
1996
+ to: operation.to.replace(/^apps\/web/, packagePath),
1997
+ }));
1998
+ }
1999
+ function sourcePathWithinNodePackage(sourceFile, workspace) {
2000
+ if (workspace.packages.length === 1) {
2001
+ return sourceFile;
2002
+ }
2003
+ return sourceFile.replace(/^(api|web)\//, "");
2004
+ }
2005
+ function gitignoreForNodeWorkspace(workspace) {
2006
+ return [
2007
+ "node_modules",
2008
+ "dist",
2009
+ ...(hasVuePackage(workspace) ? ["playwright-report", "test-results"] : []),
2010
+ ".env",
2011
+ ".template/",
2012
+ ".pnpm-store/",
2013
+ "",
2014
+ ].join("\n");
2015
+ }
2016
+ function fullStackApiAnchorOperations(context, workspace) {
2017
+ if (workspace.packageLinks?.some((link) => link.consumerPackagePath === "apps/web" &&
2018
+ link.providerPackagePath === "apps/api") !== true) {
2019
+ return [];
2020
+ }
2021
+ const packageScope = packageScopeFromNodeWorkspace(context);
2022
+ return [
2023
+ {
2024
+ kind: "replaceAnchors",
2025
+ path: "apps/web/src/api.ts",
2026
+ language: "typescript",
2027
+ replacements: {
2028
+ "api-type-import-start": `import type { AppType } from "@${packageScope}/api";\nimport { hc } from "hono/client";\n/*`,
2029
+ "api-type-import-end": "*/",
2030
+ },
2031
+ },
2032
+ ];
2033
+ }
2034
+ function strictTypescriptOperations({ context, state, }) {
2035
+ if (state.package === undefined && state.nodeWorkspace === undefined) {
2036
+ throw new Error("strict-typescript-root requires a workspace package layout");
2037
+ }
2038
+ if (state.nodeWorkspace !== undefined) {
2039
+ return [
2040
+ {
2041
+ kind: "writeJson",
2042
+ to: "tsconfig.json",
2043
+ value: {
2044
+ files: [],
2045
+ references: rootTsconfigReferences(state.nodeWorkspace),
2046
+ },
2047
+ },
2048
+ {
2049
+ kind: "writeJson",
2050
+ to: "tsconfig.config.json",
2051
+ value: {
2052
+ compilerOptions: {
2053
+ module: "NodeNext",
2054
+ moduleResolution: "NodeNext",
2055
+ noEmitOnError: true,
2056
+ skipLibCheck: false,
2057
+ strict: true,
2058
+ target: "ES2022",
2059
+ },
2060
+ include: ["oxlint.config.ts", "oxfmt.config.ts"],
2061
+ },
2062
+ },
2063
+ ];
2064
+ }
2065
+ const libraryPackage = state.package;
2066
+ return [
2067
+ {
2068
+ kind: "writeJson",
2069
+ to: "tsconfig.json",
2070
+ value: {
2071
+ files: [],
2072
+ },
2073
+ },
2074
+ {
2075
+ kind: "writeJson",
2076
+ to: "tsconfig.config.json",
2077
+ value: {
2078
+ compilerOptions: {
2079
+ module: "NodeNext",
2080
+ moduleResolution: "NodeNext",
2081
+ noEmitOnError: true,
2082
+ skipLibCheck: false,
2083
+ strict: true,
2084
+ target: "ES2022",
2085
+ },
2086
+ include: ["oxlint.config.ts", "oxfmt.config.ts"],
2087
+ },
2088
+ },
2089
+ {
2090
+ kind: "writeJson",
2091
+ to: `${workspacePackagePath(context, libraryPackage)}/tsconfig.json`,
2092
+ value: {
2093
+ compilerOptions: {
2094
+ composite: true,
2095
+ declaration: true,
2096
+ declarationMap: true,
2097
+ module: "NodeNext",
2098
+ moduleResolution: "NodeNext",
2099
+ noEmitOnError: true,
2100
+ rootDir: "src",
2101
+ skipLibCheck: false,
2102
+ strict: true,
2103
+ target: "ES2022",
2104
+ types: ["node"],
2105
+ },
2106
+ include: ["src/**/*.ts"],
2107
+ },
2108
+ },
2109
+ ];
2110
+ }
2111
+ function rootTsconfigReferences(workspace) {
2112
+ return workspace.packages.flatMap((nodePackage) => {
2113
+ if (nodePackage.kind === "hono-api") {
2114
+ return hasVuePackage(workspace)
2115
+ ? []
2116
+ : [{ path: `./${nodePackage.path}/tsconfig.json` }];
2117
+ }
2118
+ return [
2119
+ { path: `./${nodePackage.path}/tsconfig.app.json` },
2120
+ { path: `./${nodePackage.path}/tsconfig.test.json` },
2121
+ { path: `./${nodePackage.path}/tsconfig.node.json` },
2122
+ ];
2123
+ });
2124
+ }
2125
+ function oxcFormatLintOperations({ state, }) {
2126
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
2127
+ return [
2128
+ {
2129
+ kind: "copyFile",
2130
+ sourceRoot: "sharedOxc",
2131
+ from: state.nodeWorkspace && hasVuePackage(state.nodeWorkspace)
2132
+ ? "vue/oxlint.config.ts"
2133
+ : "node/oxlint.config.ts",
2134
+ to: "oxlint.config.ts",
2135
+ },
2136
+ {
2137
+ kind: "copyFile",
2138
+ sourceRoot: "sharedOxc",
2139
+ from: "oxfmt.config.ts",
2140
+ to: "oxfmt.config.ts",
2141
+ },
2142
+ {
2143
+ kind: "writeJson",
2144
+ to: ".vscode/extensions.json",
2145
+ value: {
2146
+ recommendations: editorCustomization.extensions,
2147
+ },
2148
+ multilineArrays: ["recommendations"],
2149
+ },
2150
+ {
2151
+ kind: "writeJson",
2152
+ to: ".vscode/settings.json",
2153
+ value: editorCustomization.settings,
2154
+ },
2155
+ ];
2156
+ }
2157
+ function nodePnpmDevcontainerOperations({ context, state, }) {
2158
+ const editorCustomization = editorCustomizationForCapabilities(state.editorCustomizationCapabilities);
2159
+ const developmentContainer = checkedDockerfileFirstNodePnpmDevcontainer({
2160
+ name: context.projectName.value,
2161
+ layer: nodePnpmToolLayer({
2162
+ nodeVersion: context.toolchain.nodeLtsMajor.value,
2163
+ packageManagerPin: context.toolchain.packageManagerPin.value,
2164
+ }),
2165
+ additionalLayers: state.nodeWorkspace && hasVuePackage(state.nodeWorkspace)
2166
+ ? [browserTestToolLayer()]
2167
+ : [],
2168
+ extensions: editorCustomization.extensions,
2169
+ settings: editorCustomization.settings,
2170
+ });
2171
+ return [
2172
+ {
2173
+ kind: "writeJson",
2174
+ to: ".devcontainer/devcontainer.json",
2175
+ value: developmentContainer.devcontainer,
2176
+ },
2177
+ {
2178
+ ...developmentContainer.dockerfileOperation,
2179
+ },
2180
+ ];
2181
+ }
2182
+ function githubMaintenanceOperations() {
2183
+ return [
2184
+ {
2185
+ kind: "copyFile",
2186
+ from: ".github/workflows/check.yml",
2187
+ to: ".github/workflows/check.yml",
2188
+ },
2189
+ {
2190
+ kind: "copyFile",
2191
+ from: ".github/dependabot.yml",
2192
+ to: ".github/dependabot.yml",
2193
+ },
2194
+ ];
2195
+ }
2196
+ function templateSourceRoot(sourcePreset) {
2197
+ return templateSourceRootForPreset(sourcePreset);
2198
+ }
2199
+ function templateSourceRootForPreset(sourcePreset) {
2200
+ return packageTemplateRoot(path.dirname(fileURLToPath(import.meta.url)), sourcePreset);
2201
+ }
2202
+ function sharedOxcSourceRoot() {
2203
+ return packageTemplateRoot(path.dirname(fileURLToPath(import.meta.url)), "shared", "oxc");
2204
+ }
2205
+ function sharedDevcontainerSourceRoot() {
2206
+ return packageTemplateRoot(path.dirname(fileURLToPath(import.meta.url)), "shared", "devcontainer");
2207
+ }