@ykdz/template 0.0.4 → 0.0.5

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.
@@ -1,16 +1,21 @@
1
1
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { findBuiltInPresetProjection } from "../templates/registry.js";
3
+ import { builtInPresetProjections, findBuiltInPresetProjection, } from "../templates/registry.js";
4
4
  import { validateProjectBlueprint, } from "./declarations.js";
5
5
  import { collectGeneratedManifestCatalogReferences, pnpmWorkspaceYamlWithCatalogDependencies, } from "./dependency-catalog.js";
6
+ import { PackageAdditionSupport } from "./package-addition-support.js";
7
+ import { assertTypeScriptPackageBoundaryForLinkIntent, packageTurboTasks, planPackageLinks, } from "./package-linking.js";
6
8
  import { renderProject } from "./renderer.js";
7
- function projectNameFromBlueprint(blueprint) {
9
+ function projectNameFromBlueprint(blueprint, fallbackProjectName) {
8
10
  const firstPackage = blueprint.packages?.[0];
9
11
  const match = firstPackage?.name.match(/^@([^/]+)\//);
10
- if (!match) {
11
- throw new Error("Cannot infer workspace package scope from the stored Project Blueprint");
12
+ if (match) {
13
+ return match[1];
12
14
  }
13
- return match[1];
15
+ if (/^[a-z0-9][a-z0-9._-]*$/.test(fallbackProjectName)) {
16
+ return fallbackProjectName;
17
+ }
18
+ throw new Error("Cannot infer workspace package scope from the stored Project Blueprint or root package.json");
14
19
  }
15
20
  function assertSafePackageLeaf(name) {
16
21
  if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
@@ -74,6 +79,19 @@ function defaultPackagePathForPreset(preset, packageLeafName) {
74
79
  ? `packages/${packageLeafName}`
75
80
  : `apps/${packageLeafName}`;
76
81
  }
82
+ function supportedPackageAdditionPresetNames() {
83
+ return builtInPresetProjections
84
+ .filter((projection) => projection.metadata.packageAdditionSupport ===
85
+ PackageAdditionSupport.Supported)
86
+ .map((projection) => projection.metadata.name);
87
+ }
88
+ function formatUnsupportedPackageAdditionPresetError(preset) {
89
+ return [
90
+ `Preset ${preset} cannot be used for Package Addition.`,
91
+ "It can still initialize a Generated Repository, but it cannot be added to an existing one.",
92
+ `Supported Package Addition presets: ${supportedPackageAdditionPresetNames().join(", ")}`,
93
+ ].join("\n");
94
+ }
77
95
  function assertNoPackageConflict(blueprint, packageName, packagePath) {
78
96
  const existingByName = blueprint.packages?.find((pkg) => pkg.name === packageName);
79
97
  if (existingByName) {
@@ -84,6 +102,78 @@ function assertNoPackageConflict(blueprint, packageName, packagePath) {
84
102
  throw new Error(`Package Path ${packagePath} conflicts with existing package ${existingByPath.name} at ${existingByPath.path}`);
85
103
  }
86
104
  }
105
+ function formatAvailablePackagePaths(blueprint) {
106
+ return (blueprint.packages ?? [])
107
+ .map((projectPackage) => projectPackage.path)
108
+ .join(", ");
109
+ }
110
+ function packageLinkIntentForSingleConsumer(options) {
111
+ if (options.consumerPackagePath === options.providerPackagePath) {
112
+ throw new Error(`Package Link Intent cannot link ${options.providerPackagePath} from itself`);
113
+ }
114
+ const consumer = options.blueprint.packages?.find((projectPackage) => projectPackage.path === options.consumerPackagePath);
115
+ if (!consumer) {
116
+ const availablePackagePaths = formatAvailablePackagePaths(options.blueprint);
117
+ throw new Error([
118
+ `Unknown Package Path for --link-from: ${options.consumerPackagePath}`,
119
+ availablePackagePaths
120
+ ? `Available Package Paths: ${availablePackagePaths}`
121
+ : undefined,
122
+ ]
123
+ .filter((line) => line !== undefined)
124
+ .join("\n"));
125
+ }
126
+ return {
127
+ consumerPackagePath: options.consumerPackagePath,
128
+ providerPackagePath: options.providerPackagePath,
129
+ };
130
+ }
131
+ function packageLinkIntentsForConsumers(options) {
132
+ const packageLinkIntents = [];
133
+ const seenConsumerPackagePaths = new Set();
134
+ for (const consumerPackagePath of options.consumerPackagePaths) {
135
+ if (seenConsumerPackagePaths.has(consumerPackagePath)) {
136
+ continue;
137
+ }
138
+ seenConsumerPackagePaths.add(consumerPackagePath);
139
+ packageLinkIntents.push(packageLinkIntentForSingleConsumer({
140
+ blueprint: options.blueprint,
141
+ consumerPackagePath,
142
+ providerPackagePath: options.providerPackagePath,
143
+ }));
144
+ }
145
+ return packageLinkIntents;
146
+ }
147
+ async function assertPackageLinkIntentConsumersAreTypeScriptBoundaries(options) {
148
+ for (const intent of options.packageLinkIntents) {
149
+ const consumer = options.blueprint.packages?.find((projectPackage) => projectPackage.path === intent.consumerPackagePath);
150
+ if (!consumer) {
151
+ continue;
152
+ }
153
+ if (consumer.role !== undefined && consumer.sourcePreset !== undefined) {
154
+ assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
155
+ continue;
156
+ }
157
+ try {
158
+ const manifest = await readJson(path.join(options.root, consumer.path, "package.json"));
159
+ const scripts = isRecord(manifest) ? manifest.scripts : undefined;
160
+ const hasTypeScriptPackageShape = isRecord(manifest) &&
161
+ manifest.type === "module" &&
162
+ isRecord(scripts) &&
163
+ typeof scripts.typecheck === "string";
164
+ if (hasTypeScriptPackageShape) {
165
+ continue;
166
+ }
167
+ assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
168
+ }
169
+ catch (error) {
170
+ if (isNodeError(error) && error.code === "ENOENT") {
171
+ assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
172
+ }
173
+ throw error;
174
+ }
175
+ }
176
+ }
87
177
  async function readJson(filePath) {
88
178
  return JSON.parse(await readFile(filePath, "utf8"));
89
179
  }
@@ -169,16 +259,23 @@ async function readGeneratedWorkspaceBlueprint(root) {
169
259
  await stat(path.join(root, "pnpm-workspace.yaml"));
170
260
  return blueprint;
171
261
  }
172
- async function readGeneratedRepositoryNodeVersion(root) {
262
+ async function readGeneratedRepositoryPackageMetadata(root) {
173
263
  const packageJson = await readJson(path.join(root, "package.json"));
174
- if (!isRecord(packageJson) || !isRecord(packageJson.engines)) {
264
+ if (!isRecord(packageJson)) {
265
+ throw new Error("Package Addition requires root package.json to be an object");
266
+ }
267
+ const projectName = packageJson.name;
268
+ if (typeof projectName !== "string" || projectName.length === 0) {
269
+ throw new Error("Package Addition requires root package.json to declare name");
270
+ }
271
+ if (!isRecord(packageJson.engines)) {
175
272
  throw new Error("Package Addition requires root package.json to declare engines.node");
176
273
  }
177
274
  const nodeVersion = packageJson.engines.node;
178
275
  if (typeof nodeVersion !== "string" || nodeVersion.length === 0) {
179
276
  throw new Error("Package Addition requires root package.json to declare engines.node");
180
277
  }
181
- return nodeVersion;
278
+ return { projectName, nodeVersion };
182
279
  }
183
280
  function workspaceTextWithPackageGlob(text, glob) {
184
281
  if (text.includes(` - ${glob}`)) {
@@ -190,34 +287,6 @@ function workspaceTextWithPackageGlob(text, glob) {
190
287
  }
191
288
  return nextText;
192
289
  }
193
- function assertRootTsconfig(input) {
194
- if (!isRecord(input)) {
195
- throw new Error("Cannot update root TypeScript project references: tsconfig.json must be an object");
196
- }
197
- const references = input.references;
198
- if (references === undefined) {
199
- return;
200
- }
201
- if (!Array.isArray(references)) {
202
- throw new Error("Cannot update root TypeScript project references: references must be an array");
203
- }
204
- for (const reference of references) {
205
- if (!isRecord(reference) || typeof reference.path !== "string") {
206
- throw new Error("Cannot update root TypeScript project references: each reference must have a string path");
207
- }
208
- }
209
- }
210
- function rootTsconfigWithReferences(input, referencePaths) {
211
- assertRootTsconfig(input);
212
- const tsconfig = input;
213
- const references = tsconfig.references ?? [];
214
- for (const referencePath of referencePaths) {
215
- if (!references.some((reference) => reference.path === referencePath)) {
216
- references.push({ path: referencePath });
217
- }
218
- }
219
- return { ...tsconfig, references };
220
- }
221
290
  function packageAdditionCatalogDependencies(operations) {
222
291
  const manifests = [];
223
292
  for (const operation of operations) {
@@ -232,13 +301,45 @@ function packageAdditionCatalogDependencies(operations) {
232
301
  }
233
302
  return collectGeneratedManifestCatalogReferences(manifests);
234
303
  }
235
- function blueprintWithPackage(blueprint, packageName, packagePath) {
304
+ function generatedPackageManifestFromOperations(operations, packagePath) {
305
+ for (const operation of operations) {
306
+ if (operation.kind !== "writeJson" ||
307
+ operation.to !== `${packagePath}/package.json`) {
308
+ continue;
309
+ }
310
+ if (!isRecord(operation.value)) {
311
+ throw new Error(`Package Addition package manifest must be an object: ${operation.to}`);
312
+ }
313
+ return operation.value;
314
+ }
315
+ return undefined;
316
+ }
317
+ async function packageManifestsForTaskGraph(options) {
318
+ const manifests = [];
319
+ for (const packageDefinition of options.blueprint.packages ?? []) {
320
+ const manifestOverride = options.manifestOverrides?.get(packageDefinition.path);
321
+ const manifestFromOperations = generatedPackageManifestFromOperations(options.operations, packageDefinition.path);
322
+ manifests.push(manifestOverride ??
323
+ manifestFromOperations ??
324
+ (await readJson(path.join(options.root, packageDefinition.path, "package.json"))));
325
+ }
326
+ return manifests;
327
+ }
328
+ function blueprintWithPackage(blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents) {
236
329
  const nextBlueprint = {
237
330
  ...blueprint,
238
331
  packages: [
239
332
  ...(blueprint.packages ?? []),
240
- { name: packageName, path: packagePath },
333
+ {
334
+ name: packageName,
335
+ path: packagePath,
336
+ role: packageRole,
337
+ sourcePreset: packageSourcePreset,
338
+ },
241
339
  ],
340
+ packageLinkIntents: packageLinkIntents.length > 0
341
+ ? [...(blueprint.packageLinkIntents ?? []), ...packageLinkIntents]
342
+ : blueprint.packageLinkIntents,
242
343
  };
243
344
  const result = validateProjectBlueprint(nextBlueprint);
244
345
  if (!result.ok) {
@@ -246,6 +347,36 @@ function blueprintWithPackage(blueprint, packageName, packagePath) {
246
347
  }
247
348
  return result.value;
248
349
  }
350
+ function assertObjectManifest(input, packagePath) {
351
+ if (!isRecord(input)) {
352
+ throw new Error(`Cannot update Package Link Intent manifest for ${packagePath}: package.json must be an object`);
353
+ }
354
+ }
355
+ function recordSortedByKey(record) {
356
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
357
+ }
358
+ async function consumerManifestUpdatesForPackageLinkIntents(options) {
359
+ const updates = [];
360
+ for (const [packagePath, packageLinkDependencies,] of options.manifestDependenciesByPackagePath) {
361
+ const manifest = await readJson(path.join(options.root, packagePath, "package.json"));
362
+ assertObjectManifest(manifest, packagePath);
363
+ if (manifest.dependencies !== undefined &&
364
+ !isRecord(manifest.dependencies)) {
365
+ throw new Error(`Cannot update Package Link Intent manifest for ${packagePath}: dependencies must be an object`);
366
+ }
367
+ updates.push({
368
+ packagePath,
369
+ manifest: {
370
+ ...manifest,
371
+ dependencies: recordSortedByKey({
372
+ ...manifest.dependencies,
373
+ ...packageLinkDependencies,
374
+ }),
375
+ },
376
+ });
377
+ }
378
+ return updates;
379
+ }
249
380
  function workspacePackageGlobsFromBlueprint(blueprint) {
250
381
  const globs = [];
251
382
  for (const packageDefinition of blueprint.packages ?? []) {
@@ -273,47 +404,243 @@ function rootScriptWithTurboPackageTask(script, task, workspacePackageGlobs) {
273
404
  commands[turboCommandIndex] = turboPackageTaskCommand(task, workspacePackageGlobs);
274
405
  return commands.join(" && ");
275
406
  }
276
- function rootPackageJsonWithPackageTaskFilters(input, blueprint) {
407
+ function turboTaskNamesForPackageManifests(manifests) {
408
+ const taskNames = [];
409
+ for (const taskName of [
410
+ "typecheck",
411
+ "build",
412
+ "test",
413
+ "test:e2e",
414
+ "check",
415
+ ]) {
416
+ if (manifests.some((manifest) => typeof manifest.scripts?.[taskName] === "string")) {
417
+ taskNames.push(taskName);
418
+ }
419
+ }
420
+ return taskNames;
421
+ }
422
+ function rootScriptWithTurboPackageTasks(options) {
423
+ const rootCommands = options.script
424
+ .split(" && ")
425
+ .filter((command) => !command.startsWith("turbo run "));
426
+ const turboCommands = options.taskNames.map((taskName) => turboPackageTaskCommand(taskName, options.workspacePackageGlobs));
427
+ return [...rootCommands, ...turboCommands].join(" && ");
428
+ }
429
+ function rootPackageJsonWithPackageTaskFilters(input, blueprint, packageManifests) {
277
430
  assertRootPackageJson(input);
278
431
  const workspacePackageGlobs = workspacePackageGlobsFromBlueprint(blueprint);
432
+ const taskNames = turboTaskNamesForPackageManifests(packageManifests);
279
433
  return {
280
434
  ...input,
281
435
  scripts: {
282
436
  ...input.scripts,
283
- check: rootScriptWithTurboPackageTask(input.scripts.check, "check", workspacePackageGlobs),
437
+ check: rootScriptWithTurboPackageTasks({
438
+ script: input.scripts.check,
439
+ taskNames,
440
+ workspacePackageGlobs,
441
+ }),
284
442
  fix: rootScriptWithTurboPackageTask(input.scripts.fix, "fix", workspacePackageGlobs),
285
443
  },
286
444
  };
287
445
  }
288
- async function planRootUpdates(root, blueprint, packageName, packagePath, workspacePackageGlob, rootTsconfigReferences, catalogDependencies) {
289
- const nextBlueprint = blueprintWithPackage(blueprint, packageName, packagePath);
290
- const workspaceText = pnpmWorkspaceYamlWithCatalogDependencies(workspaceTextWithPackageGlob(await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8"), workspacePackageGlob), catalogDependencies);
291
- const rootTsconfig = rootTsconfigWithReferences(await readJson(path.join(root, "tsconfig.json")), rootTsconfigReferences);
292
- const rootPackageJson = rootPackageJsonWithPackageTaskFilters(await readJson(path.join(root, "package.json")), nextBlueprint);
446
+ function manifestExportUsesCompiledRuntime(value) {
447
+ if (!isRecord(value)) {
448
+ return false;
449
+ }
450
+ const rootExport = value["."];
451
+ if (!isRecord(rootExport)) {
452
+ return false;
453
+ }
454
+ return (typeof rootExport.default === "string" &&
455
+ rootExport.default.startsWith("./dist/"));
456
+ }
457
+ function packageManifestsNeedDependencyBuilds(manifests) {
458
+ const compiledPackageNames = new Set(manifests
459
+ .filter((manifest) => manifestExportUsesCompiledRuntime(manifest.exports))
460
+ .map((manifest) => manifest.name)
461
+ .filter((name) => typeof name === "string"));
462
+ if (compiledPackageNames.size === 0) {
463
+ return false;
464
+ }
465
+ return manifests.some((manifest) => Object.entries(manifest.dependencies ?? {}).some(([dependencyName, specifier]) => specifier === "workspace:*" && compiledPackageNames.has(dependencyName)));
466
+ }
467
+ function turboConfigForPackageManifests(manifests) {
293
468
  return {
469
+ tasks: packageTurboTasks({
470
+ dependencyBuildsRequired: packageManifestsNeedDependencyBuilds(manifests),
471
+ }),
472
+ };
473
+ }
474
+ function rootPackageJsonWithSharedOxcRuntimeDependencies(input, requiresSharedOxcConfiguration) {
475
+ if (!requiresSharedOxcConfiguration) {
476
+ return input;
477
+ }
478
+ if (input.type !== undefined && input.type !== "module") {
479
+ throw new Error('Cannot update root OXC configuration: package.json type must be "module" or omitted');
480
+ }
481
+ if (input.devDependencies !== undefined && !isRecord(input.devDependencies)) {
482
+ throw new Error("Cannot update root OXC configuration dependencies: devDependencies must be an object");
483
+ }
484
+ return {
485
+ ...input,
486
+ type: "module",
487
+ devDependencies: {
488
+ ...input.devDependencies,
489
+ oxfmt: input.devDependencies?.oxfmt ?? "catalog:",
490
+ oxlint: input.devDependencies?.oxlint ?? "catalog:",
491
+ },
492
+ };
493
+ }
494
+ function catalogDependenciesWithSharedOxcRuntimeDependencies(catalogDependencies, requiresSharedOxcConfiguration) {
495
+ const dependencies = [...catalogDependencies];
496
+ if (!requiresSharedOxcConfiguration) {
497
+ return dependencies;
498
+ }
499
+ for (const dependency of ["oxfmt", "oxlint"]) {
500
+ if (!dependencies.includes(dependency)) {
501
+ dependencies.push(dependency);
502
+ }
503
+ }
504
+ return dependencies;
505
+ }
506
+ async function planRootUpdates(root, blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents, workspacePackageGlob, catalogDependencies, requiresSharedOxcConfiguration, operations) {
507
+ const nextBlueprint = blueprintWithPackage(blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents);
508
+ const consumerManifestUpdates = packageLinkIntents.length > 0
509
+ ? await consumerManifestUpdatesForPackageLinkIntents({
510
+ root,
511
+ manifestDependenciesByPackagePath: planPackageLinks([
512
+ {
513
+ name: packageName,
514
+ path: packagePath,
515
+ role: packageRole,
516
+ sourcePreset: packageSourcePreset,
517
+ },
518
+ ], packageLinkIntents).manifestDependenciesByPackagePath,
519
+ })
520
+ : [];
521
+ const manifestOverrides = new Map(consumerManifestUpdates.map((update) => [
522
+ update.packagePath,
523
+ update.manifest,
524
+ ]));
525
+ const packageManifests = await packageManifestsForTaskGraph({
526
+ root,
294
527
  blueprint: nextBlueprint,
295
- rootPackageJson,
296
- rootTsconfig,
528
+ operations,
529
+ manifestOverrides,
530
+ });
531
+ const workspaceText = pnpmWorkspaceYamlWithCatalogDependencies(workspaceTextWithPackageGlob(await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8"), workspacePackageGlob), catalogDependenciesWithSharedOxcRuntimeDependencies(catalogDependencies, requiresSharedOxcConfiguration));
532
+ const rootPackageJson = rootPackageJsonWithPackageTaskFilters(await readJson(path.join(root, "package.json")), nextBlueprint, packageManifests);
533
+ return {
534
+ blueprint: nextBlueprint,
535
+ rootPackageJson: rootPackageJsonWithSharedOxcRuntimeDependencies(rootPackageJson, requiresSharedOxcConfiguration),
536
+ turboConfig: turboConfigForPackageManifests(packageManifests),
297
537
  workspaceText,
538
+ consumerManifestUpdates,
298
539
  };
299
540
  }
541
+ async function readTextIfExists(filePath) {
542
+ try {
543
+ return await readFile(filePath, "utf8");
544
+ }
545
+ catch (error) {
546
+ if (isNodeError(error) && error.code === "ENOENT") {
547
+ return undefined;
548
+ }
549
+ throw error;
550
+ }
551
+ }
552
+ async function writeIfMissing(filePath, text) {
553
+ if ((await readTextIfExists(filePath)) !== undefined) {
554
+ return;
555
+ }
556
+ await writeFile(filePath, text, "utf8");
557
+ }
558
+ async function ensureRootOxfmtConfig(root, sharedOxcRoot) {
559
+ await writeIfMissing(path.join(root, "oxfmt.config.ts"), await readFile(path.join(sharedOxcRoot, "oxfmt.config.ts"), "utf8"));
560
+ }
561
+ async function ensureRootOxlintConfig(options) {
562
+ const nodeConfig = await readFile(path.join(options.sharedOxcRoot, "node", "oxlint.config.ts"), "utf8");
563
+ const vueConfig = await readFile(path.join(options.sharedOxcRoot, "vue", "oxlint.config.ts"), "utf8");
564
+ const targetPath = path.join(options.root, "oxlint.config.ts");
565
+ const currentConfig = await readTextIfExists(targetPath);
566
+ if (currentConfig === undefined) {
567
+ await writeFile(targetPath, options.addedPreset === "vue-app" ? vueConfig : nodeConfig, "utf8");
568
+ return;
569
+ }
570
+ if (options.addedPreset !== "vue-app" || currentConfig === vueConfig) {
571
+ return;
572
+ }
573
+ if (currentConfig === nodeConfig) {
574
+ await writeFile(targetPath, vueConfig, "utf8");
575
+ return;
576
+ }
577
+ throw new Error("Cannot update root OXC lint configuration for Vue Package Addition: oxlint.config.ts has local changes");
578
+ }
579
+ async function ensureRootOxcConfiguration(options) {
580
+ if (!options.sharedOxcRoot) {
581
+ return;
582
+ }
583
+ await ensureRootOxfmtConfig(options.root, options.sharedOxcRoot);
584
+ await ensureRootOxlintConfig({
585
+ root: options.root,
586
+ sharedOxcRoot: options.sharedOxcRoot,
587
+ addedPreset: options.addedPreset,
588
+ });
589
+ }
590
+ function gitignoreContainsEntry(text, entry) {
591
+ return text
592
+ .split(/\r?\n/)
593
+ .some((line) => line.trim() === entry || line.trim() === `${entry}/`);
594
+ }
595
+ function gitignoreTextWithEntries(text, entries) {
596
+ const missingEntries = entries.filter((entry) => !gitignoreContainsEntry(text, entry));
597
+ if (missingEntries.length === 0) {
598
+ return text;
599
+ }
600
+ const prefix = text.trimEnd();
601
+ return `${prefix}${prefix.length > 0 ? "\n" : ""}${missingEntries.join("\n")}\n`;
602
+ }
603
+ function rootGitignoreEntriesForPackageAddition(preset, requiresSharedOxcConfiguration) {
604
+ const entries = requiresSharedOxcConfiguration
605
+ ? ["node_modules", "dist"]
606
+ : [];
607
+ if (preset === "vue-app") {
608
+ entries.push("playwright-report", "test-results");
609
+ }
610
+ return entries;
611
+ }
612
+ async function ensureRootGitignoreEntries(root, entries) {
613
+ if (entries.length === 0) {
614
+ return;
615
+ }
616
+ const gitignorePath = path.join(root, ".gitignore");
617
+ const currentText = (await readTextIfExists(gitignorePath)) ?? "";
618
+ const nextText = gitignoreTextWithEntries(currentText, entries);
619
+ if (nextText !== currentText) {
620
+ await writeFile(gitignorePath, nextText, "utf8");
621
+ }
622
+ }
300
623
  export async function addPackage(options) {
301
624
  assertSafePackageLeaf(options.name);
302
625
  const root = path.resolve(options.cwd);
303
626
  const blueprint = await readGeneratedWorkspaceBlueprint(root);
304
- const nodeVersion = await readGeneratedRepositoryNodeVersion(root);
305
- const projectName = projectNameFromBlueprint(blueprint);
627
+ const repositoryMetadata = await readGeneratedRepositoryPackageMetadata(root);
628
+ const projectName = projectNameFromBlueprint(blueprint, repositoryMetadata.projectName);
306
629
  const packageName = `@${projectName}/${options.name}`;
307
630
  const packagePath = options.path
308
631
  ? validateExplicitPackagePath(options.path)
309
632
  : defaultPackagePathForPreset(options.preset, options.name);
310
633
  const projection = findBuiltInPresetProjection(options.preset);
311
- const packageAddition = projection?.capabilities?.packageAddition;
312
634
  if (!projection) {
313
635
  throw new Error(`Unknown preset for Package Addition: ${options.preset}`);
314
636
  }
637
+ if (projection.metadata.packageAdditionSupport !==
638
+ PackageAdditionSupport.Supported) {
639
+ throw new Error(formatUnsupportedPackageAdditionPresetError(options.preset));
640
+ }
641
+ const packageAddition = projection.capabilities?.packageAddition;
315
642
  if (!packageAddition) {
316
- throw new Error(`Package Addition is not supported by preset: ${options.preset}`);
643
+ throw new Error(`Preset ${options.preset} declares Package Addition support but has no Package Addition implementation`);
317
644
  }
318
645
  const additionPlan = await packageAddition.planPackageAddition({
319
646
  root,
@@ -321,12 +648,29 @@ export async function addPackage(options) {
321
648
  packageLeafName: options.name,
322
649
  packageName,
323
650
  packagePath,
324
- nodeVersion,
651
+ nodeVersion: repositoryMetadata.nodeVersion,
325
652
  });
653
+ const requiresSharedOxcConfiguration = additionPlan.sourceRoots?.sharedOxc !== undefined;
326
654
  assertNoPackageConflict(blueprint, packageName, additionPlan.packagePath);
655
+ const packageLinkIntents = packageLinkIntentsForConsumers({
656
+ blueprint,
657
+ consumerPackagePaths: options.linkFrom ?? [],
658
+ providerPackagePath: additionPlan.packagePath,
659
+ });
660
+ await assertPackageLinkIntentConsumersAreTypeScriptBoundaries({
661
+ root,
662
+ blueprint,
663
+ packageLinkIntents,
664
+ });
327
665
  await assertMissingPackagePath(additionPlan.packagePath, path.join(root, additionPlan.packagePath));
328
- const rootUpdatePlan = await planRootUpdates(root, blueprint, packageName, additionPlan.packagePath, additionPlan.workspacePackageGlob, additionPlan.rootTsconfigReferences, packageAdditionCatalogDependencies(additionPlan.operations));
666
+ const rootUpdatePlan = await planRootUpdates(root, blueprint, packageName, additionPlan.packagePath, additionPlan.packageRole, additionPlan.packageSourcePreset, packageLinkIntents, additionPlan.workspacePackageGlob, packageAdditionCatalogDependencies(additionPlan.operations), requiresSharedOxcConfiguration, additionPlan.operations);
329
667
  await mkdir(path.join(root, additionPlan.packagePath), { recursive: true });
668
+ await ensureRootOxcConfiguration({
669
+ root,
670
+ sharedOxcRoot: additionPlan.sourceRoots?.sharedOxc,
671
+ addedPreset: options.preset,
672
+ });
673
+ await ensureRootGitignoreEntries(root, rootGitignoreEntriesForPackageAddition(options.preset, requiresSharedOxcConfiguration));
330
674
  await renderProject({
331
675
  sourceRoot: additionPlan.sourceRoot,
332
676
  sourceRoots: additionPlan.sourceRoots,
@@ -338,6 +682,9 @@ export async function addPackage(options) {
338
682
  }
339
683
  await writeFile(path.join(root, "pnpm-workspace.yaml"), rootUpdatePlan.workspaceText, "utf8");
340
684
  await writeJson(path.join(root, "package.json"), rootUpdatePlan.rootPackageJson);
341
- await writeJson(path.join(root, "tsconfig.json"), rootUpdatePlan.rootTsconfig);
685
+ await writeJson(path.join(root, "turbo.json"), rootUpdatePlan.turboConfig);
686
+ for (const update of rootUpdatePlan.consumerManifestUpdates) {
687
+ await writeJson(path.join(root, update.packagePath, "package.json"), update.manifest);
688
+ }
342
689
  await writeJson(path.join(root, ".template/blueprint.json"), rootUpdatePlan.blueprint);
343
690
  }
@@ -0,0 +1,55 @@
1
+ export type PackageRole = "runtime-service" | "shared-library";
2
+ export type PackageSourcePreset = "hono-api" | "ts-lib" | "vue-app";
3
+ export type PackageDefinition = {
4
+ readonly name: string;
5
+ readonly path: string;
6
+ readonly role?: PackageRole;
7
+ readonly sourcePreset?: PackageSourcePreset;
8
+ };
9
+ export type PackageLinkIntent = {
10
+ readonly consumerPackagePath: string;
11
+ readonly providerPackagePath: string;
12
+ };
13
+ export type JitSourcePackageExposure = {
14
+ readonly kind: "jit-source";
15
+ readonly entrypoint: string;
16
+ readonly packageLocalImportPattern: string;
17
+ readonly packageLocalImportTarget: string;
18
+ };
19
+ export type CompiledPackageExposure = {
20
+ readonly kind: "compiled";
21
+ readonly entrypoint: string;
22
+ readonly sourceTypes: string;
23
+ readonly packageLocalImportPattern: string;
24
+ readonly packageLocalImportRuntimeTarget: string;
25
+ readonly packageLocalImportTypesTarget: string;
26
+ };
27
+ export type PackageExposure = CompiledPackageExposure | JitSourcePackageExposure;
28
+ export type PackageLinkPlan = {
29
+ readonly exposuresByPackagePath: ReadonlyMap<string, PackageExposure>;
30
+ readonly manifestDependenciesByPackagePath: ReadonlyMap<string, Readonly<Record<string, "workspace:*">>>;
31
+ readonly turboTasks: TurboTaskGraph;
32
+ };
33
+ export type PackageManifestExposureFields = {
34
+ readonly exports: Record<string, unknown>;
35
+ readonly imports: Record<string, unknown>;
36
+ readonly types?: string;
37
+ };
38
+ export type PackageLinkBoundaryDirection = "consumer" | "provider";
39
+ export type TurboTaskDefinition = {
40
+ readonly dependsOn?: readonly string[];
41
+ readonly outputs?: readonly string[];
42
+ readonly cache?: boolean;
43
+ };
44
+ export type TurboTaskGraph = Readonly<Record<string, TurboTaskDefinition>>;
45
+ export type PackageTurboTaskOptions = {
46
+ readonly dependencyBuildsRequired: boolean;
47
+ };
48
+ export declare function derivePackageExposure(definition: PackageDefinition): PackageExposure;
49
+ export declare function assertTypeScriptPackageBoundaryForLinkIntent(definition: PackageDefinition, direction: PackageLinkBoundaryDirection): asserts definition is PackageDefinition & {
50
+ readonly role: PackageRole;
51
+ readonly sourcePreset: PackageSourcePreset;
52
+ };
53
+ export declare function planPackageLinks(definitions: readonly PackageDefinition[], intents?: readonly PackageLinkIntent[]): PackageLinkPlan;
54
+ export declare function packageTurboTasks({ dependencyBuildsRequired, }: PackageTurboTaskOptions): TurboTaskGraph;
55
+ export declare function packageManifestExposureFields(exposure: PackageExposure): PackageManifestExposureFields;