@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command17 } from "commander";
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
- this.regenerateInstanceIds(instanceCopy);
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.values(slots)) {
4296
+ for (const [slotName, slotInstances] of Object.entries(slots)) {
4291
4297
  if (!Array.isArray(slotInstances)) continue;
4292
- for (const instance of slotInstances) {
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
- this.regenerateInstanceIds(instanceCopy);
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.38",
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 Command17();
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