@uniformdev/transformer 1.1.38 → 1.1.40
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/cli/index.js +633 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +23 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command20 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/cli/commands/propagate-root-component-property.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -4123,8 +4123,7 @@ var ComponentAdderService = class {
|
|
|
4123
4123
|
}
|
|
4124
4124
|
const newInstance = {
|
|
4125
4125
|
type: componentTypeInPattern,
|
|
4126
|
-
_pattern: pattern.componentPatternId
|
|
4127
|
-
_id: randomUUID()
|
|
4126
|
+
_pattern: pattern.componentPatternId
|
|
4128
4127
|
};
|
|
4129
4128
|
const compositionsResult = await this.addComponentToDirectory(
|
|
4130
4129
|
fullCompositionsDir,
|
|
@@ -4238,13 +4237,19 @@ var ComponentAdderService = class {
|
|
|
4238
4237
|
continue;
|
|
4239
4238
|
}
|
|
4240
4239
|
const rootInstance = composition.composition;
|
|
4240
|
+
const rootPath = `${rootInstance._id ?? ""}${rootInstance.type}`;
|
|
4241
4241
|
let modified = false;
|
|
4242
4242
|
if (this.matchesAnyParentType(rootInstance.type, parentTypes, strict)) {
|
|
4243
4243
|
if (!rootInstance.slots) {
|
|
4244
4244
|
rootInstance.slots = {};
|
|
4245
4245
|
}
|
|
4246
4246
|
const instanceCopy = JSON.parse(JSON.stringify(newInstance));
|
|
4247
|
-
|
|
4247
|
+
if (instanceCopy._pattern !== void 0) {
|
|
4248
|
+
const slotIndex = rootInstance.slots[slot]?.length ?? 0;
|
|
4249
|
+
instanceCopy._id = computeGuidHash(`${rootPath}.${slot}[${slotIndex}]${instanceCopy.type}`);
|
|
4250
|
+
} else {
|
|
4251
|
+
this.regenerateInstanceIds(instanceCopy);
|
|
4252
|
+
}
|
|
4248
4253
|
this.addComponentToSlot(rootInstance.slots, slot, instanceCopy);
|
|
4249
4254
|
modified = true;
|
|
4250
4255
|
}
|
|
@@ -4254,7 +4259,8 @@ var ComponentAdderService = class {
|
|
|
4254
4259
|
parentTypes,
|
|
4255
4260
|
slot,
|
|
4256
4261
|
newInstance,
|
|
4257
|
-
strict
|
|
4262
|
+
strict,
|
|
4263
|
+
rootPath
|
|
4258
4264
|
);
|
|
4259
4265
|
if (nestedModified) {
|
|
4260
4266
|
modified = true;
|
|
@@ -4285,17 +4291,24 @@ var ComponentAdderService = class {
|
|
|
4285
4291
|
}
|
|
4286
4292
|
return filesModified;
|
|
4287
4293
|
}
|
|
4288
|
-
addComponentToNestedSlots(slots, parentTypes, slot, newInstance, strict) {
|
|
4294
|
+
addComponentToNestedSlots(slots, parentTypes, slot, newInstance, strict, pathPrefix) {
|
|
4289
4295
|
let modified = false;
|
|
4290
|
-
for (const slotInstances of Object.
|
|
4296
|
+
for (const [slotName, slotInstances] of Object.entries(slots)) {
|
|
4291
4297
|
if (!Array.isArray(slotInstances)) continue;
|
|
4292
|
-
for (
|
|
4298
|
+
for (let i = 0; i < slotInstances.length; i++) {
|
|
4299
|
+
const instance = slotInstances[i];
|
|
4300
|
+
const instancePath = `${pathPrefix}.${slotName}[${i}]${instance.type}`;
|
|
4293
4301
|
if (this.matchesAnyParentType(instance.type, parentTypes, strict)) {
|
|
4294
4302
|
if (!instance.slots) {
|
|
4295
4303
|
instance.slots = {};
|
|
4296
4304
|
}
|
|
4297
4305
|
const instanceCopy = JSON.parse(JSON.stringify(newInstance));
|
|
4298
|
-
|
|
4306
|
+
if (instanceCopy._pattern !== void 0) {
|
|
4307
|
+
const slotIndex = instance.slots[slot]?.length ?? 0;
|
|
4308
|
+
instanceCopy._id = computeGuidHash(`${instancePath}.${slot}[${slotIndex}]${instanceCopy.type}`);
|
|
4309
|
+
} else {
|
|
4310
|
+
this.regenerateInstanceIds(instanceCopy);
|
|
4311
|
+
}
|
|
4299
4312
|
this.addComponentToSlot(instance.slots, slot, instanceCopy);
|
|
4300
4313
|
modified = true;
|
|
4301
4314
|
}
|
|
@@ -4305,7 +4318,8 @@ var ComponentAdderService = class {
|
|
|
4305
4318
|
parentTypes,
|
|
4306
4319
|
slot,
|
|
4307
4320
|
newInstance,
|
|
4308
|
-
strict
|
|
4321
|
+
strict,
|
|
4322
|
+
instancePath
|
|
4309
4323
|
);
|
|
4310
4324
|
if (nestedModified) {
|
|
4311
4325
|
modified = true;
|
|
@@ -6370,10 +6384,614 @@ function createRemoveOrphanEntriesCommand() {
|
|
|
6370
6384
|
return command;
|
|
6371
6385
|
}
|
|
6372
6386
|
|
|
6387
|
+
// src/cli/commands/remove-unused-content-types.ts
|
|
6388
|
+
import { Command as Command17 } from "commander";
|
|
6389
|
+
|
|
6390
|
+
// src/core/services/unused-content-type-remover.service.ts
|
|
6391
|
+
var UnusedContentTypeRemoverService = class {
|
|
6392
|
+
constructor(fileSystem, logger) {
|
|
6393
|
+
this.fileSystem = fileSystem;
|
|
6394
|
+
this.logger = logger;
|
|
6395
|
+
}
|
|
6396
|
+
async remove(options) {
|
|
6397
|
+
const {
|
|
6398
|
+
rootDir,
|
|
6399
|
+
contentTypesDir,
|
|
6400
|
+
entriesDir,
|
|
6401
|
+
compositionsDir,
|
|
6402
|
+
compositionPatternsDir,
|
|
6403
|
+
componentPatternsDir,
|
|
6404
|
+
whatIf,
|
|
6405
|
+
strict
|
|
6406
|
+
} = options;
|
|
6407
|
+
const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
|
|
6408
|
+
const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
|
|
6409
|
+
const ctDirExists = await this.fileSystem.fileExists(contentTypesDirFull);
|
|
6410
|
+
if (!ctDirExists) {
|
|
6411
|
+
this.logger.warn(`Content types directory not found: ${contentTypesDir} \u2014 nothing to do`);
|
|
6412
|
+
return { totalContentTypes: 0, removedContentTypes: 0, retainedContentTypes: 0 };
|
|
6413
|
+
}
|
|
6414
|
+
const ctFiles = await this.fileSystem.findFiles(contentTypesDirFull, "**/*.{json,yaml,yml}");
|
|
6415
|
+
if (ctFiles.length === 0) {
|
|
6416
|
+
this.logger.warn("No content type files found \u2014 nothing to do");
|
|
6417
|
+
return { totalContentTypes: 0, removedContentTypes: 0, retainedContentTypes: 0 };
|
|
6418
|
+
}
|
|
6419
|
+
this.logger.info(`Loaded ${ctFiles.length} content type file(s)`);
|
|
6420
|
+
const contentTypeMap = /* @__PURE__ */ new Map();
|
|
6421
|
+
for (const filePath of ctFiles) {
|
|
6422
|
+
let ct;
|
|
6423
|
+
try {
|
|
6424
|
+
ct = await this.fileSystem.readFile(filePath);
|
|
6425
|
+
} catch {
|
|
6426
|
+
this.logger.warn(`Could not read content type file: ${filePath} \u2014 skipping`);
|
|
6427
|
+
continue;
|
|
6428
|
+
}
|
|
6429
|
+
if (!ct?.id) {
|
|
6430
|
+
this.logger.warn(`Content type file missing id: ${filePath} \u2014 skipping`);
|
|
6431
|
+
continue;
|
|
6432
|
+
}
|
|
6433
|
+
contentTypeMap.set(filePath, { filePath, id: ct.id });
|
|
6434
|
+
}
|
|
6435
|
+
const usedTypeIds = /* @__PURE__ */ new Set();
|
|
6436
|
+
const entriesDirExists = await this.fileSystem.fileExists(entriesDirFull);
|
|
6437
|
+
if (!entriesDirExists) {
|
|
6438
|
+
this.logger.warn(`Entries directory not found: ${entriesDir} \u2014 treating all content types as unused`);
|
|
6439
|
+
} else {
|
|
6440
|
+
const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "**/*.{json,yaml,yml}");
|
|
6441
|
+
this.logger.info(`Loaded ${entryFiles.length} entry file(s)`);
|
|
6442
|
+
for (const filePath of entryFiles) {
|
|
6443
|
+
let entryData;
|
|
6444
|
+
try {
|
|
6445
|
+
entryData = await this.fileSystem.readFile(filePath);
|
|
6446
|
+
} catch {
|
|
6447
|
+
this.logger.warn(`Could not read entry file: ${filePath} \u2014 skipping`);
|
|
6448
|
+
continue;
|
|
6449
|
+
}
|
|
6450
|
+
if (!entryData?.entry?.type) {
|
|
6451
|
+
continue;
|
|
6452
|
+
}
|
|
6453
|
+
usedTypeIds.add(entryData.entry.type);
|
|
6454
|
+
const fields = entryData.entry.fields;
|
|
6455
|
+
if (fields && typeof fields === "object") {
|
|
6456
|
+
this.extractBlockTypesFromParameters(fields, usedTypeIds);
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
const dirsToScan = [
|
|
6461
|
+
{ dir: this.fileSystem.resolvePath(rootDir, compositionsDir), label: compositionsDir },
|
|
6462
|
+
{
|
|
6463
|
+
dir: this.fileSystem.resolvePath(rootDir, compositionPatternsDir),
|
|
6464
|
+
label: compositionPatternsDir
|
|
6465
|
+
},
|
|
6466
|
+
{
|
|
6467
|
+
dir: this.fileSystem.resolvePath(rootDir, componentPatternsDir),
|
|
6468
|
+
label: componentPatternsDir
|
|
6469
|
+
}
|
|
6470
|
+
];
|
|
6471
|
+
for (const { dir, label } of dirsToScan) {
|
|
6472
|
+
const blockTypes = await this.collectBlockTypesFromDir(dir, label);
|
|
6473
|
+
for (const t of blockTypes) {
|
|
6474
|
+
usedTypeIds.add(t);
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
let removedContentTypes = 0;
|
|
6478
|
+
let retainedContentTypes = 0;
|
|
6479
|
+
for (const { filePath, id } of contentTypeMap.values()) {
|
|
6480
|
+
const isUsed = this.isUsed(id, usedTypeIds, strict);
|
|
6481
|
+
if (isUsed) {
|
|
6482
|
+
retainedContentTypes++;
|
|
6483
|
+
} else {
|
|
6484
|
+
const relPath = this.fileSystem.joinPath(
|
|
6485
|
+
contentTypesDir,
|
|
6486
|
+
this.fileSystem.getBasename(filePath)
|
|
6487
|
+
);
|
|
6488
|
+
this.logger.action(whatIf, "DELETE", relPath);
|
|
6489
|
+
if (!whatIf) {
|
|
6490
|
+
this.fileSystem.deleteFile(filePath);
|
|
6491
|
+
}
|
|
6492
|
+
removedContentTypes++;
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
return {
|
|
6496
|
+
totalContentTypes: contentTypeMap.size,
|
|
6497
|
+
removedContentTypes,
|
|
6498
|
+
retainedContentTypes
|
|
6499
|
+
};
|
|
6500
|
+
}
|
|
6501
|
+
async collectBlockTypesFromDir(dir, label) {
|
|
6502
|
+
const types = /* @__PURE__ */ new Set();
|
|
6503
|
+
const dirExists = await this.fileSystem.fileExists(dir);
|
|
6504
|
+
if (!dirExists) return types;
|
|
6505
|
+
const files = await this.fileSystem.findFiles(dir, "**/*.{json,yaml,yml}");
|
|
6506
|
+
for (const filePath of files) {
|
|
6507
|
+
let data;
|
|
6508
|
+
try {
|
|
6509
|
+
data = await this.fileSystem.readFile(filePath);
|
|
6510
|
+
} catch {
|
|
6511
|
+
this.logger.warn(`Could not read ${label} file: ${filePath} \u2014 skipping`);
|
|
6512
|
+
continue;
|
|
6513
|
+
}
|
|
6514
|
+
this.extractBlockTypesFromFile(data, types);
|
|
6515
|
+
}
|
|
6516
|
+
return types;
|
|
6517
|
+
}
|
|
6518
|
+
extractBlockTypesFromFile(data, types) {
|
|
6519
|
+
if (!data || typeof data !== "object") return;
|
|
6520
|
+
const comp = data.composition;
|
|
6521
|
+
if (!comp || typeof comp !== "object") return;
|
|
6522
|
+
const node = comp;
|
|
6523
|
+
this.extractBlockTypesFromNode(node, types);
|
|
6524
|
+
const overrides = node._overrides;
|
|
6525
|
+
if (overrides && typeof overrides === "object") {
|
|
6526
|
+
for (const override of Object.values(overrides)) {
|
|
6527
|
+
if (!override || typeof override !== "object") continue;
|
|
6528
|
+
const params = override.parameters;
|
|
6529
|
+
if (params && typeof params === "object") {
|
|
6530
|
+
this.extractBlockTypesFromParameters(params, types);
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
extractBlockTypesFromNode(node, types) {
|
|
6536
|
+
const params = node.parameters;
|
|
6537
|
+
if (params && typeof params === "object") {
|
|
6538
|
+
this.extractBlockTypesFromParameters(params, types);
|
|
6539
|
+
}
|
|
6540
|
+
const slots = node.slots;
|
|
6541
|
+
if (slots && typeof slots === "object") {
|
|
6542
|
+
for (const slotInstances of Object.values(slots)) {
|
|
6543
|
+
if (!Array.isArray(slotInstances)) continue;
|
|
6544
|
+
for (const instance of slotInstances) {
|
|
6545
|
+
if (instance && typeof instance === "object") {
|
|
6546
|
+
this.extractBlockTypesFromNode(instance, types);
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
extractBlockTypesFromParameters(params, types) {
|
|
6553
|
+
for (const param of Object.values(params)) {
|
|
6554
|
+
if (!param || typeof param !== "object") continue;
|
|
6555
|
+
const p = param;
|
|
6556
|
+
if (p.type === "$block" && Array.isArray(p.value)) {
|
|
6557
|
+
for (const item of p.value) {
|
|
6558
|
+
if (item && typeof item === "object") {
|
|
6559
|
+
const blockType = item.type;
|
|
6560
|
+
if (typeof blockType === "string") {
|
|
6561
|
+
types.add(blockType);
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
isUsed(contentTypeId, usedTypeIds, strict) {
|
|
6569
|
+
if (strict) {
|
|
6570
|
+
return usedTypeIds.has(contentTypeId);
|
|
6571
|
+
}
|
|
6572
|
+
const lower = contentTypeId.toLowerCase();
|
|
6573
|
+
for (const used of usedTypeIds) {
|
|
6574
|
+
if (used.toLowerCase() === lower) return true;
|
|
6575
|
+
}
|
|
6576
|
+
return false;
|
|
6577
|
+
}
|
|
6578
|
+
};
|
|
6579
|
+
|
|
6580
|
+
// src/cli/commands/remove-unused-content-types.ts
|
|
6581
|
+
function createRemoveUnusedContentTypesCommand() {
|
|
6582
|
+
const command = new Command17("remove-unused-content-types");
|
|
6583
|
+
command.description("Removes content type definition files that have zero entries referencing them.").action(async (_opts, cmd) => {
|
|
6584
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6585
|
+
const options = {
|
|
6586
|
+
...globalOpts
|
|
6587
|
+
};
|
|
6588
|
+
const logger = new Logger();
|
|
6589
|
+
logger.info(`rootDir: ${options.rootDir}`);
|
|
6590
|
+
logger.info(`contentTypesDir: ${options.contentTypesDir}`);
|
|
6591
|
+
logger.info(`entriesDir: ${options.entriesDir}`);
|
|
6592
|
+
const fileSystem = new FileSystemService();
|
|
6593
|
+
const service = new UnusedContentTypeRemoverService(fileSystem, logger);
|
|
6594
|
+
const result = await service.remove({
|
|
6595
|
+
rootDir: options.rootDir,
|
|
6596
|
+
contentTypesDir: options.contentTypesDir,
|
|
6597
|
+
entriesDir: options.entriesDir,
|
|
6598
|
+
compositionsDir: options.compositionsDir,
|
|
6599
|
+
compositionPatternsDir: options.compositionPatternsDir,
|
|
6600
|
+
componentPatternsDir: options.componentPatternsDir,
|
|
6601
|
+
whatIf: options.whatIf ?? false,
|
|
6602
|
+
strict: options.strict ?? false
|
|
6603
|
+
});
|
|
6604
|
+
logger.success(
|
|
6605
|
+
`Removed ${result.removedContentTypes} unused content type(s). ${result.retainedContentTypes} content type(s) retained.`
|
|
6606
|
+
);
|
|
6607
|
+
});
|
|
6608
|
+
return command;
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
// src/cli/commands/remove-unused-component-types.ts
|
|
6612
|
+
import { Command as Command18 } from "commander";
|
|
6613
|
+
|
|
6614
|
+
// src/core/services/unused-component-type-remover.service.ts
|
|
6615
|
+
var UnusedComponentTypeRemoverService = class {
|
|
6616
|
+
constructor(fileSystem, logger) {
|
|
6617
|
+
this.fileSystem = fileSystem;
|
|
6618
|
+
this.logger = logger;
|
|
6619
|
+
}
|
|
6620
|
+
async remove(options) {
|
|
6621
|
+
const {
|
|
6622
|
+
rootDir,
|
|
6623
|
+
componentsDir,
|
|
6624
|
+
compositionsDir,
|
|
6625
|
+
compositionPatternsDir,
|
|
6626
|
+
componentPatternsDir,
|
|
6627
|
+
whatIf,
|
|
6628
|
+
strict
|
|
6629
|
+
} = options;
|
|
6630
|
+
const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
|
|
6631
|
+
const ctDirExists = await this.fileSystem.fileExists(componentsDirFull);
|
|
6632
|
+
if (!ctDirExists) {
|
|
6633
|
+
this.logger.warn(`Components directory not found: ${componentsDir} \u2014 nothing to do`);
|
|
6634
|
+
return { totalComponentTypes: 0, removedComponentTypes: 0, retainedComponentTypes: 0 };
|
|
6635
|
+
}
|
|
6636
|
+
const componentFiles = await this.fileSystem.findFiles(componentsDirFull, "*.{json,yaml,yml}");
|
|
6637
|
+
if (componentFiles.length === 0) {
|
|
6638
|
+
this.logger.warn("No component files found \u2014 nothing to do");
|
|
6639
|
+
return { totalComponentTypes: 0, removedComponentTypes: 0, retainedComponentTypes: 0 };
|
|
6640
|
+
}
|
|
6641
|
+
this.logger.info(`Found ${componentFiles.length} component file(s)`);
|
|
6642
|
+
const componentMap = /* @__PURE__ */ new Map();
|
|
6643
|
+
for (const filePath of componentFiles) {
|
|
6644
|
+
let comp;
|
|
6645
|
+
try {
|
|
6646
|
+
comp = await this.fileSystem.readFile(filePath);
|
|
6647
|
+
} catch {
|
|
6648
|
+
this.logger.warn(`Could not read component file: ${filePath} \u2014 skipping`);
|
|
6649
|
+
continue;
|
|
6650
|
+
}
|
|
6651
|
+
if (!comp?.id) {
|
|
6652
|
+
this.logger.warn(`Component file missing id: ${filePath} \u2014 skipping`);
|
|
6653
|
+
continue;
|
|
6654
|
+
}
|
|
6655
|
+
componentMap.set(filePath, { filePath, id: comp.id });
|
|
6656
|
+
}
|
|
6657
|
+
const usedTypeIds = /* @__PURE__ */ new Set();
|
|
6658
|
+
await this.collectTypesFromDirectory(
|
|
6659
|
+
this.fileSystem.resolvePath(rootDir, compositionsDir),
|
|
6660
|
+
usedTypeIds,
|
|
6661
|
+
"compositions"
|
|
6662
|
+
);
|
|
6663
|
+
await this.collectTypesFromDirectory(
|
|
6664
|
+
this.fileSystem.resolvePath(rootDir, compositionPatternsDir),
|
|
6665
|
+
usedTypeIds,
|
|
6666
|
+
"composition patterns"
|
|
6667
|
+
);
|
|
6668
|
+
await this.collectTypesFromDirectory(
|
|
6669
|
+
this.fileSystem.resolvePath(rootDir, componentPatternsDir),
|
|
6670
|
+
usedTypeIds,
|
|
6671
|
+
"component patterns"
|
|
6672
|
+
);
|
|
6673
|
+
await this.collectAllowedComponents(componentFiles, usedTypeIds);
|
|
6674
|
+
this.logger.info(`Found ${usedTypeIds.size} referenced component type(s) across all sources`);
|
|
6675
|
+
let removedComponentTypes = 0;
|
|
6676
|
+
let retainedComponentTypes = 0;
|
|
6677
|
+
for (const { filePath, id } of componentMap.values()) {
|
|
6678
|
+
const isUsed = this.isUsed(id, usedTypeIds, strict);
|
|
6679
|
+
if (isUsed) {
|
|
6680
|
+
retainedComponentTypes++;
|
|
6681
|
+
} else {
|
|
6682
|
+
const relPath = this.fileSystem.joinPath(
|
|
6683
|
+
componentsDir,
|
|
6684
|
+
this.fileSystem.getBasename(filePath)
|
|
6685
|
+
);
|
|
6686
|
+
this.logger.action(whatIf, "DELETE", relPath);
|
|
6687
|
+
if (!whatIf) {
|
|
6688
|
+
this.fileSystem.deleteFile(filePath);
|
|
6689
|
+
}
|
|
6690
|
+
removedComponentTypes++;
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
return {
|
|
6694
|
+
totalComponentTypes: componentMap.size,
|
|
6695
|
+
removedComponentTypes,
|
|
6696
|
+
retainedComponentTypes
|
|
6697
|
+
};
|
|
6698
|
+
}
|
|
6699
|
+
async collectTypesFromDirectory(dirPath, usedTypeIds, label) {
|
|
6700
|
+
const dirExists = await this.fileSystem.fileExists(dirPath);
|
|
6701
|
+
if (!dirExists) {
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
let files;
|
|
6705
|
+
try {
|
|
6706
|
+
files = await this.fileSystem.findFiles(dirPath, "**/*.{json,yaml,yml}");
|
|
6707
|
+
} catch {
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
6710
|
+
if (files.length === 0) {
|
|
6711
|
+
return;
|
|
6712
|
+
}
|
|
6713
|
+
this.logger.info(`Scanning ${files.length} file(s) in ${label}`);
|
|
6714
|
+
for (const filePath of files) {
|
|
6715
|
+
let composition;
|
|
6716
|
+
try {
|
|
6717
|
+
composition = await this.fileSystem.readFile(filePath);
|
|
6718
|
+
} catch {
|
|
6719
|
+
this.logger.warn(`Could not read file: ${filePath} \u2014 skipping`);
|
|
6720
|
+
continue;
|
|
6721
|
+
}
|
|
6722
|
+
if (!composition?.composition) continue;
|
|
6723
|
+
this.collectTypesFromTree(composition.composition, usedTypeIds);
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
collectTypesFromTree(node, usedTypeIds) {
|
|
6727
|
+
if (node.type) {
|
|
6728
|
+
usedTypeIds.add(node.type);
|
|
6729
|
+
}
|
|
6730
|
+
if (node.slots) {
|
|
6731
|
+
for (const slotInstances of Object.values(node.slots)) {
|
|
6732
|
+
if (!Array.isArray(slotInstances)) continue;
|
|
6733
|
+
for (const instance of slotInstances) {
|
|
6734
|
+
if (instance && typeof instance === "object") {
|
|
6735
|
+
this.collectTypesFromTree(
|
|
6736
|
+
instance,
|
|
6737
|
+
usedTypeIds
|
|
6738
|
+
);
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
}
|
|
6744
|
+
async collectAllowedComponents(componentFiles, usedTypeIds) {
|
|
6745
|
+
for (const filePath of componentFiles) {
|
|
6746
|
+
let comp;
|
|
6747
|
+
try {
|
|
6748
|
+
comp = await this.fileSystem.readFile(filePath);
|
|
6749
|
+
} catch {
|
|
6750
|
+
continue;
|
|
6751
|
+
}
|
|
6752
|
+
if (!comp?.slots) continue;
|
|
6753
|
+
for (const slot of comp.slots) {
|
|
6754
|
+
if (!slot.allowedComponents) continue;
|
|
6755
|
+
for (const allowedType of slot.allowedComponents) {
|
|
6756
|
+
usedTypeIds.add(allowedType);
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
isUsed(componentId, usedTypeIds, strict) {
|
|
6762
|
+
if (strict) {
|
|
6763
|
+
return usedTypeIds.has(componentId);
|
|
6764
|
+
}
|
|
6765
|
+
const lower = componentId.toLowerCase();
|
|
6766
|
+
for (const used of usedTypeIds) {
|
|
6767
|
+
if (used.toLowerCase() === lower) return true;
|
|
6768
|
+
}
|
|
6769
|
+
return false;
|
|
6770
|
+
}
|
|
6771
|
+
};
|
|
6772
|
+
|
|
6773
|
+
// src/cli/commands/remove-unused-component-types.ts
|
|
6774
|
+
function createRemoveUnusedComponentTypesCommand() {
|
|
6775
|
+
const command = new Command18("remove-unused-component-types");
|
|
6776
|
+
command.description(
|
|
6777
|
+
"Removes component definition files that are not referenced in any composition, pattern, or allowedComponents list."
|
|
6778
|
+
).action(async (_opts, cmd) => {
|
|
6779
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6780
|
+
const options = {
|
|
6781
|
+
...globalOpts
|
|
6782
|
+
};
|
|
6783
|
+
const logger = new Logger();
|
|
6784
|
+
logger.info(`rootDir: ${options.rootDir}`);
|
|
6785
|
+
logger.info(`componentsDir: ${options.componentsDir}`);
|
|
6786
|
+
logger.info(`compositionsDir: ${options.compositionsDir}`);
|
|
6787
|
+
logger.info(`compositionPatternsDir: ${options.compositionPatternsDir}`);
|
|
6788
|
+
logger.info(`componentPatternsDir: ${options.componentPatternsDir}`);
|
|
6789
|
+
const fileSystem = new FileSystemService();
|
|
6790
|
+
const service = new UnusedComponentTypeRemoverService(fileSystem, logger);
|
|
6791
|
+
const result = await service.remove({
|
|
6792
|
+
rootDir: options.rootDir,
|
|
6793
|
+
componentsDir: options.componentsDir,
|
|
6794
|
+
compositionsDir: options.compositionsDir,
|
|
6795
|
+
compositionPatternsDir: options.compositionPatternsDir,
|
|
6796
|
+
componentPatternsDir: options.componentPatternsDir,
|
|
6797
|
+
whatIf: options.whatIf ?? false,
|
|
6798
|
+
strict: options.strict ?? false
|
|
6799
|
+
});
|
|
6800
|
+
logger.success(
|
|
6801
|
+
`Removed ${result.removedComponentTypes} unused component type(s). ${result.retainedComponentTypes} component type(s) retained.`
|
|
6802
|
+
);
|
|
6803
|
+
});
|
|
6804
|
+
return command;
|
|
6805
|
+
}
|
|
6806
|
+
|
|
6807
|
+
// src/cli/commands/generate-missing-project-map-nodes.ts
|
|
6808
|
+
import { Command as Command19 } from "commander";
|
|
6809
|
+
|
|
6810
|
+
// src/core/services/generate-missing-project-map-nodes.service.ts
|
|
6811
|
+
var GenerateMissingProjectMapNodesService = class {
|
|
6812
|
+
constructor(fileSystem, logger) {
|
|
6813
|
+
this.fileSystem = fileSystem;
|
|
6814
|
+
this.logger = logger;
|
|
6815
|
+
}
|
|
6816
|
+
async generate(options) {
|
|
6817
|
+
const { rootDir, projectMapNodesDir, compositionsDir, rootContentTypes, whatIf, strict } = options;
|
|
6818
|
+
const nodesDirFull = this.fileSystem.resolvePath(rootDir, projectMapNodesDir);
|
|
6819
|
+
const nodesDirExists = await this.fileSystem.fileExists(nodesDirFull);
|
|
6820
|
+
if (!nodesDirExists) {
|
|
6821
|
+
this.logger.warn(`Project map nodes directory not found: ${projectMapNodesDir} \u2014 nothing to do`);
|
|
6822
|
+
return { generatedCount: 0, alreadyCoveredCount: 0 };
|
|
6823
|
+
}
|
|
6824
|
+
const nodeFiles = await this.fileSystem.findFiles(nodesDirFull, "**/*.{json,yaml,yml}");
|
|
6825
|
+
if (nodeFiles.length === 0) {
|
|
6826
|
+
this.logger.warn("No project map node files found \u2014 nothing to do");
|
|
6827
|
+
return { generatedCount: 0, alreadyCoveredCount: 0 };
|
|
6828
|
+
}
|
|
6829
|
+
this.logger.info(`Loaded ${nodeFiles.length} project map node(s)`);
|
|
6830
|
+
const allNodes = [];
|
|
6831
|
+
for (const filePath of nodeFiles) {
|
|
6832
|
+
let node;
|
|
6833
|
+
try {
|
|
6834
|
+
node = await this.fileSystem.readFile(filePath);
|
|
6835
|
+
} catch {
|
|
6836
|
+
this.logger.warn(`Could not read project map node file: ${filePath} \u2014 skipping`);
|
|
6837
|
+
continue;
|
|
6838
|
+
}
|
|
6839
|
+
if (!node?.path) {
|
|
6840
|
+
this.logger.warn(`Project map node missing path field: ${filePath} \u2014 skipping`);
|
|
6841
|
+
continue;
|
|
6842
|
+
}
|
|
6843
|
+
allNodes.push({ filePath, node });
|
|
6844
|
+
}
|
|
6845
|
+
const existingPaths = new Set(allNodes.map(({ node }) => node.path));
|
|
6846
|
+
const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
|
|
6847
|
+
const compositionIdToType = /* @__PURE__ */ new Map();
|
|
6848
|
+
const compositionsDirExists = await this.fileSystem.fileExists(compositionsDirFull);
|
|
6849
|
+
if (!compositionsDirExists) {
|
|
6850
|
+
this.logger.warn(`Compositions directory not found: ${compositionsDir} \u2014 no source nodes will match`);
|
|
6851
|
+
} else {
|
|
6852
|
+
const compositionFiles = await this.fileSystem.findFiles(
|
|
6853
|
+
compositionsDirFull,
|
|
6854
|
+
"**/*.{json,yaml,yml}"
|
|
6855
|
+
);
|
|
6856
|
+
for (const filePath of compositionFiles) {
|
|
6857
|
+
let comp;
|
|
6858
|
+
try {
|
|
6859
|
+
comp = await this.fileSystem.readFile(filePath);
|
|
6860
|
+
} catch {
|
|
6861
|
+
this.logger.warn(`Could not read composition file: ${filePath} \u2014 skipping`);
|
|
6862
|
+
continue;
|
|
6863
|
+
}
|
|
6864
|
+
if (comp?.composition?._id && comp?.composition?.type) {
|
|
6865
|
+
compositionIdToType.set(comp.composition._id, comp.composition.type);
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
const sourceNodes = allNodes.filter(({ node }) => {
|
|
6870
|
+
if (!node.compositionId) return false;
|
|
6871
|
+
const rootType = compositionIdToType.get(node.compositionId);
|
|
6872
|
+
if (!rootType) {
|
|
6873
|
+
this.logger.warn(
|
|
6874
|
+
`No composition found for compositionId "${node.compositionId}" (node path: ${node.path}) \u2014 skipping`
|
|
6875
|
+
);
|
|
6876
|
+
return false;
|
|
6877
|
+
}
|
|
6878
|
+
return this.matchesContentType(rootType, rootContentTypes, strict);
|
|
6879
|
+
});
|
|
6880
|
+
this.logger.info(`${sourceNodes.length} node(s) match root content types`);
|
|
6881
|
+
const ancestorPaths = /* @__PURE__ */ new Set();
|
|
6882
|
+
for (const { node } of sourceNodes) {
|
|
6883
|
+
for (const ancestor of this.computeAncestorPaths(node.path)) {
|
|
6884
|
+
ancestorPaths.add(ancestor);
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
const missingPaths = [];
|
|
6888
|
+
let alreadyCoveredCount = 0;
|
|
6889
|
+
for (const ancestorPath of ancestorPaths) {
|
|
6890
|
+
if (existingPaths.has(ancestorPath)) {
|
|
6891
|
+
alreadyCoveredCount++;
|
|
6892
|
+
} else {
|
|
6893
|
+
missingPaths.push(ancestorPath);
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
this.logger.info(
|
|
6897
|
+
`Collected ${ancestorPaths.size} unique ancestor path(s), ${alreadyCoveredCount} already covered`
|
|
6898
|
+
);
|
|
6899
|
+
let generatedCount = 0;
|
|
6900
|
+
for (const missingPath of missingPaths) {
|
|
6901
|
+
const fileName = this.pathToFileName(missingPath);
|
|
6902
|
+
const nodeId = this.pathToNodeId(missingPath);
|
|
6903
|
+
const destFilePath = this.fileSystem.resolvePath(nodesDirFull, fileName);
|
|
6904
|
+
const relPath = this.fileSystem.joinPath(projectMapNodesDir, fileName);
|
|
6905
|
+
this.logger.action(whatIf, "CREATE", `${relPath} (path: ${missingPath}, type: group)`);
|
|
6906
|
+
if (!whatIf) {
|
|
6907
|
+
const newNode = {
|
|
6908
|
+
id: nodeId,
|
|
6909
|
+
path: missingPath,
|
|
6910
|
+
type: "group"
|
|
6911
|
+
};
|
|
6912
|
+
await this.fileSystem.writeFile(destFilePath, newNode);
|
|
6913
|
+
}
|
|
6914
|
+
generatedCount++;
|
|
6915
|
+
}
|
|
6916
|
+
return { generatedCount, alreadyCoveredCount };
|
|
6917
|
+
}
|
|
6918
|
+
computeAncestorPaths(nodePath) {
|
|
6919
|
+
const segments = nodePath.split("/").filter((s) => s.length > 0);
|
|
6920
|
+
const ancestors = [];
|
|
6921
|
+
ancestors.push("/");
|
|
6922
|
+
for (let i = 1; i < segments.length; i++) {
|
|
6923
|
+
ancestors.push("/" + segments.slice(0, i).join("/"));
|
|
6924
|
+
}
|
|
6925
|
+
return ancestors;
|
|
6926
|
+
}
|
|
6927
|
+
pathToNodeId(nodePath) {
|
|
6928
|
+
if (nodePath === "/") return "pmn-group-root";
|
|
6929
|
+
const slug = nodePath.split("/").filter((s) => s.length > 0).join("-");
|
|
6930
|
+
return `pmn-group-${slug}`;
|
|
6931
|
+
}
|
|
6932
|
+
pathToFileName(nodePath) {
|
|
6933
|
+
if (nodePath === "/") return "root-node.yaml";
|
|
6934
|
+
const slug = nodePath.split("/").filter((s) => s.length > 0).join("-");
|
|
6935
|
+
return `${slug}-node.yaml`;
|
|
6936
|
+
}
|
|
6937
|
+
matchesContentType(rootType, rootContentTypes, strict) {
|
|
6938
|
+
for (const ct of rootContentTypes) {
|
|
6939
|
+
if (strict) {
|
|
6940
|
+
if (rootType === ct) return true;
|
|
6941
|
+
} else {
|
|
6942
|
+
if (rootType.toLowerCase() === ct.toLowerCase()) return true;
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
return false;
|
|
6946
|
+
}
|
|
6947
|
+
};
|
|
6948
|
+
|
|
6949
|
+
// src/cli/commands/generate-missing-project-map-nodes.ts
|
|
6950
|
+
function createGenerateMissingProjectMapNodesCommand() {
|
|
6951
|
+
const command = new Command19("generate-missing-project-map-nodes");
|
|
6952
|
+
command.description(
|
|
6953
|
+
"Creates group project map nodes for ancestor path segments that are missing from the project map. Only paths reachable from compositions whose root type matches --rootContentTypes are considered."
|
|
6954
|
+
).option(
|
|
6955
|
+
"--rootContentTypes <types>",
|
|
6956
|
+
'Pipe-separated list of root component types identifying the leaf-page compositions whose ancestor paths should be filled in (e.g. "BlogPost|NewsArticle")'
|
|
6957
|
+
).hook("preAction", (thisCommand) => {
|
|
6958
|
+
const opts = thisCommand.opts();
|
|
6959
|
+
const requiredOptions = [{ name: "rootContentTypes", flag: "--rootContentTypes" }];
|
|
6960
|
+
const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
|
|
6961
|
+
if (missing.length > 0) {
|
|
6962
|
+
console.error(`error: missing required options: ${missing.join(", ")}`);
|
|
6963
|
+
process.exit(1);
|
|
6964
|
+
}
|
|
6965
|
+
}).action(async (opts, cmd) => {
|
|
6966
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6967
|
+
const options = {
|
|
6968
|
+
...globalOpts,
|
|
6969
|
+
rootContentTypes: opts.rootContentTypes
|
|
6970
|
+
};
|
|
6971
|
+
const logger = new Logger();
|
|
6972
|
+
logger.info(`rootContentTypes: ${options.rootContentTypes}`);
|
|
6973
|
+
const rootContentTypes = options.rootContentTypes.split("|").map((t) => t.trim()).filter(Boolean);
|
|
6974
|
+
const fileSystem = new FileSystemService();
|
|
6975
|
+
const service = new GenerateMissingProjectMapNodesService(fileSystem, logger);
|
|
6976
|
+
const result = await service.generate({
|
|
6977
|
+
rootDir: options.rootDir,
|
|
6978
|
+
projectMapNodesDir: options.projectMapNodesDir,
|
|
6979
|
+
compositionsDir: options.compositionsDir,
|
|
6980
|
+
rootContentTypes,
|
|
6981
|
+
whatIf: options.whatIf ?? false,
|
|
6982
|
+
strict: options.strict ?? false
|
|
6983
|
+
});
|
|
6984
|
+
logger.success(
|
|
6985
|
+
`Created ${result.generatedCount} missing project map node(s). ${result.alreadyCoveredCount} ancestor path(s) already existed.`
|
|
6986
|
+
);
|
|
6987
|
+
});
|
|
6988
|
+
return command;
|
|
6989
|
+
}
|
|
6990
|
+
|
|
6373
6991
|
// package.json
|
|
6374
6992
|
var package_default = {
|
|
6375
6993
|
name: "@uniformdev/transformer",
|
|
6376
|
-
version: "1.1.
|
|
6994
|
+
version: "1.1.40",
|
|
6377
6995
|
description: "CLI tool for transforming Uniform.dev serialization files offline",
|
|
6378
6996
|
type: "module",
|
|
6379
6997
|
bin: {
|
|
@@ -6442,7 +7060,7 @@ var package_default = {
|
|
|
6442
7060
|
};
|
|
6443
7061
|
|
|
6444
7062
|
// src/cli/index.ts
|
|
6445
|
-
var program = new
|
|
7063
|
+
var program = new Command20();
|
|
6446
7064
|
var appVersion = package_default.version;
|
|
6447
7065
|
console.error(`uniform-transform v${appVersion}`);
|
|
6448
7066
|
program.name("uniform-transform").description("CLI tool for transforming Uniform.dev serialization files offline").version(appVersion);
|
|
@@ -6471,5 +7089,8 @@ program.addCommand(createAddComponentParameterCommand());
|
|
|
6471
7089
|
program.addCommand(createAddContentTypeFieldCommand());
|
|
6472
7090
|
program.addCommand(createFlattenBlockFieldCommand());
|
|
6473
7091
|
program.addCommand(createRemoveOrphanEntriesCommand());
|
|
7092
|
+
program.addCommand(createRemoveUnusedContentTypesCommand());
|
|
7093
|
+
program.addCommand(createRemoveUnusedComponentTypesCommand());
|
|
7094
|
+
program.addCommand(createGenerateMissingProjectMapNodesCommand());
|
|
6474
7095
|
program.parse();
|
|
6475
7096
|
//# sourceMappingURL=index.js.map
|