@sculptor/cli 0.3.2 → 0.3.4
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/README.md +1 -1
- package/bin/sc.js +5 -1
- package/dist/cli.js +21 -11
- package/dist/cli.js.map +1 -1
- package/dist/package-commands.js +109 -57
- package/dist/package-commands.js.map +1 -1
- package/dist/package-registry.d.ts +7 -2
- package/dist/package-registry.js +282 -103
- package/dist/package-registry.js.map +1 -1
- package/dist/scaffold.js +1 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +4 -4
package/dist/package-registry.js
CHANGED
|
@@ -60,6 +60,23 @@ const inferFileType = (filePath) => {
|
|
|
60
60
|
}
|
|
61
61
|
return "type";
|
|
62
62
|
};
|
|
63
|
+
const isRegistryTrackableFile = (filePath) => {
|
|
64
|
+
const normalized = normalizeRelativePath(filePath);
|
|
65
|
+
return (/\.(controller|service|repository|middleware|module|dto|route|type|types)\.ts$/.test(normalized) ||
|
|
66
|
+
normalized.endsWith(".route.handler.ts"));
|
|
67
|
+
};
|
|
68
|
+
const createPackageFileRecord = (type, filePath, registered = false, tags = []) => ({
|
|
69
|
+
type,
|
|
70
|
+
path: normalizeRelativePath(filePath),
|
|
71
|
+
registered,
|
|
72
|
+
tags: [...tags]
|
|
73
|
+
});
|
|
74
|
+
const normalizeFileRecord = (record) => ({
|
|
75
|
+
type: record.type ?? inferFileType(record.path),
|
|
76
|
+
path: normalizeRelativePath(record.path),
|
|
77
|
+
registered: record.registered ?? true,
|
|
78
|
+
tags: Array.isArray(record.tags) ? record.tags.map((tag) => String(tag)) : []
|
|
79
|
+
});
|
|
63
80
|
const inferSymbolFromFile = (filePath) => {
|
|
64
81
|
const base = path.posix.basename(filePath).replace(/\.ts$/, "");
|
|
65
82
|
if (base.endsWith(".route.handler")) {
|
|
@@ -128,16 +145,71 @@ const getPackageDecorator = (node) => {
|
|
|
128
145
|
}
|
|
129
146
|
return undefined;
|
|
130
147
|
};
|
|
148
|
+
const getPackageCallExpression = (node) => {
|
|
149
|
+
const visit = (current) => {
|
|
150
|
+
if (ts.isCallExpression(current)) {
|
|
151
|
+
const callee = current.expression;
|
|
152
|
+
if (ts.isIdentifier(callee) && callee.text === "Package") {
|
|
153
|
+
return current;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return ts.forEachChild(current, visit);
|
|
157
|
+
};
|
|
158
|
+
return visit(node);
|
|
159
|
+
};
|
|
160
|
+
const getFunctionalPackageDefinition = (sourceFile) => {
|
|
161
|
+
for (const statement of sourceFile.statements) {
|
|
162
|
+
if (!ts.isVariableStatement(statement)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
166
|
+
if (!ts.isIdentifier(declaration.name)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const identifier = declaration.name.text;
|
|
170
|
+
if (!identifier.endsWith("PackageDefinition")) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const initializer = declaration.initializer;
|
|
174
|
+
if (initializer && ts.isObjectLiteralExpression(initializer)) {
|
|
175
|
+
return initializer;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
};
|
|
181
|
+
const resolvePackageDefinitionByName = (sourceFile, identifier) => {
|
|
182
|
+
for (const statement of sourceFile.statements) {
|
|
183
|
+
if (!ts.isVariableStatement(statement)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
187
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== identifier) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
191
|
+
return declaration.initializer;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return undefined;
|
|
196
|
+
};
|
|
131
197
|
const parsePackageMetadata = (rootDir, filePath) => {
|
|
132
198
|
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
133
199
|
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
134
200
|
const classes = sourceFile.statements.filter(ts.isClassDeclaration);
|
|
135
201
|
const decoratedClass = classes.find((node) => getPackageDecorator(node));
|
|
136
|
-
|
|
202
|
+
const decorator = decoratedClass ? getPackageDecorator(decoratedClass) : undefined;
|
|
203
|
+
const functionalCall = decorator ? undefined : getPackageCallExpression(sourceFile);
|
|
204
|
+
const functionalDefinition = decorator || functionalCall ? undefined : getFunctionalPackageDefinition(sourceFile);
|
|
205
|
+
if (!decorator && !functionalCall && !functionalDefinition) {
|
|
137
206
|
return undefined;
|
|
138
207
|
}
|
|
139
|
-
const
|
|
140
|
-
const
|
|
208
|
+
const packageCallArgument = functionalCall?.arguments[0];
|
|
209
|
+
const resolvedFunctionalArgument = packageCallArgument && ts.isIdentifier(packageCallArgument)
|
|
210
|
+
? resolvePackageDefinitionByName(sourceFile, packageCallArgument.text)
|
|
211
|
+
: packageCallArgument;
|
|
212
|
+
const argument = decorator?.arguments[0] ?? resolvedFunctionalArgument ?? functionalDefinition;
|
|
141
213
|
if (!argument || !ts.isObjectLiteralExpression(argument)) {
|
|
142
214
|
throw new Error(`Malformed @Package() metadata in ${filePath}.`);
|
|
143
215
|
}
|
|
@@ -156,7 +228,19 @@ const parsePackageMetadata = (rootDir, filePath) => {
|
|
|
156
228
|
}
|
|
157
229
|
return value.text;
|
|
158
230
|
};
|
|
159
|
-
const
|
|
231
|
+
const inferredName = decoratedClass?.name?.text ??
|
|
232
|
+
(sourceFile.statements.find(ts.isVariableStatement)?.declarationList.declarations.find((declaration) => {
|
|
233
|
+
if (!ts.isIdentifier(declaration.name)) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const initializer = declaration.initializer;
|
|
237
|
+
if (!initializer || !ts.isCallExpression(initializer)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const callee = initializer.expression;
|
|
241
|
+
return ts.isIdentifier(callee) && callee.text === "Package";
|
|
242
|
+
})?.name.getText(sourceFile) ?? "");
|
|
243
|
+
const name = getString("name", inferredName);
|
|
160
244
|
const packagePath = getString("path", "");
|
|
161
245
|
if (!name || !packagePath) {
|
|
162
246
|
throw new Error(`Malformed @Package() metadata in ${filePath}.`);
|
|
@@ -175,8 +259,18 @@ export const loadPackageRegistry = (rootDir) => {
|
|
|
175
259
|
return { packages: {}, files: [] };
|
|
176
260
|
}
|
|
177
261
|
return {
|
|
178
|
-
packages: data.packages ?? {},
|
|
179
|
-
|
|
262
|
+
packages: Object.fromEntries(Object.entries(data.packages ?? {}).map(([name, record]) => [
|
|
263
|
+
name,
|
|
264
|
+
{
|
|
265
|
+
...record,
|
|
266
|
+
files: (record.files ?? [])
|
|
267
|
+
.map((file) => normalizeFileRecord(file))
|
|
268
|
+
.filter((file) => isRegistryTrackableFile(file.path))
|
|
269
|
+
}
|
|
270
|
+
])),
|
|
271
|
+
files: (data.files ?? [])
|
|
272
|
+
.map((file) => normalizeFileRecord(file))
|
|
273
|
+
.filter((file) => isRegistryTrackableFile(file.path))
|
|
180
274
|
};
|
|
181
275
|
};
|
|
182
276
|
export const savePackageRegistry = (rootDir, registry) => {
|
|
@@ -185,6 +279,16 @@ export const savePackageRegistry = (rootDir, registry) => {
|
|
|
185
279
|
export const scanPackageRegistry = (rootDir) => {
|
|
186
280
|
const srcRoot = getSrcRoot(rootDir);
|
|
187
281
|
const sourceRoot = path.join(rootDir, srcRoot);
|
|
282
|
+
const existingRegistry = loadPackageRegistry(rootDir);
|
|
283
|
+
const existingFileState = new Map();
|
|
284
|
+
for (const record of Object.values(existingRegistry.packages)) {
|
|
285
|
+
for (const file of record.files) {
|
|
286
|
+
existingFileState.set(file.path, file);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const file of existingRegistry.files) {
|
|
290
|
+
existingFileState.set(file.path, file);
|
|
291
|
+
}
|
|
188
292
|
const packages = [];
|
|
189
293
|
const visit = (dir) => {
|
|
190
294
|
if (!fs.existsSync(dir)) {
|
|
@@ -213,24 +317,31 @@ export const scanPackageRegistry = (rootDir) => {
|
|
|
213
317
|
const allTsFiles = collectTsFiles(sourceRoot, rootDir);
|
|
214
318
|
const ownedFiles = new Set();
|
|
215
319
|
for (const file of allTsFiles) {
|
|
216
|
-
if (file === PACKAGE_REGISTRY_FILE ||
|
|
320
|
+
if (file === PACKAGE_REGISTRY_FILE ||
|
|
321
|
+
file.endsWith(".d.ts") ||
|
|
322
|
+
file.endsWith("index.ts") ||
|
|
323
|
+
!isRegistryTrackableFile(file)) {
|
|
217
324
|
continue;
|
|
218
325
|
}
|
|
219
326
|
const owner = [...packages]
|
|
220
327
|
.filter((pkg) => fileBelongsToPackage(pkg.path, file))
|
|
221
328
|
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
path: file
|
|
225
|
-
};
|
|
329
|
+
const existingFile = existingFileState.get(file);
|
|
330
|
+
const nextFile = createPackageFileRecord(inferFileType(file), file, owner ? existingFile?.registered ?? true : existingFile?.registered ?? false, existingFile?.tags ?? []);
|
|
226
331
|
if (owner) {
|
|
227
|
-
owner.files.push(
|
|
332
|
+
owner.files.push({
|
|
333
|
+
...nextFile,
|
|
334
|
+
registered: existingFile?.registered ?? true
|
|
335
|
+
});
|
|
228
336
|
ownedFiles.add(file);
|
|
229
337
|
}
|
|
230
338
|
}
|
|
231
339
|
const unpackaged = [];
|
|
232
340
|
for (const file of allTsFiles) {
|
|
233
|
-
if (file === PACKAGE_REGISTRY_FILE ||
|
|
341
|
+
if (file === PACKAGE_REGISTRY_FILE ||
|
|
342
|
+
file.endsWith(".d.ts") ||
|
|
343
|
+
file.endsWith("index.ts") ||
|
|
344
|
+
!isRegistryTrackableFile(file)) {
|
|
234
345
|
continue;
|
|
235
346
|
}
|
|
236
347
|
if (ownedFiles.has(file)) {
|
|
@@ -238,7 +349,9 @@ export const scanPackageRegistry = (rootDir) => {
|
|
|
238
349
|
}
|
|
239
350
|
unpackaged.push({
|
|
240
351
|
type: inferFileType(file),
|
|
241
|
-
path: file
|
|
352
|
+
path: file,
|
|
353
|
+
registered: existingFileState.get(file)?.registered ?? false,
|
|
354
|
+
tags: existingFileState.get(file)?.tags ?? []
|
|
242
355
|
});
|
|
243
356
|
}
|
|
244
357
|
return {
|
|
@@ -258,9 +371,9 @@ export const syncPackageRegistry = (rootDir) => {
|
|
|
258
371
|
savePackageRegistry(rootDir, registry);
|
|
259
372
|
return registry;
|
|
260
373
|
};
|
|
261
|
-
const derivePackageIndexView = (record) => {
|
|
262
|
-
const ownedFiles = record.files.filter((file) => file.path !== record.index);
|
|
263
|
-
const controllerFiles = ownedFiles.filter((file) => file.path.endsWith(".controller.ts"));
|
|
374
|
+
const derivePackageIndexView = (record, style = "decorator") => {
|
|
375
|
+
const ownedFiles = record.files.filter((file) => file.path !== record.index && file.registered);
|
|
376
|
+
const controllerFiles = style === "decorator" ? ownedFiles.filter((file) => file.path.endsWith(".controller.ts")) : [];
|
|
264
377
|
const serviceFiles = ownedFiles.filter((file) => file.path.endsWith(".service.ts"));
|
|
265
378
|
const repositoryFiles = ownedFiles.filter((file) => file.path.endsWith(".repository.ts"));
|
|
266
379
|
const middlewareFiles = ownedFiles.filter((file) => file.path.endsWith(".middleware.ts"));
|
|
@@ -268,29 +381,28 @@ const derivePackageIndexView = (record) => {
|
|
|
268
381
|
const routeHandlerFiles = ownedFiles.filter((file) => file.path.endsWith(".route.handler.ts"));
|
|
269
382
|
const dtoFiles = ownedFiles.filter((file) => file.path.endsWith(".dto.ts"));
|
|
270
383
|
const typeFiles = ownedFiles.filter((file) => file.path.endsWith(".types.ts") || file.path.endsWith(".type.ts"));
|
|
271
|
-
const importableFiles =
|
|
272
|
-
...
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
...repositoryFiles,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
];
|
|
384
|
+
const importableFiles = style === "functional"
|
|
385
|
+
? [...routeFiles]
|
|
386
|
+
: [
|
|
387
|
+
...controllerFiles,
|
|
388
|
+
...serviceFiles,
|
|
389
|
+
...repositoryFiles,
|
|
390
|
+
...middlewareFiles,
|
|
391
|
+
...routeFiles,
|
|
392
|
+
...dtoFiles
|
|
393
|
+
];
|
|
394
|
+
const exportFiles = style === "functional"
|
|
395
|
+
? [...serviceFiles, ...repositoryFiles, ...routeFiles, ...routeHandlerFiles, ...typeFiles]
|
|
396
|
+
: [
|
|
397
|
+
...controllerFiles,
|
|
398
|
+
...serviceFiles,
|
|
399
|
+
...repositoryFiles,
|
|
400
|
+
...middlewareFiles,
|
|
401
|
+
...routeFiles,
|
|
402
|
+
...routeHandlerFiles,
|
|
403
|
+
...dtoFiles
|
|
404
|
+
];
|
|
405
|
+
const packageExportFiles = style === "functional" ? [] : [...serviceFiles, ...repositoryFiles, ...middlewareFiles, ...dtoFiles];
|
|
294
406
|
const renderArray = (values) => values.length === 0 ? "[]" : `[\n${values.map((value) => ` ${value}`).join(",\n")}\n ]`;
|
|
295
407
|
const imports = importableFiles
|
|
296
408
|
.map((file) => {
|
|
@@ -308,7 +420,21 @@ const derivePackageIndexView = (record) => {
|
|
|
308
420
|
return `export * from "./${relative}";`;
|
|
309
421
|
})
|
|
310
422
|
.join("\n");
|
|
311
|
-
const packageBlock =
|
|
423
|
+
const packageBlock = style === "functional"
|
|
424
|
+
? `const ${toPascalCase(record.name)}PackageDefinition = {
|
|
425
|
+
name: "${record.name}",
|
|
426
|
+
path: "${record.path}",
|
|
427
|
+
imports: [],
|
|
428
|
+
exports: [],
|
|
429
|
+
controllers: [],
|
|
430
|
+
services: [],
|
|
431
|
+
repositories: [],
|
|
432
|
+
middlewares: [],
|
|
433
|
+
routes: ${renderArray(routeFiles.map((file) => inferSymbolFromFile(file.path)))}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
export const ${toPascalCase(record.name)}Package = Package(${toPascalCase(record.name)}PackageDefinition)(() => ${toPascalCase(record.name)}PackageDefinition);`
|
|
437
|
+
: `@Package({
|
|
312
438
|
name: "${record.name}",
|
|
313
439
|
path: "${record.path}",
|
|
314
440
|
imports: [],
|
|
@@ -322,9 +448,12 @@ const derivePackageIndexView = (record) => {
|
|
|
322
448
|
export class ${toPascalCase(record.name)}Package {}`;
|
|
323
449
|
return { imports, exports, packageBlock };
|
|
324
450
|
};
|
|
325
|
-
export const renderPackageIndex = (record) => {
|
|
326
|
-
const sections = derivePackageIndexView(record);
|
|
327
|
-
|
|
451
|
+
export const renderPackageIndex = (record, style = "decorator") => {
|
|
452
|
+
const sections = derivePackageIndexView(record, style);
|
|
453
|
+
const importLine = style === "functional"
|
|
454
|
+
? 'import { Package, type SculptorFunctionalPackage } from "@sculptor/core";'
|
|
455
|
+
: 'import { Package } from "@sculptor/core";';
|
|
456
|
+
return `/**\n * @generated true\n */\n${importLine}\n\n// [sculptor:imports:start]\n${sections.imports}\n// [sculptor:imports:end]\n\n// [sculptor:exports:start]\n${sections.exports}\n// [sculptor:exports:end]\n\n// [sculptor:package:start]\n${sections.packageBlock}\n// [sculptor:package:end]\n`;
|
|
328
457
|
};
|
|
329
458
|
export const inferPackageNameFromPath = (packagePath) => normalizeRelativePath(path.posix.basename(packagePath));
|
|
330
459
|
export const inferPackagePath = (rootDir, packageName, explicitPath) => {
|
|
@@ -333,7 +462,7 @@ export const inferPackagePath = (rootDir, packageName, explicitPath) => {
|
|
|
333
462
|
if (explicitPath) {
|
|
334
463
|
return normalizeRelativePath(path.posix.join(normalizeRelativePath(explicitPath), packageFolder));
|
|
335
464
|
}
|
|
336
|
-
return normalizeRelativePath(path.posix.join(srcRoot, packageFolder));
|
|
465
|
+
return normalizeRelativePath(path.posix.join(srcRoot, "app", packageFolder));
|
|
337
466
|
};
|
|
338
467
|
export const inferPackageIndexPath = (packagePath) => normalizeRelativePath(path.posix.join(packagePath, "index.ts"));
|
|
339
468
|
export const buildPackageRecord = (rootDir, packageName, explicitPath) => {
|
|
@@ -341,11 +470,11 @@ export const buildPackageRecord = (rootDir, packageName, explicitPath) => {
|
|
|
341
470
|
const index = inferPackageIndexPath(packagePath);
|
|
342
471
|
const normalizedPackageName = normalizeRelativePath(packageName);
|
|
343
472
|
const files = [
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
473
|
+
createPackageFileRecord("controller", path.posix.join(packagePath, `${normalizedPackageName}.controller.ts`), true),
|
|
474
|
+
createPackageFileRecord("service", path.posix.join(packagePath, `${normalizedPackageName}.service.ts`), true),
|
|
475
|
+
createPackageFileRecord("repository", path.posix.join(packagePath, `${normalizedPackageName}.repository.ts`), true),
|
|
476
|
+
createPackageFileRecord("dto", path.posix.join(packagePath, `${normalizedPackageName}.dto.ts`), true),
|
|
477
|
+
createPackageFileRecord("type", path.posix.join(packagePath, `${normalizedPackageName}.types.ts`), true)
|
|
349
478
|
];
|
|
350
479
|
return {
|
|
351
480
|
name: normalizedPackageName,
|
|
@@ -368,52 +497,67 @@ const renderPackageRegistryImport = (srcRoot, record) => {
|
|
|
368
497
|
: record.path;
|
|
369
498
|
return `import { ${toPascalCase(record.name)}Package } from "./${normalizeRelativePath(path.posix.join(packageImportPath, "index.js"))}";`;
|
|
370
499
|
};
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
500
|
+
const renderDirectRegistryImport = (srcRoot, file) => {
|
|
501
|
+
if (!["controller", "service", "repository", "middleware", "route"].includes(file.type)) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
const sourcePath = file.path.startsWith(`${srcRoot}/`)
|
|
505
|
+
? file.path.slice(`${srcRoot}/`.length)
|
|
506
|
+
: file.path;
|
|
507
|
+
const symbol = inferSymbolFromFile(file.path);
|
|
508
|
+
return `import { ${symbol} } from "./${normalizeRelativePath(path.posix.join(sourcePath.replace(/\.ts$/, ".js")))}";`;
|
|
509
|
+
};
|
|
510
|
+
const renderRegistryRootFile = (rootDir, registry) => {
|
|
511
|
+
const srcRoot = getSrcRoot(rootDir);
|
|
512
|
+
const packageRecords = Object.values(registry.packages).sort((left, right) => left.name.localeCompare(right.name));
|
|
513
|
+
const directFiles = registry.files
|
|
514
|
+
.filter((file) => file.registered)
|
|
515
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
516
|
+
const packageImports = packageRecords.map((record) => renderPackageRegistryImport(srcRoot, record));
|
|
517
|
+
const directImports = directFiles
|
|
518
|
+
.map((file) => renderDirectRegistryImport(srcRoot, file))
|
|
519
|
+
.filter((value) => Boolean(value));
|
|
520
|
+
const directControllers = directFiles
|
|
521
|
+
.filter((file) => file.type === "controller")
|
|
522
|
+
.map((file) => inferSymbolFromFile(file.path));
|
|
523
|
+
const directServices = directFiles
|
|
524
|
+
.filter((file) => file.type === "service")
|
|
525
|
+
.map((file) => inferSymbolFromFile(file.path));
|
|
526
|
+
const directRepositories = directFiles
|
|
527
|
+
.filter((file) => file.type === "repository")
|
|
528
|
+
.map((file) => inferSymbolFromFile(file.path));
|
|
529
|
+
const directMiddlewares = directFiles
|
|
530
|
+
.filter((file) => file.type === "middleware")
|
|
531
|
+
.map((file) => inferSymbolFromFile(file.path));
|
|
532
|
+
const directRoutes = directFiles
|
|
533
|
+
.filter((file) => file.type === "route")
|
|
534
|
+
.map((file) => inferSymbolFromFile(file.path));
|
|
535
|
+
const packageEntries = packageRecords.map((record) => `${toPascalCase(record.name)}Package`);
|
|
536
|
+
return `/**
|
|
537
|
+
* @generated true
|
|
538
|
+
*/
|
|
539
|
+
${[...packageImports, ...directImports].join("\n")}
|
|
540
|
+
|
|
541
|
+
export const registry = {
|
|
542
|
+
packages: [${packageEntries.join(", ")}],
|
|
543
|
+
controllers: [${directControllers.join(", ")}],
|
|
544
|
+
routes: [${directRoutes.join(", ")}],
|
|
545
|
+
services: [${directServices.join(", ")}],
|
|
546
|
+
repositories: [${directRepositories.join(", ")}],
|
|
547
|
+
middlewares: [${directMiddlewares.join(", ")}]
|
|
548
|
+
};
|
|
549
|
+
`;
|
|
375
550
|
};
|
|
376
551
|
export const syncRootRegistryForPackages = (rootDir) => {
|
|
377
552
|
const srcRoot = getSrcRoot(rootDir);
|
|
378
553
|
const registryPath = path.join(rootDir, srcRoot, "registry.ts");
|
|
379
554
|
const registry = loadPackageRegistry(rootDir);
|
|
380
|
-
|
|
381
|
-
if (packageRecords.length === 0 && !fs.existsSync(registryPath)) {
|
|
555
|
+
if (Object.keys(registry.packages).length === 0 && registry.files.length === 0 && !fs.existsSync(registryPath)) {
|
|
382
556
|
return registryPath;
|
|
383
557
|
}
|
|
384
558
|
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
fs.writeFileSync(registryPath, next, "utf8");
|
|
388
|
-
return registryPath;
|
|
389
|
-
}
|
|
390
|
-
const current = fs.readFileSync(registryPath, "utf8");
|
|
391
|
-
const packageImportPattern = /^import \{ [^}]+Package \} from "\.\/.*\/index\.js";\n?/gm;
|
|
392
|
-
const hasPackageRegistryShape = packageImportPattern.test(current) || /packages:\s*\[[^\]]*\]/m.test(current);
|
|
393
|
-
packageImportPattern.lastIndex = 0;
|
|
394
|
-
if (packageRecords.length === 0 && !hasPackageRegistryShape) {
|
|
395
|
-
return registryPath;
|
|
396
|
-
}
|
|
397
|
-
const imports = packageRecords.map((record) => renderPackageRegistryImport(srcRoot, record)).join("\n");
|
|
398
|
-
const packageEntries = packageRecords.map((record) => `${toPascalCase(record.name)}Package`).join(", ");
|
|
399
|
-
const strippedImports = current.replace(packageImportPattern, "");
|
|
400
|
-
const withImports = `${imports}${imports ? "\n\n" : ""}${strippedImports}`.replace(/^\n+/, "");
|
|
401
|
-
const packagePattern = /packages:\s*\[[^\]]*\]/m;
|
|
402
|
-
if (packagePattern.test(withImports)) {
|
|
403
|
-
const next = withImports.replace(packagePattern, `packages: [${packageEntries}]`);
|
|
404
|
-
if (next !== current) {
|
|
405
|
-
fs.writeFileSync(registryPath, next, "utf8");
|
|
406
|
-
}
|
|
407
|
-
return registryPath;
|
|
408
|
-
}
|
|
409
|
-
const registryOpenPattern = /(export const registry = \{\n)/;
|
|
410
|
-
if (!registryOpenPattern.test(withImports)) {
|
|
411
|
-
throw new Error(`Unable to update ${registryPath} safely. registry.ts is malformed.`);
|
|
412
|
-
}
|
|
413
|
-
const next = withImports.replace(registryOpenPattern, `$1 packages: [${packageEntries}],\n`);
|
|
414
|
-
if (next !== current) {
|
|
415
|
-
fs.writeFileSync(registryPath, next, "utf8");
|
|
416
|
-
}
|
|
559
|
+
const next = renderRegistryRootFile(rootDir, registry);
|
|
560
|
+
fs.writeFileSync(registryPath, next, "utf8");
|
|
417
561
|
return registryPath;
|
|
418
562
|
};
|
|
419
563
|
export const upsertFileIntoRegistry = (registry, filePath) => {
|
|
@@ -427,10 +571,7 @@ export const upsertFileIntoRegistry = (registry, filePath) => {
|
|
|
427
571
|
continue;
|
|
428
572
|
}
|
|
429
573
|
const existingIndex = record.files.findIndex((entry) => entry.path === normalizedPath);
|
|
430
|
-
const nextFile =
|
|
431
|
-
type: fileType,
|
|
432
|
-
path: normalizedPath
|
|
433
|
-
};
|
|
574
|
+
const nextFile = createPackageFileRecord(fileType, normalizedPath, true);
|
|
434
575
|
if (existingIndex >= 0) {
|
|
435
576
|
record.files[existingIndex] = nextFile;
|
|
436
577
|
}
|
|
@@ -440,10 +581,7 @@ export const upsertFileIntoRegistry = (registry, filePath) => {
|
|
|
440
581
|
return registry;
|
|
441
582
|
}
|
|
442
583
|
const existing = registry.files.findIndex((entry) => entry.path === normalizedPath);
|
|
443
|
-
const nextFile =
|
|
444
|
-
type: fileType,
|
|
445
|
-
path: normalizedPath
|
|
446
|
-
};
|
|
584
|
+
const nextFile = createPackageFileRecord(fileType, normalizedPath, true);
|
|
447
585
|
if (existing >= 0) {
|
|
448
586
|
registry.files[existing] = nextFile;
|
|
449
587
|
}
|
|
@@ -452,7 +590,39 @@ export const upsertFileIntoRegistry = (registry, filePath) => {
|
|
|
452
590
|
}
|
|
453
591
|
return registry;
|
|
454
592
|
};
|
|
455
|
-
export const
|
|
593
|
+
export const unregisterFileFromRegistry = (registry, filePath) => {
|
|
594
|
+
const normalizedPath = normalizeRelativePath(filePath);
|
|
595
|
+
for (const record of Object.values(registry.packages)) {
|
|
596
|
+
if (normalizedPath === record.index) {
|
|
597
|
+
return registry;
|
|
598
|
+
}
|
|
599
|
+
if (!isFileOwnedByPackage(record, normalizedPath)) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
const existingIndex = record.files.findIndex((entry) => entry.path === normalizedPath);
|
|
603
|
+
if (existingIndex >= 0) {
|
|
604
|
+
record.files[existingIndex] = {
|
|
605
|
+
...record.files[existingIndex],
|
|
606
|
+
registered: false
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
record.files.push(createPackageFileRecord(inferFileType(normalizedPath), normalizedPath, false));
|
|
611
|
+
}
|
|
612
|
+
return registry;
|
|
613
|
+
}
|
|
614
|
+
const existingIndex = registry.files.findIndex((entry) => entry.path === normalizedPath);
|
|
615
|
+
if (existingIndex >= 0) {
|
|
616
|
+
registry.files[existingIndex] = {
|
|
617
|
+
...registry.files[existingIndex],
|
|
618
|
+
registered: false
|
|
619
|
+
};
|
|
620
|
+
return registry;
|
|
621
|
+
}
|
|
622
|
+
registry.files.push(createPackageFileRecord(inferFileType(normalizedPath), normalizedPath, false));
|
|
623
|
+
return registry;
|
|
624
|
+
};
|
|
625
|
+
export const deleteFileFromRegistry = (registry, filePath) => {
|
|
456
626
|
const normalizedPath = normalizeRelativePath(filePath);
|
|
457
627
|
for (const record of Object.values(registry.packages)) {
|
|
458
628
|
if (normalizedPath === record.index) {
|
|
@@ -471,7 +641,7 @@ export const getOwningPackage = (registry, filePath) => {
|
|
|
471
641
|
const normalizedPath = normalizeRelativePath(filePath);
|
|
472
642
|
return Object.values(registry.packages).find((record) => isFileOwnedByPackage(record, normalizedPath));
|
|
473
643
|
};
|
|
474
|
-
export const renderPackageIndexForRecord = (record) => renderPackageIndex(record);
|
|
644
|
+
export const renderPackageIndexForRecord = (record) => renderPackageIndex(record, inferPackageIndexStyle(record));
|
|
475
645
|
const generatedMarkers = {
|
|
476
646
|
imports: {
|
|
477
647
|
start: "// [sculptor:imports:start]",
|
|
@@ -498,8 +668,17 @@ const replaceMarkerBlock = (source, section, content) => {
|
|
|
498
668
|
}
|
|
499
669
|
return `${source.slice(0, startIndex + start.length)}\n${content}\n${source.slice(endIndex)}`;
|
|
500
670
|
};
|
|
501
|
-
const
|
|
502
|
-
|
|
671
|
+
const inferPackageIndexStyle = (record, sourceText) => {
|
|
672
|
+
if (sourceText?.includes("SculptorFunctionalPackage")) {
|
|
673
|
+
return "functional";
|
|
674
|
+
}
|
|
675
|
+
if (record.files.some((file) => file.path.endsWith(".controller.ts"))) {
|
|
676
|
+
return "decorator";
|
|
677
|
+
}
|
|
678
|
+
return "functional";
|
|
679
|
+
};
|
|
680
|
+
const renderPackageIndexSections = (record, style = "decorator") => {
|
|
681
|
+
const sections = derivePackageIndexView(record, style);
|
|
503
682
|
return {
|
|
504
683
|
imports: sections.imports,
|
|
505
684
|
exports: sections.exports,
|
|
@@ -507,20 +686,20 @@ const renderPackageIndexSections = (record) => {
|
|
|
507
686
|
};
|
|
508
687
|
};
|
|
509
688
|
export const updatePackageIndexForRecord = (filePath, record) => {
|
|
510
|
-
const next = renderPackageIndex(record);
|
|
511
689
|
if (!fs.existsSync(filePath)) {
|
|
512
690
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
513
|
-
fs.writeFileSync(filePath,
|
|
691
|
+
fs.writeFileSync(filePath, renderPackageIndex(record, inferPackageIndexStyle(record)), "utf8");
|
|
514
692
|
return;
|
|
515
693
|
}
|
|
516
694
|
const current = fs.readFileSync(filePath, "utf8");
|
|
695
|
+
const style = inferPackageIndexStyle(record, current);
|
|
517
696
|
for (const section of Object.keys(generatedMarkers)) {
|
|
518
697
|
if (!current.includes(generatedMarkers[section].start) || !current.includes(generatedMarkers[section].end)) {
|
|
519
698
|
throw new Error(`Package index markers are missing or malformed in ${filePath}. Refusing to rewrite the file unsafely.`);
|
|
520
699
|
}
|
|
521
700
|
}
|
|
522
701
|
let updated = current;
|
|
523
|
-
const sections = renderPackageIndexSections(record);
|
|
702
|
+
const sections = renderPackageIndexSections(record, style);
|
|
524
703
|
updated = replaceMarkerBlock(updated, "imports", sections.imports);
|
|
525
704
|
updated = replaceMarkerBlock(updated, "exports", sections.exports);
|
|
526
705
|
updated = replaceMarkerBlock(updated, "package", sections.package);
|