@uniformdev/transformer 1.1.15 → 1.1.17

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,681 +513,765 @@ 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;
695
+ }
696
+ if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
697
+ continue;
756
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);
882
+ if (Object.keys(dataResources).length > 0) {
883
+ entry.entry._dataResources = {
884
+ ...entry.entry._dataResources ?? {},
885
+ ...dataResources
886
+ };
887
+ }
888
+ }
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);
945
899
  }
900
+ } catch {
901
+ continue;
946
902
  }
947
903
  }
948
- return count;
904
+ return sourceItemMap;
949
905
  }
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 };
906
+ findExistingEntryBySourceItem(inst, sourceItemMap) {
907
+ const sourceItemParam = inst.instance.parameters?.sourceItem;
908
+ if (sourceItemParam?.value == null) {
909
+ return void 0;
956
910
  }
957
- const newSlots = {};
958
- for (const key of Object.keys(slots)) {
959
- if (key === matchingKey) {
960
- newSlots[newSlotId] = slots[key];
911
+ return sourceItemMap.get(String(sourceItemParam.value));
912
+ }
913
+ // --- Utilities ---
914
+ compareTypes(type1, type2, strict) {
915
+ if (strict) {
916
+ return type1 === type2;
917
+ }
918
+ return type1.toLowerCase() === type2.toLowerCase();
919
+ }
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);
961
998
  } else {
962
- newSlots[key] = slots[key];
999
+ result[key] = walkAndRegenerate(val, childPath);
963
1000
  }
964
1001
  }
965
- return { renamed: true, slots: newSlots };
1002
+ return result;
966
1003
  }
967
- };
1004
+ return value;
1005
+ }
968
1006
 
969
- // src/core/services/component-renamer.service.ts
970
- var ComponentRenamerService = class {
971
- constructor(fileSystem, componentService, logger) {
1007
+ // src/core/services/property-propagator.service.ts
1008
+ var PropertyPropagatorService = class {
1009
+ constructor(fileSystem, componentService, compositionService, logger) {
972
1010
  this.fileSystem = fileSystem;
973
1011
  this.componentService = componentService;
1012
+ this.compositionService = compositionService;
974
1013
  this.logger = logger;
975
1014
  }
976
- compareIds(id1, id2, strict) {
977
- if (strict) {
978
- return id1 === id2;
979
- }
980
- return id1.toLowerCase() === id2.toLowerCase();
981
- }
982
- async rename(options) {
1015
+ async propagate(options) {
983
1016
  const {
984
1017
  rootDir,
985
1018
  componentsDir,
986
1019
  compositionsDir,
987
- compositionPatternsDir,
988
- componentPatternsDir,
989
- componentType,
990
- newComponentType,
991
- newComponentName,
1020
+ compositionType,
1021
+ property,
1022
+ targetComponentType,
1023
+ targetGroup,
992
1024
  whatIf,
993
- strict
1025
+ strict,
1026
+ deleteSourceParameter
994
1027
  } = options;
1028
+ const findOptions = { strict };
1029
+ const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
995
1030
  const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
996
1031
  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");
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 });
1002
1037
  }
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;
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
+ }
1012
1061
  }
1013
1062
  }
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)}`
1021
- );
1022
- component.id = newComponentType;
1023
- if (newComponentName !== void 0) {
1024
- component.name = newComponentName;
1025
- }
1026
- if (!whatIf) {
1027
- await this.componentService.saveComponent(componentFilePath, component);
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
+ }
1028
1074
  }
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)}`
1075
+ if (groupSources.length > 0) {
1076
+ this.logger.info(
1077
+ `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
1038
1078
  );
1039
- if (!whatIf) {
1040
- await this.fileSystem.renameFile(componentFilePath, newFilePath);
1041
- }
1042
- fileRenamed = true;
1079
+ } else {
1080
+ this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
1043
1081
  }
1044
- const allowedComponentsUpdated = await this.updateAllowedComponents(
1045
- fullComponentsDir,
1046
- componentType,
1047
- newComponentType,
1048
- componentFilePath,
1049
- whatIf,
1050
- strict
1051
- );
1052
- const compositionsResult = await this.renameTypeInDirectory(
1053
- fullCompositionsDir,
1054
- componentType,
1055
- newComponentType,
1056
- whatIf,
1057
- strict,
1058
- "composition"
1059
- );
1060
- const compositionPatternsResult = await this.renameTypeInDirectory(
1061
- fullCompositionPatternsDir,
1062
- componentType,
1063
- newComponentType,
1064
- whatIf,
1065
- strict,
1066
- "compositionPattern"
1067
- );
1068
- const componentPatternsResult = await this.renameTypeInDirectory(
1069
- fullComponentPatternsDir,
1070
- componentType,
1071
- newComponentType,
1072
- whatIf,
1073
- strict,
1074
- "componentPattern"
1075
- );
1076
- const totalCompositionFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1077
- const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
1078
- this.logger.info("");
1079
- this.logger.info(
1080
- `Summary: 1 component renamed, ${allowedComponentsUpdated} component(s) with allowedComponents updated, ${totalCompositionFiles} composition file(s) (${totalInstances} instance(s)) updated.`
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
1081
1089
  );
1082
- return {
1083
- componentRenamed: true,
1084
- fileRenamed,
1085
- allowedComponentsUpdated,
1086
- compositionsModified: compositionsResult.filesModified,
1087
- compositionPatternsModified: compositionPatternsResult.filesModified,
1088
- componentPatternsModified: componentPatternsResult.filesModified,
1089
- instancesRenamed: totalInstances
1090
- };
1091
- }
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;
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;
1098
1099
  }
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) {
1100
+ for (const param of resolvedParams) {
1101
+ const existingParam = this.componentService.findParameter(
1102
+ modifiedComponent,
1103
+ param.id,
1104
+ findOptions
1105
+ );
1106
+ if (!existingParam) {
1120
1107
  this.logger.action(
1121
1108
  whatIf,
1122
- "UPDATE",
1123
- `allowedComponents in component/${this.fileSystem.getBasename(filePath)}: ${oldType} \u2192 ${newType}`
1109
+ "COPY",
1110
+ `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
1124
1111
  );
1125
- if (!whatIf) {
1126
- await this.fileSystem.writeFile(filePath, comp);
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
+ }
1127
1144
  }
1128
- updatedCount++;
1129
1145
  }
1130
1146
  }
1131
- return updatedCount;
1132
- }
1133
- async renameTypeInDirectory(directory, oldType, newType, whatIf, strict, label) {
1134
- let files;
1135
- try {
1136
- files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1137
- } catch {
1138
- return { filesModified: 0, instancesRenamed: 0 };
1139
- }
1140
- if (files.length === 0) {
1141
- return { filesModified: 0, instancesRenamed: 0 };
1147
+ if (componentModified && !whatIf) {
1148
+ await this.componentService.saveComponent(targetFilePath, modifiedComponent);
1142
1149
  }
1143
- let filesModified = 0;
1144
- let instancesRenamed = 0;
1145
- for (const filePath of files) {
1146
- let composition;
1147
- try {
1148
- composition = await this.fileSystem.readFile(filePath);
1149
- } catch {
1150
- continue;
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;
1151
1166
  }
1152
- if (!composition?.composition) continue;
1153
- const count = this.renameTypeInTree(composition.composition, oldType, newType, strict);
1154
- if (count > 0) {
1155
- const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
1156
- this.logger.action(
1157
- whatIf,
1158
- "UPDATE",
1159
- `${label}/${relativePath} (${count} instance(s))`
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(", ")}`
1160
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
+ }
1161
1194
  if (!whatIf) {
1162
- await this.fileSystem.writeFile(filePath, composition);
1195
+ await this.compositionService.saveComposition(filePath, composition);
1163
1196
  }
1164
- filesModified++;
1165
- instancesRenamed += count;
1197
+ modifiedCompositions++;
1166
1198
  }
1167
1199
  }
1168
- return { filesModified, instancesRenamed };
1169
- }
1170
- renameTypeInTree(node, oldType, newType, strict) {
1171
- let count = 0;
1172
- if (this.compareIds(node.type, oldType, strict)) {
1173
- node.type = newType;
1174
- count++;
1175
- }
1176
- if (node.slots) {
1177
- for (const slotInstances of Object.values(node.slots)) {
1178
- if (!Array.isArray(slotInstances)) continue;
1179
- for (const instance of slotInstances) {
1180
- count += this.renameTypeInTree(instance, oldType, newType, strict);
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
+ }
1181
1249
  }
1182
1250
  }
1183
1251
  }
1184
- return count;
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()
1264
+ );
1265
+ if (!exists) {
1266
+ normalized.push(entry);
1267
+ }
1268
+ }
1269
+ return normalized;
1185
1270
  }
1186
1271
  };
1187
1272
 
1188
- // src/core/services/component-adder.service.ts
1189
- import { randomUUID } from "crypto";
1190
- var ComponentAdderService = class {
1273
+ // src/core/services/slot-renamer.service.ts
1274
+ var SlotRenamerService = class {
1191
1275
  constructor(fileSystem, componentService, logger) {
1192
1276
  this.fileSystem = fileSystem;
1193
1277
  this.componentService = componentService;
@@ -1199,871 +1283,862 @@ var ComponentAdderService = class {
1199
1283
  }
1200
1284
  return id1.toLowerCase() === id2.toLowerCase();
1201
1285
  }
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);
1226
- }
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
- }
1234
- }
1235
- }
1236
- }
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;
1252
- }
1253
- addComponentToSlot(slots, slotId, instance) {
1254
- if (!slots[slotId]) {
1255
- slots[slotId] = [];
1256
- }
1257
- slots[slotId].push(instance);
1258
- }
1259
- async addComponent(options) {
1286
+ async rename(options) {
1260
1287
  const {
1261
1288
  rootDir,
1262
1289
  componentsDir,
1263
1290
  compositionsDir,
1264
1291
  compositionPatternsDir,
1265
1292
  componentPatternsDir,
1266
- parentComponentType,
1267
- slot,
1268
- newComponentType,
1269
- parameters: paramString,
1293
+ componentType,
1294
+ slotId,
1295
+ newSlotId,
1296
+ newSlotName,
1270
1297
  whatIf,
1271
1298
  strict
1272
1299
  } = options;
1273
- if (!newComponentType) {
1274
- throw new TransformError("newComponentType is required for add-component");
1275
- }
1300
+ const findOptions = { strict };
1276
1301
  const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1277
1302
  const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1278
1303
  const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1279
1304
  const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1280
- const findOptions = { strict };
1281
- const parentTypes = this.parseParentComponentTypes(parentComponentType);
1282
- if (parentTypes.length === 0) {
1283
- throw new TransformError("parentComponentType cannot be empty");
1305
+ if (this.compareIds(slotId, newSlotId, strict)) {
1306
+ throw new TransformError("New slot ID is the same as the current slot ID");
1284
1307
  }
1285
- this.logger.info(`Validating new component: ${newComponentType}`);
1286
- try {
1287
- await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
1288
- } catch (error) {
1289
- if (error instanceof ComponentNotFoundError) {
1290
- throw new TransformError(
1291
- `Component type "${newComponentType}" not found in ${fullComponentsDir}`
1292
- );
1293
- }
1294
- throw error;
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);
1295
1312
  }
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)
1313
- );
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
- }
1334
- }
1313
+ const slotIndex = component.slots.findIndex(
1314
+ (s) => this.compareIds(s.id, slotId, strict)
1315
+ );
1316
+ if (slotIndex === -1) {
1317
+ throw new SlotNotFoundError(slotId, componentType);
1335
1318
  }
1336
- const parsedParams = this.parseParameters(paramString);
1337
- const newInstance = this.createComponentInstance(newComponentType, parsedParams);
1338
- const compositionsResult = await this.addComponentToDirectory(
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(
1339
1338
  fullCompositionsDir,
1340
- parentTypes,
1341
- slot,
1342
- newInstance,
1339
+ componentType,
1340
+ slotId,
1341
+ newSlotId,
1343
1342
  whatIf,
1344
1343
  strict,
1345
1344
  "composition"
1346
1345
  );
1347
- const compositionPatternsResult = await this.addComponentToDirectory(
1346
+ const compositionPatternsResult = await this.renameSlotInDirectory(
1348
1347
  fullCompositionPatternsDir,
1349
- parentTypes,
1350
- slot,
1351
- newInstance,
1348
+ componentType,
1349
+ slotId,
1350
+ newSlotId,
1352
1351
  whatIf,
1353
1352
  strict,
1354
1353
  "compositionPattern"
1355
1354
  );
1356
- const componentPatternsResult = await this.addComponentToDirectory(
1355
+ const componentPatternsResult = await this.renameSlotInDirectory(
1357
1356
  fullComponentPatternsDir,
1358
- parentTypes,
1359
- slot,
1360
- newInstance,
1357
+ componentType,
1358
+ slotId,
1359
+ newSlotId,
1361
1360
  whatIf,
1362
1361
  strict,
1363
1362
  "componentPattern"
1364
1363
  );
1364
+ const totalFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1365
+ const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
1365
1366
  this.logger.info("");
1366
1367
  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.`
1368
+ `Summary: 1 component definition, ${totalFiles} file(s) (${totalInstances} instance(s)) updated.`
1368
1369
  );
1369
1370
  return {
1370
- allowedComponentsUpdated,
1371
- compositionsModified: compositionsResult,
1372
- compositionPatternsModified: compositionPatternsResult,
1373
- componentPatternsModified: componentPatternsResult,
1374
- instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1371
+ componentDefinitionUpdated: true,
1372
+ compositionsModified: compositionsResult.filesModified,
1373
+ compositionPatternsModified: compositionPatternsResult.filesModified,
1374
+ componentPatternsModified: componentPatternsResult.filesModified,
1375
+ instancesRenamed: totalInstances
1375
1376
  };
1376
1377
  }
1377
- async addComponentPattern(options) {
1378
+ async renameSlotInDirectory(directory, componentType, oldSlotId, newSlotId, whatIf, strict, label) {
1379
+ let files;
1380
+ try {
1381
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1382
+ } catch {
1383
+ return { filesModified: 0, instancesRenamed: 0 };
1384
+ }
1385
+ if (files.length === 0) {
1386
+ return { filesModified: 0, instancesRenamed: 0 };
1387
+ }
1388
+ let filesModified = 0;
1389
+ let instancesRenamed = 0;
1390
+ for (const filePath of files) {
1391
+ let composition;
1392
+ try {
1393
+ composition = await this.fileSystem.readFile(filePath);
1394
+ } catch {
1395
+ continue;
1396
+ }
1397
+ if (!composition?.composition) {
1398
+ continue;
1399
+ }
1400
+ const count = this.renameSlotInTree(
1401
+ composition.composition,
1402
+ componentType,
1403
+ oldSlotId,
1404
+ newSlotId,
1405
+ strict
1406
+ );
1407
+ if (count > 0) {
1408
+ const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
1409
+ this.logger.action(
1410
+ whatIf,
1411
+ "UPDATE",
1412
+ `${label}/${relativePath} (${count} instance(s) of ${componentType})`
1413
+ );
1414
+ if (!whatIf) {
1415
+ await this.fileSystem.writeFile(filePath, composition);
1416
+ }
1417
+ filesModified++;
1418
+ instancesRenamed += count;
1419
+ }
1420
+ }
1421
+ return { filesModified, instancesRenamed };
1422
+ }
1423
+ renameSlotInTree(node, componentType, oldSlotId, newSlotId, strict) {
1424
+ let count = 0;
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
+ }
1431
+ }
1432
+ if (node.slots) {
1433
+ for (const slotInstances of Object.values(node.slots)) {
1434
+ if (!Array.isArray(slotInstances)) continue;
1435
+ for (const instance of slotInstances) {
1436
+ count += this.renameSlotInTree(instance, componentType, oldSlotId, newSlotId, strict);
1437
+ }
1438
+ }
1439
+ }
1440
+ return count;
1441
+ }
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 };
1448
+ }
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];
1455
+ }
1456
+ }
1457
+ return { renamed: true, slots: newSlots };
1458
+ }
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;
1467
+ }
1468
+ compareIds(id1, id2, strict) {
1469
+ if (strict) {
1470
+ return id1 === id2;
1471
+ }
1472
+ return id1.toLowerCase() === id2.toLowerCase();
1473
+ }
1474
+ async rename(options) {
1378
1475
  const {
1379
1476
  rootDir,
1380
1477
  componentsDir,
1381
1478
  compositionsDir,
1382
1479
  compositionPatternsDir,
1383
1480
  componentPatternsDir,
1384
- parentComponentType,
1385
- slot,
1386
- componentPatternId,
1481
+ componentType,
1482
+ newComponentType,
1483
+ newComponentName,
1387
1484
  whatIf,
1388
1485
  strict
1389
1486
  } = options;
1390
- if (!componentPatternId) {
1391
- throw new TransformError("componentPatternId is required for add-component-pattern");
1392
- }
1393
1487
  const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1394
1488
  const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1395
1489
  const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1396
1490
  const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1397
1491
  const findOptions = { strict };
1398
- const parentTypes = this.parseParentComponentTypes(parentComponentType);
1399
- if (parentTypes.length === 0) {
1400
- 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");
1401
1494
  }
1402
- this.logger.info(`Loading component pattern: ${componentPatternId}`);
1403
- const pattern = await this.loadComponentPattern(
1404
- fullComponentPatternsDir,
1405
- componentPatternId,
1406
- strict
1495
+ this.logger.info(`Loading component: ${componentType}`);
1496
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
1497
+ let targetExists = false;
1498
+ try {
1499
+ await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
1500
+ targetExists = true;
1501
+ } catch (error) {
1502
+ if (!(error instanceof ComponentNotFoundError)) {
1503
+ throw error;
1504
+ }
1505
+ }
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)}`
1407
1513
  );
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
- );
1514
+ component.id = newComponentType;
1515
+ if (newComponentName !== void 0) {
1516
+ component.name = newComponentName;
1413
1517
  }
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 = [];
1421
- }
1422
- let slotDef = parentComponent.slots.find(
1423
- (s) => this.compareIds(s.id, slot, strict)
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)}`
1424
1530
  );
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);
1429
- }
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 = [];
1437
- }
1438
- const isAlreadyAllowed = actualSlot.allowedComponents.some(
1439
- (c) => this.compareIds(c, componentTypeInPattern, strict)
1440
- );
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;
1452
- }
1531
+ if (!whatIf) {
1532
+ await this.fileSystem.renameFile(componentFilePath, newFilePath);
1453
1533
  }
1534
+ fileRenamed = true;
1454
1535
  }
1455
- const newInstance = {
1456
- type: componentTypeInPattern,
1457
- _pattern: pattern.componentPatternId,
1458
- _id: randomUUID()
1459
- };
1460
- 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(
1461
1545
  fullCompositionsDir,
1462
- parentTypes,
1463
- slot,
1464
- newInstance,
1546
+ componentType,
1547
+ newComponentType,
1465
1548
  whatIf,
1466
1549
  strict,
1467
1550
  "composition"
1468
1551
  );
1469
- const compositionPatternsResult = await this.addComponentToDirectory(
1552
+ const compositionPatternsResult = await this.renameTypeInDirectory(
1470
1553
  fullCompositionPatternsDir,
1471
- parentTypes,
1472
- slot,
1473
- newInstance,
1554
+ componentType,
1555
+ newComponentType,
1474
1556
  whatIf,
1475
1557
  strict,
1476
1558
  "compositionPattern"
1477
1559
  );
1478
- const componentPatternsResult = await this.addComponentToDirectory(
1560
+ const componentPatternsResult = await this.renameTypeInDirectory(
1479
1561
  fullComponentPatternsDir,
1480
- parentTypes,
1481
- slot,
1482
- newInstance,
1562
+ componentType,
1563
+ newComponentType,
1483
1564
  whatIf,
1484
1565
  strict,
1485
1566
  "componentPattern"
1486
1567
  );
1568
+ const totalCompositionFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
1569
+ const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
1487
1570
  this.logger.info("");
1488
1571
  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.`
1572
+ `Summary: 1 component renamed, ${allowedComponentsUpdated} component(s) with allowedComponents updated, ${totalCompositionFiles} composition file(s) (${totalInstances} instance(s)) updated.`
1490
1573
  );
1491
1574
  return {
1575
+ componentRenamed: true,
1576
+ fileRenamed,
1492
1577
  allowedComponentsUpdated,
1493
- compositionsModified: compositionsResult,
1494
- compositionPatternsModified: compositionPatternsResult,
1495
- componentPatternsModified: componentPatternsResult,
1496
- instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1578
+ compositionsModified: compositionsResult.filesModified,
1579
+ compositionPatternsModified: compositionPatternsResult.filesModified,
1580
+ componentPatternsModified: componentPatternsResult.filesModified,
1581
+ instancesRenamed: totalInstances
1497
1582
  };
1498
1583
  }
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);
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;
1510
1590
  }
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;
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;
1599
+ }
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
+ }
1519
1609
  }
1520
1610
  }
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);
1619
+ }
1620
+ updatedCount++;
1621
+ }
1521
1622
  }
1522
- if (!raw) {
1523
- throw new TransformError(`Component pattern "${patternId}" not found in ${componentPatternsDir}`);
1524
- }
1525
- return this.normalizeComponentPattern(raw, patternId);
1623
+ return updatedCount;
1526
1624
  }
1527
- normalizeComponentPattern(raw, patternId) {
1528
- if (raw.definition && typeof raw.definition === "object" && raw.definition.type) {
1529
- return raw;
1530
- }
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
- };
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 };
1537
1631
  }
1538
- return raw;
1539
- }
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;
1632
+ if (files.length === 0) {
1633
+ return { filesModified: 0, instancesRenamed: 0 };
1545
1634
  }
1546
- const files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1547
1635
  let filesModified = 0;
1636
+ let instancesRenamed = 0;
1548
1637
  for (const filePath of files) {
1638
+ let composition;
1549
1639
  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);
1608
- }
1609
- filesModified++;
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))`
1652
+ );
1653
+ if (!whatIf) {
1654
+ await this.fileSystem.writeFile(filePath, composition);
1610
1655
  }
1611
- } catch (error) {
1612
- if (error instanceof Error && !error.message.includes("skipping")) {
1613
- this.logger.detail(`Skipping ${filePath}: ${error.message}`);
1656
+ filesModified++;
1657
+ instancesRenamed += count;
1658
+ }
1659
+ }
1660
+ return { filesModified, instancesRenamed };
1661
+ }
1662
+ renameTypeInTree(node, oldType, newType, strict) {
1663
+ let count = 0;
1664
+ if (this.compareIds(node.type, oldType, strict)) {
1665
+ node.type = newType;
1666
+ count++;
1667
+ }
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);
1614
1673
  }
1615
1674
  }
1616
1675
  }
1617
- return filesModified;
1618
- }
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
- }
1645
- }
1646
- }
1647
- return modified;
1676
+ return count;
1648
1677
  }
1649
1678
  };
1650
1679
 
1651
- // src/core/services/composition-converter.service.ts
1652
- import * as crypto from "crypto";
1653
- var CompositionConverterService = class {
1654
- constructor(fileSystem, componentService, compositionService, logger) {
1680
+ // src/core/services/component-adder.service.ts
1681
+ import { randomUUID } from "crypto";
1682
+ var ComponentAdderService = class {
1683
+ constructor(fileSystem, componentService, logger) {
1655
1684
  this.fileSystem = fileSystem;
1656
1685
  this.componentService = componentService;
1657
- this.compositionService = compositionService;
1658
1686
  this.logger = logger;
1659
1687
  }
1660
- async convert(options) {
1688
+ compareIds(id1, id2, strict) {
1689
+ if (strict) {
1690
+ return id1 === id2;
1691
+ }
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
+ }
1709
+ }
1710
+ return result;
1711
+ }
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);
1718
+ }
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);
1724
+ }
1725
+ }
1726
+ }
1727
+ }
1728
+ }
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
+ };
1741
+ }
1742
+ }
1743
+ return instance;
1744
+ }
1745
+ addComponentToSlot(slots, slotId, instance) {
1746
+ if (!slots[slotId]) {
1747
+ slots[slotId] = [];
1748
+ }
1749
+ slots[slotId].push(instance);
1750
+ }
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
- this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
1680
- if (flattenComponentIds.length > 0) {
1681
- this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
1682
- }
1683
- const compositionResults = await this.compositionService.findCompositionsByTypes(
1684
- compositionsDirFull,
1685
- compositionTypes,
1686
- { strict }
1687
- );
1688
- if (compositionResults.length === 0) {
1689
- this.logger.warn("No compositions found matching the specified types");
1690
- return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0 };
1765
+ if (!newComponentType) {
1766
+ throw new TransformError("newComponentType is required for add-component");
1691
1767
  }
1692
- this.logger.info(`Found ${compositionResults.length} composition(s)`);
1693
- const rootComponentTypes = /* @__PURE__ */ new Set();
1694
- for (const { composition } of compositionResults) {
1695
- rootComponentTypes.add(composition.composition.type);
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");
1696
1776
  }
1697
- const contentTypeMap = /* @__PURE__ */ new Map();
1698
- for (const rootType of rootComponentTypes) {
1699
- const { component } = await this.componentService.loadComponent(
1700
- componentsDirFull,
1701
- rootType,
1702
- { strict }
1703
- );
1704
- const contentType = this.generateContentType(component);
1705
- contentTypeMap.set(rootType, contentType);
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
+ );
1785
+ }
1786
+ throw error;
1706
1787
  }
1707
- const flattenContentTypeMap = /* @__PURE__ */ new Map();
1708
- const missingFlattenTypes = [];
1709
- const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
1710
- for (const flattenType of flattenComponentIds) {
1711
- const isRootType = [...rootComponentTypes].some(
1712
- (rt) => this.compareTypes(rt, flattenType, strict)
1788
+ let allowedComponentsUpdated = false;
1789
+ for (const parentType of parentTypes) {
1790
+ this.logger.info(`Loading parent component: ${parentType}`);
1791
+ const { component: parentComponent, filePath: parentComponentFilePath } = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1792
+ if (!parentComponent.slots) {
1793
+ parentComponent.slots = [];
1794
+ }
1795
+ let slotDef = parentComponent.slots.find(
1796
+ (s) => this.compareIds(s.id, slot, strict)
1713
1797
  );
1714
- if (isRootType) {
1715
- continue;
1798
+ if (!slotDef) {
1799
+ this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1800
+ slotDef = { id: slot, name: slot, allowedComponents: [] };
1801
+ parentComponent.slots.push(slotDef);
1716
1802
  }
1717
- try {
1718
- const { component } = await this.componentService.loadComponent(
1719
- componentsDirFull,
1720
- flattenType,
1721
- { strict }
1722
- );
1723
- const contentType = this.generateContentType(component);
1724
- flattenContentTypeMap.set(flattenType, contentType);
1725
- } catch (error) {
1726
- if (error instanceof ComponentNotFoundError) {
1727
- this.logger.info(`Flatten component type not found: ${flattenType}`);
1728
- missingFlattenTypes.push(flattenType);
1729
- continue;
1803
+ const slotIndex = parentComponent.slots?.findIndex(
1804
+ (s) => this.compareIds(s.id, slotDef.id, strict)
1805
+ );
1806
+ if (slotIndex !== void 0 && slotIndex >= 0) {
1807
+ const actualSlot = parentComponent.slots[slotIndex];
1808
+ if (!actualSlot.allowedComponents) {
1809
+ actualSlot.allowedComponents = [];
1730
1810
  }
1731
- throw error;
1732
- }
1733
- }
1734
- for (const { composition } of compositionResults) {
1735
- const comp = composition.composition;
1736
- const compositionId = comp._id;
1737
- const compositionName = comp._name ?? compositionId;
1738
- const compositionType = comp.type;
1739
- const entry = this.generateEntryFromComposition(composition);
1740
- const flattenedByType = /* @__PURE__ */ new Map();
1741
- if (flattenComponentIds.length > 0 && comp.slots) {
1742
- for (const flattenType of flattenComponentIds) {
1743
- if (this.compareTypes(flattenType, compositionType, strict)) {
1744
- this.logger.warn(
1745
- `Skipping flatten of "${flattenType}" \u2014 same as root component type`
1746
- );
1747
- continue;
1748
- }
1749
- const instances = this.findFlattenTargets(
1750
- comp.slots,
1751
- flattenType,
1752
- compositionId,
1753
- compositionName,
1754
- strict
1811
+ const isAlreadyAllowed = actualSlot.allowedComponents.some(
1812
+ (c) => this.compareIds(c, newComponentType, strict)
1813
+ );
1814
+ if (!isAlreadyAllowed) {
1815
+ this.logger.action(
1816
+ whatIf,
1817
+ "UPDATE",
1818
+ `Adding "${newComponentType}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1755
1819
  );
1756
- if (instances.length > 0) {
1757
- flattenedByType.set(flattenType, instances);
1758
- if (missingFlattenTypes.includes(flattenType)) {
1759
- foundMissingFlattenTypes.add(flattenType);
1760
- }
1820
+ actualSlot.allowedComponents.push(newComponentType);
1821
+ if (!whatIf) {
1822
+ await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1761
1823
  }
1824
+ allowedComponentsUpdated = true;
1762
1825
  }
1763
1826
  }
1764
- for (const [flattenType, instances] of flattenedByType) {
1765
- entry.entry.fields[flattenType] = {
1766
- type: "contentReference",
1767
- value: instances.map((inst) => inst.determinisiticId)
1768
- };
1769
- }
1770
- if (flattenComponentIds.length > 0) {
1771
- this.transformContentReferences(entry);
1772
- }
1773
- const entryId = entry.entry._id;
1774
- const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${entryId}.json`);
1775
- this.logger.action(
1776
- whatIf,
1777
- "WRITE",
1778
- `${entriesDir}/${entryId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
1779
- );
1780
- if (!whatIf) {
1781
- await this.fileSystem.writeFile(entryFilePath, entry);
1827
+ }
1828
+ const parsedParams = this.parseParameters(paramString);
1829
+ const newInstance = this.createComponentInstance(newComponentType, parsedParams);
1830
+ const compositionsResult = await this.addComponentToDirectory(
1831
+ fullCompositionsDir,
1832
+ parentTypes,
1833
+ slot,
1834
+ newInstance,
1835
+ whatIf,
1836
+ strict,
1837
+ "composition"
1838
+ );
1839
+ const compositionPatternsResult = await this.addComponentToDirectory(
1840
+ fullCompositionPatternsDir,
1841
+ parentTypes,
1842
+ slot,
1843
+ newInstance,
1844
+ whatIf,
1845
+ strict,
1846
+ "compositionPattern"
1847
+ );
1848
+ const componentPatternsResult = await this.addComponentToDirectory(
1849
+ fullComponentPatternsDir,
1850
+ parentTypes,
1851
+ slot,
1852
+ newInstance,
1853
+ whatIf,
1854
+ strict,
1855
+ "componentPattern"
1856
+ );
1857
+ this.logger.info("");
1858
+ this.logger.info(
1859
+ `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.`
1860
+ );
1861
+ return {
1862
+ allowedComponentsUpdated,
1863
+ compositionsModified: compositionsResult,
1864
+ compositionPatternsModified: compositionPatternsResult,
1865
+ componentPatternsModified: componentPatternsResult,
1866
+ instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1867
+ };
1868
+ }
1869
+ async addComponentPattern(options) {
1870
+ const {
1871
+ rootDir,
1872
+ componentsDir,
1873
+ compositionsDir,
1874
+ compositionPatternsDir,
1875
+ componentPatternsDir,
1876
+ parentComponentType,
1877
+ slot,
1878
+ componentPatternId,
1879
+ whatIf,
1880
+ strict
1881
+ } = options;
1882
+ if (!componentPatternId) {
1883
+ throw new TransformError("componentPatternId is required for add-component-pattern");
1884
+ }
1885
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1886
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1887
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
1888
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
1889
+ const findOptions = { strict };
1890
+ const parentTypes = this.parseParentComponentTypes(parentComponentType);
1891
+ if (parentTypes.length === 0) {
1892
+ throw new TransformError("parentComponentType cannot be empty");
1893
+ }
1894
+ this.logger.info(`Loading component pattern: ${componentPatternId}`);
1895
+ const pattern = await this.loadComponentPattern(
1896
+ fullComponentPatternsDir,
1897
+ componentPatternId,
1898
+ strict
1899
+ );
1900
+ const patternDefinition = pattern.definition;
1901
+ if (!patternDefinition || !patternDefinition.type) {
1902
+ throw new TransformError(
1903
+ `Component pattern "${componentPatternId}" has no valid definition or missing type`
1904
+ );
1905
+ }
1906
+ const componentTypeInPattern = patternDefinition.type;
1907
+ let allowedComponentsUpdated = false;
1908
+ for (const parentType of parentTypes) {
1909
+ this.logger.info(`Loading parent component: ${parentType}`);
1910
+ const { component: parentComponent, filePath: parentComponentFilePath } = await this.componentService.loadComponent(fullComponentsDir, parentType, findOptions);
1911
+ if (!parentComponent.slots) {
1912
+ parentComponent.slots = [];
1782
1913
  }
1783
- entriesFromCompositions++;
1784
- for (const [flattenType, instances] of flattenedByType) {
1785
- for (const inst of instances) {
1786
- const flatEntry = this.generateEntryFromFlattenedInstance(inst);
1787
- const flatEntryPath = this.fileSystem.joinPath(
1788
- entriesDirFull,
1789
- `${inst.determinisiticId}.json`
1790
- );
1914
+ let slotDef = parentComponent.slots.find(
1915
+ (s) => this.compareIds(s.id, slot, strict)
1916
+ );
1917
+ if (!slotDef) {
1918
+ this.logger.info(`Slot "${slot}" not found on component "${parentType}", creating it`);
1919
+ slotDef = { id: slot, name: slot, allowedComponents: [] };
1920
+ parentComponent.slots.push(slotDef);
1921
+ }
1922
+ const slotIndex = parentComponent.slots?.findIndex(
1923
+ (s) => this.compareIds(s.id, slotDef.id, strict)
1924
+ );
1925
+ if (slotIndex !== void 0 && slotIndex >= 0) {
1926
+ const actualSlot = parentComponent.slots[slotIndex];
1927
+ if (!actualSlot.allowedComponents) {
1928
+ actualSlot.allowedComponents = [];
1929
+ }
1930
+ const isAlreadyAllowed = actualSlot.allowedComponents.some(
1931
+ (c) => this.compareIds(c, componentTypeInPattern, strict)
1932
+ );
1933
+ if (!isAlreadyAllowed) {
1791
1934
  this.logger.action(
1792
1935
  whatIf,
1793
- "WRITE",
1794
- `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
1936
+ "UPDATE",
1937
+ `Adding "${componentTypeInPattern}" to allowedComponents for slot "${slot}" on component "${parentType}"`
1795
1938
  );
1939
+ actualSlot.allowedComponents.push(componentTypeInPattern);
1796
1940
  if (!whatIf) {
1797
- await this.fileSystem.writeFile(flatEntryPath, flatEntry);
1798
- }
1799
- entriesFromFlattened++;
1800
- }
1801
- }
1802
- }
1803
- if (flattenComponentIds.length > 0) {
1804
- for (const contentType of contentTypeMap.values()) {
1805
- for (const flattenType of flattenComponentIds) {
1806
- if (this.compareTypes(flattenType, contentType.id, strict)) {
1807
- continue;
1808
- }
1809
- if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
1810
- continue;
1941
+ await this.componentService.saveComponent(parentComponentFilePath, parentComponent);
1811
1942
  }
1812
- contentType.fields.push({
1813
- id: flattenType,
1814
- name: flattenType,
1815
- type: "contentReference",
1816
- typeConfig: {
1817
- isMulti: true,
1818
- allowedContentTypes: [flattenType]
1819
- },
1820
- localizable: false
1821
- });
1943
+ allowedComponentsUpdated = true;
1822
1944
  }
1823
1945
  }
1824
1946
  }
1825
- for (const [typeName, contentType] of contentTypeMap) {
1826
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1827
- const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
1828
- const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
1829
- const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
1830
- this.logger.action(
1831
- whatIf,
1832
- "WRITE",
1833
- `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
1834
- );
1835
- if (!whatIf) {
1836
- await this.fileSystem.writeFile(filePath, contentType);
1837
- }
1838
- contentTypesWritten++;
1839
- }
1840
- for (const [typeName, contentType] of flattenContentTypeMap) {
1841
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1842
- this.logger.action(
1843
- whatIf,
1844
- "WRITE",
1845
- `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
1846
- );
1847
- if (!whatIf) {
1848
- await this.fileSystem.writeFile(filePath, contentType);
1849
- }
1850
- contentTypesWritten++;
1851
- }
1852
- const neverFoundMissingTypes = missingFlattenTypes.filter(
1853
- (type) => !foundMissingFlattenTypes.has(type)
1947
+ const newInstance = {
1948
+ type: componentTypeInPattern,
1949
+ _pattern: pattern.componentPatternId,
1950
+ _id: randomUUID()
1951
+ };
1952
+ const compositionsResult = await this.addComponentToDirectory(
1953
+ fullCompositionsDir,
1954
+ parentTypes,
1955
+ slot,
1956
+ newInstance,
1957
+ whatIf,
1958
+ strict,
1959
+ "composition"
1960
+ );
1961
+ const compositionPatternsResult = await this.addComponentToDirectory(
1962
+ fullCompositionPatternsDir,
1963
+ parentTypes,
1964
+ slot,
1965
+ newInstance,
1966
+ whatIf,
1967
+ strict,
1968
+ "compositionPattern"
1969
+ );
1970
+ const componentPatternsResult = await this.addComponentToDirectory(
1971
+ fullComponentPatternsDir,
1972
+ parentTypes,
1973
+ slot,
1974
+ newInstance,
1975
+ whatIf,
1976
+ strict,
1977
+ "componentPattern"
1978
+ );
1979
+ this.logger.info("");
1980
+ this.logger.info(
1981
+ `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.`
1854
1982
  );
1855
- if (neverFoundMissingTypes.length > 0) {
1856
- this.logger.warn(
1857
- `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
1858
- );
1859
- }
1860
- return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened };
1861
- }
1862
- // --- Content Type Generation ---
1863
- generateContentType(component) {
1864
- const fields = [];
1865
- if (component.parameters) {
1866
- for (const param of component.parameters) {
1867
- fields.push(this.parameterToField(param));
1868
- }
1869
- }
1870
1983
  return {
1871
- id: component.id,
1872
- name: component.name,
1873
- fields
1874
- };
1875
- }
1876
- parameterToField(param) {
1877
- const field = {
1878
- id: param.id,
1879
- name: param.name,
1880
- type: param.type
1984
+ allowedComponentsUpdated,
1985
+ compositionsModified: compositionsResult,
1986
+ compositionPatternsModified: compositionPatternsResult,
1987
+ componentPatternsModified: componentPatternsResult,
1988
+ instancesAdded: compositionsResult + compositionPatternsResult + componentPatternsResult
1881
1989
  };
1882
- if (param.helpText !== void 0) {
1883
- field.helpText = param.helpText;
1884
- }
1885
- if (param.localizable !== void 0) {
1886
- field.localizable = param.localizable;
1887
- }
1888
- if (param.typeConfig !== void 0) {
1889
- field.typeConfig = param.typeConfig;
1890
- }
1891
- return field;
1892
1990
  }
1893
- // --- Entry Generation ---
1894
- generateEntryFromComposition(composition) {
1895
- const comp = composition.composition;
1896
- const compositionSpecificKeys = /* @__PURE__ */ new Set(["_id", "_name", "type", "parameters", "slots", "_overrides"]);
1897
- const extraRootProps = {};
1898
- for (const [key, value] of Object.entries(comp)) {
1899
- if (!compositionSpecificKeys.has(key) && value != null) {
1900
- extraRootProps[key] = value;
1901
- }
1991
+ async loadComponentPattern(componentPatternsDir, patternId, strict) {
1992
+ const jsonPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.json`);
1993
+ const yamlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yaml`);
1994
+ const ymlPath = this.fileSystem.joinPath(componentPatternsDir, `${patternId}.yml`);
1995
+ let raw;
1996
+ if (await this.fileSystem.fileExists(jsonPath)) {
1997
+ raw = await this.fileSystem.readFile(jsonPath);
1998
+ } else if (await this.fileSystem.fileExists(yamlPath)) {
1999
+ raw = await this.fileSystem.readFile(yamlPath);
2000
+ } else if (await this.fileSystem.fileExists(ymlPath)) {
2001
+ raw = await this.fileSystem.readFile(ymlPath);
1902
2002
  }
1903
- const wrapperKeys = /* @__PURE__ */ new Set(["composition"]);
1904
- const extraWrapperProps = {};
1905
- for (const [key, value] of Object.entries(composition)) {
1906
- if (!wrapperKeys.has(key) && value != null) {
1907
- extraWrapperProps[key] = value;
2003
+ if (!raw && !strict) {
2004
+ const files = await this.fileSystem.findFiles(componentPatternsDir, "*.{json,yaml,yml}");
2005
+ for (const filePath of files) {
2006
+ const basename2 = this.fileSystem.getBasename(filePath);
2007
+ const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
2008
+ if (nameWithoutExt.toLowerCase() === patternId.toLowerCase()) {
2009
+ raw = await this.fileSystem.readFile(filePath);
2010
+ break;
2011
+ }
1908
2012
  }
1909
2013
  }
1910
- const entryId = computeGuidHash(`entry${comp._id}`);
1911
- const entryName = this.truncateName(comp._name ?? comp._id, 60);
1912
- return {
1913
- entry: {
1914
- _id: entryId,
1915
- _name: entryName,
1916
- type: comp.type,
1917
- fields: { ...comp.parameters ?? {} },
1918
- ...extraRootProps
1919
- },
1920
- ...extraWrapperProps
1921
- };
1922
- }
1923
- generateEntryFromFlattenedInstance(inst) {
1924
- const entryName = this.truncateName(
1925
- `${inst.componentType} (from ${inst.compositionName})`,
1926
- 60
1927
- );
1928
- return {
1929
- entry: {
1930
- _id: inst.determinisiticId,
1931
- _name: entryName,
1932
- type: inst.componentType,
1933
- fields: { ...inst.instance.parameters ?? {} }
1934
- }
1935
- };
2014
+ if (!raw) {
2015
+ throw new TransformError(`Component pattern "${patternId}" not found in ${componentPatternsDir}`);
2016
+ }
2017
+ return this.normalizeComponentPattern(raw, patternId);
1936
2018
  }
1937
- // --- Flatten Tree Walking ---
1938
- findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
1939
- const results = [];
1940
- this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
1941
- return results;
2019
+ normalizeComponentPattern(raw, patternId) {
2020
+ if (raw.definition && typeof raw.definition === "object" && raw.definition.type) {
2021
+ return raw;
2022
+ }
2023
+ if (raw.composition && typeof raw.composition === "object" && raw.composition.type) {
2024
+ const composition = raw.composition;
2025
+ return {
2026
+ componentPatternId: patternId,
2027
+ definition: composition
2028
+ };
2029
+ }
2030
+ return raw;
1942
2031
  }
1943
- walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
1944
- for (const [slotName, instances] of Object.entries(slots)) {
1945
- if (!Array.isArray(instances)) continue;
1946
- for (let i = 0; i < instances.length; i++) {
1947
- const instance = instances[i];
1948
- if (instance._pattern) {
2032
+ async addComponentToDirectory(directory, parentTypes, slot, newInstance, whatIf, strict, dirType) {
2033
+ const exists = await this.fileSystem.fileExists(directory);
2034
+ if (!exists) {
2035
+ this.logger.detail(`${dirType} directory does not exist, skipping`);
2036
+ return 0;
2037
+ }
2038
+ const files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
2039
+ let filesModified = 0;
2040
+ for (const filePath of files) {
2041
+ try {
2042
+ const content = await this.fileSystem.readFile(filePath);
2043
+ let composition = null;
2044
+ let isComponentPattern = false;
2045
+ if (dirType === "componentPattern" && content && typeof content === "object" && "definition" in content) {
2046
+ isComponentPattern = true;
2047
+ const pattern = content;
2048
+ if (pattern.definition && pattern.definition.slots) {
2049
+ composition = {
2050
+ composition: {
2051
+ _id: "pattern-root",
2052
+ type: pattern.definition.type,
2053
+ slots: pattern.definition.slots
2054
+ }
2055
+ };
2056
+ }
2057
+ } else {
2058
+ composition = content;
2059
+ }
2060
+ if (!composition?.composition) {
1949
2061
  continue;
1950
2062
  }
1951
- const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
1952
- if (this.compareTypes(instance.type, targetType, strict)) {
1953
- const fullPath = `${compositionId}-${currentPath}`;
1954
- const deterministicId = computeGuidHash(fullPath);
1955
- results.push({
1956
- instance,
1957
- path: fullPath,
1958
- determinisiticId: deterministicId,
1959
- componentType: instance.type,
1960
- compositionId,
1961
- compositionName
1962
- });
2063
+ const rootInstance = composition.composition;
2064
+ let modified = false;
2065
+ if (this.matchesAnyParentType(rootInstance.type, parentTypes, strict)) {
2066
+ if (!rootInstance.slots) {
2067
+ rootInstance.slots = {};
2068
+ }
2069
+ const instanceCopy = JSON.parse(JSON.stringify(newInstance));
2070
+ this.regenerateInstanceIds(instanceCopy);
2071
+ this.addComponentToSlot(rootInstance.slots, slot, instanceCopy);
2072
+ modified = true;
1963
2073
  }
1964
- if (instance.slots) {
1965
- this.walkSlots(
1966
- instance.slots,
1967
- targetType,
1968
- compositionId,
1969
- compositionName,
1970
- currentPath,
1971
- results,
2074
+ if (rootInstance.slots) {
2075
+ const nestedModified = this.addComponentToNestedSlots(
2076
+ rootInstance.slots,
2077
+ parentTypes,
2078
+ slot,
2079
+ newInstance,
1972
2080
  strict
1973
2081
  );
2082
+ if (nestedModified) {
2083
+ modified = true;
2084
+ }
2085
+ }
2086
+ if (modified) {
2087
+ this.logger.action(
2088
+ whatIf,
2089
+ "UPDATE",
2090
+ `Adding "${newInstance.type}" to slot "${slot}" in ${dirType}/${this.fileSystem.getBasename(filePath)}`
2091
+ );
2092
+ if (!whatIf) {
2093
+ if (isComponentPattern && content && typeof content === "object" && "definition" in content) {
2094
+ const pattern = content;
2095
+ if (rootInstance.slots) {
2096
+ pattern.definition.slots = rootInstance.slots;
2097
+ }
2098
+ }
2099
+ await this.fileSystem.writeFile(filePath, content);
2100
+ }
2101
+ filesModified++;
2102
+ }
2103
+ } catch (error) {
2104
+ if (error instanceof Error && !error.message.includes("skipping")) {
2105
+ this.logger.detail(`Skipping ${filePath}: ${error.message}`);
1974
2106
  }
1975
2107
  }
1976
2108
  }
2109
+ return filesModified;
1977
2110
  }
1978
- // --- Content Reference Transformation ---
1979
- transformContentReferences(entry) {
1980
- const dataResources = {};
1981
- for (const [fieldName, field] of Object.entries(entry.entry.fields)) {
1982
- if (field.type === "contentReference" && Array.isArray(field.value)) {
1983
- const entryIds = field.value;
1984
- const resourceKey = `ref-${entry.entry._id}-${fieldName}`;
1985
- field.value = `\${#jptr:/${resourceKey}/entries}`;
1986
- dataResources[resourceKey] = {
1987
- type: "uniformContentInternalReference",
1988
- variables: {
1989
- locale: "${locale}",
1990
- entryIds: entryIds.join(",")
2111
+ addComponentToNestedSlots(slots, parentTypes, slot, newInstance, strict) {
2112
+ let modified = false;
2113
+ for (const slotInstances of Object.values(slots)) {
2114
+ if (!Array.isArray(slotInstances)) continue;
2115
+ for (const instance of slotInstances) {
2116
+ if (this.matchesAnyParentType(instance.type, parentTypes, strict)) {
2117
+ if (!instance.slots) {
2118
+ instance.slots = {};
1991
2119
  }
1992
- };
2120
+ const instanceCopy = JSON.parse(JSON.stringify(newInstance));
2121
+ this.regenerateInstanceIds(instanceCopy);
2122
+ this.addComponentToSlot(instance.slots, slot, instanceCopy);
2123
+ modified = true;
2124
+ }
2125
+ if (instance.slots) {
2126
+ const nestedModified = this.addComponentToNestedSlots(
2127
+ instance.slots,
2128
+ parentTypes,
2129
+ slot,
2130
+ newInstance,
2131
+ strict
2132
+ );
2133
+ if (nestedModified) {
2134
+ modified = true;
2135
+ }
2136
+ }
1993
2137
  }
1994
2138
  }
1995
- if (Object.keys(dataResources).length > 0) {
1996
- entry.entry._dataResources = {
1997
- ...entry.entry._dataResources ?? {},
1998
- ...dataResources
1999
- };
2000
- }
2001
- }
2002
- // --- Utilities ---
2003
- compareTypes(type1, type2, strict) {
2004
- if (strict) {
2005
- return type1 === type2;
2006
- }
2007
- return type1.toLowerCase() === type2.toLowerCase();
2008
- }
2009
- truncate(str, maxLength) {
2010
- if (str.length <= maxLength) return str;
2011
- return str.substring(0, maxLength - 3) + "...";
2012
- }
2013
- truncateName(name, maxLength) {
2014
- if (name.length <= maxLength) return name;
2015
- return name.substring(0, maxLength);
2139
+ return modified;
2016
2140
  }
2017
2141
  };
2018
- function computeGuidHash(guidOrSeed) {
2019
- let uuidStr;
2020
- if (isValidUuid(guidOrSeed)) {
2021
- uuidStr = formatAsBracedUuid(guidOrSeed);
2022
- } else {
2023
- const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
2024
- const hex = [
2025
- // First 4 bytes: little-endian in .NET Guid
2026
- hash[3].toString(16).padStart(2, "0"),
2027
- hash[2].toString(16).padStart(2, "0"),
2028
- hash[1].toString(16).padStart(2, "0"),
2029
- hash[0].toString(16).padStart(2, "0"),
2030
- "-",
2031
- // Bytes 4-5: little-endian
2032
- hash[5].toString(16).padStart(2, "0"),
2033
- hash[4].toString(16).padStart(2, "0"),
2034
- "-",
2035
- // Bytes 6-7: little-endian
2036
- hash[7].toString(16).padStart(2, "0"),
2037
- hash[6].toString(16).padStart(2, "0"),
2038
- "-",
2039
- // Bytes 8-9: big-endian
2040
- hash[8].toString(16).padStart(2, "0"),
2041
- hash[9].toString(16).padStart(2, "0"),
2042
- "-",
2043
- // Bytes 10-15: big-endian
2044
- hash[10].toString(16).padStart(2, "0"),
2045
- hash[11].toString(16).padStart(2, "0"),
2046
- hash[12].toString(16).padStart(2, "0"),
2047
- hash[13].toString(16).padStart(2, "0"),
2048
- hash[14].toString(16).padStart(2, "0"),
2049
- hash[15].toString(16).padStart(2, "0")
2050
- ].join("");
2051
- uuidStr = `{${hex}}`.toUpperCase();
2052
- }
2053
- const chars = uuidStr.split("");
2054
- chars[15] = "4";
2055
- const arr20 = ["8", "9", "A", "B"];
2056
- const hexVal = parseInt(chars[20], 16);
2057
- chars[20] = arr20[hexVal % 4];
2058
- return chars.join("").slice(1, -1).toLowerCase();
2059
- }
2060
- function isValidUuid(str) {
2061
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
2062
- }
2063
- function formatAsBracedUuid(uuid) {
2064
- const clean = uuid.replace(/[{}]/g, "").toUpperCase();
2065
- return `{${clean}}`;
2066
- }
2067
2142
 
2068
2143
  // src/core/services/parameter-remover.service.ts
2069
2144
  var ParameterRemoverService = class {
@@ -2537,6 +2612,7 @@ export {
2537
2612
  SlotNotFoundError,
2538
2613
  SlotRenamerService,
2539
2614
  TransformError,
2540
- computeGuidHash
2615
+ computeGuidHash,
2616
+ regenerateIds
2541
2617
  };
2542
2618
  //# sourceMappingURL=index.js.map