@uniformdev/transformer 1.1.16 → 1.1.18

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/index.js CHANGED
@@ -513,624 +513,869 @@ var CompositionService = class {
513
513
  }
514
514
  };
515
515
 
516
- // src/core/services/property-propagator.service.ts
517
- var PropertyPropagatorService = class {
516
+ // src/core/services/composition-converter.service.ts
517
+ import * as crypto from "crypto";
518
+ var CompositionConverterService = class {
518
519
  constructor(fileSystem, componentService, compositionService, logger) {
519
520
  this.fileSystem = fileSystem;
520
521
  this.componentService = componentService;
521
522
  this.compositionService = compositionService;
522
523
  this.logger = logger;
523
524
  }
524
- async propagate(options) {
525
+ async convert(options) {
525
526
  const {
526
527
  rootDir,
527
- componentsDir,
528
528
  compositionsDir,
529
- compositionType,
530
- property,
531
- targetComponentType,
532
- targetGroup,
529
+ componentsDir,
530
+ contentTypesDir,
531
+ entriesDir,
532
+ compositionTypes,
533
+ flattenComponentIds,
533
534
  whatIf,
534
- strict,
535
- deleteSourceParameter
535
+ strict
536
536
  } = options;
537
- const findOptions = { strict };
538
- const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
539
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
540
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
541
- const sourceComponents = [];
542
- for (const sourceType of compositionTypes) {
543
- this.logger.info(`Loading component: ${sourceType}`);
544
- const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
545
- sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
537
+ const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
538
+ const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
539
+ const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
540
+ const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
541
+ let contentTypesWritten = 0;
542
+ let entriesFromCompositions = 0;
543
+ let entriesFromFlattened = 0;
544
+ let entriesReused = 0;
545
+ this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
546
+ if (flattenComponentIds.length > 0) {
547
+ this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
546
548
  }
547
- this.logger.info(`Loading component: ${targetComponentType}`);
548
- const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
549
- const propertyNames = property.split("|").map((p) => p.trim()).filter((p) => p.length > 0);
550
- const resolvedParams = [];
551
- const resolvedNames = [];
552
- for (const { sourceType, sourceComponent } of sourceComponents) {
553
- const { parameters: sourceParams, notFound } = this.componentService.resolveProperties(
554
- sourceComponent,
555
- propertyNames,
556
- findOptions
549
+ const sourceItemMap = flattenComponentIds.length > 0 ? await this.buildSourceItemMap(entriesDirFull) : /* @__PURE__ */ new Map();
550
+ if (sourceItemMap.size > 0) {
551
+ this.logger.info(`Found ${sourceItemMap.size} existing entry(ies) with sourceItem values`);
552
+ }
553
+ const compositionResults = await this.compositionService.findCompositionsByTypes(
554
+ compositionsDirFull,
555
+ compositionTypes,
556
+ { strict }
557
+ );
558
+ if (compositionResults.length === 0) {
559
+ this.logger.warn("No compositions found matching the specified types");
560
+ return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0, entriesReused: 0 };
561
+ }
562
+ this.logger.info(`Found ${compositionResults.length} composition(s)`);
563
+ const rootComponentTypes = /* @__PURE__ */ new Set();
564
+ for (const { composition } of compositionResults) {
565
+ rootComponentTypes.add(composition.composition.type);
566
+ }
567
+ const contentTypeMap = /* @__PURE__ */ new Map();
568
+ for (const rootType of rootComponentTypes) {
569
+ const { component } = await this.componentService.loadComponent(
570
+ componentsDirFull,
571
+ rootType,
572
+ { strict }
557
573
  );
558
- if (notFound.length > 0) {
559
- this.logger.warn(`Property "${notFound.join(", ")}" not found on component "${sourceType}"`);
574
+ const contentType = this.generateContentType(component);
575
+ contentTypeMap.set(rootType, contentType);
576
+ }
577
+ const flattenContentTypeMap = /* @__PURE__ */ new Map();
578
+ const missingFlattenTypes = [];
579
+ const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
580
+ for (const flattenType of flattenComponentIds) {
581
+ const isRootType = [...rootComponentTypes].some(
582
+ (rt) => this.compareTypes(rt, flattenType, strict)
583
+ );
584
+ if (isRootType) {
560
585
  continue;
561
586
  }
562
- for (const param of sourceParams) {
563
- const exists = resolvedParams.some(
564
- (existing) => strict ? existing.id === param.id : existing.id.toLowerCase() === param.id.toLowerCase()
587
+ try {
588
+ const { component } = await this.componentService.loadComponent(
589
+ componentsDirFull,
590
+ flattenType,
591
+ { strict }
565
592
  );
566
- if (!exists) {
567
- resolvedParams.push(param);
568
- resolvedNames.push(param.id);
593
+ const contentType = this.generateContentType(component);
594
+ flattenContentTypeMap.set(flattenType, contentType);
595
+ } catch (error) {
596
+ if (error instanceof ComponentNotFoundError) {
597
+ this.logger.info(`Flatten component type not found: ${flattenType}`);
598
+ missingFlattenTypes.push(flattenType);
599
+ continue;
569
600
  }
601
+ throw error;
570
602
  }
571
603
  }
572
- const groupSources = [];
573
- for (const { sourceType, sourceComponent } of sourceComponents) {
574
- for (const name of propertyNames) {
575
- const param = this.componentService.findParameter(sourceComponent, name, findOptions);
576
- if (param && this.componentService.isGroupParameter(param)) {
577
- const groupSource = `${sourceType}.${name}`;
578
- if (!groupSources.includes(groupSource)) {
579
- groupSources.push(groupSource);
604
+ for (const { composition } of compositionResults) {
605
+ const comp = composition.composition;
606
+ const compositionId = comp._id;
607
+ const compositionName = comp._name ?? compositionId;
608
+ const compositionType = comp.type;
609
+ const entry = this.generateEntryFromComposition(composition);
610
+ const flattenedByType = /* @__PURE__ */ new Map();
611
+ if (flattenComponentIds.length > 0 && comp.slots) {
612
+ for (const flattenType of flattenComponentIds) {
613
+ if (this.compareTypes(flattenType, compositionType, strict)) {
614
+ this.logger.warn(
615
+ `Skipping flatten of "${flattenType}" \u2014 same as root component type`
616
+ );
617
+ continue;
580
618
  }
581
- }
582
- }
583
- }
584
- if (groupSources.length > 0) {
585
- this.logger.info(
586
- `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
587
- );
588
- } else {
589
- this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
590
- }
591
- let modifiedComponent = { ...targetComponent };
592
- let componentModified = false;
593
- const groupId = this.componentService.generateGroupId(targetGroup);
594
- const existingGroup = this.componentService.findParameter(
595
- modifiedComponent,
596
- groupId,
597
- findOptions
598
- );
599
- if (!existingGroup) {
600
- this.logger.action(whatIf, "CREATE", `Group "${targetGroup}" on ${targetComponentType}`);
601
- modifiedComponent = this.componentService.ensureGroupExists(
602
- modifiedComponent,
603
- groupId,
604
- targetGroup,
605
- findOptions
606
- );
607
- componentModified = true;
608
- }
609
- for (const param of resolvedParams) {
610
- const existingParam = this.componentService.findParameter(
611
- modifiedComponent,
612
- param.id,
613
- findOptions
614
- );
615
- if (!existingParam) {
616
- this.logger.action(
617
- whatIf,
618
- "COPY",
619
- `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
620
- );
621
- modifiedComponent = this.componentService.addParameterToComponent(
622
- modifiedComponent,
623
- param,
624
- findOptions
625
- );
626
- modifiedComponent = this.componentService.addParameterToGroup(
627
- modifiedComponent,
628
- groupId,
629
- param.id,
630
- findOptions
631
- );
632
- componentModified = true;
633
- } else {
634
- this.logger.info(`Parameter "${param.id}" already exists on ${targetComponentType}`);
635
- const group = this.componentService.findParameter(
636
- modifiedComponent,
637
- groupId,
638
- findOptions
639
- );
640
- if (group && this.componentService.isGroupParameter(group)) {
641
- const isInGroup = group.typeConfig?.childrenParams?.some(
642
- (id) => strict ? id === param.id : id.toLowerCase() === param.id.toLowerCase()
619
+ const instances = this.findFlattenTargets(
620
+ comp.slots,
621
+ flattenType,
622
+ compositionId,
623
+ compositionName,
624
+ strict
643
625
  );
644
- if (!isInGroup) {
645
- modifiedComponent = this.componentService.addParameterToGroup(
646
- modifiedComponent,
647
- groupId,
648
- param.id,
649
- findOptions
650
- );
651
- componentModified = true;
626
+ if (instances.length > 0) {
627
+ flattenedByType.set(flattenType, instances);
628
+ if (missingFlattenTypes.includes(flattenType)) {
629
+ foundMissingFlattenTypes.add(flattenType);
630
+ }
652
631
  }
653
632
  }
654
633
  }
655
- }
656
- if (componentModified && !whatIf) {
657
- await this.componentService.saveComponent(targetFilePath, modifiedComponent);
658
- }
659
- const compositions = await this.compositionService.findCompositionsByTypes(
660
- fullCompositionsDir,
661
- compositionTypes,
662
- findOptions
663
- );
664
- let modifiedCompositions = 0;
665
- let propagatedInstances = 0;
666
- for (const { composition, filePath } of compositions) {
667
- const rootOverrides = this.compositionService.getRootOverrides(composition);
668
- const instances = this.compositionService.findComponentInstances(
669
- composition,
670
- targetComponentType,
671
- findOptions
672
- );
673
- if (instances.length === 0) {
674
- continue;
675
- }
676
- const valuesToPropagate = {};
677
- for (const param of resolvedParams) {
678
- if (rootOverrides[param.id]) {
679
- valuesToPropagate[param.id] = rootOverrides[param.id];
634
+ const resolvedRefIds = /* @__PURE__ */ new Map();
635
+ for (const [flattenType, instances] of flattenedByType) {
636
+ const refIds = [];
637
+ for (const inst of instances) {
638
+ const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
639
+ refIds.push(existingId ?? inst.determinisiticId);
680
640
  }
641
+ resolvedRefIds.set(flattenType, refIds);
681
642
  }
682
- if (Object.keys(valuesToPropagate).length === 0) {
683
- continue;
643
+ for (const [flattenType] of flattenedByType) {
644
+ entry.entry.fields[flattenType] = {
645
+ type: "contentReference",
646
+ value: resolvedRefIds.get(flattenType)
647
+ };
684
648
  }
685
- let compositionModified = false;
686
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
687
- const instanceUpdates = [];
688
- for (const { instance, instanceId } of instances) {
689
- const instanceName = instance._id ?? instanceId;
690
- this.compositionService.setInstanceParameters(instance, valuesToPropagate);
691
- compositionModified = true;
692
- propagatedInstances++;
693
- instanceUpdates.push(
694
- `${targetComponentType} "${instanceName}": ${Object.keys(valuesToPropagate).join(", ")}`
695
- );
649
+ if (flattenComponentIds.length > 0) {
650
+ this.transformContentReferences(entry);
696
651
  }
697
- if (compositionModified) {
698
- this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
699
- for (const update of instanceUpdates) {
700
- this.logger.detail(`\u2192 ${update}`);
701
- }
702
- if (!whatIf) {
703
- await this.compositionService.saveComposition(filePath, composition);
704
- }
705
- modifiedCompositions++;
652
+ const entryId = entry.entry._id;
653
+ const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${entryId}.json`);
654
+ this.logger.action(
655
+ whatIf,
656
+ "WRITE",
657
+ `${entriesDir}/${entryId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
658
+ );
659
+ if (!whatIf) {
660
+ await this.fileSystem.writeFile(entryFilePath, entry);
706
661
  }
707
- }
708
- let modifiedSourceComponents = 0;
709
- if (deleteSourceParameter) {
710
- for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
711
- let modifiedSource = { ...sourceComponent };
712
- let sourceComponentModified = false;
713
- for (const param of resolvedParams) {
714
- const exists = this.componentService.findParameter(modifiedSource, param.id, findOptions);
715
- if (exists) {
716
- this.logger.action(whatIf, "DELETE", `Parameter "${param.id}" from ${sourceType}`);
717
- modifiedSource = this.componentService.removeParameter(modifiedSource, param.id, findOptions);
718
- sourceComponentModified = true;
662
+ entriesFromCompositions++;
663
+ for (const [flattenType, instances] of flattenedByType) {
664
+ for (const inst of instances) {
665
+ const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
666
+ if (existingId) {
667
+ this.logger.info(
668
+ `Reusing existing entry ${existingId} for ${flattenType} (sourceItem match)`
669
+ );
670
+ entriesReused++;
671
+ continue;
719
672
  }
720
- }
721
- const beforeGroupCount = modifiedSource.parameters?.filter(
722
- (p) => this.componentService.isGroupParameter(p)
723
- ).length ?? 0;
724
- modifiedSource = this.componentService.removeEmptyGroups(modifiedSource);
725
- const afterGroupCount = modifiedSource.parameters?.filter(
726
- (p) => this.componentService.isGroupParameter(p)
727
- ).length ?? 0;
728
- if (afterGroupCount < beforeGroupCount) {
729
- const removedCount = beforeGroupCount - afterGroupCount;
673
+ const flatEntry = this.generateEntryFromFlattenedInstance(inst);
674
+ const flatEntryPath = this.fileSystem.joinPath(
675
+ entriesDirFull,
676
+ `${inst.determinisiticId}.json`
677
+ );
730
678
  this.logger.action(
731
679
  whatIf,
732
- "DELETE",
733
- `${removedCount} empty group(s) from ${sourceType}`
680
+ "WRITE",
681
+ `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
734
682
  );
735
- sourceComponentModified = true;
736
- }
737
- if (sourceComponentModified) {
738
- modifiedSourceComponents++;
739
- }
740
- if (sourceComponentModified && !whatIf) {
741
- await this.componentService.saveComponent(sourceFilePath, modifiedSource);
683
+ if (!whatIf) {
684
+ await this.fileSystem.writeFile(flatEntryPath, flatEntry);
685
+ }
686
+ entriesFromFlattened++;
742
687
  }
743
688
  }
744
- for (const { composition, filePath } of compositions) {
745
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
746
- const deleted = this.compositionService.deleteRootOverrides(
747
- composition,
748
- resolvedNames,
749
- findOptions
750
- );
751
- if (deleted) {
752
- this.logger.action(whatIf, "DELETE", `Root overrides from composition/${relativePath}`);
753
- this.logger.detail(`\u2192 Removed: ${resolvedNames.join(", ")}`);
754
- if (!whatIf) {
755
- await this.compositionService.saveComposition(filePath, composition);
689
+ }
690
+ if (flattenComponentIds.length > 0) {
691
+ for (const contentType of contentTypeMap.values()) {
692
+ for (const flattenType of flattenComponentIds) {
693
+ if (this.compareTypes(flattenType, contentType.id, strict)) {
694
+ continue;
756
695
  }
696
+ if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
697
+ continue;
698
+ }
699
+ contentType.fields.push({
700
+ id: flattenType,
701
+ name: flattenType,
702
+ type: "contentReference",
703
+ typeConfig: {
704
+ isMulti: true,
705
+ allowedContentTypes: [flattenType]
706
+ },
707
+ localizable: false
708
+ });
757
709
  }
758
710
  }
759
711
  }
760
- return {
761
- modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
762
- modifiedCompositions,
763
- propagatedInstances
764
- };
765
- }
766
- parsePipeSeparatedValues(value, strict) {
767
- const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
768
- const normalized = [];
769
- for (const entry of entries) {
770
- const exists = normalized.some(
771
- (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
712
+ for (const [typeName, contentType] of contentTypeMap) {
713
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
714
+ const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
715
+ const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
716
+ const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
717
+ this.logger.action(
718
+ whatIf,
719
+ "WRITE",
720
+ `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
772
721
  );
773
- if (!exists) {
774
- normalized.push(entry);
722
+ if (!whatIf) {
723
+ await this.fileSystem.writeFile(filePath, contentType);
775
724
  }
725
+ contentTypesWritten++;
776
726
  }
777
- return normalized;
778
- }
779
- };
780
-
781
- // src/core/services/slot-renamer.service.ts
782
- var SlotRenamerService = class {
783
- constructor(fileSystem, componentService, logger) {
784
- this.fileSystem = fileSystem;
785
- this.componentService = componentService;
786
- this.logger = logger;
787
- }
788
- compareIds(id1, id2, strict) {
789
- if (strict) {
790
- return id1 === id2;
727
+ for (const [typeName, contentType] of flattenContentTypeMap) {
728
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
729
+ this.logger.action(
730
+ whatIf,
731
+ "WRITE",
732
+ `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
733
+ );
734
+ if (!whatIf) {
735
+ await this.fileSystem.writeFile(filePath, contentType);
736
+ }
737
+ contentTypesWritten++;
791
738
  }
792
- return id1.toLowerCase() === id2.toLowerCase();
739
+ const neverFoundMissingTypes = missingFlattenTypes.filter(
740
+ (type) => !foundMissingFlattenTypes.has(type)
741
+ );
742
+ if (neverFoundMissingTypes.length > 0) {
743
+ this.logger.warn(
744
+ `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
745
+ );
746
+ }
747
+ return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened, entriesReused };
793
748
  }
794
- async rename(options) {
795
- const {
796
- rootDir,
797
- componentsDir,
798
- compositionsDir,
799
- compositionPatternsDir,
800
- componentPatternsDir,
801
- componentType,
802
- slotId,
803
- newSlotId,
804
- newSlotName,
805
- whatIf,
806
- strict
807
- } = options;
808
- const findOptions = { strict };
809
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
810
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
811
- const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
812
- const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
813
- if (this.compareIds(slotId, newSlotId, strict)) {
814
- throw new TransformError("New slot ID is the same as the current slot ID");
749
+ // --- Content Type Generation ---
750
+ generateContentType(component) {
751
+ const fields = [];
752
+ if (component.parameters) {
753
+ for (const param of component.parameters) {
754
+ fields.push(this.parameterToField(param));
755
+ }
815
756
  }
816
- this.logger.info(`Loading component: ${componentType}`);
817
- const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
818
- if (!component.slots || component.slots.length === 0) {
819
- throw new SlotNotFoundError(slotId, componentType);
757
+ return {
758
+ id: component.id,
759
+ name: component.name,
760
+ fields
761
+ };
762
+ }
763
+ parameterToField(param) {
764
+ const field = {
765
+ id: param.id,
766
+ name: param.name,
767
+ type: param.type
768
+ };
769
+ if (param.helpText !== void 0) {
770
+ field.helpText = param.helpText;
820
771
  }
821
- const slotIndex = component.slots.findIndex(
822
- (s) => this.compareIds(s.id, slotId, strict)
823
- );
824
- if (slotIndex === -1) {
825
- throw new SlotNotFoundError(slotId, componentType);
772
+ if (param.localizable !== void 0) {
773
+ field.localizable = param.localizable;
826
774
  }
827
- const existingNewSlot = component.slots.find(
828
- (s) => this.compareIds(s.id, newSlotId, strict)
829
- );
830
- if (existingNewSlot) {
831
- throw new SlotAlreadyExistsError(newSlotId, componentType);
775
+ if (param.typeConfig !== void 0) {
776
+ field.typeConfig = param.typeConfig;
832
777
  }
833
- this.logger.action(
834
- whatIf,
835
- "UPDATE",
836
- `Slot "${slotId}" \u2192 "${newSlotId}" in component/${this.fileSystem.getBasename(componentFilePath)}`
837
- );
838
- component.slots[slotIndex].id = newSlotId;
839
- if (newSlotName !== void 0) {
840
- component.slots[slotIndex].name = newSlotName;
778
+ return field;
779
+ }
780
+ // --- Entry Generation ---
781
+ generateEntryFromComposition(composition) {
782
+ const comp = composition.composition;
783
+ const compositionSpecificKeys = /* @__PURE__ */ new Set(["_id", "_name", "type", "parameters", "slots", "_overrides"]);
784
+ const extraRootProps = {};
785
+ for (const [key, value] of Object.entries(comp)) {
786
+ if (!compositionSpecificKeys.has(key) && value != null) {
787
+ extraRootProps[key] = value;
788
+ }
841
789
  }
842
- if (!whatIf) {
843
- await this.componentService.saveComponent(componentFilePath, component);
790
+ const wrapperKeys = /* @__PURE__ */ new Set(["composition"]);
791
+ const extraWrapperProps = {};
792
+ for (const [key, value] of Object.entries(composition)) {
793
+ if (!wrapperKeys.has(key) && value != null) {
794
+ extraWrapperProps[key] = value;
795
+ }
844
796
  }
845
- const compositionsResult = await this.renameSlotInDirectory(
846
- fullCompositionsDir,
847
- componentType,
848
- slotId,
849
- newSlotId,
850
- whatIf,
851
- strict,
852
- "composition"
853
- );
854
- const compositionPatternsResult = await this.renameSlotInDirectory(
855
- fullCompositionPatternsDir,
856
- componentType,
857
- slotId,
858
- newSlotId,
859
- whatIf,
860
- strict,
861
- "compositionPattern"
862
- );
863
- const componentPatternsResult = await this.renameSlotInDirectory(
864
- fullComponentPatternsDir,
865
- componentType,
866
- slotId,
867
- newSlotId,
868
- whatIf,
869
- strict,
870
- "componentPattern"
871
- );
872
- const totalFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
873
- const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
874
- this.logger.info("");
875
- this.logger.info(
876
- `Summary: 1 component definition, ${totalFiles} file(s) (${totalInstances} instance(s)) updated.`
877
- );
797
+ const entryId = computeGuidHash(`entry${comp._id}`);
798
+ const entryName = this.truncateName(comp._name ?? comp._id, 60);
878
799
  return {
879
- componentDefinitionUpdated: true,
880
- compositionsModified: compositionsResult.filesModified,
881
- compositionPatternsModified: compositionPatternsResult.filesModified,
882
- componentPatternsModified: componentPatternsResult.filesModified,
883
- instancesRenamed: totalInstances
800
+ entry: {
801
+ _id: entryId,
802
+ _name: entryName,
803
+ type: comp.type,
804
+ fields: { ...comp.parameters ?? {} },
805
+ ...extraRootProps
806
+ },
807
+ ...extraWrapperProps
884
808
  };
885
809
  }
886
- async renameSlotInDirectory(directory, componentType, oldSlotId, newSlotId, whatIf, strict, label) {
887
- let files;
888
- try {
889
- files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
890
- } catch {
891
- return { filesModified: 0, instancesRenamed: 0 };
892
- }
893
- if (files.length === 0) {
894
- return { filesModified: 0, instancesRenamed: 0 };
895
- }
896
- let filesModified = 0;
897
- let instancesRenamed = 0;
898
- for (const filePath of files) {
899
- let composition;
900
- try {
901
- composition = await this.fileSystem.readFile(filePath);
902
- } catch {
903
- continue;
904
- }
905
- if (!composition?.composition) {
906
- continue;
810
+ generateEntryFromFlattenedInstance(inst) {
811
+ const entryName = this.truncateName(
812
+ `${inst.componentType} (from ${inst.compositionName})`,
813
+ 60
814
+ );
815
+ return {
816
+ entry: {
817
+ _id: inst.determinisiticId,
818
+ _name: entryName,
819
+ type: inst.componentType,
820
+ fields: { ...inst.instance.parameters ?? {} }
907
821
  }
908
- const count = this.renameSlotInTree(
909
- composition.composition,
910
- componentType,
911
- oldSlotId,
912
- newSlotId,
913
- strict
914
- );
915
- if (count > 0) {
916
- const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
917
- this.logger.action(
918
- whatIf,
919
- "UPDATE",
920
- `${label}/${relativePath} (${count} instance(s) of ${componentType})`
921
- );
922
- if (!whatIf) {
923
- await this.fileSystem.writeFile(filePath, composition);
822
+ };
823
+ }
824
+ // --- Flatten Tree Walking ---
825
+ findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
826
+ const results = [];
827
+ this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
828
+ return results;
829
+ }
830
+ walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
831
+ for (const [slotName, instances] of Object.entries(slots)) {
832
+ if (!Array.isArray(instances)) continue;
833
+ for (let i = 0; i < instances.length; i++) {
834
+ const instance = instances[i];
835
+ if (instance._pattern) {
836
+ continue;
837
+ }
838
+ const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
839
+ if (this.compareTypes(instance.type, targetType, strict)) {
840
+ const fullPath = `${compositionId}-${currentPath}`;
841
+ const deterministicId = computeGuidHash(fullPath);
842
+ results.push({
843
+ instance,
844
+ path: fullPath,
845
+ determinisiticId: deterministicId,
846
+ componentType: instance.type,
847
+ compositionId,
848
+ compositionName
849
+ });
850
+ }
851
+ if (instance.slots) {
852
+ this.walkSlots(
853
+ instance.slots,
854
+ targetType,
855
+ compositionId,
856
+ compositionName,
857
+ currentPath,
858
+ results,
859
+ strict
860
+ );
924
861
  }
925
- filesModified++;
926
- instancesRenamed += count;
927
862
  }
928
863
  }
929
- return { filesModified, instancesRenamed };
930
864
  }
931
- renameSlotInTree(node, componentType, oldSlotId, newSlotId, strict) {
932
- let count = 0;
933
- if (this.compareIds(node.type, componentType, strict) && node.slots) {
934
- const result = this.renameSlotKey(node.slots, oldSlotId, newSlotId, strict);
935
- if (result.renamed) {
936
- node.slots = result.slots;
937
- count++;
865
+ // --- Content Reference Transformation ---
866
+ transformContentReferences(entry) {
867
+ const dataResources = {};
868
+ for (const [fieldName, field] of Object.entries(entry.entry.fields)) {
869
+ if (field.type === "contentReference" && Array.isArray(field.value)) {
870
+ const entryIds = field.value;
871
+ const resourceKey = `ref-${entry.entry._id}-${fieldName}`;
872
+ field.value = `\${#jptr:/${resourceKey}/entries}`;
873
+ dataResources[resourceKey] = {
874
+ type: "uniformContentInternalReference",
875
+ variables: {
876
+ locale: "${locale}",
877
+ entryIds: entryIds.join(",")
878
+ }
879
+ };
938
880
  }
939
881
  }
940
- if (node.slots) {
941
- for (const slotInstances of Object.values(node.slots)) {
942
- if (!Array.isArray(slotInstances)) continue;
943
- for (const instance of slotInstances) {
944
- count += this.renameSlotInTree(instance, componentType, oldSlotId, newSlotId, strict);
945
- }
946
- }
882
+ if (Object.keys(dataResources).length > 0) {
883
+ entry.entry._dataResources = {
884
+ ...entry.entry._dataResources ?? {},
885
+ ...dataResources
886
+ };
947
887
  }
948
- return count;
949
888
  }
950
- renameSlotKey(slots, oldSlotId, newSlotId, strict) {
951
- const matchingKey = Object.keys(slots).find(
952
- (key) => this.compareIds(key, oldSlotId, strict)
953
- );
954
- if (!matchingKey) {
955
- return { renamed: false, slots };
956
- }
957
- const newSlots = {};
958
- for (const key of Object.keys(slots)) {
959
- if (key === matchingKey) {
960
- newSlots[newSlotId] = slots[key];
961
- } else {
962
- newSlots[key] = slots[key];
889
+ // --- Source Item Matching ---
890
+ async buildSourceItemMap(entriesDirFull) {
891
+ const sourceItemMap = /* @__PURE__ */ new Map();
892
+ const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "*.json");
893
+ for (const filePath of entryFiles) {
894
+ try {
895
+ const entryData = await this.fileSystem.readFile(filePath);
896
+ const sourceItemField = entryData?.entry?.fields?.sourceItem;
897
+ if (sourceItemField?.value != null) {
898
+ sourceItemMap.set(String(sourceItemField.value), entryData.entry._id);
899
+ }
900
+ } catch {
901
+ continue;
963
902
  }
964
903
  }
965
- return { renamed: true, slots: newSlots };
904
+ return sourceItemMap;
966
905
  }
967
- };
968
-
969
- // src/core/services/component-renamer.service.ts
970
- var ComponentRenamerService = class {
971
- constructor(fileSystem, componentService, logger) {
972
- this.fileSystem = fileSystem;
973
- this.componentService = componentService;
974
- this.logger = logger;
906
+ findExistingEntryBySourceItem(inst, sourceItemMap) {
907
+ const sourceItemParam = inst.instance.parameters?.sourceItem;
908
+ if (sourceItemParam?.value == null) {
909
+ return void 0;
910
+ }
911
+ return sourceItemMap.get(String(sourceItemParam.value));
975
912
  }
976
- compareIds(id1, id2, strict) {
913
+ // --- Utilities ---
914
+ compareTypes(type1, type2, strict) {
977
915
  if (strict) {
978
- return id1 === id2;
916
+ return type1 === type2;
979
917
  }
980
- return id1.toLowerCase() === id2.toLowerCase();
918
+ return type1.toLowerCase() === type2.toLowerCase();
981
919
  }
982
- async rename(options) {
983
- const {
984
- rootDir,
985
- componentsDir,
986
- compositionsDir,
987
- compositionPatternsDir,
988
- componentPatternsDir,
989
- componentType,
990
- newComponentType,
991
- newComponentName,
992
- whatIf,
993
- strict
994
- } = options;
995
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
996
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
997
- const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
998
- const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
999
- const findOptions = { strict };
1000
- if (this.compareIds(componentType, newComponentType, strict)) {
1001
- throw new TransformError("New component type is the same as the current component type");
1002
- }
1003
- this.logger.info(`Loading component: ${componentType}`);
1004
- const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
1005
- let targetExists = false;
1006
- try {
1007
- await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
1008
- targetExists = true;
1009
- } catch (error) {
1010
- if (!(error instanceof ComponentNotFoundError)) {
1011
- throw error;
1012
- }
1013
- }
1014
- if (targetExists) {
1015
- throw new ComponentAlreadyExistsError(newComponentType, fullComponentsDir);
1016
- }
1017
- this.logger.action(
1018
- whatIf,
1019
- "UPDATE",
1020
- `Component ID "${componentType}" \u2192 "${newComponentType}" in component/${this.fileSystem.getBasename(componentFilePath)}`
920
+ truncate(str, maxLength) {
921
+ if (str.length <= maxLength) return str;
922
+ return str.substring(0, maxLength - 3) + "...";
923
+ }
924
+ truncateName(name, maxLength) {
925
+ if (name.length <= maxLength) return name;
926
+ return name.substring(0, maxLength);
927
+ }
928
+ };
929
+ function computeGuidHash(guidOrSeed) {
930
+ let uuidStr;
931
+ if (isValidUuid(guidOrSeed)) {
932
+ uuidStr = formatAsBracedUuid(guidOrSeed);
933
+ } else {
934
+ const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
935
+ const hex = [
936
+ // First 4 bytes: little-endian in .NET Guid
937
+ hash[3].toString(16).padStart(2, "0"),
938
+ hash[2].toString(16).padStart(2, "0"),
939
+ hash[1].toString(16).padStart(2, "0"),
940
+ hash[0].toString(16).padStart(2, "0"),
941
+ "-",
942
+ // Bytes 4-5: little-endian
943
+ hash[5].toString(16).padStart(2, "0"),
944
+ hash[4].toString(16).padStart(2, "0"),
945
+ "-",
946
+ // Bytes 6-7: little-endian
947
+ hash[7].toString(16).padStart(2, "0"),
948
+ hash[6].toString(16).padStart(2, "0"),
949
+ "-",
950
+ // Bytes 8-9: big-endian
951
+ hash[8].toString(16).padStart(2, "0"),
952
+ hash[9].toString(16).padStart(2, "0"),
953
+ "-",
954
+ // Bytes 10-15: big-endian
955
+ hash[10].toString(16).padStart(2, "0"),
956
+ hash[11].toString(16).padStart(2, "0"),
957
+ hash[12].toString(16).padStart(2, "0"),
958
+ hash[13].toString(16).padStart(2, "0"),
959
+ hash[14].toString(16).padStart(2, "0"),
960
+ hash[15].toString(16).padStart(2, "0")
961
+ ].join("");
962
+ uuidStr = `{${hex}}`.toUpperCase();
963
+ }
964
+ const chars = uuidStr.split("");
965
+ chars[15] = "4";
966
+ const arr20 = ["8", "9", "A", "B"];
967
+ const hexVal = parseInt(chars[20], 16);
968
+ chars[20] = arr20[hexVal % 4];
969
+ return chars.join("").slice(1, -1).toLowerCase();
970
+ }
971
+ function isValidUuid(str) {
972
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
973
+ }
974
+ function formatAsBracedUuid(uuid) {
975
+ const clean = uuid.replace(/[{}]/g, "").toUpperCase();
976
+ return `{${clean}}`;
977
+ }
978
+
979
+ // src/core/services/id-regenerator.ts
980
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
981
+ function regenerateIds(value, basePath) {
982
+ return walkAndRegenerate(value, basePath);
983
+ }
984
+ function walkAndRegenerate(value, currentPath) {
985
+ if (value === null || value === void 0) {
986
+ return value;
987
+ }
988
+ if (Array.isArray(value)) {
989
+ return value.map((item, index) => walkAndRegenerate(item, `${currentPath}[${index}]`));
990
+ }
991
+ if (typeof value === "object") {
992
+ const obj = value;
993
+ const result = {};
994
+ for (const [key, val] of Object.entries(obj)) {
995
+ const childPath = `${currentPath}.${key}`;
996
+ if (key === "_id" && typeof val === "string" && UUID_PATTERN.test(val)) {
997
+ result[key] = computeGuidHash(val + childPath);
998
+ } else {
999
+ result[key] = walkAndRegenerate(val, childPath);
1000
+ }
1001
+ }
1002
+ return result;
1003
+ }
1004
+ return value;
1005
+ }
1006
+
1007
+ // src/core/services/property-propagator.service.ts
1008
+ var PropertyPropagatorService = class {
1009
+ constructor(fileSystem, componentService, compositionService, logger) {
1010
+ this.fileSystem = fileSystem;
1011
+ this.componentService = componentService;
1012
+ this.compositionService = compositionService;
1013
+ this.logger = logger;
1014
+ }
1015
+ async propagate(options) {
1016
+ const {
1017
+ rootDir,
1018
+ componentsDir,
1019
+ compositionsDir,
1020
+ compositionType,
1021
+ property,
1022
+ targetComponentType,
1023
+ targetGroup,
1024
+ whatIf,
1025
+ strict,
1026
+ deleteSourceParameter
1027
+ } = options;
1028
+ const findOptions = { strict };
1029
+ const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
1030
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1031
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1032
+ const sourceComponents = [];
1033
+ for (const sourceType of compositionTypes) {
1034
+ this.logger.info(`Loading component: ${sourceType}`);
1035
+ const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
1036
+ sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
1037
+ }
1038
+ this.logger.info(`Loading component: ${targetComponentType}`);
1039
+ const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
1040
+ const propertyNames = property.split("|").map((p) => p.trim()).filter((p) => p.length > 0);
1041
+ const resolvedParams = [];
1042
+ const resolvedNames = [];
1043
+ for (const { sourceType, sourceComponent } of sourceComponents) {
1044
+ const { parameters: sourceParams, notFound } = this.componentService.resolveProperties(
1045
+ sourceComponent,
1046
+ propertyNames,
1047
+ findOptions
1048
+ );
1049
+ if (notFound.length > 0) {
1050
+ this.logger.warn(`Property "${notFound.join(", ")}" not found on component "${sourceType}"`);
1051
+ continue;
1052
+ }
1053
+ for (const param of sourceParams) {
1054
+ const exists = resolvedParams.some(
1055
+ (existing) => strict ? existing.id === param.id : existing.id.toLowerCase() === param.id.toLowerCase()
1056
+ );
1057
+ if (!exists) {
1058
+ resolvedParams.push(param);
1059
+ resolvedNames.push(param.id);
1060
+ }
1061
+ }
1062
+ }
1063
+ const groupSources = [];
1064
+ for (const { sourceType, sourceComponent } of sourceComponents) {
1065
+ for (const name of propertyNames) {
1066
+ const param = this.componentService.findParameter(sourceComponent, name, findOptions);
1067
+ if (param && this.componentService.isGroupParameter(param)) {
1068
+ const groupSource = `${sourceType}.${name}`;
1069
+ if (!groupSources.includes(groupSource)) {
1070
+ groupSources.push(groupSource);
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ if (groupSources.length > 0) {
1076
+ this.logger.info(
1077
+ `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
1078
+ );
1079
+ } else {
1080
+ this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
1081
+ }
1082
+ let modifiedComponent = { ...targetComponent };
1083
+ let componentModified = false;
1084
+ const groupId = this.componentService.generateGroupId(targetGroup);
1085
+ const existingGroup = this.componentService.findParameter(
1086
+ modifiedComponent,
1087
+ groupId,
1088
+ findOptions
1021
1089
  );
1022
- component.id = newComponentType;
1023
- if (newComponentName !== void 0) {
1024
- component.name = newComponentName;
1090
+ if (!existingGroup) {
1091
+ this.logger.action(whatIf, "CREATE", `Group "${targetGroup}" on ${targetComponentType}`);
1092
+ modifiedComponent = this.componentService.ensureGroupExists(
1093
+ modifiedComponent,
1094
+ groupId,
1095
+ targetGroup,
1096
+ findOptions
1097
+ );
1098
+ componentModified = true;
1025
1099
  }
1026
- if (!whatIf) {
1027
- await this.componentService.saveComponent(componentFilePath, component);
1100
+ for (const param of resolvedParams) {
1101
+ const existingParam = this.componentService.findParameter(
1102
+ modifiedComponent,
1103
+ param.id,
1104
+ findOptions
1105
+ );
1106
+ if (!existingParam) {
1107
+ this.logger.action(
1108
+ whatIf,
1109
+ "COPY",
1110
+ `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
1111
+ );
1112
+ modifiedComponent = this.componentService.addParameterToComponent(
1113
+ modifiedComponent,
1114
+ param,
1115
+ findOptions
1116
+ );
1117
+ modifiedComponent = this.componentService.addParameterToGroup(
1118
+ modifiedComponent,
1119
+ groupId,
1120
+ param.id,
1121
+ findOptions
1122
+ );
1123
+ componentModified = true;
1124
+ } else {
1125
+ this.logger.info(`Parameter "${param.id}" already exists on ${targetComponentType}`);
1126
+ const group = this.componentService.findParameter(
1127
+ modifiedComponent,
1128
+ groupId,
1129
+ findOptions
1130
+ );
1131
+ if (group && this.componentService.isGroupParameter(group)) {
1132
+ const isInGroup = group.typeConfig?.childrenParams?.some(
1133
+ (id) => strict ? id === param.id : id.toLowerCase() === param.id.toLowerCase()
1134
+ );
1135
+ if (!isInGroup) {
1136
+ modifiedComponent = this.componentService.addParameterToGroup(
1137
+ modifiedComponent,
1138
+ groupId,
1139
+ param.id,
1140
+ findOptions
1141
+ );
1142
+ componentModified = true;
1143
+ }
1144
+ }
1145
+ }
1028
1146
  }
1029
- const ext = this.fileSystem.getExtension(componentFilePath);
1030
- const dir = this.fileSystem.getDirname(componentFilePath);
1031
- const newFilePath = this.fileSystem.joinPath(dir, `${newComponentType}${ext}`);
1032
- let fileRenamed = false;
1033
- if (componentFilePath !== newFilePath) {
1034
- this.logger.action(
1035
- whatIf,
1036
- "RENAME",
1037
- `component/${this.fileSystem.getBasename(componentFilePath)} \u2192 component/${this.fileSystem.getBasename(newFilePath)}`
1147
+ if (componentModified && !whatIf) {
1148
+ await this.componentService.saveComponent(targetFilePath, modifiedComponent);
1149
+ }
1150
+ const compositions = await this.compositionService.findCompositionsByTypes(
1151
+ fullCompositionsDir,
1152
+ compositionTypes,
1153
+ findOptions
1154
+ );
1155
+ let modifiedCompositions = 0;
1156
+ let propagatedInstances = 0;
1157
+ for (const { composition, filePath } of compositions) {
1158
+ const rootOverrides = this.compositionService.getRootOverrides(composition);
1159
+ const instances = this.compositionService.findComponentInstances(
1160
+ composition,
1161
+ targetComponentType,
1162
+ findOptions
1163
+ );
1164
+ if (instances.length === 0) {
1165
+ continue;
1166
+ }
1167
+ const valuesToPropagate = {};
1168
+ for (const param of resolvedParams) {
1169
+ if (rootOverrides[param.id]) {
1170
+ valuesToPropagate[param.id] = rootOverrides[param.id];
1171
+ }
1172
+ }
1173
+ if (Object.keys(valuesToPropagate).length === 0) {
1174
+ continue;
1175
+ }
1176
+ let compositionModified = false;
1177
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
1178
+ const instanceUpdates = [];
1179
+ for (const { instance, instanceId } of instances) {
1180
+ const instanceName = instance._id ?? instanceId;
1181
+ const clonedValues = regenerateIds(valuesToPropagate, instanceName);
1182
+ this.compositionService.setInstanceParameters(instance, clonedValues);
1183
+ compositionModified = true;
1184
+ propagatedInstances++;
1185
+ instanceUpdates.push(
1186
+ `${targetComponentType} "${instanceName}": ${Object.keys(valuesToPropagate).join(", ")}`
1187
+ );
1188
+ }
1189
+ if (compositionModified) {
1190
+ this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
1191
+ for (const update of instanceUpdates) {
1192
+ this.logger.detail(`\u2192 ${update}`);
1193
+ }
1194
+ if (!whatIf) {
1195
+ await this.compositionService.saveComposition(filePath, composition);
1196
+ }
1197
+ modifiedCompositions++;
1198
+ }
1199
+ }
1200
+ let modifiedSourceComponents = 0;
1201
+ if (deleteSourceParameter) {
1202
+ for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
1203
+ let modifiedSource = { ...sourceComponent };
1204
+ let sourceComponentModified = false;
1205
+ for (const param of resolvedParams) {
1206
+ const exists = this.componentService.findParameter(modifiedSource, param.id, findOptions);
1207
+ if (exists) {
1208
+ this.logger.action(whatIf, "DELETE", `Parameter "${param.id}" from ${sourceType}`);
1209
+ modifiedSource = this.componentService.removeParameter(modifiedSource, param.id, findOptions);
1210
+ sourceComponentModified = true;
1211
+ }
1212
+ }
1213
+ const beforeGroupCount = modifiedSource.parameters?.filter(
1214
+ (p) => this.componentService.isGroupParameter(p)
1215
+ ).length ?? 0;
1216
+ modifiedSource = this.componentService.removeEmptyGroups(modifiedSource);
1217
+ const afterGroupCount = modifiedSource.parameters?.filter(
1218
+ (p) => this.componentService.isGroupParameter(p)
1219
+ ).length ?? 0;
1220
+ if (afterGroupCount < beforeGroupCount) {
1221
+ const removedCount = beforeGroupCount - afterGroupCount;
1222
+ this.logger.action(
1223
+ whatIf,
1224
+ "DELETE",
1225
+ `${removedCount} empty group(s) from ${sourceType}`
1226
+ );
1227
+ sourceComponentModified = true;
1228
+ }
1229
+ if (sourceComponentModified) {
1230
+ modifiedSourceComponents++;
1231
+ }
1232
+ if (sourceComponentModified && !whatIf) {
1233
+ await this.componentService.saveComponent(sourceFilePath, modifiedSource);
1234
+ }
1235
+ }
1236
+ for (const { composition, filePath } of compositions) {
1237
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
1238
+ const deleted = this.compositionService.deleteRootOverrides(
1239
+ composition,
1240
+ resolvedNames,
1241
+ findOptions
1242
+ );
1243
+ if (deleted) {
1244
+ this.logger.action(whatIf, "DELETE", `Root overrides from composition/${relativePath}`);
1245
+ this.logger.detail(`\u2192 Removed: ${resolvedNames.join(", ")}`);
1246
+ if (!whatIf) {
1247
+ await this.compositionService.saveComposition(filePath, composition);
1248
+ }
1249
+ }
1250
+ }
1251
+ }
1252
+ return {
1253
+ modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
1254
+ modifiedCompositions,
1255
+ propagatedInstances
1256
+ };
1257
+ }
1258
+ parsePipeSeparatedValues(value, strict) {
1259
+ const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1260
+ const normalized = [];
1261
+ for (const entry of entries) {
1262
+ const exists = normalized.some(
1263
+ (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
1038
1264
  );
1039
- if (!whatIf) {
1040
- await this.fileSystem.renameFile(componentFilePath, newFilePath);
1265
+ if (!exists) {
1266
+ normalized.push(entry);
1041
1267
  }
1042
- fileRenamed = true;
1043
1268
  }
1044
- const allowedComponentsUpdated = await this.updateAllowedComponents(
1045
- fullComponentsDir,
1269
+ return normalized;
1270
+ }
1271
+ };
1272
+
1273
+ // src/core/services/slot-renamer.service.ts
1274
+ var SlotRenamerService = class {
1275
+ constructor(fileSystem, componentService, logger) {
1276
+ this.fileSystem = fileSystem;
1277
+ this.componentService = componentService;
1278
+ this.logger = logger;
1279
+ }
1280
+ compareIds(id1, id2, strict) {
1281
+ if (strict) {
1282
+ return id1 === id2;
1283
+ }
1284
+ return id1.toLowerCase() === id2.toLowerCase();
1285
+ }
1286
+ async rename(options) {
1287
+ const {
1288
+ rootDir,
1289
+ componentsDir,
1290
+ compositionsDir,
1291
+ compositionPatternsDir,
1292
+ componentPatternsDir,
1046
1293
  componentType,
1047
- newComponentType,
1048
- componentFilePath,
1294
+ slotId,
1295
+ newSlotId,
1296
+ newSlotName,
1049
1297
  whatIf,
1050
1298
  strict
1299
+ } = options;
1300
+ const findOptions = { strict };
1301
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1302
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1303
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1304
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1305
+ if (this.compareIds(slotId, newSlotId, strict)) {
1306
+ throw new TransformError("New slot ID is the same as the current slot ID");
1307
+ }
1308
+ this.logger.info(`Loading component: ${componentType}`);
1309
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
1310
+ if (!component.slots || component.slots.length === 0) {
1311
+ throw new SlotNotFoundError(slotId, componentType);
1312
+ }
1313
+ const slotIndex = component.slots.findIndex(
1314
+ (s) => this.compareIds(s.id, slotId, strict)
1051
1315
  );
1052
- const compositionsResult = await this.renameTypeInDirectory(
1316
+ if (slotIndex === -1) {
1317
+ throw new SlotNotFoundError(slotId, componentType);
1318
+ }
1319
+ const existingNewSlot = component.slots.find(
1320
+ (s) => this.compareIds(s.id, newSlotId, strict)
1321
+ );
1322
+ if (existingNewSlot) {
1323
+ throw new SlotAlreadyExistsError(newSlotId, componentType);
1324
+ }
1325
+ this.logger.action(
1326
+ whatIf,
1327
+ "UPDATE",
1328
+ `Slot "${slotId}" \u2192 "${newSlotId}" in component/${this.fileSystem.getBasename(componentFilePath)}`
1329
+ );
1330
+ component.slots[slotIndex].id = newSlotId;
1331
+ if (newSlotName !== void 0) {
1332
+ component.slots[slotIndex].name = newSlotName;
1333
+ }
1334
+ if (!whatIf) {
1335
+ await this.componentService.saveComponent(componentFilePath, component);
1336
+ }
1337
+ const compositionsResult = await this.renameSlotInDirectory(
1053
1338
  fullCompositionsDir,
1054
1339
  componentType,
1055
- newComponentType,
1340
+ slotId,
1341
+ newSlotId,
1056
1342
  whatIf,
1057
1343
  strict,
1058
1344
  "composition"
1059
1345
  );
1060
- const compositionPatternsResult = await this.renameTypeInDirectory(
1346
+ const compositionPatternsResult = await this.renameSlotInDirectory(
1061
1347
  fullCompositionPatternsDir,
1062
1348
  componentType,
1063
- newComponentType,
1349
+ slotId,
1350
+ newSlotId,
1064
1351
  whatIf,
1065
1352
  strict,
1066
1353
  "compositionPattern"
1067
1354
  );
1068
- const componentPatternsResult = await this.renameTypeInDirectory(
1355
+ const componentPatternsResult = await this.renameSlotInDirectory(
1069
1356
  fullComponentPatternsDir,
1070
1357
  componentType,
1071
- newComponentType,
1358
+ slotId,
1359
+ newSlotId,
1072
1360
  whatIf,
1073
1361
  strict,
1074
1362
  "componentPattern"
1075
1363
  );
1076
- const totalCompositionFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1364
+ const totalFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1077
1365
  const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
1078
1366
  this.logger.info("");
1079
1367
  this.logger.info(
1080
- `Summary: 1 component renamed, ${allowedComponentsUpdated} component(s) with allowedComponents updated, ${totalCompositionFiles} composition file(s) (${totalInstances} instance(s)) updated.`
1368
+ `Summary: 1 component definition, ${totalFiles} file(s) (${totalInstances} instance(s)) updated.`
1081
1369
  );
1082
1370
  return {
1083
- componentRenamed: true,
1084
- fileRenamed,
1085
- allowedComponentsUpdated,
1371
+ componentDefinitionUpdated: true,
1086
1372
  compositionsModified: compositionsResult.filesModified,
1087
1373
  compositionPatternsModified: compositionPatternsResult.filesModified,
1088
1374
  componentPatternsModified: componentPatternsResult.filesModified,
1089
1375
  instancesRenamed: totalInstances
1090
1376
  };
1091
1377
  }
1092
- async updateAllowedComponents(componentsDir, oldType, newType, sourceFilePath, whatIf, strict) {
1093
- let files;
1094
- try {
1095
- files = await this.fileSystem.findFiles(componentsDir, "*.{json,yaml,yml}");
1096
- } catch {
1097
- return 0;
1098
- }
1099
- let updatedCount = 0;
1100
- for (const filePath of files) {
1101
- if (filePath === sourceFilePath) continue;
1102
- let comp;
1103
- try {
1104
- comp = await this.fileSystem.readFile(filePath);
1105
- } catch {
1106
- continue;
1107
- }
1108
- if (!comp.slots || comp.slots.length === 0) continue;
1109
- let fileModified = false;
1110
- for (const slot of comp.slots) {
1111
- if (!slot.allowedComponents) continue;
1112
- for (let i = 0; i < slot.allowedComponents.length; i++) {
1113
- if (this.compareIds(slot.allowedComponents[i], oldType, strict)) {
1114
- slot.allowedComponents[i] = newType;
1115
- fileModified = true;
1116
- }
1117
- }
1118
- }
1119
- if (fileModified) {
1120
- this.logger.action(
1121
- whatIf,
1122
- "UPDATE",
1123
- `allowedComponents in component/${this.fileSystem.getBasename(filePath)}: ${oldType} \u2192 ${newType}`
1124
- );
1125
- if (!whatIf) {
1126
- await this.fileSystem.writeFile(filePath, comp);
1127
- }
1128
- updatedCount++;
1129
- }
1130
- }
1131
- return updatedCount;
1132
- }
1133
- async renameTypeInDirectory(directory, oldType, newType, whatIf, strict, label) {
1378
+ async renameSlotInDirectory(directory, componentType, oldSlotId, newSlotId, whatIf, strict, label) {
1134
1379
  let files;
1135
1380
  try {
1136
1381
  files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
@@ -1149,14 +1394,22 @@ var ComponentRenamerService = class {
1149
1394
  } catch {
1150
1395
  continue;
1151
1396
  }
1152
- if (!composition?.composition) continue;
1153
- const count = this.renameTypeInTree(composition.composition, oldType, newType, strict);
1397
+ if (!composition?.composition) {
1398
+ continue;
1399
+ }
1400
+ const count = this.renameSlotInTree(
1401
+ composition.composition,
1402
+ componentType,
1403
+ oldSlotId,
1404
+ newSlotId,
1405
+ strict
1406
+ );
1154
1407
  if (count > 0) {
1155
1408
  const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
1156
1409
  this.logger.action(
1157
1410
  whatIf,
1158
1411
  "UPDATE",
1159
- `${label}/${relativePath} (${count} instance(s))`
1412
+ `${label}/${relativePath} (${count} instance(s) of ${componentType})`
1160
1413
  );
1161
1414
  if (!whatIf) {
1162
1415
  await this.fileSystem.writeFile(filePath, composition);
@@ -1167,949 +1420,753 @@ var ComponentRenamerService = class {
1167
1420
  }
1168
1421
  return { filesModified, instancesRenamed };
1169
1422
  }
1170
- renameTypeInTree(node, oldType, newType, strict) {
1423
+ renameSlotInTree(node, componentType, oldSlotId, newSlotId, strict) {
1171
1424
  let count = 0;
1172
- if (this.compareIds(node.type, oldType, strict)) {
1173
- node.type = newType;
1174
- count++;
1425
+ if (this.compareIds(node.type, componentType, strict) && node.slots) {
1426
+ const result = this.renameSlotKey(node.slots, oldSlotId, newSlotId, strict);
1427
+ if (result.renamed) {
1428
+ node.slots = result.slots;
1429
+ count++;
1430
+ }
1175
1431
  }
1176
1432
  if (node.slots) {
1177
1433
  for (const slotInstances of Object.values(node.slots)) {
1178
1434
  if (!Array.isArray(slotInstances)) continue;
1179
1435
  for (const instance of slotInstances) {
1180
- count += this.renameTypeInTree(instance, oldType, newType, strict);
1436
+ count += this.renameSlotInTree(instance, componentType, oldSlotId, newSlotId, strict);
1181
1437
  }
1182
1438
  }
1183
1439
  }
1184
1440
  return count;
1185
1441
  }
1186
- };
1187
-
1188
- // src/core/services/component-adder.service.ts
1189
- import { randomUUID } from "crypto";
1190
- var ComponentAdderService = class {
1191
- constructor(fileSystem, componentService, logger) {
1192
- this.fileSystem = fileSystem;
1193
- this.componentService = componentService;
1194
- this.logger = logger;
1195
- }
1196
- compareIds(id1, id2, strict) {
1197
- if (strict) {
1198
- return id1 === id2;
1199
- }
1200
- return id1.toLowerCase() === id2.toLowerCase();
1201
- }
1202
- parseParentComponentTypes(parentComponentType) {
1203
- return parentComponentType.split("|").map((t) => t.trim()).filter((t) => t.length > 0);
1204
- }
1205
- matchesAnyParentType(instanceType, parentTypes, strict) {
1206
- return parentTypes.some((pt) => this.compareIds(instanceType, pt, strict));
1207
- }
1208
- parseParameters(parameterString) {
1209
- const result = {};
1210
- if (!parameterString) return result;
1211
- const pairs = parameterString.split("|");
1212
- for (const pair of pairs) {
1213
- const [key, ...valueParts] = pair.split(":");
1214
- if (key && valueParts.length > 0) {
1215
- result[key.trim()] = valueParts.join(":").trim();
1216
- }
1217
- }
1218
- return result;
1219
- }
1220
- generateId(baseName) {
1221
- return `${baseName}-${randomUUID().slice(0, 8)}`;
1222
- }
1223
- regenerateInstanceIds(instance) {
1224
- if (instance._id) {
1225
- instance._id = instance._pattern ? randomUUID() : this.generateId(instance.type);
1442
+ renameSlotKey(slots, oldSlotId, newSlotId, strict) {
1443
+ const matchingKey = Object.keys(slots).find(
1444
+ (key) => this.compareIds(key, oldSlotId, strict)
1445
+ );
1446
+ if (!matchingKey) {
1447
+ return { renamed: false, slots };
1226
1448
  }
1227
- if (instance.slots) {
1228
- for (const slotInstances of Object.values(instance.slots)) {
1229
- if (Array.isArray(slotInstances)) {
1230
- for (const nestedInstance of slotInstances) {
1231
- this.regenerateInstanceIds(nestedInstance);
1232
- }
1233
- }
1449
+ const newSlots = {};
1450
+ for (const key of Object.keys(slots)) {
1451
+ if (key === matchingKey) {
1452
+ newSlots[newSlotId] = slots[key];
1453
+ } else {
1454
+ newSlots[key] = slots[key];
1234
1455
  }
1235
1456
  }
1457
+ return { renamed: true, slots: newSlots };
1236
1458
  }
1237
- createComponentInstance(componentType, parameters) {
1238
- const instance = {
1239
- type: componentType,
1240
- _id: this.generateId(componentType)
1241
- };
1242
- if (Object.keys(parameters).length > 0) {
1243
- instance.parameters = {};
1244
- for (const [key, value] of Object.entries(parameters)) {
1245
- instance.parameters[key] = {
1246
- type: "text",
1247
- value
1248
- };
1249
- }
1250
- }
1251
- return instance;
1459
+ };
1460
+
1461
+ // src/core/services/component-renamer.service.ts
1462
+ var ComponentRenamerService = class {
1463
+ constructor(fileSystem, componentService, logger) {
1464
+ this.fileSystem = fileSystem;
1465
+ this.componentService = componentService;
1466
+ this.logger = logger;
1252
1467
  }
1253
- addComponentToSlot(slots, slotId, instance) {
1254
- if (!slots[slotId]) {
1255
- slots[slotId] = [];
1468
+ compareIds(id1, id2, strict) {
1469
+ if (strict) {
1470
+ return id1 === id2;
1256
1471
  }
1257
- slots[slotId].push(instance);
1472
+ return id1.toLowerCase() === id2.toLowerCase();
1258
1473
  }
1259
- async addComponent(options) {
1474
+ async rename(options) {
1260
1475
  const {
1261
1476
  rootDir,
1262
1477
  componentsDir,
1263
1478
  compositionsDir,
1264
1479
  compositionPatternsDir,
1265
1480
  componentPatternsDir,
1266
- parentComponentType,
1267
- slot,
1481
+ componentType,
1268
1482
  newComponentType,
1269
- parameters: paramString,
1483
+ newComponentName,
1270
1484
  whatIf,
1271
1485
  strict
1272
1486
  } = options;
1273
- if (!newComponentType) {
1274
- throw new TransformError("newComponentType is required for add-component");
1275
- }
1276
1487
  const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1277
1488
  const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1278
1489
  const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1279
1490
  const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1280
1491
  const findOptions = { strict };
1281
- const parentTypes = this.parseParentComponentTypes(parentComponentType);
1282
- if (parentTypes.length === 0) {
1283
- throw new TransformError("parentComponentType cannot be empty");
1492
+ if (this.compareIds(componentType, newComponentType, strict)) {
1493
+ throw new TransformError("New component type is the same as the current component type");
1284
1494
  }
1285
- this.logger.info(`Validating new component: ${newComponentType}`);
1495
+ this.logger.info(`Loading component: ${componentType}`);
1496
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
1497
+ let targetExists = false;
1286
1498
  try {
1287
1499
  await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
1500
+ targetExists = true;
1288
1501
  } catch (error) {
1289
- if (error instanceof ComponentNotFoundError) {
1290
- throw new TransformError(
1291
- `Component type "${newComponentType}" not found in ${fullComponentsDir}`
1292
- );
1502
+ if (!(error instanceof ComponentNotFoundError)) {
1503
+ throw error;
1293
1504
  }
1294
- throw error;
1295
1505
  }
1296
- let allowedComponentsUpdated = false;
1297
- for (const parentType of parentTypes) {
1298
- this.logger.info(`Loading parent component: ${parentType}`);
1299
- const { component: parentComponent, filePath: parentComponentFilePath } = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1300
- if (!parentComponent.slots) {
1301
- parentComponent.slots = [];
1302
- }
1303
- let slotDef = parentComponent.slots.find(
1304
- (s) => this.compareIds(s.id, slot, strict)
1305
- );
1306
- if (!slotDef) {
1307
- this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1308
- slotDef = { id: slot, name: slot, allowedComponents: [] };
1309
- parentComponent.slots.push(slotDef);
1310
- }
1311
- const slotIndex = parentComponent.slots?.findIndex(
1312
- (s) => this.compareIds(s.id, slotDef.id, strict)
1506
+ if (targetExists) {
1507
+ throw new ComponentAlreadyExistsError(newComponentType, fullComponentsDir);
1508
+ }
1509
+ this.logger.action(
1510
+ whatIf,
1511
+ "UPDATE",
1512
+ `Component ID "${componentType}" \u2192 "${newComponentType}" in component/${this.fileSystem.getBasename(componentFilePath)}`
1513
+ );
1514
+ component.id = newComponentType;
1515
+ if (newComponentName !== void 0) {
1516
+ component.name = newComponentName;
1517
+ }
1518
+ if (!whatIf) {
1519
+ await this.componentService.saveComponent(componentFilePath, component);
1520
+ }
1521
+ const ext = this.fileSystem.getExtension(componentFilePath);
1522
+ const dir = this.fileSystem.getDirname(componentFilePath);
1523
+ const newFilePath = this.fileSystem.joinPath(dir, `${newComponentType}${ext}`);
1524
+ let fileRenamed = false;
1525
+ if (componentFilePath !== newFilePath) {
1526
+ this.logger.action(
1527
+ whatIf,
1528
+ "RENAME",
1529
+ `component/${this.fileSystem.getBasename(componentFilePath)} \u2192 component/${this.fileSystem.getBasename(newFilePath)}`
1313
1530
  );
1314
- if (slotIndex !== void 0 && slotIndex >= 0) {
1315
- const actualSlot = parentComponent.slots[slotIndex];
1316
- if (!actualSlot.allowedComponents) {
1317
- actualSlot.allowedComponents = [];
1318
- }
1319
- const isAlreadyAllowed = actualSlot.allowedComponents.some(
1320
- (c) => this.compareIds(c, newComponentType, strict)
1321
- );
1322
- if (!isAlreadyAllowed) {
1323
- this.logger.action(
1324
- whatIf,
1325
- "UPDATE",
1326
- `Adding "${newComponentType}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1327
- );
1328
- actualSlot.allowedComponents.push(newComponentType);
1329
- if (!whatIf) {
1330
- await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1331
- }
1332
- allowedComponentsUpdated = true;
1333
- }
1531
+ if (!whatIf) {
1532
+ await this.fileSystem.renameFile(componentFilePath, newFilePath);
1334
1533
  }
1534
+ fileRenamed = true;
1335
1535
  }
1336
- const parsedParams = this.parseParameters(paramString);
1337
- const newInstance = this.createComponentInstance(newComponentType, parsedParams);
1338
- const compositionsResult = await this.addComponentToDirectory(
1536
+ const allowedComponentsUpdated = await this.updateAllowedComponents(
1537
+ fullComponentsDir,
1538
+ componentType,
1539
+ newComponentType,
1540
+ componentFilePath,
1541
+ whatIf,
1542
+ strict
1543
+ );
1544
+ const compositionsResult = await this.renameTypeInDirectory(
1339
1545
  fullCompositionsDir,
1340
- parentTypes,
1341
- slot,
1342
- newInstance,
1546
+ componentType,
1547
+ newComponentType,
1343
1548
  whatIf,
1344
1549
  strict,
1345
1550
  "composition"
1346
1551
  );
1347
- const compositionPatternsResult = await this.addComponentToDirectory(
1552
+ const compositionPatternsResult = await this.renameTypeInDirectory(
1348
1553
  fullCompositionPatternsDir,
1349
- parentTypes,
1350
- slot,
1351
- newInstance,
1554
+ componentType,
1555
+ newComponentType,
1352
1556
  whatIf,
1353
1557
  strict,
1354
1558
  "compositionPattern"
1355
1559
  );
1356
- const componentPatternsResult = await this.addComponentToDirectory(
1560
+ const componentPatternsResult = await this.renameTypeInDirectory(
1357
1561
  fullComponentPatternsDir,
1358
- parentTypes,
1359
- slot,
1360
- newInstance,
1562
+ componentType,
1563
+ newComponentType,
1361
1564
  whatIf,
1362
1565
  strict,
1363
1566
  "componentPattern"
1364
1567
  );
1568
+ const totalCompositionFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1569
+ const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
1365
1570
  this.logger.info("");
1366
1571
  this.logger.info(
1367
- `Summary: ${allowedComponentsUpdated ? `${parentTypes.length} component definition(s) updated, ` : ""}${compositionsResult} composition(s), ${compositionPatternsResult} composition pattern(s), ${componentPatternsResult} component pattern(s) updated. ${compositionsResult + compositionPatternsResult + componentPatternsResult} instance(s) added.`
1572
+ `Summary: 1 component renamed, ${allowedComponentsUpdated} component(s) with allowedComponents updated, ${totalCompositionFiles} composition file(s) (${totalInstances} instance(s)) updated.`
1368
1573
  );
1369
1574
  return {
1575
+ componentRenamed: true,
1576
+ fileRenamed,
1370
1577
  allowedComponentsUpdated,
1371
- compositionsModified: compositionsResult,
1372
- compositionPatternsModified: compositionPatternsResult,
1373
- componentPatternsModified: componentPatternsResult,
1374
- instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1578
+ compositionsModified: compositionsResult.filesModified,
1579
+ compositionPatternsModified: compositionPatternsResult.filesModified,
1580
+ componentPatternsModified: componentPatternsResult.filesModified,
1581
+ instancesRenamed: totalInstances
1375
1582
  };
1376
1583
  }
1377
- async addComponentPattern(options) {
1378
- const {
1379
- rootDir,
1380
- componentsDir,
1381
- compositionsDir,
1382
- compositionPatternsDir,
1383
- componentPatternsDir,
1384
- parentComponentType,
1385
- slot,
1386
- componentPatternId,
1387
- whatIf,
1388
- strict
1389
- } = options;
1390
- if (!componentPatternId) {
1391
- throw new TransformError("componentPatternId is required for add-component-pattern");
1392
- }
1393
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1394
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1395
- const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1396
- const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1397
- const findOptions = { strict };
1398
- const parentTypes = this.parseParentComponentTypes(parentComponentType);
1399
- if (parentTypes.length === 0) {
1400
- throw new TransformError("parentComponentType cannot be empty");
1401
- }
1402
- this.logger.info(`Loading component pattern: ${componentPatternId}`);
1403
- const pattern = await this.loadComponentPattern(
1404
- fullComponentPatternsDir,
1405
- componentPatternId,
1406
- strict
1407
- );
1408
- const patternDefinition = pattern.definition;
1409
- if (!patternDefinition || !patternDefinition.type) {
1410
- throw new TransformError(
1411
- `Component pattern "${componentPatternId}" has no valid definition or missing type`
1412
- );
1584
+ async updateAllowedComponents(componentsDir, oldType, newType, sourceFilePath, whatIf, strict) {
1585
+ let files;
1586
+ try {
1587
+ files = await this.fileSystem.findFiles(componentsDir, "*.{json,yaml,yml}");
1588
+ } catch {
1589
+ return 0;
1413
1590
  }
1414
- const componentTypeInPattern = patternDefinition.type;
1415
- let allowedComponentsUpdated = false;
1416
- for (const parentType of parentTypes) {
1417
- this.logger.info(`Loading parent component: ${parentType}`);
1418
- const { component: parentComponent, filePath: parentComponentFilePath } = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1419
- if (!parentComponent.slots) {
1420
- parentComponent.slots = [];
1591
+ let updatedCount = 0;
1592
+ for (const filePath of files) {
1593
+ if (filePath === sourceFilePath) continue;
1594
+ let comp;
1595
+ try {
1596
+ comp = await this.fileSystem.readFile(filePath);
1597
+ } catch {
1598
+ continue;
1421
1599
  }
1422
- let slotDef = parentComponent.slots.find(
1423
- (s) => this.compareIds(s.id, slot, strict)
1424
- );
1425
- if (!slotDef) {
1426
- this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1427
- slotDef = { id: slot, name: slot, allowedComponents: [] };
1428
- parentComponent.slots.push(slotDef);
1600
+ if (!comp.slots || comp.slots.length === 0) continue;
1601
+ let fileModified = false;
1602
+ for (const slot of comp.slots) {
1603
+ if (!slot.allowedComponents) continue;
1604
+ for (let i = 0; i < slot.allowedComponents.length; i++) {
1605
+ if (this.compareIds(slot.allowedComponents[i], oldType, strict)) {
1606
+ slot.allowedComponents[i] = newType;
1607
+ fileModified = true;
1608
+ }
1609
+ }
1429
1610
  }
1430
- const slotIndex = parentComponent.slots?.findIndex(
1431
- (s) => this.compareIds(s.id, slotDef.id, strict)
1432
- );
1433
- if (slotIndex !== void 0 && slotIndex >= 0) {
1434
- const actualSlot = parentComponent.slots[slotIndex];
1435
- if (!actualSlot.allowedComponents) {
1436
- actualSlot.allowedComponents = [];
1611
+ if (fileModified) {
1612
+ this.logger.action(
1613
+ whatIf,
1614
+ "UPDATE",
1615
+ `allowedComponents in component/${this.fileSystem.getBasename(filePath)}: ${oldType} \u2192 ${newType}`
1616
+ );
1617
+ if (!whatIf) {
1618
+ await this.fileSystem.writeFile(filePath, comp);
1437
1619
  }
1438
- const isAlreadyAllowed = actualSlot.allowedComponents.some(
1439
- (c) => this.compareIds(c, componentTypeInPattern, strict)
1620
+ updatedCount++;
1621
+ }
1622
+ }
1623
+ return updatedCount;
1624
+ }
1625
+ async renameTypeInDirectory(directory, oldType, newType, whatIf, strict, label) {
1626
+ let files;
1627
+ try {
1628
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1629
+ } catch {
1630
+ return { filesModified: 0, instancesRenamed: 0 };
1631
+ }
1632
+ if (files.length === 0) {
1633
+ return { filesModified: 0, instancesRenamed: 0 };
1634
+ }
1635
+ let filesModified = 0;
1636
+ let instancesRenamed = 0;
1637
+ for (const filePath of files) {
1638
+ let composition;
1639
+ try {
1640
+ composition = await this.fileSystem.readFile(filePath);
1641
+ } catch {
1642
+ continue;
1643
+ }
1644
+ if (!composition?.composition) continue;
1645
+ const count = this.renameTypeInTree(composition.composition, oldType, newType, strict);
1646
+ if (count > 0) {
1647
+ const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
1648
+ this.logger.action(
1649
+ whatIf,
1650
+ "UPDATE",
1651
+ `${label}/${relativePath} (${count} instance(s))`
1440
1652
  );
1441
- if (!isAlreadyAllowed) {
1442
- this.logger.action(
1443
- whatIf,
1444
- "UPDATE",
1445
- `Adding "${componentTypeInPattern}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1446
- );
1447
- actualSlot.allowedComponents.push(componentTypeInPattern);
1448
- if (!whatIf) {
1449
- await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1450
- }
1451
- allowedComponentsUpdated = true;
1653
+ if (!whatIf) {
1654
+ await this.fileSystem.writeFile(filePath, composition);
1452
1655
  }
1656
+ filesModified++;
1657
+ instancesRenamed += count;
1453
1658
  }
1454
1659
  }
1455
- const newInstance = {
1456
- type: componentTypeInPattern,
1457
- _pattern: pattern.componentPatternId,
1458
- _id: randomUUID()
1459
- };
1460
- const compositionsResult = await this.addComponentToDirectory(
1461
- fullCompositionsDir,
1462
- parentTypes,
1463
- slot,
1464
- newInstance,
1465
- whatIf,
1466
- strict,
1467
- "composition"
1468
- );
1469
- const compositionPatternsResult = await this.addComponentToDirectory(
1470
- fullCompositionPatternsDir,
1471
- parentTypes,
1472
- slot,
1473
- newInstance,
1474
- whatIf,
1475
- strict,
1476
- "compositionPattern"
1477
- );
1478
- const componentPatternsResult = await this.addComponentToDirectory(
1479
- fullComponentPatternsDir,
1480
- parentTypes,
1481
- slot,
1482
- newInstance,
1483
- whatIf,
1484
- strict,
1485
- "componentPattern"
1486
- );
1487
- this.logger.info("");
1488
- this.logger.info(
1489
- `Summary: ${allowedComponentsUpdated ? `${parentTypes.length} component definition(s) updated, ` : ""}${compositionsResult} composition(s), ${compositionPatternsResult} composition pattern(s), ${componentPatternsResult} component pattern(s) updated. ${compositionsResult + compositionPatternsResult + componentPatternsResult} instance(s) added.`
1490
- );
1491
- return {
1492
- allowedComponentsUpdated,
1493
- compositionsModified: compositionsResult,
1494
- compositionPatternsModified: compositionPatternsResult,
1495
- componentPatternsModified: componentPatternsResult,
1496
- instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1497
- };
1660
+ return { filesModified, instancesRenamed };
1498
1661
  }
1499
- async loadComponentPattern(componentPatternsDir, patternId, strict) {
1500
- const jsonPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.json`);
1501
- const yamlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yaml`);
1502
- const ymlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yml`);
1503
- let raw;
1504
- if (await this.fileSystem.fileExists(jsonPath)) {
1505
- raw = await this.fileSystem.readFile(jsonPath);
1506
- } else if (await this.fileSystem.fileExists(yamlPath)) {
1507
- raw = await this.fileSystem.readFile(yamlPath);
1508
- } else if (await this.fileSystem.fileExists(ymlPath)) {
1509
- raw = await this.fileSystem.readFile(ymlPath);
1662
+ renameTypeInTree(node, oldType, newType, strict) {
1663
+ let count = 0;
1664
+ if (this.compareIds(node.type, oldType, strict)) {
1665
+ node.type = newType;
1666
+ count++;
1510
1667
  }
1511
- if (!raw && !strict) {
1512
- const files = await this.fileSystem.findFiles(componentPatternsDir, "*.{json,yaml,yml}");
1513
- for (const filePath of files) {
1514
- const basename2 = this.fileSystem.getBasename(filePath);
1515
- const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
1516
- if (nameWithoutExt.toLowerCase() === patternId.toLowerCase()) {
1517
- raw = await this.fileSystem.readFile(filePath);
1518
- break;
1668
+ if (node.slots) {
1669
+ for (const slotInstances of Object.values(node.slots)) {
1670
+ if (!Array.isArray(slotInstances)) continue;
1671
+ for (const instance of slotInstances) {
1672
+ count += this.renameTypeInTree(instance, oldType, newType, strict);
1519
1673
  }
1520
1674
  }
1521
1675
  }
1522
- if (!raw) {
1523
- throw new TransformError(`Component pattern "${patternId}" not found in ${componentPatternsDir}`);
1524
- }
1525
- return this.normalizeComponentPattern(raw, patternId);
1676
+ return count;
1526
1677
  }
1527
- normalizeComponentPattern(raw, patternId) {
1528
- if (raw.definition && typeof raw.definition === "object" && raw.definition.type) {
1529
- return raw;
1678
+ };
1679
+
1680
+ // src/core/services/component-adder.service.ts
1681
+ import { randomUUID } from "crypto";
1682
+ var ComponentAdderService = class {
1683
+ constructor(fileSystem, componentService, logger) {
1684
+ this.fileSystem = fileSystem;
1685
+ this.componentService = componentService;
1686
+ this.logger = logger;
1687
+ }
1688
+ compareIds(id1, id2, strict) {
1689
+ if (strict) {
1690
+ return id1 === id2;
1530
1691
  }
1531
- if (raw.composition && typeof raw.composition === "object" && raw.composition.type) {
1532
- const composition = raw.composition;
1533
- return {
1534
- componentPatternId: patternId,
1535
- definition: composition
1536
- };
1692
+ return id1.toLowerCase() === id2.toLowerCase();
1693
+ }
1694
+ parseParentComponentTypes(parentComponentType) {
1695
+ return parentComponentType.split("|").map((t) => t.trim()).filter((t) => t.length > 0);
1696
+ }
1697
+ matchesAnyParentType(instanceType, parentTypes, strict) {
1698
+ return parentTypes.some((pt) => this.compareIds(instanceType, pt, strict));
1699
+ }
1700
+ parseParameters(parameterString) {
1701
+ const result = {};
1702
+ if (!parameterString) return result;
1703
+ const pairs = parameterString.split("|");
1704
+ for (const pair of pairs) {
1705
+ const [key, ...valueParts] = pair.split(":");
1706
+ if (key && valueParts.length > 0) {
1707
+ result[key.trim()] = valueParts.join(":").trim();
1708
+ }
1537
1709
  }
1538
- return raw;
1710
+ return result;
1539
1711
  }
1540
- async addComponentToDirectory(directory, parentTypes, slot, newInstance, whatIf, strict, dirType) {
1541
- const exists = await this.fileSystem.fileExists(directory);
1542
- if (!exists) {
1543
- this.logger.detail(`${dirType} directory does not exist, skipping`);
1544
- return 0;
1712
+ generateId(baseName) {
1713
+ return `${baseName}-${randomUUID().slice(0, 8)}`;
1714
+ }
1715
+ regenerateInstanceIds(instance) {
1716
+ if (instance._id) {
1717
+ instance._id = instance._pattern ? randomUUID() : this.generateId(instance.type);
1545
1718
  }
1546
- const files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1547
- let filesModified = 0;
1548
- for (const filePath of files) {
1549
- try {
1550
- const content = await this.fileSystem.readFile(filePath);
1551
- let composition = null;
1552
- let isComponentPattern = false;
1553
- if (dirType === "componentPattern" && content && typeof content === "object" && "definition" in content) {
1554
- isComponentPattern = true;
1555
- const pattern = content;
1556
- if (pattern.definition && pattern.definition.slots) {
1557
- composition = {
1558
- composition: {
1559
- _id: "pattern-root",
1560
- type: pattern.definition.type,
1561
- slots: pattern.definition.slots
1562
- }
1563
- };
1564
- }
1565
- } else {
1566
- composition = content;
1567
- }
1568
- if (!composition?.composition) {
1569
- continue;
1570
- }
1571
- const rootInstance = composition.composition;
1572
- let modified = false;
1573
- if (this.matchesAnyParentType(rootInstance.type, parentTypes, strict)) {
1574
- if (!rootInstance.slots) {
1575
- rootInstance.slots = {};
1576
- }
1577
- const instanceCopy = JSON.parse(JSON.stringify(newInstance));
1578
- this.regenerateInstanceIds(instanceCopy);
1579
- this.addComponentToSlot(rootInstance.slots, slot, instanceCopy);
1580
- modified = true;
1581
- }
1582
- if (rootInstance.slots) {
1583
- const nestedModified = this.addComponentToNestedSlots(
1584
- rootInstance.slots,
1585
- parentTypes,
1586
- slot,
1587
- newInstance,
1588
- strict
1589
- );
1590
- if (nestedModified) {
1591
- modified = true;
1592
- }
1593
- }
1594
- if (modified) {
1595
- this.logger.action(
1596
- whatIf,
1597
- "UPDATE",
1598
- `Adding "${newInstance.type}" to slot "${slot}" in ${dirType}/${this.fileSystem.getBasename(filePath)}`
1599
- );
1600
- if (!whatIf) {
1601
- if (isComponentPattern && content && typeof content === "object" && "definition" in content) {
1602
- const pattern = content;
1603
- if (rootInstance.slots) {
1604
- pattern.definition.slots = rootInstance.slots;
1605
- }
1606
- }
1607
- await this.fileSystem.writeFile(filePath, content);
1719
+ if (instance.slots) {
1720
+ for (const slotInstances of Object.values(instance.slots)) {
1721
+ if (Array.isArray(slotInstances)) {
1722
+ for (const nestedInstance of slotInstances) {
1723
+ this.regenerateInstanceIds(nestedInstance);
1608
1724
  }
1609
- filesModified++;
1610
- }
1611
- } catch (error) {
1612
- if (error instanceof Error && !error.message.includes("skipping")) {
1613
- this.logger.detail(`Skipping ${filePath}: ${error.message}`);
1614
1725
  }
1615
1726
  }
1616
1727
  }
1617
- return filesModified;
1618
1728
  }
1619
- addComponentToNestedSlots(slots, parentTypes, slot, newInstance, strict) {
1620
- let modified = false;
1621
- for (const slotInstances of Object.values(slots)) {
1622
- if (!Array.isArray(slotInstances)) continue;
1623
- for (const instance of slotInstances) {
1624
- if (this.matchesAnyParentType(instance.type, parentTypes, strict)) {
1625
- if (!instance.slots) {
1626
- instance.slots = {};
1627
- }
1628
- const instanceCopy = JSON.parse(JSON.stringify(newInstance));
1629
- this.regenerateInstanceIds(instanceCopy);
1630
- this.addComponentToSlot(instance.slots, slot, instanceCopy);
1631
- modified = true;
1632
- }
1633
- if (instance.slots) {
1634
- const nestedModified = this.addComponentToNestedSlots(
1635
- instance.slots,
1636
- parentTypes,
1637
- slot,
1638
- newInstance,
1639
- strict
1640
- );
1641
- if (nestedModified) {
1642
- modified = true;
1643
- }
1644
- }
1729
+ createComponentInstance(componentType, parameters) {
1730
+ const instance = {
1731
+ type: componentType,
1732
+ _id: this.generateId(componentType)
1733
+ };
1734
+ if (Object.keys(parameters).length > 0) {
1735
+ instance.parameters = {};
1736
+ for (const [key, value] of Object.entries(parameters)) {
1737
+ instance.parameters[key] = {
1738
+ type: "text",
1739
+ value
1740
+ };
1645
1741
  }
1646
1742
  }
1647
- return modified;
1743
+ return instance;
1648
1744
  }
1649
- };
1650
-
1651
- // src/core/services/composition-converter.service.ts
1652
- import * as crypto from "crypto";
1653
- var CompositionConverterService = class {
1654
- constructor(fileSystem, componentService, compositionService, logger) {
1655
- this.fileSystem = fileSystem;
1656
- this.componentService = componentService;
1657
- this.compositionService = compositionService;
1658
- this.logger = logger;
1745
+ addComponentToSlot(slots, slotId, instance) {
1746
+ if (!slots[slotId]) {
1747
+ slots[slotId] = [];
1748
+ }
1749
+ slots[slotId].push(instance);
1659
1750
  }
1660
- async convert(options) {
1751
+ async addComponent(options) {
1661
1752
  const {
1662
1753
  rootDir,
1663
- compositionsDir,
1664
1754
  componentsDir,
1665
- contentTypesDir,
1666
- entriesDir,
1667
- compositionTypes,
1668
- flattenComponentIds,
1755
+ compositionsDir,
1756
+ compositionPatternsDir,
1757
+ componentPatternsDir,
1758
+ parentComponentType,
1759
+ slot,
1760
+ newComponentType,
1761
+ parameters: paramString,
1669
1762
  whatIf,
1670
1763
  strict
1671
1764
  } = options;
1672
- const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
1673
- const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
1674
- const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
1675
- const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
1676
- let contentTypesWritten = 0;
1677
- let entriesFromCompositions = 0;
1678
- let entriesFromFlattened = 0;
1679
- let entriesReused = 0;
1680
- this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
1681
- if (flattenComponentIds.length > 0) {
1682
- this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
1683
- }
1684
- const sourceItemMap = flattenComponentIds.length > 0 ? await this.buildSourceItemMap(entriesDirFull) : /* @__PURE__ */ new Map();
1685
- if (sourceItemMap.size > 0) {
1686
- this.logger.info(`Found ${sourceItemMap.size} existing entry(ies) with sourceItem values`);
1687
- }
1688
- const compositionResults = await this.compositionService.findCompositionsByTypes(
1689
- compositionsDirFull,
1690
- compositionTypes,
1691
- { strict }
1692
- );
1693
- if (compositionResults.length === 0) {
1694
- this.logger.warn("No compositions found matching the specified types");
1695
- return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0, entriesReused: 0 };
1696
- }
1697
- this.logger.info(`Found ${compositionResults.length} composition(s)`);
1698
- const rootComponentTypes = /* @__PURE__ */ new Set();
1699
- for (const { composition } of compositionResults) {
1700
- rootComponentTypes.add(composition.composition.type);
1765
+ if (!newComponentType) {
1766
+ throw new TransformError("newComponentType is required for add-component");
1701
1767
  }
1702
- const contentTypeMap = /* @__PURE__ */ new Map();
1703
- for (const rootType of rootComponentTypes) {
1704
- const { component } = await this.componentService.loadComponent(
1705
- componentsDirFull,
1706
- rootType,
1707
- { strict }
1708
- );
1709
- const contentType = this.generateContentType(component);
1710
- contentTypeMap.set(rootType, contentType);
1768
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1769
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1770
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1771
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1772
+ const findOptions = { strict };
1773
+ const parentTypes = this.parseParentComponentTypes(parentComponentType);
1774
+ if (parentTypes.length === 0) {
1775
+ throw new TransformError("parentComponentType cannot be empty");
1711
1776
  }
1712
- const flattenContentTypeMap = /* @__PURE__ */ new Map();
1713
- const missingFlattenTypes = [];
1714
- const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
1715
- for (const flattenType of flattenComponentIds) {
1716
- const isRootType = [...rootComponentTypes].some(
1717
- (rt) => this.compareTypes(rt, flattenType, strict)
1718
- );
1719
- if (isRootType) {
1720
- continue;
1777
+ this.logger.info(`Validating new component: ${newComponentType}`);
1778
+ try {
1779
+ await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
1780
+ } catch (error) {
1781
+ if (error instanceof ComponentNotFoundError) {
1782
+ throw new TransformError(
1783
+ `Component type "${newComponentType}" not found in ${fullComponentsDir}`
1784
+ );
1721
1785
  }
1786
+ throw error;
1787
+ }
1788
+ let allowedComponentsUpdated = false;
1789
+ const resolvedParentTypes = [];
1790
+ for (const parentType of parentTypes) {
1791
+ this.logger.info(`Loading component: ${parentType}`);
1792
+ let parentComponent;
1793
+ let parentComponentFilePath;
1722
1794
  try {
1723
- const { component } = await this.componentService.loadComponent(
1724
- componentsDirFull,
1725
- flattenType,
1726
- { strict }
1727
- );
1728
- const contentType = this.generateContentType(component);
1729
- flattenContentTypeMap.set(flattenType, contentType);
1795
+ const result = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1796
+ parentComponent = result.component;
1797
+ parentComponentFilePath = result.filePath;
1730
1798
  } catch (error) {
1731
1799
  if (error instanceof ComponentNotFoundError) {
1732
- this.logger.info(`Flatten component type not found: ${flattenType}`);
1733
- missingFlattenTypes.push(flattenType);
1800
+ this.logger.warn(`Component not found: ${parentType} (searched: ${fullComponentsDir})`);
1734
1801
  continue;
1735
1802
  }
1736
1803
  throw error;
1737
1804
  }
1738
- }
1739
- for (const { composition } of compositionResults) {
1740
- const comp = composition.composition;
1741
- const compositionId = comp._id;
1742
- const compositionName = comp._name ?? compositionId;
1743
- const compositionType = comp.type;
1744
- const entry = this.generateEntryFromComposition(composition);
1745
- const flattenedByType = /* @__PURE__ */ new Map();
1746
- if (flattenComponentIds.length > 0 && comp.slots) {
1747
- for (const flattenType of flattenComponentIds) {
1748
- if (this.compareTypes(flattenType, compositionType, strict)) {
1749
- this.logger.warn(
1750
- `Skipping flatten of "${flattenType}" \u2014 same as root component type`
1751
- );
1752
- continue;
1753
- }
1754
- const instances = this.findFlattenTargets(
1755
- comp.slots,
1756
- flattenType,
1757
- compositionId,
1758
- compositionName,
1759
- strict
1805
+ resolvedParentTypes.push(parentType);
1806
+ if (!parentComponent.slots) {
1807
+ parentComponent.slots = [];
1808
+ }
1809
+ let slotDef = parentComponent.slots.find(
1810
+ (s) => this.compareIds(s.id, slot, strict)
1811
+ );
1812
+ if (!slotDef) {
1813
+ this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1814
+ slotDef = { id: slot, name: slot, allowedComponents: [] };
1815
+ parentComponent.slots.push(slotDef);
1816
+ }
1817
+ const slotIndex = parentComponent.slots?.findIndex(
1818
+ (s) => this.compareIds(s.id, slotDef.id, strict)
1819
+ );
1820
+ if (slotIndex !== void 0 && slotIndex >= 0) {
1821
+ const actualSlot = parentComponent.slots[slotIndex];
1822
+ if (!actualSlot.allowedComponents) {
1823
+ actualSlot.allowedComponents = [];
1824
+ }
1825
+ const isAlreadyAllowed = actualSlot.allowedComponents.some(
1826
+ (c) => this.compareIds(c, newComponentType, strict)
1827
+ );
1828
+ if (!isAlreadyAllowed) {
1829
+ this.logger.action(
1830
+ whatIf,
1831
+ "UPDATE",
1832
+ `Adding "${newComponentType}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1760
1833
  );
1761
- if (instances.length > 0) {
1762
- flattenedByType.set(flattenType, instances);
1763
- if (missingFlattenTypes.includes(flattenType)) {
1764
- foundMissingFlattenTypes.add(flattenType);
1765
- }
1834
+ actualSlot.allowedComponents.push(newComponentType);
1835
+ if (!whatIf) {
1836
+ await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1766
1837
  }
1838
+ allowedComponentsUpdated = true;
1767
1839
  }
1768
1840
  }
1769
- const resolvedRefIds = /* @__PURE__ */ new Map();
1770
- for (const [flattenType, instances] of flattenedByType) {
1771
- const refIds = [];
1772
- for (const inst of instances) {
1773
- const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
1774
- refIds.push(existingId ?? inst.determinisiticId);
1841
+ }
1842
+ const parsedParams = this.parseParameters(paramString);
1843
+ const newInstance = this.createComponentInstance(newComponentType, parsedParams);
1844
+ const compositionsResult = await this.addComponentToDirectory(
1845
+ fullCompositionsDir,
1846
+ parentTypes,
1847
+ slot,
1848
+ newInstance,
1849
+ whatIf,
1850
+ strict,
1851
+ "composition"
1852
+ );
1853
+ const compositionPatternsResult = await this.addComponentToDirectory(
1854
+ fullCompositionPatternsDir,
1855
+ parentTypes,
1856
+ slot,
1857
+ newInstance,
1858
+ whatIf,
1859
+ strict,
1860
+ "compositionPattern"
1861
+ );
1862
+ const componentPatternsResult = await this.addComponentToDirectory(
1863
+ fullComponentPatternsDir,
1864
+ parentTypes,
1865
+ slot,
1866
+ newInstance,
1867
+ whatIf,
1868
+ strict,
1869
+ "componentPattern"
1870
+ );
1871
+ this.logger.info("");
1872
+ this.logger.info(
1873
+ `Summary: ${allowedComponentsUpdated ? `${resolvedParentTypes.length} component definition(s) updated, ` : ""}${compositionsResult} composition(s), ${compositionPatternsResult} composition pattern(s), ${componentPatternsResult} component pattern(s) updated. ${compositionsResult + compositionPatternsResult + componentPatternsResult} instance(s) added.`
1874
+ );
1875
+ return {
1876
+ allowedComponentsUpdated,
1877
+ compositionsModified: compositionsResult,
1878
+ compositionPatternsModified: compositionPatternsResult,
1879
+ componentPatternsModified: componentPatternsResult,
1880
+ instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1881
+ };
1882
+ }
1883
+ async addComponentPattern(options) {
1884
+ const {
1885
+ rootDir,
1886
+ componentsDir,
1887
+ compositionsDir,
1888
+ compositionPatternsDir,
1889
+ componentPatternsDir,
1890
+ parentComponentType,
1891
+ slot,
1892
+ componentPatternId,
1893
+ whatIf,
1894
+ strict
1895
+ } = options;
1896
+ if (!componentPatternId) {
1897
+ throw new TransformError("componentPatternId is required for add-component-pattern");
1898
+ }
1899
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1900
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1901
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1902
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1903
+ const findOptions = { strict };
1904
+ const parentTypes = this.parseParentComponentTypes(parentComponentType);
1905
+ if (parentTypes.length === 0) {
1906
+ throw new TransformError("parentComponentType cannot be empty");
1907
+ }
1908
+ this.logger.info(`Loading component pattern: ${componentPatternId}`);
1909
+ const pattern = await this.loadComponentPattern(
1910
+ fullComponentPatternsDir,
1911
+ componentPatternId,
1912
+ strict
1913
+ );
1914
+ const patternDefinition = pattern.definition;
1915
+ if (!patternDefinition || !patternDefinition.type) {
1916
+ throw new TransformError(
1917
+ `Component pattern "${componentPatternId}" has no valid definition or missing type`
1918
+ );
1919
+ }
1920
+ const componentTypeInPattern = patternDefinition.type;
1921
+ let allowedComponentsUpdated = false;
1922
+ const resolvedParentTypes = [];
1923
+ for (const parentType of parentTypes) {
1924
+ this.logger.info(`Loading component: ${parentType}`);
1925
+ let parentComponent;
1926
+ let parentComponentFilePath;
1927
+ try {
1928
+ const result = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1929
+ parentComponent = result.component;
1930
+ parentComponentFilePath = result.filePath;
1931
+ } catch (error) {
1932
+ if (error instanceof ComponentNotFoundError) {
1933
+ this.logger.warn(`Component not found: ${parentType} (searched: ${fullComponentsDir})`);
1934
+ continue;
1775
1935
  }
1776
- resolvedRefIds.set(flattenType, refIds);
1777
- }
1778
- for (const [flattenType] of flattenedByType) {
1779
- entry.entry.fields[flattenType] = {
1780
- type: "contentReference",
1781
- value: resolvedRefIds.get(flattenType)
1782
- };
1936
+ throw error;
1783
1937
  }
1784
- if (flattenComponentIds.length > 0) {
1785
- this.transformContentReferences(entry);
1938
+ resolvedParentTypes.push(parentType);
1939
+ if (!parentComponent.slots) {
1940
+ parentComponent.slots = [];
1786
1941
  }
1787
- const entryId = entry.entry._id;
1788
- const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${entryId}.json`);
1789
- this.logger.action(
1790
- whatIf,
1791
- "WRITE",
1792
- `${entriesDir}/${entryId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
1942
+ let slotDef = parentComponent.slots.find(
1943
+ (s) => this.compareIds(s.id, slot, strict)
1793
1944
  );
1794
- if (!whatIf) {
1795
- await this.fileSystem.writeFile(entryFilePath, entry);
1945
+ if (!slotDef) {
1946
+ this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1947
+ slotDef = { id: slot, name: slot, allowedComponents: [] };
1948
+ parentComponent.slots.push(slotDef);
1796
1949
  }
1797
- entriesFromCompositions++;
1798
- for (const [flattenType, instances] of flattenedByType) {
1799
- for (const inst of instances) {
1800
- const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
1801
- if (existingId) {
1802
- this.logger.info(
1803
- `Reusing existing entry ${existingId} for ${flattenType} (sourceItem match)`
1804
- );
1805
- entriesReused++;
1806
- continue;
1807
- }
1808
- const flatEntry = this.generateEntryFromFlattenedInstance(inst);
1809
- const flatEntryPath = this.fileSystem.joinPath(
1810
- entriesDirFull,
1811
- `${inst.determinisiticId}.json`
1812
- );
1950
+ const slotIndex = parentComponent.slots?.findIndex(
1951
+ (s) => this.compareIds(s.id, slotDef.id, strict)
1952
+ );
1953
+ if (slotIndex !== void 0 && slotIndex >= 0) {
1954
+ const actualSlot = parentComponent.slots[slotIndex];
1955
+ if (!actualSlot.allowedComponents) {
1956
+ actualSlot.allowedComponents = [];
1957
+ }
1958
+ const isAlreadyAllowed = actualSlot.allowedComponents.some(
1959
+ (c) => this.compareIds(c, componentTypeInPattern, strict)
1960
+ );
1961
+ if (!isAlreadyAllowed) {
1813
1962
  this.logger.action(
1814
1963
  whatIf,
1815
- "WRITE",
1816
- `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
1964
+ "UPDATE",
1965
+ `Adding "${componentTypeInPattern}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1817
1966
  );
1967
+ actualSlot.allowedComponents.push(componentTypeInPattern);
1818
1968
  if (!whatIf) {
1819
- await this.fileSystem.writeFile(flatEntryPath, flatEntry);
1820
- }
1821
- entriesFromFlattened++;
1822
- }
1823
- }
1824
- }
1825
- if (flattenComponentIds.length > 0) {
1826
- for (const contentType of contentTypeMap.values()) {
1827
- for (const flattenType of flattenComponentIds) {
1828
- if (this.compareTypes(flattenType, contentType.id, strict)) {
1829
- continue;
1830
- }
1831
- if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
1832
- continue;
1969
+ await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1833
1970
  }
1834
- contentType.fields.push({
1835
- id: flattenType,
1836
- name: flattenType,
1837
- type: "contentReference",
1838
- typeConfig: {
1839
- isMulti: true,
1840
- allowedContentTypes: [flattenType]
1841
- },
1842
- localizable: false
1843
- });
1971
+ allowedComponentsUpdated = true;
1844
1972
  }
1845
1973
  }
1846
1974
  }
1847
- for (const [typeName, contentType] of contentTypeMap) {
1848
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1849
- const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
1850
- const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
1851
- const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
1852
- this.logger.action(
1853
- whatIf,
1854
- "WRITE",
1855
- `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
1856
- );
1857
- if (!whatIf) {
1858
- await this.fileSystem.writeFile(filePath, contentType);
1859
- }
1860
- contentTypesWritten++;
1861
- }
1862
- for (const [typeName, contentType] of flattenContentTypeMap) {
1863
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1864
- this.logger.action(
1865
- whatIf,
1866
- "WRITE",
1867
- `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
1868
- );
1869
- if (!whatIf) {
1870
- await this.fileSystem.writeFile(filePath, contentType);
1871
- }
1872
- contentTypesWritten++;
1873
- }
1874
- const neverFoundMissingTypes = missingFlattenTypes.filter(
1875
- (type) => !foundMissingFlattenTypes.has(type)
1975
+ const newInstance = {
1976
+ type: componentTypeInPattern,
1977
+ _pattern: pattern.componentPatternId,
1978
+ _id: randomUUID()
1979
+ };
1980
+ const compositionsResult = await this.addComponentToDirectory(
1981
+ fullCompositionsDir,
1982
+ parentTypes,
1983
+ slot,
1984
+ newInstance,
1985
+ whatIf,
1986
+ strict,
1987
+ "composition"
1988
+ );
1989
+ const compositionPatternsResult = await this.addComponentToDirectory(
1990
+ fullCompositionPatternsDir,
1991
+ parentTypes,
1992
+ slot,
1993
+ newInstance,
1994
+ whatIf,
1995
+ strict,
1996
+ "compositionPattern"
1997
+ );
1998
+ const componentPatternsResult = await this.addComponentToDirectory(
1999
+ fullComponentPatternsDir,
2000
+ parentTypes,
2001
+ slot,
2002
+ newInstance,
2003
+ whatIf,
2004
+ strict,
2005
+ "componentPattern"
2006
+ );
2007
+ this.logger.info("");
2008
+ this.logger.info(
2009
+ `Summary: ${allowedComponentsUpdated ? `${resolvedParentTypes.length} component definition(s) updated, ` : ""}${compositionsResult} composition(s), ${compositionPatternsResult} composition pattern(s), ${componentPatternsResult} component pattern(s) updated. ${compositionsResult + compositionPatternsResult + componentPatternsResult} instance(s) added.`
1876
2010
  );
1877
- if (neverFoundMissingTypes.length > 0) {
1878
- this.logger.warn(
1879
- `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
1880
- );
1881
- }
1882
- return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened, entriesReused };
1883
- }
1884
- // --- Content Type Generation ---
1885
- generateContentType(component) {
1886
- const fields = [];
1887
- if (component.parameters) {
1888
- for (const param of component.parameters) {
1889
- fields.push(this.parameterToField(param));
1890
- }
1891
- }
1892
2011
  return {
1893
- id: component.id,
1894
- name: component.name,
1895
- fields
1896
- };
1897
- }
1898
- parameterToField(param) {
1899
- const field = {
1900
- id: param.id,
1901
- name: param.name,
1902
- type: param.type
2012
+ allowedComponentsUpdated,
2013
+ compositionsModified: compositionsResult,
2014
+ compositionPatternsModified: compositionPatternsResult,
2015
+ componentPatternsModified: componentPatternsResult,
2016
+ instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1903
2017
  };
1904
- if (param.helpText !== void 0) {
1905
- field.helpText = param.helpText;
1906
- }
1907
- if (param.localizable !== void 0) {
1908
- field.localizable = param.localizable;
1909
- }
1910
- if (param.typeConfig !== void 0) {
1911
- field.typeConfig = param.typeConfig;
1912
- }
1913
- return field;
1914
2018
  }
1915
- // --- Entry Generation ---
1916
- generateEntryFromComposition(composition) {
1917
- const comp = composition.composition;
1918
- const compositionSpecificKeys = /* @__PURE__ */ new Set(["_id", "_name", "type", "parameters", "slots", "_overrides"]);
1919
- const extraRootProps = {};
1920
- for (const [key, value] of Object.entries(comp)) {
1921
- if (!compositionSpecificKeys.has(key) && value != null) {
1922
- extraRootProps[key] = value;
1923
- }
1924
- }
1925
- const wrapperKeys = /* @__PURE__ */ new Set(["composition"]);
1926
- const extraWrapperProps = {};
1927
- for (const [key, value] of Object.entries(composition)) {
1928
- if (!wrapperKeys.has(key) && value != null) {
1929
- extraWrapperProps[key] = value;
1930
- }
2019
+ async loadComponentPattern(componentPatternsDir, patternId, strict) {
2020
+ const jsonPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.json`);
2021
+ const yamlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yaml`);
2022
+ const ymlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yml`);
2023
+ let raw;
2024
+ if (await this.fileSystem.fileExists(jsonPath)) {
2025
+ raw = await this.fileSystem.readFile(jsonPath);
2026
+ } else if (await this.fileSystem.fileExists(yamlPath)) {
2027
+ raw = await this.fileSystem.readFile(yamlPath);
2028
+ } else if (await this.fileSystem.fileExists(ymlPath)) {
2029
+ raw = await this.fileSystem.readFile(ymlPath);
1931
2030
  }
1932
- const entryId = computeGuidHash(`entry${comp._id}`);
1933
- const entryName = this.truncateName(comp._name ?? comp._id, 60);
1934
- return {
1935
- entry: {
1936
- _id: entryId,
1937
- _name: entryName,
1938
- type: comp.type,
1939
- fields: { ...comp.parameters ?? {} },
1940
- ...extraRootProps
1941
- },
1942
- ...extraWrapperProps
1943
- };
1944
- }
1945
- generateEntryFromFlattenedInstance(inst) {
1946
- const entryName = this.truncateName(
1947
- `${inst.componentType} (from ${inst.compositionName})`,
1948
- 60
1949
- );
1950
- return {
1951
- entry: {
1952
- _id: inst.determinisiticId,
1953
- _name: entryName,
1954
- type: inst.componentType,
1955
- fields: { ...inst.instance.parameters ?? {} }
2031
+ if (!raw && !strict) {
2032
+ const files = await this.fileSystem.findFiles(componentPatternsDir, "*.{json,yaml,yml}");
2033
+ for (const filePath of files) {
2034
+ const basename2 = this.fileSystem.getBasename(filePath);
2035
+ const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
2036
+ if (nameWithoutExt.toLowerCase() === patternId.toLowerCase()) {
2037
+ raw = await this.fileSystem.readFile(filePath);
2038
+ break;
2039
+ }
1956
2040
  }
1957
- };
2041
+ }
2042
+ if (!raw) {
2043
+ throw new TransformError(`Component pattern "${patternId}" not found in ${componentPatternsDir}`);
2044
+ }
2045
+ return this.normalizeComponentPattern(raw, patternId);
1958
2046
  }
1959
- // --- Flatten Tree Walking ---
1960
- findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
1961
- const results = [];
1962
- this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
1963
- return results;
2047
+ normalizeComponentPattern(raw, patternId) {
2048
+ if (raw.definition && typeof raw.definition === "object" && raw.definition.type) {
2049
+ return raw;
2050
+ }
2051
+ if (raw.composition && typeof raw.composition === "object" && raw.composition.type) {
2052
+ const composition = raw.composition;
2053
+ return {
2054
+ componentPatternId: patternId,
2055
+ definition: composition
2056
+ };
2057
+ }
2058
+ return raw;
1964
2059
  }
1965
- walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
1966
- for (const [slotName, instances] of Object.entries(slots)) {
1967
- if (!Array.isArray(instances)) continue;
1968
- for (let i = 0; i < instances.length; i++) {
1969
- const instance = instances[i];
1970
- if (instance._pattern) {
2060
+ async addComponentToDirectory(directory, parentTypes, slot, newInstance, whatIf, strict, dirType) {
2061
+ const exists = await this.fileSystem.fileExists(directory);
2062
+ if (!exists) {
2063
+ this.logger.detail(`${dirType} directory does not exist, skipping`);
2064
+ return 0;
2065
+ }
2066
+ const files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
2067
+ let filesModified = 0;
2068
+ for (const filePath of files) {
2069
+ try {
2070
+ const content = await this.fileSystem.readFile(filePath);
2071
+ let composition = null;
2072
+ let isComponentPattern = false;
2073
+ if (dirType === "componentPattern" && content && typeof content === "object" && "definition" in content) {
2074
+ isComponentPattern = true;
2075
+ const pattern = content;
2076
+ if (pattern.definition && pattern.definition.slots) {
2077
+ composition = {
2078
+ composition: {
2079
+ _id: "pattern-root",
2080
+ type: pattern.definition.type,
2081
+ slots: pattern.definition.slots
2082
+ }
2083
+ };
2084
+ }
2085
+ } else {
2086
+ composition = content;
2087
+ }
2088
+ if (!composition?.composition) {
1971
2089
  continue;
1972
2090
  }
1973
- const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
1974
- if (this.compareTypes(instance.type, targetType, strict)) {
1975
- const fullPath = `${compositionId}-${currentPath}`;
1976
- const deterministicId = computeGuidHash(fullPath);
1977
- results.push({
1978
- instance,
1979
- path: fullPath,
1980
- determinisiticId: deterministicId,
1981
- componentType: instance.type,
1982
- compositionId,
1983
- compositionName
1984
- });
2091
+ const rootInstance = composition.composition;
2092
+ let modified = false;
2093
+ if (this.matchesAnyParentType(rootInstance.type, parentTypes, strict)) {
2094
+ if (!rootInstance.slots) {
2095
+ rootInstance.slots = {};
2096
+ }
2097
+ const instanceCopy = JSON.parse(JSON.stringify(newInstance));
2098
+ this.regenerateInstanceIds(instanceCopy);
2099
+ this.addComponentToSlot(rootInstance.slots, slot, instanceCopy);
2100
+ modified = true;
1985
2101
  }
1986
- if (instance.slots) {
1987
- this.walkSlots(
1988
- instance.slots,
1989
- targetType,
1990
- compositionId,
1991
- compositionName,
1992
- currentPath,
1993
- results,
2102
+ if (rootInstance.slots) {
2103
+ const nestedModified = this.addComponentToNestedSlots(
2104
+ rootInstance.slots,
2105
+ parentTypes,
2106
+ slot,
2107
+ newInstance,
1994
2108
  strict
1995
2109
  );
2110
+ if (nestedModified) {
2111
+ modified = true;
2112
+ }
1996
2113
  }
1997
- }
1998
- }
1999
- }
2000
- // --- Content Reference Transformation ---
2001
- transformContentReferences(entry) {
2002
- const dataResources = {};
2003
- for (const [fieldName, field] of Object.entries(entry.entry.fields)) {
2004
- if (field.type === "contentReference" && Array.isArray(field.value)) {
2005
- const entryIds = field.value;
2006
- const resourceKey = `ref-${entry.entry._id}-${fieldName}`;
2007
- field.value = `\${#jptr:/${resourceKey}/entries}`;
2008
- dataResources[resourceKey] = {
2009
- type: "uniformContentInternalReference",
2010
- variables: {
2011
- locale: "${locale}",
2012
- entryIds: entryIds.join(",")
2114
+ if (modified) {
2115
+ this.logger.action(
2116
+ whatIf,
2117
+ "UPDATE",
2118
+ `Adding "${newInstance.type}" to slot "${slot}" in ${dirType}/${this.fileSystem.getBasename(filePath)}`
2119
+ );
2120
+ if (!whatIf) {
2121
+ if (isComponentPattern && content && typeof content === "object" && "definition" in content) {
2122
+ const pattern = content;
2123
+ if (rootInstance.slots) {
2124
+ pattern.definition.slots = rootInstance.slots;
2125
+ }
2126
+ }
2127
+ await this.fileSystem.writeFile(filePath, content);
2013
2128
  }
2014
- };
2129
+ filesModified++;
2130
+ }
2131
+ } catch (error) {
2132
+ if (error instanceof Error && !error.message.includes("skipping")) {
2133
+ this.logger.detail(`Skipping ${filePath}: ${error.message}`);
2134
+ }
2015
2135
  }
2016
2136
  }
2017
- if (Object.keys(dataResources).length > 0) {
2018
- entry.entry._dataResources = {
2019
- ...entry.entry._dataResources ?? {},
2020
- ...dataResources
2021
- };
2022
- }
2137
+ return filesModified;
2023
2138
  }
2024
- // --- Source Item Matching ---
2025
- async buildSourceItemMap(entriesDirFull) {
2026
- const sourceItemMap = /* @__PURE__ */ new Map();
2027
- const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "*.json");
2028
- for (const filePath of entryFiles) {
2029
- try {
2030
- const entryData = await this.fileSystem.readFile(filePath);
2031
- const sourceItemField = entryData?.entry?.fields?.sourceItem;
2032
- if (sourceItemField?.value != null) {
2033
- sourceItemMap.set(String(sourceItemField.value), entryData.entry._id);
2139
+ addComponentToNestedSlots(slots, parentTypes, slot, newInstance, strict) {
2140
+ let modified = false;
2141
+ for (const slotInstances of Object.values(slots)) {
2142
+ if (!Array.isArray(slotInstances)) continue;
2143
+ for (const instance of slotInstances) {
2144
+ if (this.matchesAnyParentType(instance.type, parentTypes, strict)) {
2145
+ if (!instance.slots) {
2146
+ instance.slots = {};
2147
+ }
2148
+ const instanceCopy = JSON.parse(JSON.stringify(newInstance));
2149
+ this.regenerateInstanceIds(instanceCopy);
2150
+ this.addComponentToSlot(instance.slots, slot, instanceCopy);
2151
+ modified = true;
2152
+ }
2153
+ if (instance.slots) {
2154
+ const nestedModified = this.addComponentToNestedSlots(
2155
+ instance.slots,
2156
+ parentTypes,
2157
+ slot,
2158
+ newInstance,
2159
+ strict
2160
+ );
2161
+ if (nestedModified) {
2162
+ modified = true;
2163
+ }
2034
2164
  }
2035
- } catch {
2036
- continue;
2037
2165
  }
2038
2166
  }
2039
- return sourceItemMap;
2040
- }
2041
- findExistingEntryBySourceItem(inst, sourceItemMap) {
2042
- const sourceItemParam = inst.instance.parameters?.sourceItem;
2043
- if (sourceItemParam?.value == null) {
2044
- return void 0;
2045
- }
2046
- return sourceItemMap.get(String(sourceItemParam.value));
2047
- }
2048
- // --- Utilities ---
2049
- compareTypes(type1, type2, strict) {
2050
- if (strict) {
2051
- return type1 === type2;
2052
- }
2053
- return type1.toLowerCase() === type2.toLowerCase();
2054
- }
2055
- truncate(str, maxLength) {
2056
- if (str.length <= maxLength) return str;
2057
- return str.substring(0, maxLength - 3) + "...";
2058
- }
2059
- truncateName(name, maxLength) {
2060
- if (name.length <= maxLength) return name;
2061
- return name.substring(0, maxLength);
2167
+ return modified;
2062
2168
  }
2063
2169
  };
2064
- function computeGuidHash(guidOrSeed) {
2065
- let uuidStr;
2066
- if (isValidUuid(guidOrSeed)) {
2067
- uuidStr = formatAsBracedUuid(guidOrSeed);
2068
- } else {
2069
- const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
2070
- const hex = [
2071
- // First 4 bytes: little-endian in .NET Guid
2072
- hash[3].toString(16).padStart(2, "0"),
2073
- hash[2].toString(16).padStart(2, "0"),
2074
- hash[1].toString(16).padStart(2, "0"),
2075
- hash[0].toString(16).padStart(2, "0"),
2076
- "-",
2077
- // Bytes 4-5: little-endian
2078
- hash[5].toString(16).padStart(2, "0"),
2079
- hash[4].toString(16).padStart(2, "0"),
2080
- "-",
2081
- // Bytes 6-7: little-endian
2082
- hash[7].toString(16).padStart(2, "0"),
2083
- hash[6].toString(16).padStart(2, "0"),
2084
- "-",
2085
- // Bytes 8-9: big-endian
2086
- hash[8].toString(16).padStart(2, "0"),
2087
- hash[9].toString(16).padStart(2, "0"),
2088
- "-",
2089
- // Bytes 10-15: big-endian
2090
- hash[10].toString(16).padStart(2, "0"),
2091
- hash[11].toString(16).padStart(2, "0"),
2092
- hash[12].toString(16).padStart(2, "0"),
2093
- hash[13].toString(16).padStart(2, "0"),
2094
- hash[14].toString(16).padStart(2, "0"),
2095
- hash[15].toString(16).padStart(2, "0")
2096
- ].join("");
2097
- uuidStr = `{${hex}}`.toUpperCase();
2098
- }
2099
- const chars = uuidStr.split("");
2100
- chars[15] = "4";
2101
- const arr20 = ["8", "9", "A", "B"];
2102
- const hexVal = parseInt(chars[20], 16);
2103
- chars[20] = arr20[hexVal % 4];
2104
- return chars.join("").slice(1, -1).toLowerCase();
2105
- }
2106
- function isValidUuid(str) {
2107
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
2108
- }
2109
- function formatAsBracedUuid(uuid) {
2110
- const clean = uuid.replace(/[{}]/g, "").toUpperCase();
2111
- return `{${clean}}`;
2112
- }
2113
2170
 
2114
2171
  // src/core/services/parameter-remover.service.ts
2115
2172
  var ParameterRemoverService = class {
@@ -2583,6 +2640,7 @@ export {
2583
2640
  SlotNotFoundError,
2584
2641
  SlotRenamerService,
2585
2642
  TransformError,
2586
- computeGuidHash
2643
+ computeGuidHash,
2644
+ regenerateIds
2587
2645
  };
2588
2646
  //# sourceMappingURL=index.js.map