@ykdz/template 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.js +79 -34
- package/dist/src/declarations.d.ts +36 -0
- package/dist/src/declarations.js +31 -2
- package/dist/src/generated-scenarios.d.ts +62 -0
- package/dist/src/generated-scenarios.js +361 -0
- package/dist/src/module-graph.d.ts +1 -1
- package/dist/src/module-graph.js +10 -2
- package/dist/src/package-addition-support.d.ts +5 -0
- package/dist/src/package-addition-support.js +4 -0
- package/dist/src/package-addition.d.ts +1 -0
- package/dist/src/package-addition.js +409 -69
- package/dist/src/package-linking.d.ts +55 -0
- package/dist/src/package-linking.js +116 -0
- package/dist/src/preset-projection.d.ts +4 -1
- package/dist/src/preset-source.d.ts +430 -0
- package/dist/src/preset-source.js +940 -0
- package/dist/src/projection-capabilities.d.ts +67 -0
- package/dist/src/projection-capabilities.js +2207 -0
- package/dist/src/renderer.js +20 -1
- package/dist/src/template-boundary-check.d.ts +7 -3
- package/dist/src/template-boundary-check.js +104 -10
- package/package.json +3 -2
- package/templates/hono-api/src/server.ts +1 -1
- package/templates/hono-api/test/app.test.ts +1 -1
- package/templates/preset-source.json +499 -0
- package/templates/ts-lib/src/index.ts +2 -2
- package/templates/ts-lib/src/name-schema.ts +3 -0
- package/templates/vue-app/playwright.package-addition.config.ts +22 -0
- package/templates/vue-app/src/App.vue +1 -1
- package/templates/vue-app/test/app.test.ts +1 -1
- package/templates/vue-hono-app/web/src/App.vue +2 -2
- package/templates/vue-hono-app/web/test/app.test.ts +1 -1
- package/dist/templates/hono-api/projection.d.ts +0 -6
- package/dist/templates/hono-api/projection.js +0 -538
- package/dist/templates/projection-plans.d.ts +0 -7
- package/dist/templates/projection-plans.js +0 -94
- package/dist/templates/registry.d.ts +0 -7
- package/dist/templates/registry.js +0 -42
- package/dist/templates/rust-bin/projection.d.ts +0 -9
- package/dist/templates/rust-bin/projection.js +0 -390
- package/dist/templates/ts-lib/projection.d.ts +0 -6
- package/dist/templates/ts-lib/projection.js +0 -523
- package/dist/templates/vue-app/projection.d.ts +0 -6
- package/dist/templates/vue-app/projection.js +0 -757
- package/dist/templates/vue-hono-app/projection.d.ts +0 -8
- package/dist/templates/vue-hono-app/projection.js +0 -693
|
@@ -1,16 +1,22 @@
|
|
|
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";
|
|
4
3
|
import { validateProjectBlueprint, } from "./declarations.js";
|
|
5
4
|
import { collectGeneratedManifestCatalogReferences, pnpmWorkspaceYamlWithCatalogDependencies, } from "./dependency-catalog.js";
|
|
5
|
+
import { PackageAdditionSupport } from "./package-addition-support.js";
|
|
6
|
+
import { assertTypeScriptPackageBoundaryForLinkIntent, packageTurboTasks, planPackageLinks, } from "./package-linking.js";
|
|
7
|
+
import { findBuiltInPresetSourceManifestPreset, loadBuiltInPresetSourceManifest, } from "./preset-source.js";
|
|
8
|
+
import { defaultPackagePathForPresetSourcePackageAddition, planPresetSourcePackageAddition, } from "./projection-capabilities.js";
|
|
6
9
|
import { renderProject } from "./renderer.js";
|
|
7
|
-
function projectNameFromBlueprint(blueprint) {
|
|
10
|
+
function projectNameFromBlueprint(blueprint, fallbackProjectName) {
|
|
8
11
|
const firstPackage = blueprint.packages?.[0];
|
|
9
12
|
const match = firstPackage?.name.match(/^@([^/]+)\//);
|
|
10
|
-
if (
|
|
11
|
-
|
|
13
|
+
if (match) {
|
|
14
|
+
return match[1];
|
|
12
15
|
}
|
|
13
|
-
|
|
16
|
+
if (/^[a-z0-9][a-z0-9._-]*$/.test(fallbackProjectName)) {
|
|
17
|
+
return fallbackProjectName;
|
|
18
|
+
}
|
|
19
|
+
throw new Error("Cannot infer workspace package scope from the stored Project Blueprint or root package.json");
|
|
14
20
|
}
|
|
15
21
|
function assertSafePackageLeaf(name) {
|
|
16
22
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
@@ -69,10 +75,17 @@ function validateExplicitPackagePath(packagePath) {
|
|
|
69
75
|
}
|
|
70
76
|
return packagePath;
|
|
71
77
|
}
|
|
72
|
-
function
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
function supportedPackageAdditionPresetNames() {
|
|
79
|
+
return loadBuiltInPresetSourceManifest()
|
|
80
|
+
.presets.filter((preset) => preset.packageAdditionSupport === PackageAdditionSupport.Supported)
|
|
81
|
+
.map((preset) => preset.name);
|
|
82
|
+
}
|
|
83
|
+
function formatUnsupportedPackageAdditionPresetError(preset) {
|
|
84
|
+
return [
|
|
85
|
+
`Preset ${preset} cannot be used for Package Addition.`,
|
|
86
|
+
"It can still initialize a Generated Repository, but it cannot be added to an existing one.",
|
|
87
|
+
`Supported Package Addition presets: ${supportedPackageAdditionPresetNames().join(", ")}`,
|
|
88
|
+
].join("\n");
|
|
76
89
|
}
|
|
77
90
|
function assertNoPackageConflict(blueprint, packageName, packagePath) {
|
|
78
91
|
const existingByName = blueprint.packages?.find((pkg) => pkg.name === packageName);
|
|
@@ -84,6 +97,78 @@ function assertNoPackageConflict(blueprint, packageName, packagePath) {
|
|
|
84
97
|
throw new Error(`Package Path ${packagePath} conflicts with existing package ${existingByPath.name} at ${existingByPath.path}`);
|
|
85
98
|
}
|
|
86
99
|
}
|
|
100
|
+
function formatAvailablePackagePaths(blueprint) {
|
|
101
|
+
return (blueprint.packages ?? [])
|
|
102
|
+
.map((projectPackage) => projectPackage.path)
|
|
103
|
+
.join(", ");
|
|
104
|
+
}
|
|
105
|
+
function packageLinkIntentForSingleConsumer(options) {
|
|
106
|
+
if (options.consumerPackagePath === options.providerPackagePath) {
|
|
107
|
+
throw new Error(`Package Link Intent cannot link ${options.providerPackagePath} from itself`);
|
|
108
|
+
}
|
|
109
|
+
const consumer = options.blueprint.packages?.find((projectPackage) => projectPackage.path === options.consumerPackagePath);
|
|
110
|
+
if (!consumer) {
|
|
111
|
+
const availablePackagePaths = formatAvailablePackagePaths(options.blueprint);
|
|
112
|
+
throw new Error([
|
|
113
|
+
`Unknown Package Path for --link-from: ${options.consumerPackagePath}`,
|
|
114
|
+
availablePackagePaths
|
|
115
|
+
? `Available Package Paths: ${availablePackagePaths}`
|
|
116
|
+
: undefined,
|
|
117
|
+
]
|
|
118
|
+
.filter((line) => line !== undefined)
|
|
119
|
+
.join("\n"));
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
consumerPackagePath: options.consumerPackagePath,
|
|
123
|
+
providerPackagePath: options.providerPackagePath,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function packageLinkIntentsForConsumers(options) {
|
|
127
|
+
const packageLinkIntents = [];
|
|
128
|
+
const seenConsumerPackagePaths = new Set();
|
|
129
|
+
for (const consumerPackagePath of options.consumerPackagePaths) {
|
|
130
|
+
if (seenConsumerPackagePaths.has(consumerPackagePath)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
seenConsumerPackagePaths.add(consumerPackagePath);
|
|
134
|
+
packageLinkIntents.push(packageLinkIntentForSingleConsumer({
|
|
135
|
+
blueprint: options.blueprint,
|
|
136
|
+
consumerPackagePath,
|
|
137
|
+
providerPackagePath: options.providerPackagePath,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
return packageLinkIntents;
|
|
141
|
+
}
|
|
142
|
+
async function assertPackageLinkIntentConsumersAreTypeScriptBoundaries(options) {
|
|
143
|
+
for (const intent of options.packageLinkIntents) {
|
|
144
|
+
const consumer = options.blueprint.packages?.find((projectPackage) => projectPackage.path === intent.consumerPackagePath);
|
|
145
|
+
if (!consumer) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (consumer.role !== undefined && consumer.sourcePreset !== undefined) {
|
|
149
|
+
assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const manifest = await readJson(path.join(options.root, consumer.path, "package.json"));
|
|
154
|
+
const scripts = isRecord(manifest) ? manifest.scripts : undefined;
|
|
155
|
+
const hasTypeScriptPackageShape = isRecord(manifest) &&
|
|
156
|
+
manifest.type === "module" &&
|
|
157
|
+
isRecord(scripts) &&
|
|
158
|
+
typeof scripts.typecheck === "string";
|
|
159
|
+
if (hasTypeScriptPackageShape) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
166
|
+
assertTypeScriptPackageBoundaryForLinkIntent(consumer, "consumer");
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
87
172
|
async function readJson(filePath) {
|
|
88
173
|
return JSON.parse(await readFile(filePath, "utf8"));
|
|
89
174
|
}
|
|
@@ -169,16 +254,23 @@ async function readGeneratedWorkspaceBlueprint(root) {
|
|
|
169
254
|
await stat(path.join(root, "pnpm-workspace.yaml"));
|
|
170
255
|
return blueprint;
|
|
171
256
|
}
|
|
172
|
-
async function
|
|
257
|
+
async function readGeneratedRepositoryPackageMetadata(root) {
|
|
173
258
|
const packageJson = await readJson(path.join(root, "package.json"));
|
|
174
|
-
if (!isRecord(packageJson)
|
|
259
|
+
if (!isRecord(packageJson)) {
|
|
260
|
+
throw new Error("Package Addition requires root package.json to be an object");
|
|
261
|
+
}
|
|
262
|
+
const projectName = packageJson.name;
|
|
263
|
+
if (typeof projectName !== "string" || projectName.length === 0) {
|
|
264
|
+
throw new Error("Package Addition requires root package.json to declare name");
|
|
265
|
+
}
|
|
266
|
+
if (!isRecord(packageJson.engines)) {
|
|
175
267
|
throw new Error("Package Addition requires root package.json to declare engines.node");
|
|
176
268
|
}
|
|
177
269
|
const nodeVersion = packageJson.engines.node;
|
|
178
270
|
if (typeof nodeVersion !== "string" || nodeVersion.length === 0) {
|
|
179
271
|
throw new Error("Package Addition requires root package.json to declare engines.node");
|
|
180
272
|
}
|
|
181
|
-
return nodeVersion;
|
|
273
|
+
return { projectName, nodeVersion };
|
|
182
274
|
}
|
|
183
275
|
function workspaceTextWithPackageGlob(text, glob) {
|
|
184
276
|
if (text.includes(` - ${glob}`)) {
|
|
@@ -190,34 +282,6 @@ function workspaceTextWithPackageGlob(text, glob) {
|
|
|
190
282
|
}
|
|
191
283
|
return nextText;
|
|
192
284
|
}
|
|
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
285
|
function packageAdditionCatalogDependencies(operations) {
|
|
222
286
|
const manifests = [];
|
|
223
287
|
for (const operation of operations) {
|
|
@@ -232,13 +296,45 @@ function packageAdditionCatalogDependencies(operations) {
|
|
|
232
296
|
}
|
|
233
297
|
return collectGeneratedManifestCatalogReferences(manifests);
|
|
234
298
|
}
|
|
235
|
-
function
|
|
299
|
+
function generatedPackageManifestFromOperations(operations, packagePath) {
|
|
300
|
+
for (const operation of operations) {
|
|
301
|
+
if (operation.kind !== "writeJson" ||
|
|
302
|
+
operation.to !== `${packagePath}/package.json`) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (!isRecord(operation.value)) {
|
|
306
|
+
throw new Error(`Package Addition package manifest must be an object: ${operation.to}`);
|
|
307
|
+
}
|
|
308
|
+
return operation.value;
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
async function packageManifestsForTaskGraph(options) {
|
|
313
|
+
const manifests = [];
|
|
314
|
+
for (const packageDefinition of options.blueprint.packages ?? []) {
|
|
315
|
+
const manifestOverride = options.manifestOverrides?.get(packageDefinition.path);
|
|
316
|
+
const manifestFromOperations = generatedPackageManifestFromOperations(options.operations, packageDefinition.path);
|
|
317
|
+
manifests.push(manifestOverride ??
|
|
318
|
+
manifestFromOperations ??
|
|
319
|
+
(await readJson(path.join(options.root, packageDefinition.path, "package.json"))));
|
|
320
|
+
}
|
|
321
|
+
return manifests;
|
|
322
|
+
}
|
|
323
|
+
function blueprintWithPackage(blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents) {
|
|
236
324
|
const nextBlueprint = {
|
|
237
325
|
...blueprint,
|
|
238
326
|
packages: [
|
|
239
327
|
...(blueprint.packages ?? []),
|
|
240
|
-
{
|
|
328
|
+
{
|
|
329
|
+
name: packageName,
|
|
330
|
+
path: packagePath,
|
|
331
|
+
role: packageRole,
|
|
332
|
+
sourcePreset: packageSourcePreset,
|
|
333
|
+
},
|
|
241
334
|
],
|
|
335
|
+
packageLinkIntents: packageLinkIntents.length > 0
|
|
336
|
+
? [...(blueprint.packageLinkIntents ?? []), ...packageLinkIntents]
|
|
337
|
+
: blueprint.packageLinkIntents,
|
|
242
338
|
};
|
|
243
339
|
const result = validateProjectBlueprint(nextBlueprint);
|
|
244
340
|
if (!result.ok) {
|
|
@@ -246,6 +342,36 @@ function blueprintWithPackage(blueprint, packageName, packagePath) {
|
|
|
246
342
|
}
|
|
247
343
|
return result.value;
|
|
248
344
|
}
|
|
345
|
+
function assertObjectManifest(input, packagePath) {
|
|
346
|
+
if (!isRecord(input)) {
|
|
347
|
+
throw new Error(`Cannot update Package Link Intent manifest for ${packagePath}: package.json must be an object`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function recordSortedByKey(record) {
|
|
351
|
+
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
352
|
+
}
|
|
353
|
+
async function consumerManifestUpdatesForPackageLinkIntents(options) {
|
|
354
|
+
const updates = [];
|
|
355
|
+
for (const [packagePath, packageLinkDependencies,] of options.manifestDependenciesByPackagePath) {
|
|
356
|
+
const manifest = await readJson(path.join(options.root, packagePath, "package.json"));
|
|
357
|
+
assertObjectManifest(manifest, packagePath);
|
|
358
|
+
if (manifest.dependencies !== undefined &&
|
|
359
|
+
!isRecord(manifest.dependencies)) {
|
|
360
|
+
throw new Error(`Cannot update Package Link Intent manifest for ${packagePath}: dependencies must be an object`);
|
|
361
|
+
}
|
|
362
|
+
updates.push({
|
|
363
|
+
packagePath,
|
|
364
|
+
manifest: {
|
|
365
|
+
...manifest,
|
|
366
|
+
dependencies: recordSortedByKey({
|
|
367
|
+
...manifest.dependencies,
|
|
368
|
+
...packageLinkDependencies,
|
|
369
|
+
}),
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
return updates;
|
|
374
|
+
}
|
|
249
375
|
function workspacePackageGlobsFromBlueprint(blueprint) {
|
|
250
376
|
const globs = [];
|
|
251
377
|
for (const packageDefinition of blueprint.packages ?? []) {
|
|
@@ -273,60 +399,271 @@ function rootScriptWithTurboPackageTask(script, task, workspacePackageGlobs) {
|
|
|
273
399
|
commands[turboCommandIndex] = turboPackageTaskCommand(task, workspacePackageGlobs);
|
|
274
400
|
return commands.join(" && ");
|
|
275
401
|
}
|
|
276
|
-
function
|
|
402
|
+
function turboTaskNamesForPackageManifests(manifests) {
|
|
403
|
+
const taskNames = [];
|
|
404
|
+
for (const taskName of [
|
|
405
|
+
"typecheck",
|
|
406
|
+
"build",
|
|
407
|
+
"test",
|
|
408
|
+
"test:e2e",
|
|
409
|
+
"check",
|
|
410
|
+
]) {
|
|
411
|
+
if (manifests.some((manifest) => typeof manifest.scripts?.[taskName] === "string")) {
|
|
412
|
+
taskNames.push(taskName);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return taskNames;
|
|
416
|
+
}
|
|
417
|
+
function rootScriptWithTurboPackageTasks(options) {
|
|
418
|
+
const rootCommands = options.script
|
|
419
|
+
.split(" && ")
|
|
420
|
+
.filter((command) => !command.startsWith("turbo run "));
|
|
421
|
+
const turboCommands = options.taskNames.map((taskName) => turboPackageTaskCommand(taskName, options.workspacePackageGlobs));
|
|
422
|
+
return [...rootCommands, ...turboCommands].join(" && ");
|
|
423
|
+
}
|
|
424
|
+
function rootPackageJsonWithPackageTaskFilters(input, blueprint, packageManifests) {
|
|
277
425
|
assertRootPackageJson(input);
|
|
278
426
|
const workspacePackageGlobs = workspacePackageGlobsFromBlueprint(blueprint);
|
|
427
|
+
const taskNames = turboTaskNamesForPackageManifests(packageManifests);
|
|
279
428
|
return {
|
|
280
429
|
...input,
|
|
281
430
|
scripts: {
|
|
282
431
|
...input.scripts,
|
|
283
|
-
check:
|
|
432
|
+
check: rootScriptWithTurboPackageTasks({
|
|
433
|
+
script: input.scripts.check,
|
|
434
|
+
taskNames,
|
|
435
|
+
workspacePackageGlobs,
|
|
436
|
+
}),
|
|
284
437
|
fix: rootScriptWithTurboPackageTask(input.scripts.fix, "fix", workspacePackageGlobs),
|
|
285
438
|
},
|
|
286
439
|
};
|
|
287
440
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const
|
|
441
|
+
function manifestExportUsesCompiledRuntime(value) {
|
|
442
|
+
if (!isRecord(value)) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const rootExport = value["."];
|
|
446
|
+
if (!isRecord(rootExport)) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return (typeof rootExport.default === "string" &&
|
|
450
|
+
rootExport.default.startsWith("./dist/"));
|
|
451
|
+
}
|
|
452
|
+
function packageManifestsNeedDependencyBuilds(manifests) {
|
|
453
|
+
const compiledPackageNames = new Set(manifests
|
|
454
|
+
.filter((manifest) => manifestExportUsesCompiledRuntime(manifest.exports))
|
|
455
|
+
.map((manifest) => manifest.name)
|
|
456
|
+
.filter((name) => typeof name === "string"));
|
|
457
|
+
if (compiledPackageNames.size === 0) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
return manifests.some((manifest) => Object.entries(manifest.dependencies ?? {}).some(([dependencyName, specifier]) => specifier === "workspace:*" && compiledPackageNames.has(dependencyName)));
|
|
461
|
+
}
|
|
462
|
+
function turboConfigForPackageManifests(manifests) {
|
|
463
|
+
return {
|
|
464
|
+
tasks: packageTurboTasks({
|
|
465
|
+
dependencyBuildsRequired: packageManifestsNeedDependencyBuilds(manifests),
|
|
466
|
+
}),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function rootPackageJsonWithSharedOxcRuntimeDependencies(input, requiresSharedOxcConfiguration) {
|
|
470
|
+
if (!requiresSharedOxcConfiguration) {
|
|
471
|
+
return input;
|
|
472
|
+
}
|
|
473
|
+
if (input.type !== undefined && input.type !== "module") {
|
|
474
|
+
throw new Error('Cannot update root OXC configuration: package.json type must be "module" or omitted');
|
|
475
|
+
}
|
|
476
|
+
if (input.devDependencies !== undefined && !isRecord(input.devDependencies)) {
|
|
477
|
+
throw new Error("Cannot update root OXC configuration dependencies: devDependencies must be an object");
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
...input,
|
|
481
|
+
type: "module",
|
|
482
|
+
devDependencies: {
|
|
483
|
+
...input.devDependencies,
|
|
484
|
+
oxfmt: input.devDependencies?.oxfmt ?? "catalog:",
|
|
485
|
+
oxlint: input.devDependencies?.oxlint ?? "catalog:",
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function catalogDependenciesWithSharedOxcRuntimeDependencies(catalogDependencies, requiresSharedOxcConfiguration) {
|
|
490
|
+
const dependencies = [...catalogDependencies];
|
|
491
|
+
if (!requiresSharedOxcConfiguration) {
|
|
492
|
+
return dependencies;
|
|
493
|
+
}
|
|
494
|
+
for (const dependency of ["oxfmt", "oxlint"]) {
|
|
495
|
+
if (!dependencies.includes(dependency)) {
|
|
496
|
+
dependencies.push(dependency);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return dependencies;
|
|
500
|
+
}
|
|
501
|
+
async function planRootUpdates(root, blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents, workspacePackageGlob, catalogDependencies, requiresSharedOxcConfiguration, operations) {
|
|
502
|
+
const nextBlueprint = blueprintWithPackage(blueprint, packageName, packagePath, packageRole, packageSourcePreset, packageLinkIntents);
|
|
503
|
+
const consumerManifestUpdates = packageLinkIntents.length > 0
|
|
504
|
+
? await consumerManifestUpdatesForPackageLinkIntents({
|
|
505
|
+
root,
|
|
506
|
+
manifestDependenciesByPackagePath: planPackageLinks([
|
|
507
|
+
{
|
|
508
|
+
name: packageName,
|
|
509
|
+
path: packagePath,
|
|
510
|
+
role: packageRole,
|
|
511
|
+
sourcePreset: packageSourcePreset,
|
|
512
|
+
},
|
|
513
|
+
], packageLinkIntents).manifestDependenciesByPackagePath,
|
|
514
|
+
})
|
|
515
|
+
: [];
|
|
516
|
+
const manifestOverrides = new Map(consumerManifestUpdates.map((update) => [
|
|
517
|
+
update.packagePath,
|
|
518
|
+
update.manifest,
|
|
519
|
+
]));
|
|
520
|
+
const packageManifests = await packageManifestsForTaskGraph({
|
|
521
|
+
root,
|
|
522
|
+
blueprint: nextBlueprint,
|
|
523
|
+
operations,
|
|
524
|
+
manifestOverrides,
|
|
525
|
+
});
|
|
526
|
+
const workspaceText = pnpmWorkspaceYamlWithCatalogDependencies(workspaceTextWithPackageGlob(await readFile(path.join(root, "pnpm-workspace.yaml"), "utf8"), workspacePackageGlob), catalogDependenciesWithSharedOxcRuntimeDependencies(catalogDependencies, requiresSharedOxcConfiguration));
|
|
527
|
+
const rootPackageJson = rootPackageJsonWithPackageTaskFilters(await readJson(path.join(root, "package.json")), nextBlueprint, packageManifests);
|
|
293
528
|
return {
|
|
294
529
|
blueprint: nextBlueprint,
|
|
295
|
-
rootPackageJson,
|
|
296
|
-
|
|
530
|
+
rootPackageJson: rootPackageJsonWithSharedOxcRuntimeDependencies(rootPackageJson, requiresSharedOxcConfiguration),
|
|
531
|
+
turboConfig: turboConfigForPackageManifests(packageManifests),
|
|
297
532
|
workspaceText,
|
|
533
|
+
consumerManifestUpdates,
|
|
298
534
|
};
|
|
299
535
|
}
|
|
536
|
+
async function readTextIfExists(filePath) {
|
|
537
|
+
try {
|
|
538
|
+
return await readFile(filePath, "utf8");
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async function writeIfMissing(filePath, text) {
|
|
548
|
+
if ((await readTextIfExists(filePath)) !== undefined) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
await writeFile(filePath, text, "utf8");
|
|
552
|
+
}
|
|
553
|
+
async function ensureRootOxfmtConfig(root, sharedOxcRoot) {
|
|
554
|
+
await writeIfMissing(path.join(root, "oxfmt.config.ts"), await readFile(path.join(sharedOxcRoot, "oxfmt.config.ts"), "utf8"));
|
|
555
|
+
}
|
|
556
|
+
async function ensureRootOxlintConfig(options) {
|
|
557
|
+
const nodeConfig = await readFile(path.join(options.sharedOxcRoot, "node", "oxlint.config.ts"), "utf8");
|
|
558
|
+
const vueConfig = await readFile(path.join(options.sharedOxcRoot, "vue", "oxlint.config.ts"), "utf8");
|
|
559
|
+
const targetPath = path.join(options.root, "oxlint.config.ts");
|
|
560
|
+
const currentConfig = await readTextIfExists(targetPath);
|
|
561
|
+
if (currentConfig === undefined) {
|
|
562
|
+
await writeFile(targetPath, options.addedPreset === "vue-app" ? vueConfig : nodeConfig, "utf8");
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (options.addedPreset !== "vue-app" || currentConfig === vueConfig) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (currentConfig === nodeConfig) {
|
|
569
|
+
await writeFile(targetPath, vueConfig, "utf8");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
throw new Error("Cannot update root OXC lint configuration for Vue Package Addition: oxlint.config.ts has local changes");
|
|
573
|
+
}
|
|
574
|
+
async function ensureRootOxcConfiguration(options) {
|
|
575
|
+
if (!options.sharedOxcRoot) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
await ensureRootOxfmtConfig(options.root, options.sharedOxcRoot);
|
|
579
|
+
await ensureRootOxlintConfig({
|
|
580
|
+
root: options.root,
|
|
581
|
+
sharedOxcRoot: options.sharedOxcRoot,
|
|
582
|
+
addedPreset: options.addedPreset,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function gitignoreContainsEntry(text, entry) {
|
|
586
|
+
return text
|
|
587
|
+
.split(/\r?\n/)
|
|
588
|
+
.some((line) => line.trim() === entry || line.trim() === `${entry}/`);
|
|
589
|
+
}
|
|
590
|
+
function gitignoreTextWithEntries(text, entries) {
|
|
591
|
+
const missingEntries = entries.filter((entry) => !gitignoreContainsEntry(text, entry));
|
|
592
|
+
if (missingEntries.length === 0) {
|
|
593
|
+
return text;
|
|
594
|
+
}
|
|
595
|
+
const prefix = text.trimEnd();
|
|
596
|
+
return `${prefix}${prefix.length > 0 ? "\n" : ""}${missingEntries.join("\n")}\n`;
|
|
597
|
+
}
|
|
598
|
+
function rootGitignoreEntriesForPackageAddition(preset, requiresSharedOxcConfiguration) {
|
|
599
|
+
const entries = requiresSharedOxcConfiguration
|
|
600
|
+
? ["node_modules", "dist"]
|
|
601
|
+
: [];
|
|
602
|
+
if (preset === "vue-app") {
|
|
603
|
+
entries.push("playwright-report", "test-results");
|
|
604
|
+
}
|
|
605
|
+
return entries;
|
|
606
|
+
}
|
|
607
|
+
async function ensureRootGitignoreEntries(root, entries) {
|
|
608
|
+
if (entries.length === 0) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const gitignorePath = path.join(root, ".gitignore");
|
|
612
|
+
const currentText = (await readTextIfExists(gitignorePath)) ?? "";
|
|
613
|
+
const nextText = gitignoreTextWithEntries(currentText, entries);
|
|
614
|
+
if (nextText !== currentText) {
|
|
615
|
+
await writeFile(gitignorePath, nextText, "utf8");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
300
618
|
export async function addPackage(options) {
|
|
301
619
|
assertSafePackageLeaf(options.name);
|
|
302
620
|
const root = path.resolve(options.cwd);
|
|
303
621
|
const blueprint = await readGeneratedWorkspaceBlueprint(root);
|
|
304
|
-
const
|
|
305
|
-
const projectName = projectNameFromBlueprint(blueprint);
|
|
622
|
+
const repositoryMetadata = await readGeneratedRepositoryPackageMetadata(root);
|
|
623
|
+
const projectName = projectNameFromBlueprint(blueprint, repositoryMetadata.projectName);
|
|
306
624
|
const packageName = `@${projectName}/${options.name}`;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
: defaultPackagePathForPreset(options.preset, options.name);
|
|
310
|
-
const projection = findBuiltInPresetProjection(options.preset);
|
|
311
|
-
const packageAddition = projection?.capabilities?.packageAddition;
|
|
312
|
-
if (!projection) {
|
|
625
|
+
const preset = findBuiltInPresetSourceManifestPreset(options.preset);
|
|
626
|
+
if (!preset) {
|
|
313
627
|
throw new Error(`Unknown preset for Package Addition: ${options.preset}`);
|
|
314
628
|
}
|
|
315
|
-
if (
|
|
316
|
-
throw new Error(
|
|
629
|
+
if (preset.packageAdditionSupport !== PackageAdditionSupport.Supported) {
|
|
630
|
+
throw new Error(formatUnsupportedPackageAdditionPresetError(options.preset));
|
|
317
631
|
}
|
|
318
|
-
const
|
|
632
|
+
const packagePath = options.path
|
|
633
|
+
? validateExplicitPackagePath(options.path)
|
|
634
|
+
: defaultPackagePathForPresetSourcePackageAddition(preset, options.name);
|
|
635
|
+
const additionPlan = await planPresetSourcePackageAddition({
|
|
636
|
+
preset,
|
|
637
|
+
addition: {
|
|
638
|
+
root,
|
|
639
|
+
blueprint,
|
|
640
|
+
packageLeafName: options.name,
|
|
641
|
+
packageName,
|
|
642
|
+
packagePath,
|
|
643
|
+
nodeVersion: repositoryMetadata.nodeVersion,
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
const requiresSharedOxcConfiguration = additionPlan.sourceRoots?.sharedOxc !== undefined;
|
|
647
|
+
assertNoPackageConflict(blueprint, packageName, additionPlan.packagePath);
|
|
648
|
+
const packageLinkIntents = packageLinkIntentsForConsumers({
|
|
649
|
+
blueprint,
|
|
650
|
+
consumerPackagePaths: options.linkFrom ?? [],
|
|
651
|
+
providerPackagePath: additionPlan.packagePath,
|
|
652
|
+
});
|
|
653
|
+
await assertPackageLinkIntentConsumersAreTypeScriptBoundaries({
|
|
319
654
|
root,
|
|
320
655
|
blueprint,
|
|
321
|
-
|
|
322
|
-
packageName,
|
|
323
|
-
packagePath,
|
|
324
|
-
nodeVersion,
|
|
656
|
+
packageLinkIntents,
|
|
325
657
|
});
|
|
326
|
-
assertNoPackageConflict(blueprint, packageName, additionPlan.packagePath);
|
|
327
658
|
await assertMissingPackagePath(additionPlan.packagePath, path.join(root, additionPlan.packagePath));
|
|
328
|
-
const rootUpdatePlan = await planRootUpdates(root, blueprint, packageName, additionPlan.packagePath, additionPlan.
|
|
659
|
+
const rootUpdatePlan = await planRootUpdates(root, blueprint, packageName, additionPlan.packagePath, additionPlan.packageRole, additionPlan.packageSourcePreset, packageLinkIntents, additionPlan.workspaceMembershipGlob ?? additionPlan.workspacePackageGlob, packageAdditionCatalogDependencies(additionPlan.operations), requiresSharedOxcConfiguration, additionPlan.operations);
|
|
329
660
|
await mkdir(path.join(root, additionPlan.packagePath), { recursive: true });
|
|
661
|
+
await ensureRootOxcConfiguration({
|
|
662
|
+
root,
|
|
663
|
+
sharedOxcRoot: additionPlan.sourceRoots?.sharedOxc,
|
|
664
|
+
addedPreset: options.preset,
|
|
665
|
+
});
|
|
666
|
+
await ensureRootGitignoreEntries(root, rootGitignoreEntriesForPackageAddition(options.preset, requiresSharedOxcConfiguration));
|
|
330
667
|
await renderProject({
|
|
331
668
|
sourceRoot: additionPlan.sourceRoot,
|
|
332
669
|
sourceRoots: additionPlan.sourceRoots,
|
|
@@ -338,6 +675,9 @@ export async function addPackage(options) {
|
|
|
338
675
|
}
|
|
339
676
|
await writeFile(path.join(root, "pnpm-workspace.yaml"), rootUpdatePlan.workspaceText, "utf8");
|
|
340
677
|
await writeJson(path.join(root, "package.json"), rootUpdatePlan.rootPackageJson);
|
|
341
|
-
await writeJson(path.join(root, "
|
|
678
|
+
await writeJson(path.join(root, "turbo.json"), rootUpdatePlan.turboConfig);
|
|
679
|
+
for (const update of rootUpdatePlan.consumerManifestUpdates) {
|
|
680
|
+
await writeJson(path.join(root, update.packagePath, "package.json"), update.manifest);
|
|
681
|
+
}
|
|
342
682
|
await writeJson(path.join(root, ".template/blueprint.json"), rootUpdatePlan.blueprint);
|
|
343
683
|
}
|
|
@@ -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;
|