@ykdz/template 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli.js +2 -2
  2. package/node_modules/@ykdz/template-builtin-source/dist/src/index.d.ts +4 -4
  3. package/node_modules/@ykdz/template-builtin-source/dist/src/index.js +12 -5
  4. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.d.ts +1 -1
  5. package/node_modules/@ykdz/template-builtin-source/dist/templates/hono-api/projection.js +1 -1
  6. package/node_modules/@ykdz/template-builtin-source/dist/templates/projection-plans.js +9 -6
  7. package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.d.ts +1 -1
  8. package/node_modules/@ykdz/template-builtin-source/dist/templates/rust-bin/projection.js +1 -1
  9. package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.d.ts +1 -1
  10. package/node_modules/@ykdz/template-builtin-source/dist/templates/ts-lib/projection.js +1 -1
  11. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.d.ts +1 -1
  12. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-app/projection.js +9 -4
  13. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.d.ts +1 -1
  14. package/node_modules/@ykdz/template-builtin-source/dist/templates/vue-hono-app/projection.js +7 -7
  15. package/node_modules/@ykdz/template-builtin-source/package.json +4 -3
  16. package/node_modules/@ykdz/template-builtin-source/templates/preset-source.json +39 -11
  17. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/scripts/run-playwright.ts +25 -0
  18. package/node_modules/@ykdz/template-builtin-source/templates/vue-app/src/App.vue +18 -4
  19. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/scripts/run-playwright.ts +25 -0
  20. package/node_modules/@ykdz/template-builtin-source/templates/vue-hono-app/web/src/App.vue +18 -4
  21. package/node_modules/@ykdz/template-core/dist/declarations.d.ts +2 -203
  22. package/node_modules/@ykdz/template-core/dist/declarations.js +1 -303
  23. package/node_modules/@ykdz/template-core/dist/devcontainer.d.ts +17 -0
  24. package/node_modules/@ykdz/template-core/dist/devcontainer.js +22 -21
  25. package/node_modules/@ykdz/template-core/dist/editor-customization.d.ts +10 -1
  26. package/node_modules/@ykdz/template-core/dist/editor-customization.js +36 -26
  27. package/node_modules/@ykdz/template-core/dist/generated-scenarios.d.ts +5 -0
  28. package/node_modules/@ykdz/template-core/dist/generated-scenarios.js +42 -24
  29. package/node_modules/@ykdz/template-core/dist/generation-context.d.ts +1 -1
  30. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +19 -1
  31. package/node_modules/@ykdz/template-core/dist/module-graph.js +42 -3
  32. package/node_modules/@ykdz/template-core/dist/next-step-instructions.d.ts +2 -0
  33. package/node_modules/@ykdz/template-core/dist/next-step-instructions.js +11 -22
  34. package/node_modules/@ykdz/template-core/dist/package-addition-support.d.ts +2 -1
  35. package/node_modules/@ykdz/template-core/dist/package-addition-support.js +2 -4
  36. package/node_modules/@ykdz/template-core/dist/package-addition.js +4 -3
  37. package/node_modules/@ykdz/template-core/dist/package-linking.d.ts +2 -6
  38. package/node_modules/@ykdz/template-core/dist/package-linking.js +1 -1
  39. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +3 -5
  40. package/node_modules/@ykdz/template-core/dist/preset-projection.d.ts +1 -2
  41. package/node_modules/@ykdz/template-core/dist/preset-source.d.ts +4 -418
  42. package/node_modules/@ykdz/template-core/dist/preset-source.js +81 -726
  43. package/node_modules/@ykdz/template-core/dist/project-github.js +2 -6
  44. package/node_modules/@ykdz/template-core/dist/projection-capabilities.d.ts +5 -53
  45. package/node_modules/@ykdz/template-core/dist/projection-capabilities.js +108 -507
  46. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +1 -1
  47. package/node_modules/@ykdz/template-core/package.json +4 -3
  48. package/node_modules/@ykdz/template-shared/dist/index.d.ts +794 -0
  49. package/node_modules/@ykdz/template-shared/dist/index.js +1637 -0
  50. package/node_modules/@ykdz/template-shared/package.json +32 -0
  51. package/package.json +9 -6
@@ -0,0 +1,1637 @@
1
+ import * as v from "valibot";
2
+ export const PackageAdditionSupport = {
3
+ Supported: "supported",
4
+ Unsupported: "unsupported",
5
+ };
6
+ export const packageAdditionSupportValues = [
7
+ PackageAdditionSupport.Supported,
8
+ PackageAdditionSupport.Unsupported,
9
+ ];
10
+ export const packageManagerValues = [
11
+ "pnpm",
12
+ ];
13
+ export const presetGenerationValues = [
14
+ "supported",
15
+ "future",
16
+ ];
17
+ export const projectKindValues = [
18
+ "single-package",
19
+ "multi-package",
20
+ ];
21
+ export const projectKindJsonSchemaEnum = [
22
+ "multi-package",
23
+ ];
24
+ export const featureNameValues = [
25
+ "pnpm-catalog",
26
+ "oxc-format-lint",
27
+ "strict-typescript",
28
+ "root-check",
29
+ "fix-command",
30
+ "devcontainer",
31
+ "github-actions",
32
+ "dependabot",
33
+ "rustfmt-clippy",
34
+ "cargo-test",
35
+ "native-binary-release",
36
+ ];
37
+ export const packageManagerJsonSchemaEnum = packageManagerValues;
38
+ export const presetGenerationJsonSchemaEnum = presetGenerationValues;
39
+ export const featureNameJsonSchemaEnum = featureNameValues;
40
+ export const projectionCapabilityKinds = [
41
+ "workspace-library-package",
42
+ "workspace-node-packages",
43
+ "rust-binary-workspace",
44
+ "strict-typescript-root",
45
+ "oxc-format-lint",
46
+ "node-pnpm-devcontainer",
47
+ "github-maintenance",
48
+ ];
49
+ export const packageLeafNamePattern = "^[a-z0-9][a-z0-9-]*$";
50
+ const packageLeafNameRegExp = new RegExp(packageLeafNamePattern);
51
+ export const requiredFixtureMatrixCheckRequirements = [
52
+ "machine-verifiable-next-steps",
53
+ "root-check-ci",
54
+ ];
55
+ export const requiredFixtureMatrixEnvironmentPreparation = [
56
+ "playwright-browser-assets",
57
+ ];
58
+ export const presetSourceManifestJsonSchema = {
59
+ $schema: "https://json-schema.org/draft/2020-12/schema",
60
+ $id: "https://ykdz.dev/schemas/template/preset-source-manifest.schema.json",
61
+ title: "Preset Source Manifest",
62
+ type: "object",
63
+ additionalProperties: false,
64
+ required: ["schemaVersion", "name", "presets"],
65
+ properties: {
66
+ schemaVersion: { const: 1 },
67
+ name: { type: "string", minLength: 1 },
68
+ sharedResources: {
69
+ type: "array",
70
+ items: {
71
+ type: "object",
72
+ additionalProperties: false,
73
+ required: ["id", "path"],
74
+ properties: {
75
+ id: { type: "string", minLength: 1 },
76
+ path: { type: "string", minLength: 1 },
77
+ },
78
+ },
79
+ },
80
+ fixtureMatrix: {
81
+ type: "object",
82
+ additionalProperties: false,
83
+ required: [
84
+ "initSupport",
85
+ "packageAdditionSupport",
86
+ "supportedCombinations",
87
+ "semanticSkips",
88
+ "checkRequirements",
89
+ "environmentPreparation",
90
+ ],
91
+ properties: {
92
+ initSupport: {
93
+ type: "array",
94
+ items: {
95
+ type: "object",
96
+ additionalProperties: false,
97
+ required: ["preset"],
98
+ properties: { preset: { type: "string", minLength: 1 } },
99
+ },
100
+ },
101
+ packageAdditionSupport: {
102
+ type: "array",
103
+ items: {
104
+ type: "object",
105
+ additionalProperties: false,
106
+ required: ["preset", "packageLeafName"],
107
+ properties: {
108
+ preset: { type: "string", minLength: 1 },
109
+ packageLeafName: {
110
+ type: "string",
111
+ minLength: 1,
112
+ pattern: packageLeafNamePattern,
113
+ },
114
+ },
115
+ },
116
+ },
117
+ supportedCombinations: {
118
+ type: "array",
119
+ items: {
120
+ type: "object",
121
+ additionalProperties: false,
122
+ required: ["basePreset", "addedPreset"],
123
+ properties: {
124
+ basePreset: { type: "string", minLength: 1 },
125
+ addedPreset: { type: "string", minLength: 1 },
126
+ linkFrom: {
127
+ type: "array",
128
+ items: { type: "string", minLength: 1 },
129
+ },
130
+ },
131
+ },
132
+ },
133
+ semanticSkips: {
134
+ type: "array",
135
+ items: {
136
+ type: "object",
137
+ additionalProperties: false,
138
+ required: ["basePreset", "addedPreset", "reason"],
139
+ properties: {
140
+ basePreset: { type: "string", minLength: 1 },
141
+ addedPreset: { type: "string", minLength: 1 },
142
+ reason: { type: "string", minLength: 1 },
143
+ },
144
+ },
145
+ },
146
+ checkRequirements: {
147
+ type: "array",
148
+ items: { enum: [...requiredFixtureMatrixCheckRequirements] },
149
+ uniqueItems: true,
150
+ },
151
+ environmentPreparation: {
152
+ type: "array",
153
+ items: { enum: [...requiredFixtureMatrixEnvironmentPreparation] },
154
+ uniqueItems: true,
155
+ },
156
+ },
157
+ },
158
+ presets: {
159
+ type: "array",
160
+ minItems: 1,
161
+ items: {
162
+ type: "object",
163
+ additionalProperties: false,
164
+ required: [
165
+ "name",
166
+ "title",
167
+ "description",
168
+ "generation",
169
+ "supportedPackageManagers",
170
+ "supportedProjectKinds",
171
+ "packageAdditionSupport",
172
+ "features",
173
+ ],
174
+ properties: {
175
+ name: { type: "string", minLength: 1 },
176
+ title: { type: "string", minLength: 1 },
177
+ description: { type: "string", minLength: 1 },
178
+ generation: { enum: presetGenerationJsonSchemaEnum },
179
+ supportedPackageManagers: {
180
+ type: "array",
181
+ items: { enum: packageManagerJsonSchemaEnum },
182
+ uniqueItems: true,
183
+ },
184
+ supportedProjectKinds: {
185
+ type: "array",
186
+ minItems: 1,
187
+ items: { enum: projectKindJsonSchemaEnum },
188
+ uniqueItems: true,
189
+ },
190
+ packageAdditionSupport: { enum: packageAdditionSupportValues },
191
+ features: {
192
+ type: "array",
193
+ items: { enum: featureNameJsonSchemaEnum },
194
+ uniqueItems: true,
195
+ },
196
+ dependencyCatalog: {
197
+ type: "array",
198
+ items: { type: "string", minLength: 1 },
199
+ uniqueItems: true,
200
+ },
201
+ projection: {
202
+ type: "object",
203
+ additionalProperties: false,
204
+ required: ["capabilities"],
205
+ properties: {
206
+ capabilities: {
207
+ type: "array",
208
+ items: {
209
+ oneOf: [
210
+ {
211
+ type: "object",
212
+ additionalProperties: false,
213
+ required: [
214
+ "kind",
215
+ "workspacePackageGlob",
216
+ "packageRole",
217
+ "packageSourcePreset",
218
+ "sourceFiles",
219
+ ],
220
+ properties: {
221
+ kind: { const: "workspace-library-package" },
222
+ workspacePackageGlob: { const: "packages/*" },
223
+ packageRole: { const: "shared-library" },
224
+ packageSourcePreset: { const: "ts-lib" },
225
+ sourceFiles: {
226
+ type: "array",
227
+ minItems: 1,
228
+ items: { type: "string", minLength: 1 },
229
+ },
230
+ },
231
+ },
232
+ {
233
+ type: "object",
234
+ additionalProperties: false,
235
+ required: ["kind", "workspacePackageGlob", "packages"],
236
+ properties: {
237
+ kind: { const: "workspace-node-packages" },
238
+ workspacePackageGlob: { const: "apps/*" },
239
+ packages: {
240
+ type: "array",
241
+ minItems: 1,
242
+ items: {
243
+ type: "object",
244
+ additionalProperties: false,
245
+ required: ["kind", "path", "sourceFiles"],
246
+ properties: {
247
+ kind: { enum: ["hono-api", "vue-app"] },
248
+ path: { enum: ["apps/api", "apps/web"] },
249
+ sourceFiles: {
250
+ type: "array",
251
+ minItems: 1,
252
+ items: { type: "string", minLength: 1 },
253
+ },
254
+ },
255
+ },
256
+ },
257
+ packageLinks: {
258
+ type: "array",
259
+ items: {
260
+ type: "object",
261
+ additionalProperties: false,
262
+ required: [
263
+ "consumerPackagePath",
264
+ "providerPackagePath",
265
+ ],
266
+ properties: {
267
+ consumerPackagePath: { const: "apps/web" },
268
+ providerPackagePath: { const: "apps/api" },
269
+ },
270
+ },
271
+ },
272
+ },
273
+ },
274
+ {
275
+ type: "object",
276
+ additionalProperties: false,
277
+ required: [
278
+ "kind",
279
+ "workspacePackageGlob",
280
+ "sourceFiles",
281
+ "devcontainerResourceId",
282
+ "editorCustomizationResourceId",
283
+ ],
284
+ properties: {
285
+ kind: { const: "rust-binary-workspace" },
286
+ workspacePackageGlob: { const: "packages/*" },
287
+ sourceFiles: {
288
+ type: "array",
289
+ minItems: 1,
290
+ items: { type: "string", minLength: 1 },
291
+ },
292
+ devcontainerResourceId: {
293
+ type: "string",
294
+ minLength: 1,
295
+ },
296
+ editorCustomizationResourceId: {
297
+ type: "string",
298
+ minLength: 1,
299
+ },
300
+ },
301
+ },
302
+ {
303
+ type: "object",
304
+ additionalProperties: false,
305
+ required: ["kind"],
306
+ properties: { kind: { const: "strict-typescript-root" } },
307
+ },
308
+ {
309
+ type: "object",
310
+ additionalProperties: false,
311
+ required: ["kind", "editorCustomizationResourceId"],
312
+ properties: {
313
+ kind: { const: "oxc-format-lint" },
314
+ editorCustomizationResourceId: {
315
+ type: "string",
316
+ minLength: 1,
317
+ },
318
+ },
319
+ },
320
+ {
321
+ type: "object",
322
+ additionalProperties: false,
323
+ required: ["kind", "devcontainerResourceId"],
324
+ properties: {
325
+ kind: { const: "node-pnpm-devcontainer" },
326
+ devcontainerResourceId: {
327
+ type: "string",
328
+ minLength: 1,
329
+ },
330
+ },
331
+ },
332
+ {
333
+ type: "object",
334
+ additionalProperties: false,
335
+ required: ["kind"],
336
+ properties: { kind: { const: "github-maintenance" } },
337
+ },
338
+ ],
339
+ },
340
+ },
341
+ },
342
+ },
343
+ source: {
344
+ type: "object",
345
+ additionalProperties: false,
346
+ properties: {
347
+ roots: {
348
+ type: "array",
349
+ items: { type: "string", minLength: 1 },
350
+ },
351
+ files: {
352
+ type: "array",
353
+ items: { type: "string", minLength: 1 },
354
+ },
355
+ sharedResources: {
356
+ type: "array",
357
+ items: { type: "string", minLength: 1 },
358
+ },
359
+ },
360
+ },
361
+ },
362
+ },
363
+ },
364
+ },
365
+ };
366
+ const nonEmptyString = v.pipe(v.string(), v.minLength(1));
367
+ export const presetGenerationSchema = v.picklist(presetGenerationValues);
368
+ export const packageManagerSchema = v.picklist(packageManagerValues);
369
+ export const projectKindSchema = v.picklist(projectKindValues);
370
+ export const featureNameSchema = v.picklist(featureNameValues);
371
+ const packageRoleSchema = v.picklist([
372
+ "runtime-service",
373
+ "shared-library",
374
+ ]);
375
+ const packageSourcePresetSchema = v.picklist([
376
+ "hono-api",
377
+ "ts-lib",
378
+ "vue-app",
379
+ ]);
380
+ const presetSourceManifestPresetSourceSchema = v.strictObject({
381
+ roots: v.optional(v.array(nonEmptyString), []),
382
+ files: v.optional(v.array(nonEmptyString), []),
383
+ sharedResources: v.optional(v.array(nonEmptyString), []),
384
+ });
385
+ const fixtureMatrixPresetSupportSchema = v.strictObject({
386
+ preset: nonEmptyString,
387
+ });
388
+ const fixtureMatrixPackageAdditionSupportSchema = v.strictObject({
389
+ preset: nonEmptyString,
390
+ packageLeafName: v.pipe(v.string(), v.regex(packageLeafNameRegExp, "Fixture Matrix Package Addition packageLeafName must be a lowercase package leaf name using letters, numbers, and hyphens")),
391
+ });
392
+ const fixtureMatrixCombinationSchema = v.strictObject({
393
+ basePreset: nonEmptyString,
394
+ addedPreset: nonEmptyString,
395
+ linkFrom: v.optional(v.array(nonEmptyString), []),
396
+ });
397
+ const fixtureMatrixSemanticSkipSchema = v.strictObject({
398
+ basePreset: nonEmptyString,
399
+ addedPreset: nonEmptyString,
400
+ reason: nonEmptyString,
401
+ });
402
+ const fixtureMatrixContractSchema = v.strictObject({
403
+ initSupport: v.array(fixtureMatrixPresetSupportSchema),
404
+ packageAdditionSupport: v.array(fixtureMatrixPackageAdditionSupportSchema),
405
+ supportedCombinations: v.array(fixtureMatrixCombinationSchema),
406
+ semanticSkips: v.array(fixtureMatrixSemanticSkipSchema),
407
+ checkRequirements: v.array(v.picklist(requiredFixtureMatrixCheckRequirements)),
408
+ environmentPreparation: v.array(v.picklist(requiredFixtureMatrixEnvironmentPreparation)),
409
+ });
410
+ export const presetProjectionDeclarationSchema = v.strictObject({
411
+ capabilities: v.array(v.looseObject({
412
+ kind: v.string(),
413
+ })),
414
+ });
415
+ export const presetSourceManifestSchema = v.strictObject({
416
+ schemaVersion: v.literal(1),
417
+ name: nonEmptyString,
418
+ sharedResources: v.optional(v.array(v.strictObject({
419
+ id: nonEmptyString,
420
+ path: nonEmptyString,
421
+ })), []),
422
+ fixtureMatrix: v.optional(fixtureMatrixContractSchema),
423
+ presets: v.pipe(v.array(v.strictObject({
424
+ name: nonEmptyString,
425
+ title: nonEmptyString,
426
+ description: nonEmptyString,
427
+ generation: presetGenerationSchema,
428
+ supportedPackageManagers: v.array(packageManagerSchema),
429
+ supportedProjectKinds: v.pipe(v.array(projectKindSchema), v.minLength(1)),
430
+ packageAdditionSupport: v.picklist(packageAdditionSupportValues),
431
+ features: v.array(featureNameSchema),
432
+ dependencyCatalog: v.optional(v.array(nonEmptyString), []),
433
+ projection: v.optional(presetProjectionDeclarationSchema),
434
+ source: v.optional(presetSourceManifestPresetSourceSchema),
435
+ })), v.minLength(1)),
436
+ });
437
+ export const presetFileJsonSchema = {
438
+ $schema: "https://json-schema.org/draft/2020-12/schema",
439
+ $id: "https://ykdz.dev/schemas/project-kit/preset-file.schema.json",
440
+ title: "Project Kit Preset File",
441
+ type: "object",
442
+ additionalProperties: false,
443
+ required: [
444
+ "schemaVersion",
445
+ "name",
446
+ "title",
447
+ "description",
448
+ "supportedPackageManagers",
449
+ "supportedProjectKinds",
450
+ "features",
451
+ ],
452
+ properties: {
453
+ schemaVersion: { const: 1 },
454
+ name: { type: "string", minLength: 1 },
455
+ title: { type: "string", minLength: 1 },
456
+ description: { type: "string", minLength: 1 },
457
+ supportedPackageManagers: {
458
+ type: "array",
459
+ items: { enum: packageManagerJsonSchemaEnum },
460
+ uniqueItems: true,
461
+ },
462
+ supportedProjectKinds: {
463
+ type: "array",
464
+ minItems: 1,
465
+ items: { enum: projectKindJsonSchemaEnum },
466
+ uniqueItems: true,
467
+ },
468
+ features: {
469
+ type: "array",
470
+ items: { enum: featureNameJsonSchemaEnum },
471
+ uniqueItems: true,
472
+ },
473
+ },
474
+ };
475
+ export const blueprintJsonSchema = {
476
+ $schema: "https://json-schema.org/draft/2020-12/schema",
477
+ $id: "https://ykdz.dev/schemas/project-kit/blueprint.schema.json",
478
+ title: "Project Kit Blueprint",
479
+ type: "object",
480
+ additionalProperties: false,
481
+ required: ["schemaVersion", "preset", "projectKind", "features"],
482
+ properties: {
483
+ schemaVersion: { const: 1 },
484
+ preset: { type: "string", minLength: 1 },
485
+ packageManager: { enum: packageManagerJsonSchemaEnum },
486
+ projectKind: { enum: projectKindJsonSchemaEnum },
487
+ features: {
488
+ type: "array",
489
+ items: { enum: featureNameJsonSchemaEnum },
490
+ uniqueItems: true,
491
+ },
492
+ packages: {
493
+ type: "array",
494
+ items: {
495
+ type: "object",
496
+ additionalProperties: false,
497
+ required: ["name", "path"],
498
+ properties: {
499
+ name: { type: "string", minLength: 1 },
500
+ path: { type: "string", minLength: 1 },
501
+ role: { enum: ["runtime-service", "shared-library"] },
502
+ sourcePreset: { enum: ["hono-api", "ts-lib", "vue-app"] },
503
+ },
504
+ },
505
+ },
506
+ packageLinkIntents: {
507
+ type: "array",
508
+ items: {
509
+ type: "object",
510
+ additionalProperties: false,
511
+ required: ["consumerPackagePath", "providerPackagePath"],
512
+ properties: {
513
+ consumerPackagePath: { type: "string", minLength: 1 },
514
+ providerPackagePath: { type: "string", minLength: 1 },
515
+ },
516
+ },
517
+ },
518
+ },
519
+ };
520
+ export const presetFileSchema = v.strictObject({
521
+ schemaVersion: v.literal(1),
522
+ name: nonEmptyString,
523
+ title: nonEmptyString,
524
+ description: nonEmptyString,
525
+ supportedPackageManagers: v.array(packageManagerSchema),
526
+ supportedProjectKinds: v.pipe(v.array(projectKindSchema), v.minLength(1)),
527
+ features: v.array(featureNameSchema),
528
+ });
529
+ export const projectBlueprintSchema = v.strictObject({
530
+ schemaVersion: v.literal(1),
531
+ preset: nonEmptyString,
532
+ packageManager: v.optional(packageManagerSchema),
533
+ projectKind: projectKindSchema,
534
+ features: v.array(featureNameSchema),
535
+ packages: v.optional(v.array(v.strictObject({
536
+ name: nonEmptyString,
537
+ path: nonEmptyString,
538
+ role: v.optional(packageRoleSchema),
539
+ sourcePreset: v.optional(packageSourcePresetSchema),
540
+ }))),
541
+ packageLinkIntents: v.optional(v.array(v.strictObject({
542
+ consumerPackagePath: nonEmptyString,
543
+ providerPackagePath: nonEmptyString,
544
+ }))),
545
+ });
546
+ function formatIssuePath(issue) {
547
+ if (!issue.path || issue.path.length === 0) {
548
+ return "$";
549
+ }
550
+ return issue.path.reduce((path, item) => `${path}.${String(item.key)}`, "$");
551
+ }
552
+ function shapeIssues(issues) {
553
+ return issues.map((issue) => ({
554
+ path: formatIssuePath(issue),
555
+ message: issue.message,
556
+ }));
557
+ }
558
+ function duplicateIssues(values, path) {
559
+ const seen = new Set();
560
+ const duplicates = new Set();
561
+ for (const value of values) {
562
+ if (seen.has(value)) {
563
+ duplicates.add(value);
564
+ }
565
+ seen.add(value);
566
+ }
567
+ return [...duplicates].map((value) => ({
568
+ path,
569
+ message: `Duplicate value: ${value}`,
570
+ }));
571
+ }
572
+ function generationSupportIssue(preset, path) {
573
+ if (preset.generation === "supported") {
574
+ return undefined;
575
+ }
576
+ return {
577
+ path,
578
+ message: `Preset ${preset.name} is not supported for generation in this version`,
579
+ };
580
+ }
581
+ function unsupportedSinglePackageProjectShapeIssue(path) {
582
+ return {
583
+ path,
584
+ message: "single-package Project Shape is unsupported in V1; use the workspace monorepo Project Shape",
585
+ };
586
+ }
587
+ function formatBracketIssuePath(issue) {
588
+ if (!issue.path || issue.path.length === 0) {
589
+ return "$";
590
+ }
591
+ return issue.path.reduce((issuePath, item) => {
592
+ if (typeof item.key === "number") {
593
+ return `${issuePath}[${item.key}]`;
594
+ }
595
+ return `${issuePath}.${String(item.key)}`;
596
+ }, "$");
597
+ }
598
+ function presetSourceShapeIssues(issues) {
599
+ return issues.map((issue) => {
600
+ const validationPath = formatBracketIssuePath(issue);
601
+ const missingPresetMetadata = validationPath.match(/^\$\.presets\[\d+\]\.(.+)$/);
602
+ if (missingPresetMetadata && issue.message.includes("received undefined")) {
603
+ return {
604
+ path: validationPath,
605
+ message: `Preset metadata is missing required field: ${missingPresetMetadata[1]}`,
606
+ };
607
+ }
608
+ if (issue.message.startsWith("Invalid key:") &&
609
+ /\.(body|content|text)$/.test(validationPath)) {
610
+ const key = validationPath.split(".").at(-1);
611
+ return {
612
+ path: validationPath,
613
+ message: `Preset Source Manifests must reference Generated Repository file bodies by path, not inline ${key}`,
614
+ };
615
+ }
616
+ return {
617
+ path: validationPath,
618
+ message: validationPath.match(/^\$\.presets\[\d+\]\.packageAdditionSupport$/)
619
+ ? "Package Addition Support must be one of: supported, unsupported"
620
+ : issue.message,
621
+ };
622
+ });
623
+ }
624
+ function duplicatePresetNameIssues(presets) {
625
+ const seen = new Set();
626
+ const duplicates = new Set();
627
+ for (const preset of presets) {
628
+ if (seen.has(preset.name)) {
629
+ duplicates.add(preset.name);
630
+ }
631
+ seen.add(preset.name);
632
+ }
633
+ return [...duplicates].map((name) => ({
634
+ path: "$.presets.name",
635
+ message: `Duplicate Preset name: ${name}`,
636
+ }));
637
+ }
638
+ function duplicateValueIssues(values, validationPath) {
639
+ const seen = new Set();
640
+ const duplicates = new Set();
641
+ for (const value of values) {
642
+ if (seen.has(value)) {
643
+ duplicates.add(value);
644
+ }
645
+ seen.add(value);
646
+ }
647
+ return [...duplicates].map((value) => ({
648
+ path: validationPath,
649
+ message: `Duplicate value: ${value}`,
650
+ }));
651
+ }
652
+ function duplicatePresetMetadataArrayIssues(presets) {
653
+ return presets.flatMap((preset, index) => [
654
+ ...duplicateValueIssues(preset.supportedPackageManagers, `$.presets[${index}].supportedPackageManagers`),
655
+ ...duplicateValueIssues(preset.supportedProjectKinds, `$.presets[${index}].supportedProjectKinds`),
656
+ ...duplicateValueIssues(preset.features, `$.presets[${index}].features`),
657
+ ...duplicateValueIssues(preset.dependencyCatalog ?? [], `$.presets[${index}].dependencyCatalog`),
658
+ ]);
659
+ }
660
+ function fixtureCombinationKey(combination) {
661
+ return [
662
+ combination.basePreset,
663
+ combination.addedPreset,
664
+ ...(combination.linkFrom ?? []),
665
+ ].join("\0");
666
+ }
667
+ function fixtureCombinationPairKey(combination) {
668
+ return [combination.basePreset, combination.addedPreset].join("\0");
669
+ }
670
+ function fixtureMatrixContractIssues(manifest) {
671
+ if (!manifest.fixtureMatrix) {
672
+ return [];
673
+ }
674
+ const issues = [];
675
+ const presetsByName = new Map(manifest.presets.map((preset) => [preset.name, preset]));
676
+ const initPresets = new Set(manifest.fixtureMatrix.initSupport.map((support) => support.preset));
677
+ const addablePresets = new Set(manifest.fixtureMatrix.packageAdditionSupport.map((support) => support.preset));
678
+ const validInitPresets = new Set();
679
+ const validAddablePresets = new Set();
680
+ const supportedCombinationKeys = new Set();
681
+ const supportedCombinationPairKeys = new Set();
682
+ const semanticSkipKeys = new Set();
683
+ const semanticSkipPairKeys = new Set();
684
+ manifest.fixtureMatrix.initSupport.forEach((support, index) => {
685
+ const preset = presetsByName.get(support.preset);
686
+ if (!preset) {
687
+ issues.push({
688
+ path: `$.fixtureMatrix.initSupport[${index}].preset`,
689
+ message: `Fixture Matrix init support references unknown Preset: ${support.preset}`,
690
+ });
691
+ return;
692
+ }
693
+ if (preset.generation !== "supported") {
694
+ issues.push({
695
+ path: `$.fixtureMatrix.initSupport[${index}].preset`,
696
+ message: `Fixture Matrix init support must reference supported Presets: ${support.preset}`,
697
+ });
698
+ return;
699
+ }
700
+ validInitPresets.add(support.preset);
701
+ });
702
+ manifest.presets.forEach((preset, index) => {
703
+ if (preset.generation === "supported" &&
704
+ preset.packageAdditionSupport === PackageAdditionSupport.Supported &&
705
+ !addablePresets.has(preset.name)) {
706
+ issues.push({
707
+ path: `$.presets[${index}].packageAdditionSupport`,
708
+ message: `Fixture Matrix Package Addition support must declare supported Preset: ${preset.name}`,
709
+ });
710
+ }
711
+ });
712
+ manifest.fixtureMatrix.packageAdditionSupport.forEach((support, index) => {
713
+ const preset = presetsByName.get(support.preset);
714
+ if (!preset) {
715
+ issues.push({
716
+ path: `$.fixtureMatrix.packageAdditionSupport[${index}].preset`,
717
+ message: `Fixture Matrix Package Addition support references unknown Preset: ${support.preset}`,
718
+ });
719
+ return;
720
+ }
721
+ if (preset.packageAdditionSupport !== PackageAdditionSupport.Supported) {
722
+ issues.push({
723
+ path: `$.fixtureMatrix.packageAdditionSupport[${index}].preset`,
724
+ message: `Fixture Matrix Package Addition support must match Preset metadata: ${support.preset} is ${preset.packageAdditionSupport}`,
725
+ });
726
+ return;
727
+ }
728
+ validAddablePresets.add(support.preset);
729
+ });
730
+ manifest.fixtureMatrix.supportedCombinations.forEach((combination, index) => {
731
+ const key = fixtureCombinationKey(combination);
732
+ supportedCombinationPairKeys.add(fixtureCombinationPairKey(combination));
733
+ if (supportedCombinationKeys.has(key)) {
734
+ issues.push({
735
+ path: `$.fixtureMatrix.supportedCombinations[${index}]`,
736
+ message: `Duplicate Fixture Matrix supported combination: ${combination.basePreset} + ${combination.addedPreset}`,
737
+ });
738
+ }
739
+ supportedCombinationKeys.add(key);
740
+ if (!initPresets.has(combination.basePreset)) {
741
+ issues.push({
742
+ path: `$.fixtureMatrix.supportedCombinations[${index}].basePreset`,
743
+ message: `Fixture Matrix supported combination base Preset is not init-supported: ${combination.basePreset}`,
744
+ });
745
+ }
746
+ if (!addablePresets.has(combination.addedPreset)) {
747
+ issues.push({
748
+ path: `$.fixtureMatrix.supportedCombinations[${index}].addedPreset`,
749
+ message: `Fixture Matrix supported combination added Preset is not Package Addition-supported: ${combination.addedPreset}`,
750
+ });
751
+ }
752
+ });
753
+ manifest.fixtureMatrix.semanticSkips.forEach((skip, index) => {
754
+ const key = fixtureCombinationKey(skip);
755
+ semanticSkipPairKeys.add(fixtureCombinationPairKey(skip));
756
+ if (semanticSkipKeys.has(key)) {
757
+ issues.push({
758
+ path: `$.fixtureMatrix.semanticSkips[${index}]`,
759
+ message: `Duplicate Fixture Matrix semantic skip: ${skip.basePreset} + ${skip.addedPreset}`,
760
+ });
761
+ }
762
+ semanticSkipKeys.add(key);
763
+ if (!initPresets.has(skip.basePreset)) {
764
+ issues.push({
765
+ path: `$.fixtureMatrix.semanticSkips[${index}].basePreset`,
766
+ message: `Fixture Matrix semantic skip base Preset is not init-supported: ${skip.basePreset}`,
767
+ });
768
+ }
769
+ if (!presetsByName.has(skip.addedPreset)) {
770
+ issues.push({
771
+ path: `$.fixtureMatrix.semanticSkips[${index}].addedPreset`,
772
+ message: `Fixture Matrix semantic skip references unknown added Preset: ${skip.addedPreset}`,
773
+ });
774
+ }
775
+ });
776
+ for (const basePreset of validInitPresets) {
777
+ for (const addedPreset of validAddablePresets) {
778
+ const key = fixtureCombinationPairKey({ basePreset, addedPreset });
779
+ if (!supportedCombinationPairKeys.has(key) &&
780
+ !semanticSkipPairKeys.has(key)) {
781
+ issues.push({
782
+ path: "$.fixtureMatrix.supportedCombinations",
783
+ message: `Fixture Matrix must explicitly cover supported combination or semantic skip: ${basePreset} + ${addedPreset}`,
784
+ });
785
+ }
786
+ }
787
+ }
788
+ for (const checkRequirement of requiredFixtureMatrixCheckRequirements) {
789
+ if (!manifest.fixtureMatrix.checkRequirements.includes(checkRequirement)) {
790
+ issues.push({
791
+ path: "$.fixtureMatrix.checkRequirements",
792
+ message: `Fixture Matrix check requirements must include ${checkRequirement}`,
793
+ });
794
+ }
795
+ }
796
+ for (const environmentPreparation of requiredFixtureMatrixEnvironmentPreparation) {
797
+ if (!manifest.fixtureMatrix.environmentPreparation.includes(environmentPreparation)) {
798
+ issues.push({
799
+ path: "$.fixtureMatrix.environmentPreparation",
800
+ message: `Fixture Matrix environment preparation must include ${environmentPreparation}`,
801
+ });
802
+ }
803
+ }
804
+ for (const key of semanticSkipPairKeys) {
805
+ if (supportedCombinationPairKeys.has(key)) {
806
+ issues.push({
807
+ path: "$.fixtureMatrix.semanticSkips",
808
+ message: "Fixture Matrix semantic skips must not duplicate supported combinations",
809
+ });
810
+ }
811
+ }
812
+ return [
813
+ ...duplicateValueIssues(manifest.fixtureMatrix.initSupport.map((support) => support.preset), "$.fixtureMatrix.initSupport.preset"),
814
+ ...duplicateValueIssues(manifest.fixtureMatrix.packageAdditionSupport.map((support) => support.preset), "$.fixtureMatrix.packageAdditionSupport.preset"),
815
+ ...duplicateValueIssues(manifest.fixtureMatrix.checkRequirements, "$.fixtureMatrix.checkRequirements"),
816
+ ...duplicateValueIssues(manifest.fixtureMatrix.environmentPreparation, "$.fixtureMatrix.environmentPreparation"),
817
+ ...issues,
818
+ ];
819
+ }
820
+ function unsupportedProjectShapeIssues(presets) {
821
+ return presets.flatMap((preset, index) => preset.supportedProjectKinds.includes("single-package")
822
+ ? [
823
+ {
824
+ path: `$.presets[${index}].supportedProjectKinds`,
825
+ message: "single-package Project Shape is unsupported in V1; use the workspace monorepo Project Shape",
826
+ },
827
+ ]
828
+ : []);
829
+ }
830
+ function isRecord(value) {
831
+ return typeof value === "object" && value !== null && !Array.isArray(value);
832
+ }
833
+ function nonEmptyStringArray(value) {
834
+ const strings = [];
835
+ for (const entry of value) {
836
+ if (typeof entry !== "string" || entry.length === 0) {
837
+ return undefined;
838
+ }
839
+ strings.push(entry);
840
+ }
841
+ return strings.length === 0 ? undefined : strings;
842
+ }
843
+ const projectionCapabilityKindSet = new Set(projectionCapabilityKinds);
844
+ const workspaceNodePackageKindSet = new Set(["hono-api", "vue-app"]);
845
+ const workspaceNodePackagePathSet = new Set(["apps/api", "apps/web"]);
846
+ function isProjectionCapabilityKind(value) {
847
+ return projectionCapabilityKindSet.has(value);
848
+ }
849
+ function isWorkspaceNodePackageKind(value) {
850
+ return typeof value === "string" && workspaceNodePackageKindSet.has(value);
851
+ }
852
+ function isWorkspaceNodePackagePath(value) {
853
+ return typeof value === "string" && workspaceNodePackagePathSet.has(value);
854
+ }
855
+ const exactCapabilityKeys = {
856
+ "workspace-library-package": [
857
+ "kind",
858
+ "workspacePackageGlob",
859
+ "packageRole",
860
+ "packageSourcePreset",
861
+ "sourceFiles",
862
+ ],
863
+ "workspace-node-packages": [
864
+ "kind",
865
+ "workspacePackageGlob",
866
+ "packages",
867
+ "packageLinks",
868
+ ],
869
+ "rust-binary-workspace": [
870
+ "kind",
871
+ "workspacePackageGlob",
872
+ "sourceFiles",
873
+ "devcontainerResourceId",
874
+ "editorCustomizationResourceId",
875
+ ],
876
+ "strict-typescript-root": ["kind"],
877
+ "oxc-format-lint": ["kind", "editorCustomizationResourceId"],
878
+ "node-pnpm-devcontainer": ["kind", "devcontainerResourceId"],
879
+ "github-maintenance": ["kind"],
880
+ };
881
+ const requiredPlanCapabilityProviders = [
882
+ { kind: "strict-typescript-root", label: "root check command" },
883
+ { kind: "oxc-format-lint", label: "fix command" },
884
+ { kind: "github-maintenance", label: "GitHub Actions maintenance" },
885
+ { kind: "github-maintenance", label: "Dependabot maintenance" },
886
+ { kind: "node-pnpm-devcontainer", label: "development container support" },
887
+ ];
888
+ function unknownCapabilityPropertyIssues(kind, capability, pathPrefix) {
889
+ const allowedKeys = new Set(exactCapabilityKeys[kind]);
890
+ return Object.keys(capability)
891
+ .filter((key) => !allowedKeys.has(key))
892
+ .map((key) => ({
893
+ path: `${pathPrefix}.${key}`,
894
+ message: `Projection Capability ${kind} does not support property: ${key}`,
895
+ }));
896
+ }
897
+ function parseWorkspaceLibraryPackageCapability(capability, pathPrefix) {
898
+ const issues = [];
899
+ let sourceFiles = [];
900
+ if (capability.workspacePackageGlob !== "packages/*") {
901
+ issues.push({
902
+ path: `${pathPrefix}.workspacePackageGlob`,
903
+ message: "workspace-library-package currently supports workspacePackageGlob: packages/*",
904
+ });
905
+ }
906
+ if (capability.packageRole !== "shared-library") {
907
+ issues.push({
908
+ path: `${pathPrefix}.packageRole`,
909
+ message: "workspace-library-package currently supports packageRole: shared-library",
910
+ });
911
+ }
912
+ if (capability.packageSourcePreset !== "ts-lib") {
913
+ issues.push({
914
+ path: `${pathPrefix}.packageSourcePreset`,
915
+ message: "workspace-library-package currently supports packageSourcePreset: ts-lib",
916
+ });
917
+ }
918
+ if (!Array.isArray(capability.sourceFiles)) {
919
+ issues.push({
920
+ path: `${pathPrefix}.sourceFiles`,
921
+ message: "workspace-library-package sourceFiles must be a non-empty array",
922
+ });
923
+ }
924
+ else {
925
+ const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
926
+ if (parsedSourceFiles === undefined) {
927
+ issues.push({
928
+ path: `${pathPrefix}.sourceFiles`,
929
+ message: "workspace-library-package sourceFiles must be a non-empty array of paths",
930
+ });
931
+ }
932
+ else {
933
+ sourceFiles = parsedSourceFiles;
934
+ }
935
+ }
936
+ if (issues.length > 0) {
937
+ return issues;
938
+ }
939
+ return {
940
+ kind: "workspace-library-package",
941
+ workspacePackageGlob: "packages/*",
942
+ packageRole: "shared-library",
943
+ packageSourcePreset: "ts-lib",
944
+ sourceFiles,
945
+ };
946
+ }
947
+ function parseWorkspaceNodePackage(value, pathPrefix) {
948
+ const issues = [];
949
+ if (!isRecord(value)) {
950
+ return [
951
+ {
952
+ path: pathPrefix,
953
+ message: "workspace-node-packages package must be an object",
954
+ },
955
+ ];
956
+ }
957
+ const kind = isWorkspaceNodePackageKind(value.kind) ? value.kind : undefined;
958
+ const packagePath = isWorkspaceNodePackagePath(value.path)
959
+ ? value.path
960
+ : undefined;
961
+ let sourceFiles = [];
962
+ if (kind === undefined) {
963
+ issues.push({
964
+ path: `${pathPrefix}.kind`,
965
+ message: "workspace-node-packages package kind must be hono-api or vue-app",
966
+ });
967
+ }
968
+ if (packagePath === undefined) {
969
+ issues.push({
970
+ path: `${pathPrefix}.path`,
971
+ message: "workspace-node-packages package path must be apps/api or apps/web",
972
+ });
973
+ }
974
+ if (!Array.isArray(value.sourceFiles)) {
975
+ issues.push({
976
+ path: `${pathPrefix}.sourceFiles`,
977
+ message: "workspace-node-packages package sourceFiles must be a non-empty array",
978
+ });
979
+ }
980
+ else {
981
+ const parsedSourceFiles = nonEmptyStringArray(value.sourceFiles);
982
+ if (parsedSourceFiles === undefined) {
983
+ issues.push({
984
+ path: `${pathPrefix}.sourceFiles`,
985
+ message: "workspace-node-packages package sourceFiles must be a non-empty array of paths",
986
+ });
987
+ }
988
+ else {
989
+ sourceFiles = parsedSourceFiles;
990
+ }
991
+ }
992
+ const allowedKeys = new Set(["kind", "path", "sourceFiles"]);
993
+ for (const key of Object.keys(value)) {
994
+ if (!allowedKeys.has(key)) {
995
+ issues.push({
996
+ path: `${pathPrefix}.${key}`,
997
+ message: `workspace-node-packages package does not support property: ${key}`,
998
+ });
999
+ }
1000
+ }
1001
+ if (issues.length > 0) {
1002
+ return issues;
1003
+ }
1004
+ if (kind === undefined || packagePath === undefined) {
1005
+ throw new Error(`workspace-node-packages package validation failed without diagnostics at ${pathPrefix}`);
1006
+ }
1007
+ return {
1008
+ kind,
1009
+ path: packagePath,
1010
+ sourceFiles,
1011
+ };
1012
+ }
1013
+ function parseWorkspaceNodePackageLinks(value, pathPrefix, declaredPackagePaths, issues) {
1014
+ if (!Array.isArray(value)) {
1015
+ issues.push({
1016
+ path: pathPrefix,
1017
+ message: "workspace-node-packages packageLinks must be an array",
1018
+ });
1019
+ return undefined;
1020
+ }
1021
+ return value.flatMap((link, linkIndex) => {
1022
+ const linkPath = `${pathPrefix}[${linkIndex}]`;
1023
+ const linkIssues = [];
1024
+ if (!isRecord(link)) {
1025
+ issues.push({
1026
+ path: linkPath,
1027
+ message: "workspace-node-packages packageLink must be an object",
1028
+ });
1029
+ return [];
1030
+ }
1031
+ const allowedKeys = new Set(["consumerPackagePath", "providerPackagePath"]);
1032
+ for (const key of Object.keys(link)) {
1033
+ if (!allowedKeys.has(key)) {
1034
+ linkIssues.push({
1035
+ path: `${linkPath}.${key}`,
1036
+ message: `workspace-node-packages packageLink does not support property: ${key}`,
1037
+ });
1038
+ }
1039
+ }
1040
+ if (link.consumerPackagePath !== "apps/web" ||
1041
+ link.providerPackagePath !== "apps/api") {
1042
+ linkIssues.push({
1043
+ path: linkPath,
1044
+ message: "workspace-node-packages currently supports links from apps/web to apps/api",
1045
+ });
1046
+ }
1047
+ else {
1048
+ if (!declaredPackagePaths.has(link.consumerPackagePath)) {
1049
+ linkIssues.push({
1050
+ path: `${linkPath}.consumerPackagePath`,
1051
+ message: "workspace-node-packages packageLink consumerPackagePath must reference a package declared in the same packages array: apps/web",
1052
+ });
1053
+ }
1054
+ if (!declaredPackagePaths.has(link.providerPackagePath)) {
1055
+ linkIssues.push({
1056
+ path: `${linkPath}.providerPackagePath`,
1057
+ message: "workspace-node-packages packageLink providerPackagePath must reference a package declared in the same packages array: apps/api",
1058
+ });
1059
+ }
1060
+ }
1061
+ if (linkIssues.length > 0) {
1062
+ issues.push(...linkIssues);
1063
+ return [];
1064
+ }
1065
+ return [
1066
+ {
1067
+ consumerPackagePath: "apps/web",
1068
+ providerPackagePath: "apps/api",
1069
+ },
1070
+ ];
1071
+ });
1072
+ }
1073
+ function parseWorkspaceNodePackagesCapability(capability, pathPrefix) {
1074
+ const issues = [];
1075
+ if (capability.workspacePackageGlob !== "apps/*") {
1076
+ issues.push({
1077
+ path: `${pathPrefix}.workspacePackageGlob`,
1078
+ message: "workspace-node-packages currently supports workspacePackageGlob: apps/*",
1079
+ });
1080
+ }
1081
+ if (!Array.isArray(capability.packages) || capability.packages.length === 0) {
1082
+ issues.push({
1083
+ path: `${pathPrefix}.packages`,
1084
+ message: "workspace-node-packages packages must be a non-empty array",
1085
+ });
1086
+ }
1087
+ const packages = Array.isArray(capability.packages)
1088
+ ? capability.packages.flatMap((nodePackage, packageIndex) => {
1089
+ const packagePath = `${pathPrefix}.packages[${packageIndex}]`;
1090
+ const parsed = parseWorkspaceNodePackage(nodePackage, packagePath);
1091
+ if (Array.isArray(parsed)) {
1092
+ issues.push(...parsed);
1093
+ return [];
1094
+ }
1095
+ return [parsed];
1096
+ })
1097
+ : [];
1098
+ const packageLinks = capability.packageLinks === undefined
1099
+ ? undefined
1100
+ : parseWorkspaceNodePackageLinks(capability.packageLinks, `${pathPrefix}.packageLinks`, new Set(packages.map((nodePackage) => nodePackage.path)), issues);
1101
+ if (issues.length > 0) {
1102
+ return issues;
1103
+ }
1104
+ return {
1105
+ kind: "workspace-node-packages",
1106
+ workspacePackageGlob: "apps/*",
1107
+ packages,
1108
+ ...(packageLinks === undefined ? {} : { packageLinks }),
1109
+ };
1110
+ }
1111
+ function parseRustBinaryWorkspaceCapability(capability, pathPrefix) {
1112
+ const issues = [];
1113
+ let sourceFiles = [];
1114
+ let devcontainerResourceId = "";
1115
+ let editorCustomizationResourceId = "";
1116
+ if (capability.workspacePackageGlob !== "packages/*") {
1117
+ issues.push({
1118
+ path: `${pathPrefix}.workspacePackageGlob`,
1119
+ message: "rust-binary-workspace currently supports workspacePackageGlob: packages/*",
1120
+ });
1121
+ }
1122
+ if (!Array.isArray(capability.sourceFiles)) {
1123
+ issues.push({
1124
+ path: `${pathPrefix}.sourceFiles`,
1125
+ message: "rust-binary-workspace sourceFiles must be a non-empty array",
1126
+ });
1127
+ }
1128
+ else {
1129
+ const parsedSourceFiles = nonEmptyStringArray(capability.sourceFiles);
1130
+ if (parsedSourceFiles === undefined) {
1131
+ issues.push({
1132
+ path: `${pathPrefix}.sourceFiles`,
1133
+ message: "rust-binary-workspace sourceFiles must be a non-empty array of paths",
1134
+ });
1135
+ }
1136
+ else {
1137
+ sourceFiles = parsedSourceFiles;
1138
+ }
1139
+ }
1140
+ if (typeof capability.devcontainerResourceId !== "string" ||
1141
+ capability.devcontainerResourceId.length === 0) {
1142
+ issues.push({
1143
+ path: `${pathPrefix}.devcontainerResourceId`,
1144
+ message: "rust-binary-workspace must declare a devcontainerResourceId Shared Resource id",
1145
+ });
1146
+ }
1147
+ else {
1148
+ devcontainerResourceId = capability.devcontainerResourceId;
1149
+ }
1150
+ if (typeof capability.editorCustomizationResourceId !== "string" ||
1151
+ capability.editorCustomizationResourceId.length === 0) {
1152
+ issues.push({
1153
+ path: `${pathPrefix}.editorCustomizationResourceId`,
1154
+ message: "rust-binary-workspace must declare an editorCustomizationResourceId Shared Resource id",
1155
+ });
1156
+ }
1157
+ else {
1158
+ editorCustomizationResourceId = capability.editorCustomizationResourceId;
1159
+ }
1160
+ if (issues.length > 0) {
1161
+ return issues;
1162
+ }
1163
+ return {
1164
+ kind: "rust-binary-workspace",
1165
+ workspacePackageGlob: "packages/*",
1166
+ sourceFiles,
1167
+ devcontainerResourceId,
1168
+ editorCustomizationResourceId,
1169
+ };
1170
+ }
1171
+ function parseOxcFormatLintCapability(capability, pathPrefix) {
1172
+ if (typeof capability.editorCustomizationResourceId !== "string" ||
1173
+ capability.editorCustomizationResourceId.length === 0) {
1174
+ return [
1175
+ {
1176
+ path: `${pathPrefix}.editorCustomizationResourceId`,
1177
+ message: "oxc-format-lint must declare an editorCustomizationResourceId Shared Resource id",
1178
+ },
1179
+ ];
1180
+ }
1181
+ return {
1182
+ kind: "oxc-format-lint",
1183
+ editorCustomizationResourceId: capability.editorCustomizationResourceId,
1184
+ };
1185
+ }
1186
+ function parseNodePnpmDevcontainerCapability(capability, pathPrefix) {
1187
+ if (typeof capability.devcontainerResourceId !== "string" ||
1188
+ capability.devcontainerResourceId.length === 0) {
1189
+ return [
1190
+ {
1191
+ path: `${pathPrefix}.devcontainerResourceId`,
1192
+ message: "node-pnpm-devcontainer must declare a devcontainerResourceId Shared Resource id",
1193
+ },
1194
+ ];
1195
+ }
1196
+ return {
1197
+ kind: "node-pnpm-devcontainer",
1198
+ devcontainerResourceId: capability.devcontainerResourceId,
1199
+ };
1200
+ }
1201
+ function duplicateCapabilityIssues(capabilities) {
1202
+ const firstSeen = new Map();
1203
+ const issues = [];
1204
+ capabilities.forEach((capability, index) => {
1205
+ const firstIndex = firstSeen.get(capability.kind);
1206
+ if (firstIndex === undefined) {
1207
+ firstSeen.set(capability.kind, index);
1208
+ return;
1209
+ }
1210
+ issues.push({
1211
+ path: `$.capabilities[${index}].kind`,
1212
+ message: `Duplicate Projection Capability kind: ${capability.kind}`,
1213
+ });
1214
+ });
1215
+ return issues;
1216
+ }
1217
+ function uniqueValues(values) {
1218
+ return [...new Set(values)];
1219
+ }
1220
+ function capabilityCompositionIssues(capabilities) {
1221
+ const kinds = new Set(capabilities.map((capability) => capability.kind));
1222
+ const issues = [];
1223
+ const companionKinds = uniqueValues(capabilities
1224
+ .map((capability) => capability.kind)
1225
+ .filter((kind) => kind !== "rust-binary-workspace"));
1226
+ if (kinds.has("rust-binary-workspace") && companionKinds.length > 0) {
1227
+ issues.push({
1228
+ path: "$.capabilities",
1229
+ message: "rust-binary-workspace is a complete domain capability and must be selected by itself; remove companion Projection Capabilities: " +
1230
+ companionKinds.join(", "),
1231
+ });
1232
+ return issues;
1233
+ }
1234
+ if (!kinds.has("workspace-library-package") &&
1235
+ !kinds.has("workspace-node-packages") &&
1236
+ !kinds.has("rust-binary-workspace")) {
1237
+ issues.push({
1238
+ path: "$.capabilities",
1239
+ message: "Projection Capability composition must include a workspace package layout capability",
1240
+ });
1241
+ }
1242
+ if (kinds.has("strict-typescript-root") &&
1243
+ !kinds.has("workspace-library-package") &&
1244
+ !kinds.has("workspace-node-packages")) {
1245
+ issues.push({
1246
+ path: "$.capabilities",
1247
+ message: "strict-typescript-root requires a workspace package layout so package typecheck tasks have a workspace target",
1248
+ });
1249
+ }
1250
+ if (kinds.has("node-pnpm-devcontainer") && !kinds.has("oxc-format-lint")) {
1251
+ issues.push({
1252
+ path: "$.capabilities",
1253
+ message: "node-pnpm-devcontainer requires oxc-format-lint so editor customization is derived from declared tooling",
1254
+ });
1255
+ }
1256
+ for (const requirement of requiredPlanCapabilityProviders) {
1257
+ if (!kinds.has(requirement.kind) && !kinds.has("rust-binary-workspace")) {
1258
+ issues.push({
1259
+ path: "$.capabilities",
1260
+ message: `Projection Capability composition must include ${requirement.kind} to provide ${requirement.label}`,
1261
+ });
1262
+ }
1263
+ }
1264
+ return issues;
1265
+ }
1266
+ export function validatePresetProjectionDeclaration(input) {
1267
+ if (!isRecord(input) || !Array.isArray(input.capabilities)) {
1268
+ return {
1269
+ ok: false,
1270
+ issues: [
1271
+ {
1272
+ path: "$.capabilities",
1273
+ message: "Projection Declaration must select Projection Capabilities",
1274
+ },
1275
+ ],
1276
+ };
1277
+ }
1278
+ const issues = [];
1279
+ const capabilities = [];
1280
+ input.capabilities.forEach((capability, index) => {
1281
+ const pathPrefix = `$.capabilities[${index}]`;
1282
+ if (!isRecord(capability)) {
1283
+ issues.push({
1284
+ path: pathPrefix,
1285
+ message: "Projection Capability must be an object",
1286
+ });
1287
+ return;
1288
+ }
1289
+ if (typeof capability.kind !== "string") {
1290
+ issues.push({
1291
+ path: `${pathPrefix}.kind`,
1292
+ message: "Projection Capability kind is required",
1293
+ });
1294
+ return;
1295
+ }
1296
+ if (!isProjectionCapabilityKind(capability.kind)) {
1297
+ issues.push({
1298
+ path: `${pathPrefix}.kind`,
1299
+ message: `Unknown Projection Capability kind: ${capability.kind}`,
1300
+ });
1301
+ return;
1302
+ }
1303
+ const kind = capability.kind;
1304
+ issues.push(...unknownCapabilityPropertyIssues(kind, capability, pathPrefix));
1305
+ if (kind === "workspace-library-package") {
1306
+ const workspaceCapability = parseWorkspaceLibraryPackageCapability(capability, pathPrefix);
1307
+ if (Array.isArray(workspaceCapability)) {
1308
+ issues.push(...workspaceCapability);
1309
+ return;
1310
+ }
1311
+ capabilities.push(workspaceCapability);
1312
+ return;
1313
+ }
1314
+ if (kind === "workspace-node-packages") {
1315
+ const workspaceCapability = parseWorkspaceNodePackagesCapability(capability, pathPrefix);
1316
+ if (Array.isArray(workspaceCapability)) {
1317
+ issues.push(...workspaceCapability);
1318
+ return;
1319
+ }
1320
+ capabilities.push(workspaceCapability);
1321
+ return;
1322
+ }
1323
+ if (kind === "rust-binary-workspace") {
1324
+ const workspaceCapability = parseRustBinaryWorkspaceCapability(capability, pathPrefix);
1325
+ if (Array.isArray(workspaceCapability)) {
1326
+ issues.push(...workspaceCapability);
1327
+ return;
1328
+ }
1329
+ capabilities.push(workspaceCapability);
1330
+ return;
1331
+ }
1332
+ if (kind === "oxc-format-lint") {
1333
+ const oxcFormatLintCapability = parseOxcFormatLintCapability(capability, pathPrefix);
1334
+ if (Array.isArray(oxcFormatLintCapability)) {
1335
+ issues.push(...oxcFormatLintCapability);
1336
+ return;
1337
+ }
1338
+ capabilities.push(oxcFormatLintCapability);
1339
+ return;
1340
+ }
1341
+ if (kind === "node-pnpm-devcontainer") {
1342
+ const nodePnpmDevcontainerCapability = parseNodePnpmDevcontainerCapability(capability, pathPrefix);
1343
+ if (Array.isArray(nodePnpmDevcontainerCapability)) {
1344
+ issues.push(...nodePnpmDevcontainerCapability);
1345
+ return;
1346
+ }
1347
+ capabilities.push(nodePnpmDevcontainerCapability);
1348
+ return;
1349
+ }
1350
+ capabilities.push({ kind });
1351
+ });
1352
+ if (issues.length === 0) {
1353
+ issues.push(...duplicateCapabilityIssues(capabilities));
1354
+ issues.push(...capabilityCompositionIssues(capabilities));
1355
+ }
1356
+ if (issues.length > 0) {
1357
+ return { ok: false, issues };
1358
+ }
1359
+ return { ok: true, value: { capabilities } };
1360
+ }
1361
+ export function projectionCapabilityIssues(presets) {
1362
+ return presets.flatMap((preset, presetIndex) => {
1363
+ if (preset.projection === undefined) {
1364
+ return [];
1365
+ }
1366
+ const result = validatePresetProjectionDeclaration(preset.projection);
1367
+ return result.ok
1368
+ ? []
1369
+ : result.issues.map((issue) => ({
1370
+ path: `$.presets[${presetIndex}].projection${issue.path.slice(1)}`,
1371
+ message: issue.message,
1372
+ }));
1373
+ });
1374
+ }
1375
+ function projectionCapabilitySharedResourceIssues(manifest) {
1376
+ const sharedResourceIds = new Set(manifest.sharedResources.map((resource) => resource.id));
1377
+ const issues = [];
1378
+ manifest.presets.forEach((preset, presetIndex) => {
1379
+ if (preset.projection === undefined) {
1380
+ return;
1381
+ }
1382
+ const result = validatePresetProjectionDeclaration(preset.projection);
1383
+ if (!result.ok) {
1384
+ return;
1385
+ }
1386
+ result.value.capabilities.forEach((capability, capabilityIndex) => {
1387
+ if (capability.kind === "oxc-format-lint") {
1388
+ const resourceId = capability.editorCustomizationResourceId;
1389
+ if (!sharedResourceIds.has(resourceId)) {
1390
+ issues.push({
1391
+ path: `$.presets[${presetIndex}].projection.capabilities[${capabilityIndex}].editorCustomizationResourceId`,
1392
+ message: `Projection Capability oxc-format-lint references undeclared Shared Resource: ${resourceId}`,
1393
+ });
1394
+ }
1395
+ }
1396
+ if (capability.kind === "node-pnpm-devcontainer") {
1397
+ const resourceId = capability.devcontainerResourceId;
1398
+ if (!sharedResourceIds.has(resourceId)) {
1399
+ issues.push({
1400
+ path: `$.presets[${presetIndex}].projection.capabilities[${capabilityIndex}].devcontainerResourceId`,
1401
+ message: `Projection Capability node-pnpm-devcontainer references undeclared Shared Resource: ${resourceId}`,
1402
+ });
1403
+ }
1404
+ }
1405
+ if (capability.kind === "rust-binary-workspace") {
1406
+ const devcontainerResourceId = capability.devcontainerResourceId;
1407
+ if (!sharedResourceIds.has(devcontainerResourceId)) {
1408
+ issues.push({
1409
+ path: `$.presets[${presetIndex}].projection.capabilities[${capabilityIndex}].devcontainerResourceId`,
1410
+ message: `Projection Capability rust-binary-workspace references undeclared Shared Resource: ${devcontainerResourceId}`,
1411
+ });
1412
+ }
1413
+ const editorCustomizationResourceId = capability.editorCustomizationResourceId;
1414
+ if (!sharedResourceIds.has(editorCustomizationResourceId)) {
1415
+ issues.push({
1416
+ path: `$.presets[${presetIndex}].projection.capabilities[${capabilityIndex}].editorCustomizationResourceId`,
1417
+ message: `Projection Capability rust-binary-workspace references undeclared Shared Resource: ${editorCustomizationResourceId}`,
1418
+ });
1419
+ }
1420
+ }
1421
+ });
1422
+ });
1423
+ return issues;
1424
+ }
1425
+ export function normalizePresetProjectionDeclaration(declaration) {
1426
+ const result = validatePresetProjectionDeclaration(declaration);
1427
+ if (!result.ok) {
1428
+ throw new Error(`Projection Declaration is invalid:\n${result.issues
1429
+ .map((issue) => ` - ${issue.path}: ${issue.message}`)
1430
+ .join("\n")}`);
1431
+ }
1432
+ return result.value;
1433
+ }
1434
+ function normalizePresetSourceManifestOutput(manifest) {
1435
+ return {
1436
+ schemaVersion: manifest.schemaVersion,
1437
+ name: manifest.name,
1438
+ sharedResources: manifest.sharedResources.map((resource) => ({
1439
+ ...resource,
1440
+ })),
1441
+ ...(manifest.fixtureMatrix
1442
+ ? {
1443
+ fixtureMatrix: {
1444
+ initSupport: manifest.fixtureMatrix.initSupport.map((support) => ({
1445
+ ...support,
1446
+ })),
1447
+ packageAdditionSupport: manifest.fixtureMatrix.packageAdditionSupport.map((support) => ({
1448
+ ...support,
1449
+ })),
1450
+ supportedCombinations: manifest.fixtureMatrix.supportedCombinations.map((combination) => ({
1451
+ basePreset: combination.basePreset,
1452
+ addedPreset: combination.addedPreset,
1453
+ linkFrom: [...combination.linkFrom],
1454
+ })),
1455
+ semanticSkips: manifest.fixtureMatrix.semanticSkips.map((skip) => ({
1456
+ ...skip,
1457
+ })),
1458
+ checkRequirements: [...manifest.fixtureMatrix.checkRequirements],
1459
+ environmentPreparation: [
1460
+ ...manifest.fixtureMatrix.environmentPreparation,
1461
+ ],
1462
+ },
1463
+ }
1464
+ : {}),
1465
+ presets: manifest.presets.map((preset) => ({
1466
+ name: preset.name,
1467
+ title: preset.title,
1468
+ description: preset.description,
1469
+ generation: preset.generation,
1470
+ packageAdditionSupport: preset.packageAdditionSupport,
1471
+ supportedPackageManagers: [...preset.supportedPackageManagers],
1472
+ supportedProjectKinds: [...preset.supportedProjectKinds],
1473
+ features: [...preset.features],
1474
+ dependencyCatalog: [...preset.dependencyCatalog],
1475
+ ...(preset.projection
1476
+ ? {
1477
+ projection: normalizePresetProjectionDeclaration(preset.projection),
1478
+ }
1479
+ : {}),
1480
+ ...(preset.source
1481
+ ? {
1482
+ source: {
1483
+ roots: [...preset.source.roots],
1484
+ files: [...preset.source.files],
1485
+ sharedResources: [...preset.source.sharedResources],
1486
+ },
1487
+ }
1488
+ : {}),
1489
+ })),
1490
+ };
1491
+ }
1492
+ export function validatePresetSourceManifestDeclaration(input) {
1493
+ const result = v.safeParse(presetSourceManifestSchema, input);
1494
+ if (!result.success) {
1495
+ return { ok: false, issues: presetSourceShapeIssues(result.issues) };
1496
+ }
1497
+ const parsedManifest = result.output;
1498
+ const semanticIssues = [
1499
+ ...duplicateValueIssues(parsedManifest.sharedResources.map((resource) => resource.id), "$.sharedResources.id"),
1500
+ ...duplicatePresetNameIssues(parsedManifest.presets),
1501
+ ...duplicatePresetMetadataArrayIssues(parsedManifest.presets),
1502
+ ...unsupportedProjectShapeIssues(parsedManifest.presets),
1503
+ ...fixtureMatrixContractIssues(parsedManifest),
1504
+ ...projectionCapabilityIssues(parsedManifest.presets),
1505
+ ...projectionCapabilitySharedResourceIssues(parsedManifest),
1506
+ ];
1507
+ if (semanticIssues.length > 0) {
1508
+ return { ok: false, issues: semanticIssues };
1509
+ }
1510
+ return {
1511
+ ok: true,
1512
+ value: normalizePresetSourceManifestOutput(parsedManifest),
1513
+ };
1514
+ }
1515
+ export function findPreset(presets, name) {
1516
+ return presets.find((preset) => preset.name === name);
1517
+ }
1518
+ export function validatePresetFile(input, options = {}) {
1519
+ const result = v.safeParse(presetFileSchema, input);
1520
+ if (!result.success) {
1521
+ return { ok: false, issues: shapeIssues(result.issues) };
1522
+ }
1523
+ const semanticIssues = [
1524
+ ...duplicateIssues(result.output.supportedPackageManagers, "$.supportedPackageManagers"),
1525
+ ...duplicateIssues(result.output.supportedProjectKinds, "$.supportedProjectKinds"),
1526
+ ...duplicateIssues(result.output.features, "$.features"),
1527
+ ];
1528
+ if (result.output.supportedProjectKinds.includes("single-package")) {
1529
+ semanticIssues.push(unsupportedSinglePackageProjectShapeIssue("$.supportedProjectKinds"));
1530
+ }
1531
+ const catalogPreset = findPreset(options.presets ?? [], result.output.name);
1532
+ const unsupportedGeneration = catalogPreset
1533
+ ? generationSupportIssue(catalogPreset, "$.name")
1534
+ : undefined;
1535
+ if (unsupportedGeneration) {
1536
+ semanticIssues.push(unsupportedGeneration);
1537
+ }
1538
+ if (semanticIssues.length > 0) {
1539
+ return { ok: false, issues: semanticIssues };
1540
+ }
1541
+ return { ok: true, value: result.output };
1542
+ }
1543
+ function normalizeProjectBlueprint(blueprint) {
1544
+ return {
1545
+ schemaVersion: blueprint.schemaVersion,
1546
+ preset: blueprint.preset,
1547
+ ...(blueprint.packageManager === undefined
1548
+ ? {}
1549
+ : { packageManager: blueprint.packageManager }),
1550
+ projectKind: blueprint.projectKind,
1551
+ features: [...blueprint.features],
1552
+ ...(blueprint.packages === undefined
1553
+ ? {}
1554
+ : {
1555
+ packages: blueprint.packages.map((projectPackage) => ({
1556
+ name: projectPackage.name,
1557
+ path: projectPackage.path,
1558
+ ...(projectPackage.role === undefined
1559
+ ? {}
1560
+ : { role: projectPackage.role }),
1561
+ ...(projectPackage.sourcePreset === undefined
1562
+ ? {}
1563
+ : { sourcePreset: projectPackage.sourcePreset }),
1564
+ })),
1565
+ }),
1566
+ ...(blueprint.packageLinkIntents === undefined
1567
+ ? {}
1568
+ : {
1569
+ packageLinkIntents: blueprint.packageLinkIntents.map((intent) => ({
1570
+ consumerPackagePath: intent.consumerPackagePath,
1571
+ providerPackagePath: intent.providerPackagePath,
1572
+ })),
1573
+ }),
1574
+ };
1575
+ }
1576
+ export function validateProjectBlueprint(input, options = {}) {
1577
+ const result = v.safeParse(projectBlueprintSchema, input);
1578
+ if (!result.success) {
1579
+ return { ok: false, issues: shapeIssues(result.issues) };
1580
+ }
1581
+ const blueprint = normalizeProjectBlueprint(result.output);
1582
+ const preset = findPreset(options.presets ?? [], blueprint.preset);
1583
+ const semanticIssues = [
1584
+ ...duplicateIssues(blueprint.features, "$.features"),
1585
+ ];
1586
+ if (blueprint.projectKind === "single-package") {
1587
+ semanticIssues.push(unsupportedSinglePackageProjectShapeIssue("$.projectKind"));
1588
+ }
1589
+ if (options.presets && !preset) {
1590
+ semanticIssues.push({
1591
+ path: "$.preset",
1592
+ message: `Unknown ${options.presetLabel ?? "built-in"} preset: ${blueprint.preset}`,
1593
+ });
1594
+ }
1595
+ else if (preset) {
1596
+ const unsupportedGeneration = generationSupportIssue(preset, "$.preset");
1597
+ if (unsupportedGeneration) {
1598
+ semanticIssues.push(unsupportedGeneration);
1599
+ }
1600
+ if (blueprint.packageManager &&
1601
+ !preset.supportedPackageManagers.includes(blueprint.packageManager)) {
1602
+ semanticIssues.push({
1603
+ path: "$.packageManager",
1604
+ message: `${blueprint.packageManager} is not supported by preset ${preset.name}`,
1605
+ });
1606
+ }
1607
+ if (!blueprint.packageManager &&
1608
+ preset.supportedPackageManagers.length > 0) {
1609
+ semanticIssues.push({
1610
+ path: "$.packageManager",
1611
+ message: `packageManager is required by preset ${preset.name}`,
1612
+ });
1613
+ }
1614
+ if (!preset.supportedProjectKinds.includes(blueprint.projectKind)) {
1615
+ semanticIssues.push({
1616
+ path: "$.projectKind",
1617
+ message: `${blueprint.projectKind} is not supported by preset ${preset.name}`,
1618
+ });
1619
+ }
1620
+ const supportedFeatures = new Set(preset.features);
1621
+ for (const feature of blueprint.features) {
1622
+ if (!supportedFeatures.has(feature)) {
1623
+ semanticIssues.push({
1624
+ path: "$.features",
1625
+ message: `${feature} is not supported by preset ${preset.name}`,
1626
+ });
1627
+ }
1628
+ }
1629
+ }
1630
+ if (blueprint.packages) {
1631
+ semanticIssues.push(...duplicateIssues(blueprint.packages.map((projectPackage) => projectPackage.name), "$.packages.name"), ...duplicateIssues(blueprint.packages.map((projectPackage) => projectPackage.path), "$.packages.path"));
1632
+ }
1633
+ if (semanticIssues.length > 0) {
1634
+ return { ok: false, issues: semanticIssues };
1635
+ }
1636
+ return { ok: true, value: blueprint };
1637
+ }