@uniformdev/transformer 1.1.16 → 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/cli/index.js CHANGED
@@ -523,301 +523,793 @@ var CompositionService = class {
523
523
  }
524
524
  };
525
525
 
526
- // src/core/services/property-propagator.service.ts
527
- var PropertyPropagatorService = class {
526
+ // src/core/services/composition-converter.service.ts
527
+ import * as crypto from "crypto";
528
+ var CompositionConverterService = class {
528
529
  constructor(fileSystem, componentService, compositionService, logger) {
529
530
  this.fileSystem = fileSystem;
530
531
  this.componentService = componentService;
531
532
  this.compositionService = compositionService;
532
533
  this.logger = logger;
533
534
  }
534
- async propagate(options) {
535
+ async convert(options) {
535
536
  const {
536
537
  rootDir,
537
- componentsDir,
538
538
  compositionsDir,
539
- compositionType,
540
- property,
541
- targetComponentType,
542
- targetGroup,
539
+ componentsDir,
540
+ contentTypesDir,
541
+ entriesDir,
542
+ compositionTypes,
543
+ flattenComponentIds,
543
544
  whatIf,
544
- strict,
545
- deleteSourceParameter
545
+ strict
546
546
  } = options;
547
- const findOptions = { strict };
548
- const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
549
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
550
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
551
- const sourceComponents = [];
552
- for (const sourceType of compositionTypes) {
553
- this.logger.info(`Loading component: ${sourceType}`);
554
- const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
555
- sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
547
+ const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
548
+ const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
549
+ const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
550
+ const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
551
+ let contentTypesWritten = 0;
552
+ let entriesFromCompositions = 0;
553
+ let entriesFromFlattened = 0;
554
+ let entriesReused = 0;
555
+ this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
556
+ if (flattenComponentIds.length > 0) {
557
+ this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
556
558
  }
557
- this.logger.info(`Loading component: ${targetComponentType}`);
558
- const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
559
- const propertyNames = property.split("|").map((p) => p.trim()).filter((p) => p.length > 0);
560
- const resolvedParams = [];
561
- const resolvedNames = [];
562
- for (const { sourceType, sourceComponent } of sourceComponents) {
563
- const { parameters: sourceParams, notFound } = this.componentService.resolveProperties(
564
- sourceComponent,
565
- propertyNames,
566
- findOptions
559
+ const sourceItemMap = flattenComponentIds.length > 0 ? await this.buildSourceItemMap(entriesDirFull) : /* @__PURE__ */ new Map();
560
+ if (sourceItemMap.size > 0) {
561
+ this.logger.info(`Found ${sourceItemMap.size} existing entry(ies) with sourceItem values`);
562
+ }
563
+ const compositionResults = await this.compositionService.findCompositionsByTypes(
564
+ compositionsDirFull,
565
+ compositionTypes,
566
+ { strict }
567
+ );
568
+ if (compositionResults.length === 0) {
569
+ this.logger.warn("No compositions found matching the specified types");
570
+ return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0, entriesReused: 0 };
571
+ }
572
+ this.logger.info(`Found ${compositionResults.length} composition(s)`);
573
+ const rootComponentTypes = /* @__PURE__ */ new Set();
574
+ for (const { composition } of compositionResults) {
575
+ rootComponentTypes.add(composition.composition.type);
576
+ }
577
+ const contentTypeMap = /* @__PURE__ */ new Map();
578
+ for (const rootType of rootComponentTypes) {
579
+ const { component } = await this.componentService.loadComponent(
580
+ componentsDirFull,
581
+ rootType,
582
+ { strict }
567
583
  );
568
- if (notFound.length > 0) {
569
- this.logger.warn(`Property "${notFound.join(", ")}" not found on component "${sourceType}"`);
584
+ const contentType = this.generateContentType(component);
585
+ contentTypeMap.set(rootType, contentType);
586
+ }
587
+ const flattenContentTypeMap = /* @__PURE__ */ new Map();
588
+ const missingFlattenTypes = [];
589
+ const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
590
+ for (const flattenType of flattenComponentIds) {
591
+ const isRootType = [...rootComponentTypes].some(
592
+ (rt) => this.compareTypes(rt, flattenType, strict)
593
+ );
594
+ if (isRootType) {
570
595
  continue;
571
596
  }
572
- for (const param of sourceParams) {
573
- const exists = resolvedParams.some(
574
- (existing) => strict ? existing.id === param.id : existing.id.toLowerCase() === param.id.toLowerCase()
597
+ try {
598
+ const { component } = await this.componentService.loadComponent(
599
+ componentsDirFull,
600
+ flattenType,
601
+ { strict }
575
602
  );
576
- if (!exists) {
577
- resolvedParams.push(param);
578
- resolvedNames.push(param.id);
603
+ const contentType = this.generateContentType(component);
604
+ flattenContentTypeMap.set(flattenType, contentType);
605
+ } catch (error) {
606
+ if (error instanceof ComponentNotFoundError) {
607
+ this.logger.info(`Flatten component type not found: ${flattenType}`);
608
+ missingFlattenTypes.push(flattenType);
609
+ continue;
579
610
  }
611
+ throw error;
580
612
  }
581
613
  }
582
- const groupSources = [];
583
- for (const { sourceType, sourceComponent } of sourceComponents) {
584
- for (const name of propertyNames) {
585
- const param = this.componentService.findParameter(sourceComponent, name, findOptions);
586
- if (param && this.componentService.isGroupParameter(param)) {
587
- const groupSource = `${sourceType}.${name}`;
588
- if (!groupSources.includes(groupSource)) {
589
- groupSources.push(groupSource);
614
+ for (const { composition } of compositionResults) {
615
+ const comp = composition.composition;
616
+ const compositionId = comp._id;
617
+ const compositionName = comp._name ?? compositionId;
618
+ const compositionType = comp.type;
619
+ const entry = this.generateEntryFromComposition(composition);
620
+ const flattenedByType = /* @__PURE__ */ new Map();
621
+ if (flattenComponentIds.length > 0 && comp.slots) {
622
+ for (const flattenType of flattenComponentIds) {
623
+ if (this.compareTypes(flattenType, compositionType, strict)) {
624
+ this.logger.warn(
625
+ `Skipping flatten of "${flattenType}" \u2014 same as root component type`
626
+ );
627
+ continue;
590
628
  }
591
- }
592
- }
593
- }
594
- if (groupSources.length > 0) {
595
- this.logger.info(
596
- `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
597
- );
598
- } else {
599
- this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
600
- }
601
- let modifiedComponent = { ...targetComponent };
602
- let componentModified = false;
603
- const groupId = this.componentService.generateGroupId(targetGroup);
604
- const existingGroup = this.componentService.findParameter(
605
- modifiedComponent,
606
- groupId,
607
- findOptions
608
- );
609
- if (!existingGroup) {
610
- this.logger.action(whatIf, "CREATE", `Group "${targetGroup}" on ${targetComponentType}`);
611
- modifiedComponent = this.componentService.ensureGroupExists(
612
- modifiedComponent,
613
- groupId,
614
- targetGroup,
615
- findOptions
616
- );
617
- componentModified = true;
618
- }
619
- for (const param of resolvedParams) {
620
- const existingParam = this.componentService.findParameter(
621
- modifiedComponent,
622
- param.id,
623
- findOptions
624
- );
625
- if (!existingParam) {
626
- this.logger.action(
627
- whatIf,
628
- "COPY",
629
- `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
630
- );
631
- modifiedComponent = this.componentService.addParameterToComponent(
632
- modifiedComponent,
633
- param,
634
- findOptions
635
- );
636
- modifiedComponent = this.componentService.addParameterToGroup(
637
- modifiedComponent,
638
- groupId,
639
- param.id,
640
- findOptions
641
- );
642
- componentModified = true;
643
- } else {
644
- this.logger.info(`Parameter "${param.id}" already exists on ${targetComponentType}`);
645
- const group = this.componentService.findParameter(
646
- modifiedComponent,
647
- groupId,
648
- findOptions
649
- );
650
- if (group && this.componentService.isGroupParameter(group)) {
651
- const isInGroup = group.typeConfig?.childrenParams?.some(
652
- (id) => strict ? id === param.id : id.toLowerCase() === param.id.toLowerCase()
629
+ const instances = this.findFlattenTargets(
630
+ comp.slots,
631
+ flattenType,
632
+ compositionId,
633
+ compositionName,
634
+ strict
653
635
  );
654
- if (!isInGroup) {
655
- modifiedComponent = this.componentService.addParameterToGroup(
656
- modifiedComponent,
657
- groupId,
658
- param.id,
659
- findOptions
660
- );
661
- componentModified = true;
636
+ if (instances.length > 0) {
637
+ flattenedByType.set(flattenType, instances);
638
+ if (missingFlattenTypes.includes(flattenType)) {
639
+ foundMissingFlattenTypes.add(flattenType);
640
+ }
662
641
  }
663
642
  }
664
643
  }
665
- }
666
- if (componentModified && !whatIf) {
667
- await this.componentService.saveComponent(targetFilePath, modifiedComponent);
668
- }
669
- const compositions = await this.compositionService.findCompositionsByTypes(
670
- fullCompositionsDir,
671
- compositionTypes,
672
- findOptions
673
- );
674
- let modifiedCompositions = 0;
675
- let propagatedInstances = 0;
676
- for (const { composition, filePath } of compositions) {
677
- const rootOverrides = this.compositionService.getRootOverrides(composition);
678
- const instances = this.compositionService.findComponentInstances(
679
- composition,
680
- targetComponentType,
681
- findOptions
682
- );
683
- if (instances.length === 0) {
684
- continue;
685
- }
686
- const valuesToPropagate = {};
687
- for (const param of resolvedParams) {
688
- if (rootOverrides[param.id]) {
689
- valuesToPropagate[param.id] = rootOverrides[param.id];
644
+ const resolvedRefIds = /* @__PURE__ */ new Map();
645
+ for (const [flattenType, instances] of flattenedByType) {
646
+ const refIds = [];
647
+ for (const inst of instances) {
648
+ const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
649
+ refIds.push(existingId ?? inst.determinisiticId);
690
650
  }
651
+ resolvedRefIds.set(flattenType, refIds);
691
652
  }
692
- if (Object.keys(valuesToPropagate).length === 0) {
693
- continue;
653
+ for (const [flattenType] of flattenedByType) {
654
+ entry.entry.fields[flattenType] = {
655
+ type: "contentReference",
656
+ value: resolvedRefIds.get(flattenType)
657
+ };
694
658
  }
695
- let compositionModified = false;
696
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
697
- const instanceUpdates = [];
698
- for (const { instance, instanceId } of instances) {
699
- const instanceName = instance._id ?? instanceId;
700
- this.compositionService.setInstanceParameters(instance, valuesToPropagate);
701
- compositionModified = true;
702
- propagatedInstances++;
703
- instanceUpdates.push(
704
- `${targetComponentType} "${instanceName}": ${Object.keys(valuesToPropagate).join(", ")}`
705
- );
659
+ if (flattenComponentIds.length > 0) {
660
+ this.transformContentReferences(entry);
706
661
  }
707
- if (compositionModified) {
708
- this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
709
- for (const update of instanceUpdates) {
710
- this.logger.detail(`\u2192 ${update}`);
711
- }
712
- if (!whatIf) {
713
- await this.compositionService.saveComposition(filePath, composition);
714
- }
715
- modifiedCompositions++;
662
+ const entryId = entry.entry._id;
663
+ const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${entryId}.json`);
664
+ this.logger.action(
665
+ whatIf,
666
+ "WRITE",
667
+ `${entriesDir}/${entryId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
668
+ );
669
+ if (!whatIf) {
670
+ await this.fileSystem.writeFile(entryFilePath, entry);
716
671
  }
717
- }
718
- let modifiedSourceComponents = 0;
719
- if (deleteSourceParameter) {
720
- for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
721
- let modifiedSource = { ...sourceComponent };
722
- let sourceComponentModified = false;
723
- for (const param of resolvedParams) {
724
- const exists = this.componentService.findParameter(modifiedSource, param.id, findOptions);
725
- if (exists) {
726
- this.logger.action(whatIf, "DELETE", `Parameter "${param.id}" from ${sourceType}`);
727
- modifiedSource = this.componentService.removeParameter(modifiedSource, param.id, findOptions);
728
- sourceComponentModified = true;
672
+ entriesFromCompositions++;
673
+ for (const [flattenType, instances] of flattenedByType) {
674
+ for (const inst of instances) {
675
+ const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
676
+ if (existingId) {
677
+ this.logger.info(
678
+ `Reusing existing entry ${existingId} for ${flattenType} (sourceItem match)`
679
+ );
680
+ entriesReused++;
681
+ continue;
729
682
  }
730
- }
731
- const beforeGroupCount = modifiedSource.parameters?.filter(
732
- (p) => this.componentService.isGroupParameter(p)
733
- ).length ?? 0;
734
- modifiedSource = this.componentService.removeEmptyGroups(modifiedSource);
735
- const afterGroupCount = modifiedSource.parameters?.filter(
736
- (p) => this.componentService.isGroupParameter(p)
737
- ).length ?? 0;
738
- if (afterGroupCount < beforeGroupCount) {
739
- const removedCount = beforeGroupCount - afterGroupCount;
683
+ const flatEntry = this.generateEntryFromFlattenedInstance(inst);
684
+ const flatEntryPath = this.fileSystem.joinPath(
685
+ entriesDirFull,
686
+ `${inst.determinisiticId}.json`
687
+ );
740
688
  this.logger.action(
741
689
  whatIf,
742
- "DELETE",
743
- `${removedCount} empty group(s) from ${sourceType}`
690
+ "WRITE",
691
+ `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
744
692
  );
745
- sourceComponentModified = true;
746
- }
747
- if (sourceComponentModified) {
748
- modifiedSourceComponents++;
749
- }
750
- if (sourceComponentModified && !whatIf) {
751
- await this.componentService.saveComponent(sourceFilePath, modifiedSource);
693
+ if (!whatIf) {
694
+ await this.fileSystem.writeFile(flatEntryPath, flatEntry);
695
+ }
696
+ entriesFromFlattened++;
752
697
  }
753
698
  }
754
- for (const { composition, filePath } of compositions) {
755
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
756
- const deleted = this.compositionService.deleteRootOverrides(
757
- composition,
758
- resolvedNames,
759
- findOptions
760
- );
761
- if (deleted) {
762
- this.logger.action(whatIf, "DELETE", `Root overrides from composition/${relativePath}`);
763
- this.logger.detail(`\u2192 Removed: ${resolvedNames.join(", ")}`);
764
- if (!whatIf) {
765
- await this.compositionService.saveComposition(filePath, composition);
699
+ }
700
+ if (flattenComponentIds.length > 0) {
701
+ for (const contentType of contentTypeMap.values()) {
702
+ for (const flattenType of flattenComponentIds) {
703
+ if (this.compareTypes(flattenType, contentType.id, strict)) {
704
+ continue;
766
705
  }
706
+ if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
707
+ continue;
708
+ }
709
+ contentType.fields.push({
710
+ id: flattenType,
711
+ name: flattenType,
712
+ type: "contentReference",
713
+ typeConfig: {
714
+ isMulti: true,
715
+ allowedContentTypes: [flattenType]
716
+ },
717
+ localizable: false
718
+ });
767
719
  }
768
720
  }
769
721
  }
770
- return {
771
- modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
772
- modifiedCompositions,
773
- propagatedInstances
774
- };
775
- }
776
- parsePipeSeparatedValues(value, strict) {
777
- const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
778
- const normalized = [];
779
- for (const entry of entries) {
780
- const exists = normalized.some(
781
- (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
722
+ for (const [typeName, contentType] of contentTypeMap) {
723
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
724
+ const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
725
+ const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
726
+ const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
727
+ this.logger.action(
728
+ whatIf,
729
+ "WRITE",
730
+ `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
782
731
  );
783
- if (!exists) {
784
- normalized.push(entry);
732
+ if (!whatIf) {
733
+ await this.fileSystem.writeFile(filePath, contentType);
785
734
  }
735
+ contentTypesWritten++;
786
736
  }
787
- return normalized;
788
- }
789
- };
790
-
791
- // src/cli/logger.ts
792
- import chalk from "chalk";
793
- var Logger = class {
794
- info(message) {
795
- console.log(`${chalk.blue("[INFO]")} ${message}`);
737
+ for (const [typeName, contentType] of flattenContentTypeMap) {
738
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
739
+ this.logger.action(
740
+ whatIf,
741
+ "WRITE",
742
+ `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
743
+ );
744
+ if (!whatIf) {
745
+ await this.fileSystem.writeFile(filePath, contentType);
746
+ }
747
+ contentTypesWritten++;
748
+ }
749
+ const neverFoundMissingTypes = missingFlattenTypes.filter(
750
+ (type) => !foundMissingFlattenTypes.has(type)
751
+ );
752
+ if (neverFoundMissingTypes.length > 0) {
753
+ this.logger.warn(
754
+ `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
755
+ );
756
+ }
757
+ return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened, entriesReused };
796
758
  }
797
- success(message) {
798
- console.log(`${chalk.green("[DONE]")} ${message}`);
759
+ // --- Content Type Generation ---
760
+ generateContentType(component) {
761
+ const fields = [];
762
+ if (component.parameters) {
763
+ for (const param of component.parameters) {
764
+ fields.push(this.parameterToField(param));
765
+ }
766
+ }
767
+ return {
768
+ id: component.id,
769
+ name: component.name,
770
+ fields
771
+ };
799
772
  }
800
- error(message) {
801
- console.log(`${chalk.red("[ERROR]")} ${message}`);
773
+ parameterToField(param) {
774
+ const field = {
775
+ id: param.id,
776
+ name: param.name,
777
+ type: param.type
778
+ };
779
+ if (param.helpText !== void 0) {
780
+ field.helpText = param.helpText;
781
+ }
782
+ if (param.localizable !== void 0) {
783
+ field.localizable = param.localizable;
784
+ }
785
+ if (param.typeConfig !== void 0) {
786
+ field.typeConfig = param.typeConfig;
787
+ }
788
+ return field;
802
789
  }
803
- warn(message) {
804
- console.log(`${chalk.yellow("[WARN]")} ${message}`);
790
+ // --- Entry Generation ---
791
+ generateEntryFromComposition(composition) {
792
+ const comp = composition.composition;
793
+ const compositionSpecificKeys = /* @__PURE__ */ new Set(["_id", "_name", "type", "parameters", "slots", "_overrides"]);
794
+ const extraRootProps = {};
795
+ for (const [key, value] of Object.entries(comp)) {
796
+ if (!compositionSpecificKeys.has(key) && value != null) {
797
+ extraRootProps[key] = value;
798
+ }
799
+ }
800
+ const wrapperKeys = /* @__PURE__ */ new Set(["composition"]);
801
+ const extraWrapperProps = {};
802
+ for (const [key, value] of Object.entries(composition)) {
803
+ if (!wrapperKeys.has(key) && value != null) {
804
+ extraWrapperProps[key] = value;
805
+ }
806
+ }
807
+ const entryId = computeGuidHash(`entry${comp._id}`);
808
+ const entryName = this.truncateName(comp._name ?? comp._id, 60);
809
+ return {
810
+ entry: {
811
+ _id: entryId,
812
+ _name: entryName,
813
+ type: comp.type,
814
+ fields: { ...comp.parameters ?? {} },
815
+ ...extraRootProps
816
+ },
817
+ ...extraWrapperProps
818
+ };
805
819
  }
806
- action(whatIf, actionType, message) {
807
- const prefix = whatIf ? chalk.yellow("[WOULD]") : chalk.green(`[${actionType}]`);
808
- console.log(`${prefix} ${message}`);
820
+ generateEntryFromFlattenedInstance(inst) {
821
+ const entryName = this.truncateName(
822
+ `${inst.componentType} (from ${inst.compositionName})`,
823
+ 60
824
+ );
825
+ return {
826
+ entry: {
827
+ _id: inst.determinisiticId,
828
+ _name: entryName,
829
+ type: inst.componentType,
830
+ fields: { ...inst.instance.parameters ?? {} }
831
+ }
832
+ };
809
833
  }
810
- detail(message) {
811
- console.log(` ${chalk.gray(message)}`);
834
+ // --- Flatten Tree Walking ---
835
+ findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
836
+ const results = [];
837
+ this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
838
+ return results;
812
839
  }
813
- };
814
-
815
- // src/cli/commands/propagate-root-component-property.ts
816
- function createPropagateRootComponentPropertyCommand() {
817
- const command = new Command("propagate-root-component-property");
818
- command.description(
819
- "Copies property definitions from a composition type's root component to a target component type, then propagates the actual values across all matching compositions."
820
- ).option(
840
+ walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
841
+ for (const [slotName, instances] of Object.entries(slots)) {
842
+ if (!Array.isArray(instances)) continue;
843
+ for (let i = 0; i < instances.length; i++) {
844
+ const instance = instances[i];
845
+ if (instance._pattern) {
846
+ continue;
847
+ }
848
+ const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
849
+ if (this.compareTypes(instance.type, targetType, strict)) {
850
+ const fullPath = `${compositionId}-${currentPath}`;
851
+ const deterministicId = computeGuidHash(fullPath);
852
+ results.push({
853
+ instance,
854
+ path: fullPath,
855
+ determinisiticId: deterministicId,
856
+ componentType: instance.type,
857
+ compositionId,
858
+ compositionName
859
+ });
860
+ }
861
+ if (instance.slots) {
862
+ this.walkSlots(
863
+ instance.slots,
864
+ targetType,
865
+ compositionId,
866
+ compositionName,
867
+ currentPath,
868
+ results,
869
+ strict
870
+ );
871
+ }
872
+ }
873
+ }
874
+ }
875
+ // --- Content Reference Transformation ---
876
+ transformContentReferences(entry) {
877
+ const dataResources = {};
878
+ for (const [fieldName, field] of Object.entries(entry.entry.fields)) {
879
+ if (field.type === "contentReference" && Array.isArray(field.value)) {
880
+ const entryIds = field.value;
881
+ const resourceKey = `ref-${entry.entry._id}-${fieldName}`;
882
+ field.value = `\${#jptr:/${resourceKey}/entries}`;
883
+ dataResources[resourceKey] = {
884
+ type: "uniformContentInternalReference",
885
+ variables: {
886
+ locale: "${locale}",
887
+ entryIds: entryIds.join(",")
888
+ }
889
+ };
890
+ }
891
+ }
892
+ if (Object.keys(dataResources).length > 0) {
893
+ entry.entry._dataResources = {
894
+ ...entry.entry._dataResources ?? {},
895
+ ...dataResources
896
+ };
897
+ }
898
+ }
899
+ // --- Source Item Matching ---
900
+ async buildSourceItemMap(entriesDirFull) {
901
+ const sourceItemMap = /* @__PURE__ */ new Map();
902
+ const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "*.json");
903
+ for (const filePath of entryFiles) {
904
+ try {
905
+ const entryData = await this.fileSystem.readFile(filePath);
906
+ const sourceItemField = entryData?.entry?.fields?.sourceItem;
907
+ if (sourceItemField?.value != null) {
908
+ sourceItemMap.set(String(sourceItemField.value), entryData.entry._id);
909
+ }
910
+ } catch {
911
+ continue;
912
+ }
913
+ }
914
+ return sourceItemMap;
915
+ }
916
+ findExistingEntryBySourceItem(inst, sourceItemMap) {
917
+ const sourceItemParam = inst.instance.parameters?.sourceItem;
918
+ if (sourceItemParam?.value == null) {
919
+ return void 0;
920
+ }
921
+ return sourceItemMap.get(String(sourceItemParam.value));
922
+ }
923
+ // --- Utilities ---
924
+ compareTypes(type1, type2, strict) {
925
+ if (strict) {
926
+ return type1 === type2;
927
+ }
928
+ return type1.toLowerCase() === type2.toLowerCase();
929
+ }
930
+ truncate(str, maxLength) {
931
+ if (str.length <= maxLength) return str;
932
+ return str.substring(0, maxLength - 3) + "...";
933
+ }
934
+ truncateName(name, maxLength) {
935
+ if (name.length <= maxLength) return name;
936
+ return name.substring(0, maxLength);
937
+ }
938
+ };
939
+ function computeGuidHash(guidOrSeed) {
940
+ let uuidStr;
941
+ if (isValidUuid(guidOrSeed)) {
942
+ uuidStr = formatAsBracedUuid(guidOrSeed);
943
+ } else {
944
+ const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
945
+ const hex = [
946
+ // First 4 bytes: little-endian in .NET Guid
947
+ hash[3].toString(16).padStart(2, "0"),
948
+ hash[2].toString(16).padStart(2, "0"),
949
+ hash[1].toString(16).padStart(2, "0"),
950
+ hash[0].toString(16).padStart(2, "0"),
951
+ "-",
952
+ // Bytes 4-5: little-endian
953
+ hash[5].toString(16).padStart(2, "0"),
954
+ hash[4].toString(16).padStart(2, "0"),
955
+ "-",
956
+ // Bytes 6-7: little-endian
957
+ hash[7].toString(16).padStart(2, "0"),
958
+ hash[6].toString(16).padStart(2, "0"),
959
+ "-",
960
+ // Bytes 8-9: big-endian
961
+ hash[8].toString(16).padStart(2, "0"),
962
+ hash[9].toString(16).padStart(2, "0"),
963
+ "-",
964
+ // Bytes 10-15: big-endian
965
+ hash[10].toString(16).padStart(2, "0"),
966
+ hash[11].toString(16).padStart(2, "0"),
967
+ hash[12].toString(16).padStart(2, "0"),
968
+ hash[13].toString(16).padStart(2, "0"),
969
+ hash[14].toString(16).padStart(2, "0"),
970
+ hash[15].toString(16).padStart(2, "0")
971
+ ].join("");
972
+ uuidStr = `{${hex}}`.toUpperCase();
973
+ }
974
+ const chars = uuidStr.split("");
975
+ chars[15] = "4";
976
+ const arr20 = ["8", "9", "A", "B"];
977
+ const hexVal = parseInt(chars[20], 16);
978
+ chars[20] = arr20[hexVal % 4];
979
+ return chars.join("").slice(1, -1).toLowerCase();
980
+ }
981
+ function isValidUuid(str) {
982
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
983
+ }
984
+ function formatAsBracedUuid(uuid) {
985
+ const clean = uuid.replace(/[{}]/g, "").toUpperCase();
986
+ return `{${clean}}`;
987
+ }
988
+
989
+ // src/core/services/id-regenerator.ts
990
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
991
+ function regenerateIds(value, basePath) {
992
+ return walkAndRegenerate(value, basePath);
993
+ }
994
+ function walkAndRegenerate(value, currentPath) {
995
+ if (value === null || value === void 0) {
996
+ return value;
997
+ }
998
+ if (Array.isArray(value)) {
999
+ return value.map((item, index) => walkAndRegenerate(item, `${currentPath}[${index}]`));
1000
+ }
1001
+ if (typeof value === "object") {
1002
+ const obj = value;
1003
+ const result = {};
1004
+ for (const [key, val] of Object.entries(obj)) {
1005
+ const childPath = `${currentPath}.${key}`;
1006
+ if (key === "_id" && typeof val === "string" && UUID_PATTERN.test(val)) {
1007
+ result[key] = computeGuidHash(val + childPath);
1008
+ } else {
1009
+ result[key] = walkAndRegenerate(val, childPath);
1010
+ }
1011
+ }
1012
+ return result;
1013
+ }
1014
+ return value;
1015
+ }
1016
+
1017
+ // src/core/services/property-propagator.service.ts
1018
+ var PropertyPropagatorService = class {
1019
+ constructor(fileSystem, componentService, compositionService, logger) {
1020
+ this.fileSystem = fileSystem;
1021
+ this.componentService = componentService;
1022
+ this.compositionService = compositionService;
1023
+ this.logger = logger;
1024
+ }
1025
+ async propagate(options) {
1026
+ const {
1027
+ rootDir,
1028
+ componentsDir,
1029
+ compositionsDir,
1030
+ compositionType,
1031
+ property,
1032
+ targetComponentType,
1033
+ targetGroup,
1034
+ whatIf,
1035
+ strict,
1036
+ deleteSourceParameter
1037
+ } = options;
1038
+ const findOptions = { strict };
1039
+ const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
1040
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
1041
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
1042
+ const sourceComponents = [];
1043
+ for (const sourceType of compositionTypes) {
1044
+ this.logger.info(`Loading component: ${sourceType}`);
1045
+ const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
1046
+ sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
1047
+ }
1048
+ this.logger.info(`Loading component: ${targetComponentType}`);
1049
+ const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
1050
+ const propertyNames = property.split("|").map((p) => p.trim()).filter((p) => p.length > 0);
1051
+ const resolvedParams = [];
1052
+ const resolvedNames = [];
1053
+ for (const { sourceType, sourceComponent } of sourceComponents) {
1054
+ const { parameters: sourceParams, notFound } = this.componentService.resolveProperties(
1055
+ sourceComponent,
1056
+ propertyNames,
1057
+ findOptions
1058
+ );
1059
+ if (notFound.length > 0) {
1060
+ this.logger.warn(`Property "${notFound.join(", ")}" not found on component "${sourceType}"`);
1061
+ continue;
1062
+ }
1063
+ for (const param of sourceParams) {
1064
+ const exists = resolvedParams.some(
1065
+ (existing) => strict ? existing.id === param.id : existing.id.toLowerCase() === param.id.toLowerCase()
1066
+ );
1067
+ if (!exists) {
1068
+ resolvedParams.push(param);
1069
+ resolvedNames.push(param.id);
1070
+ }
1071
+ }
1072
+ }
1073
+ const groupSources = [];
1074
+ for (const { sourceType, sourceComponent } of sourceComponents) {
1075
+ for (const name of propertyNames) {
1076
+ const param = this.componentService.findParameter(sourceComponent, name, findOptions);
1077
+ if (param && this.componentService.isGroupParameter(param)) {
1078
+ const groupSource = `${sourceType}.${name}`;
1079
+ if (!groupSources.includes(groupSource)) {
1080
+ groupSources.push(groupSource);
1081
+ }
1082
+ }
1083
+ }
1084
+ }
1085
+ if (groupSources.length > 0) {
1086
+ this.logger.info(
1087
+ `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
1088
+ );
1089
+ } else {
1090
+ this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
1091
+ }
1092
+ let modifiedComponent = { ...targetComponent };
1093
+ let componentModified = false;
1094
+ const groupId = this.componentService.generateGroupId(targetGroup);
1095
+ const existingGroup = this.componentService.findParameter(
1096
+ modifiedComponent,
1097
+ groupId,
1098
+ findOptions
1099
+ );
1100
+ if (!existingGroup) {
1101
+ this.logger.action(whatIf, "CREATE", `Group "${targetGroup}" on ${targetComponentType}`);
1102
+ modifiedComponent = this.componentService.ensureGroupExists(
1103
+ modifiedComponent,
1104
+ groupId,
1105
+ targetGroup,
1106
+ findOptions
1107
+ );
1108
+ componentModified = true;
1109
+ }
1110
+ for (const param of resolvedParams) {
1111
+ const existingParam = this.componentService.findParameter(
1112
+ modifiedComponent,
1113
+ param.id,
1114
+ findOptions
1115
+ );
1116
+ if (!existingParam) {
1117
+ this.logger.action(
1118
+ whatIf,
1119
+ "COPY",
1120
+ `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
1121
+ );
1122
+ modifiedComponent = this.componentService.addParameterToComponent(
1123
+ modifiedComponent,
1124
+ param,
1125
+ findOptions
1126
+ );
1127
+ modifiedComponent = this.componentService.addParameterToGroup(
1128
+ modifiedComponent,
1129
+ groupId,
1130
+ param.id,
1131
+ findOptions
1132
+ );
1133
+ componentModified = true;
1134
+ } else {
1135
+ this.logger.info(`Parameter "${param.id}" already exists on ${targetComponentType}`);
1136
+ const group = this.componentService.findParameter(
1137
+ modifiedComponent,
1138
+ groupId,
1139
+ findOptions
1140
+ );
1141
+ if (group && this.componentService.isGroupParameter(group)) {
1142
+ const isInGroup = group.typeConfig?.childrenParams?.some(
1143
+ (id) => strict ? id === param.id : id.toLowerCase() === param.id.toLowerCase()
1144
+ );
1145
+ if (!isInGroup) {
1146
+ modifiedComponent = this.componentService.addParameterToGroup(
1147
+ modifiedComponent,
1148
+ groupId,
1149
+ param.id,
1150
+ findOptions
1151
+ );
1152
+ componentModified = true;
1153
+ }
1154
+ }
1155
+ }
1156
+ }
1157
+ if (componentModified && !whatIf) {
1158
+ await this.componentService.saveComponent(targetFilePath, modifiedComponent);
1159
+ }
1160
+ const compositions = await this.compositionService.findCompositionsByTypes(
1161
+ fullCompositionsDir,
1162
+ compositionTypes,
1163
+ findOptions
1164
+ );
1165
+ let modifiedCompositions = 0;
1166
+ let propagatedInstances = 0;
1167
+ for (const { composition, filePath } of compositions) {
1168
+ const rootOverrides = this.compositionService.getRootOverrides(composition);
1169
+ const instances = this.compositionService.findComponentInstances(
1170
+ composition,
1171
+ targetComponentType,
1172
+ findOptions
1173
+ );
1174
+ if (instances.length === 0) {
1175
+ continue;
1176
+ }
1177
+ const valuesToPropagate = {};
1178
+ for (const param of resolvedParams) {
1179
+ if (rootOverrides[param.id]) {
1180
+ valuesToPropagate[param.id] = rootOverrides[param.id];
1181
+ }
1182
+ }
1183
+ if (Object.keys(valuesToPropagate).length === 0) {
1184
+ continue;
1185
+ }
1186
+ let compositionModified = false;
1187
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
1188
+ const instanceUpdates = [];
1189
+ for (const { instance, instanceId } of instances) {
1190
+ const instanceName = instance._id ?? instanceId;
1191
+ const clonedValues = regenerateIds(valuesToPropagate, instanceName);
1192
+ this.compositionService.setInstanceParameters(instance, clonedValues);
1193
+ compositionModified = true;
1194
+ propagatedInstances++;
1195
+ instanceUpdates.push(
1196
+ `${targetComponentType} "${instanceName}": ${Object.keys(valuesToPropagate).join(", ")}`
1197
+ );
1198
+ }
1199
+ if (compositionModified) {
1200
+ this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
1201
+ for (const update of instanceUpdates) {
1202
+ this.logger.detail(`\u2192 ${update}`);
1203
+ }
1204
+ if (!whatIf) {
1205
+ await this.compositionService.saveComposition(filePath, composition);
1206
+ }
1207
+ modifiedCompositions++;
1208
+ }
1209
+ }
1210
+ let modifiedSourceComponents = 0;
1211
+ if (deleteSourceParameter) {
1212
+ for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
1213
+ let modifiedSource = { ...sourceComponent };
1214
+ let sourceComponentModified = false;
1215
+ for (const param of resolvedParams) {
1216
+ const exists = this.componentService.findParameter(modifiedSource, param.id, findOptions);
1217
+ if (exists) {
1218
+ this.logger.action(whatIf, "DELETE", `Parameter "${param.id}" from ${sourceType}`);
1219
+ modifiedSource = this.componentService.removeParameter(modifiedSource, param.id, findOptions);
1220
+ sourceComponentModified = true;
1221
+ }
1222
+ }
1223
+ const beforeGroupCount = modifiedSource.parameters?.filter(
1224
+ (p) => this.componentService.isGroupParameter(p)
1225
+ ).length ?? 0;
1226
+ modifiedSource = this.componentService.removeEmptyGroups(modifiedSource);
1227
+ const afterGroupCount = modifiedSource.parameters?.filter(
1228
+ (p) => this.componentService.isGroupParameter(p)
1229
+ ).length ?? 0;
1230
+ if (afterGroupCount < beforeGroupCount) {
1231
+ const removedCount = beforeGroupCount - afterGroupCount;
1232
+ this.logger.action(
1233
+ whatIf,
1234
+ "DELETE",
1235
+ `${removedCount} empty group(s) from ${sourceType}`
1236
+ );
1237
+ sourceComponentModified = true;
1238
+ }
1239
+ if (sourceComponentModified) {
1240
+ modifiedSourceComponents++;
1241
+ }
1242
+ if (sourceComponentModified && !whatIf) {
1243
+ await this.componentService.saveComponent(sourceFilePath, modifiedSource);
1244
+ }
1245
+ }
1246
+ for (const { composition, filePath } of compositions) {
1247
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
1248
+ const deleted = this.compositionService.deleteRootOverrides(
1249
+ composition,
1250
+ resolvedNames,
1251
+ findOptions
1252
+ );
1253
+ if (deleted) {
1254
+ this.logger.action(whatIf, "DELETE", `Root overrides from composition/${relativePath}`);
1255
+ this.logger.detail(`\u2192 Removed: ${resolvedNames.join(", ")}`);
1256
+ if (!whatIf) {
1257
+ await this.compositionService.saveComposition(filePath, composition);
1258
+ }
1259
+ }
1260
+ }
1261
+ }
1262
+ return {
1263
+ modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
1264
+ modifiedCompositions,
1265
+ propagatedInstances
1266
+ };
1267
+ }
1268
+ parsePipeSeparatedValues(value, strict) {
1269
+ const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1270
+ const normalized = [];
1271
+ for (const entry of entries) {
1272
+ const exists = normalized.some(
1273
+ (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
1274
+ );
1275
+ if (!exists) {
1276
+ normalized.push(entry);
1277
+ }
1278
+ }
1279
+ return normalized;
1280
+ }
1281
+ };
1282
+
1283
+ // src/cli/logger.ts
1284
+ import chalk from "chalk";
1285
+ var Logger = class {
1286
+ info(message) {
1287
+ console.log(`${chalk.blue("[INFO]")} ${message}`);
1288
+ }
1289
+ success(message) {
1290
+ console.log(`${chalk.green("[DONE]")} ${message}`);
1291
+ }
1292
+ error(message) {
1293
+ console.log(`${chalk.red("[ERROR]")} ${message}`);
1294
+ }
1295
+ warn(message) {
1296
+ console.log(`${chalk.yellow("[WARN]")} ${message}`);
1297
+ }
1298
+ action(whatIf, actionType, message) {
1299
+ const prefix = whatIf ? chalk.yellow("[WOULD]") : chalk.green(`[${actionType}]`);
1300
+ console.log(`${prefix} ${message}`);
1301
+ }
1302
+ detail(message) {
1303
+ console.log(` ${chalk.gray(message)}`);
1304
+ }
1305
+ };
1306
+
1307
+ // src/cli/commands/propagate-root-component-property.ts
1308
+ function createPropagateRootComponentPropertyCommand() {
1309
+ const command = new Command("propagate-root-component-property");
1310
+ command.description(
1311
+ "Copies property definitions from a composition type's root component to a target component type, then propagates the actual values across all matching compositions."
1312
+ ).option(
821
1313
  "--compositionType <type>",
822
1314
  "The composition type(s) to process. Supports pipe-separated lists (e.g., HomePage|LandingPage)"
823
1315
  ).option("--property <properties>", "Pipe-separated list of properties and/or groups to copy").option(
@@ -3159,74 +3651,17 @@ var ComponentAdderService = class {
3159
3651
  }
3160
3652
  return modified;
3161
3653
  }
3162
- };
3163
-
3164
- // src/cli/commands/add-component.ts
3165
- function createAddComponentCommand() {
3166
- const command = new Command7("add-component");
3167
- command.description("Adds a component instance to existing component slots across all compositions.").option("--parentComponentType <type>", "The component type that owns the slot").option("--slot <slotId>", "The slot ID where the component will be added").option("--newComponentType <type>", "The component type to add").option("--parameters <params>", "Pipe-separated parameter assignments (key1:value1|key2:value2)").hook("preAction", (thisCommand) => {
3168
- const opts = thisCommand.opts();
3169
- const requiredOptions = [
3170
- { name: "parentComponentType", flag: "--parentComponentType" },
3171
- { name: "slot", flag: "--slot" },
3172
- { name: "newComponentType", flag: "--newComponentType" }
3173
- ];
3174
- const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
3175
- if (missing.length > 0) {
3176
- console.error(`error: missing required options: ${missing.join(", ")}`);
3177
- process.exit(1);
3178
- }
3179
- }).action(async (opts, cmd) => {
3180
- const globalOpts = cmd.optsWithGlobals();
3181
- const options = {
3182
- ...globalOpts,
3183
- parentComponentType: opts.parentComponentType,
3184
- slot: opts.slot,
3185
- newComponentType: opts.newComponentType,
3186
- parameters: opts.parameters
3187
- };
3188
- const logger = new Logger();
3189
- const fileSystem = new FileSystemService();
3190
- const componentService = new ComponentService(fileSystem);
3191
- const adder = new ComponentAdderService(fileSystem, componentService, logger);
3192
- try {
3193
- const result = await adder.addComponent({
3194
- rootDir: options.rootDir,
3195
- componentsDir: options.componentsDir,
3196
- compositionsDir: options.compositionsDir,
3197
- compositionPatternsDir: options.compositionPatternsDir,
3198
- componentPatternsDir: options.componentPatternsDir,
3199
- parentComponentType: options.parentComponentType,
3200
- slot: options.slot,
3201
- newComponentType: options.newComponentType,
3202
- parameters: options.parameters,
3203
- whatIf: options.whatIf ?? false,
3204
- strict: options.strict ?? false
3205
- });
3206
- logger.success(
3207
- `Added component: ${result.allowedComponentsUpdated ? "1 component definition updated, " : ""}${result.instancesAdded} instance(s) added across ${result.compositionsModified} composition(s), ${result.compositionPatternsModified} composition pattern(s), ${result.componentPatternsModified} component pattern(s)`
3208
- );
3209
- } catch (error) {
3210
- if (error instanceof TransformError) {
3211
- logger.error(error.message);
3212
- process.exit(1);
3213
- }
3214
- throw error;
3215
- }
3216
- });
3217
- return command;
3218
- }
3654
+ };
3219
3655
 
3220
- // src/cli/commands/add-component-pattern.ts
3221
- import { Command as Command8 } from "commander";
3222
- function createAddComponentPatternCommand() {
3223
- const command = new Command8("add-component-pattern");
3224
- command.description("Adds a component pattern instance to existing component slots across all compositions.").option("--parentComponentType <type>", "The component type that owns the slot").option("--slot <slotId>", "The slot ID where the component pattern will be added").option("--componentPatternId <id>", "The component pattern ID to add").hook("preAction", (thisCommand) => {
3656
+ // src/cli/commands/add-component.ts
3657
+ function createAddComponentCommand() {
3658
+ const command = new Command7("add-component");
3659
+ command.description("Adds a component instance to existing component slots across all compositions.").option("--parentComponentType <type>", "The component type that owns the slot").option("--slot <slotId>", "The slot ID where the component will be added").option("--newComponentType <type>", "The component type to add").option("--parameters <params>", "Pipe-separated parameter assignments (key1:value1|key2:value2)").hook("preAction", (thisCommand) => {
3225
3660
  const opts = thisCommand.opts();
3226
3661
  const requiredOptions = [
3227
3662
  { name: "parentComponentType", flag: "--parentComponentType" },
3228
3663
  { name: "slot", flag: "--slot" },
3229
- { name: "componentPatternId", flag: "--componentPatternId" }
3664
+ { name: "newComponentType", flag: "--newComponentType" }
3230
3665
  ];
3231
3666
  const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
3232
3667
  if (missing.length > 0) {
@@ -3239,288 +3674,51 @@ function createAddComponentPatternCommand() {
3239
3674
  ...globalOpts,
3240
3675
  parentComponentType: opts.parentComponentType,
3241
3676
  slot: opts.slot,
3242
- componentPatternId: opts.componentPatternId
3677
+ newComponentType: opts.newComponentType,
3678
+ parameters: opts.parameters
3243
3679
  };
3244
3680
  const logger = new Logger();
3245
3681
  const fileSystem = new FileSystemService();
3246
3682
  const componentService = new ComponentService(fileSystem);
3247
3683
  const adder = new ComponentAdderService(fileSystem, componentService, logger);
3248
3684
  try {
3249
- const result = await adder.addComponentPattern({
3685
+ const result = await adder.addComponent({
3250
3686
  rootDir: options.rootDir,
3251
- componentsDir: options.componentsDir,
3252
- compositionsDir: options.compositionsDir,
3253
- compositionPatternsDir: options.compositionPatternsDir,
3254
- componentPatternsDir: options.componentPatternsDir,
3255
- parentComponentType: options.parentComponentType,
3256
- slot: options.slot,
3257
- componentPatternId: options.componentPatternId,
3258
- whatIf: options.whatIf ?? false,
3259
- strict: options.strict ?? false
3260
- });
3261
- logger.success(
3262
- `Added component pattern: ${result.allowedComponentsUpdated ? "1 component definition updated, " : ""}${result.instancesAdded} instance(s) added across ${result.compositionsModified} composition(s), ${result.compositionPatternsModified} composition pattern(s), ${result.componentPatternsModified} component pattern(s)`
3263
- );
3264
- } catch (error) {
3265
- if (error instanceof TransformError) {
3266
- logger.error(error.message);
3267
- process.exit(1);
3268
- }
3269
- throw error;
3270
- }
3271
- });
3272
- return command;
3273
- }
3274
-
3275
- // src/cli/commands/propagate-root-component-slot.ts
3276
- import { Command as Command9 } from "commander";
3277
-
3278
- // src/core/services/slot-propagator.service.ts
3279
- var SlotPropagatorService = class {
3280
- constructor(fileSystem, componentService, compositionService, logger) {
3281
- this.fileSystem = fileSystem;
3282
- this.componentService = componentService;
3283
- this.compositionService = compositionService;
3284
- this.logger = logger;
3285
- }
3286
- async propagate(options) {
3287
- const {
3288
- rootDir,
3289
- componentsDir,
3290
- compositionsDir,
3291
- compositionType,
3292
- slot,
3293
- targetComponentType,
3294
- targetSlot,
3295
- whatIf,
3296
- strict,
3297
- deleteSourceSlot
3298
- } = options;
3299
- const findOptions = { strict };
3300
- const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
3301
- const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
3302
- const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
3303
- const sourceComponents = [];
3304
- for (const sourceType of compositionTypes) {
3305
- this.logger.info(`Loading component: ${sourceType}`);
3306
- const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
3307
- sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
3308
- }
3309
- this.logger.info(`Loading component: ${targetComponentType}`);
3310
- const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
3311
- const slotNames = slot.split("|").map((s) => s.trim()).filter((s) => s.length > 0);
3312
- const resolvedSlots = [];
3313
- const resolvedSlotIds = [];
3314
- for (const { sourceType, sourceComponent } of sourceComponents) {
3315
- const notFound = [];
3316
- for (const slotName of slotNames) {
3317
- const slotDef = this.componentService.findSlot(sourceComponent, slotName, findOptions);
3318
- if (!slotDef) {
3319
- notFound.push(slotName);
3320
- continue;
3321
- }
3322
- const exists = resolvedSlots.some(
3323
- (existing) => strict ? existing.id === slotDef.id : existing.id.toLowerCase() === slotDef.id.toLowerCase()
3324
- );
3325
- if (!exists) {
3326
- resolvedSlots.push(slotDef);
3327
- resolvedSlotIds.push(slotDef.id);
3328
- }
3329
- }
3330
- if (notFound.length > 0) {
3331
- throw new PropertyNotFoundError(`Slot "${notFound.join(", ")}"`, sourceType);
3332
- }
3333
- }
3334
- this.logger.info(`Resolved slots: ${resolvedSlotIds.join(", ")}`);
3335
- let modifiedComponent = { ...targetComponent };
3336
- let componentModified = false;
3337
- const existingSlot = this.componentService.findSlot(modifiedComponent, targetSlot, findOptions);
3338
- if (!existingSlot) {
3339
- const mergedAllowedComponents = this.mergeAllowedComponents(resolvedSlots);
3340
- this.logger.action(whatIf, "CREATE", `Slot "${targetSlot}" on ${targetComponentType}`);
3341
- modifiedComponent = this.componentService.addSlot(modifiedComponent, {
3342
- id: targetSlot,
3343
- name: targetSlot,
3344
- allowedComponents: mergedAllowedComponents
3345
- });
3346
- componentModified = true;
3347
- } else {
3348
- const mergedAllowedComponents = this.mergeAllowedComponents([existingSlot, ...resolvedSlots]);
3349
- const existingAllowed = existingSlot.allowedComponents ?? [];
3350
- if (mergedAllowedComponents.length > existingAllowed.length) {
3351
- this.logger.action(
3352
- whatIf,
3353
- "UPDATE",
3354
- `Slot "${targetSlot}" allowedComponents on ${targetComponentType}`
3355
- );
3356
- modifiedComponent = this.componentService.updateSlotAllowedComponents(
3357
- modifiedComponent,
3358
- targetSlot,
3359
- mergedAllowedComponents,
3360
- findOptions
3361
- );
3362
- componentModified = true;
3363
- } else {
3364
- this.logger.info(`Slot "${targetSlot}" already exists on ${targetComponentType}`);
3365
- }
3366
- }
3367
- if (componentModified && !whatIf) {
3368
- await this.componentService.saveComponent(targetFilePath, modifiedComponent);
3369
- }
3370
- const compositions = await this.compositionService.findCompositionsByTypes(
3371
- fullCompositionsDir,
3372
- compositionTypes,
3373
- findOptions
3374
- );
3375
- let modifiedCompositions = 0;
3376
- let propagatedInstances = 0;
3377
- for (const { composition, filePath } of compositions) {
3378
- const rootSlots = composition.composition.slots ?? {};
3379
- const instances = this.compositionService.findComponentInstances(
3380
- composition,
3381
- targetComponentType,
3382
- findOptions
3383
- );
3384
- if (instances.length === 0) {
3385
- continue;
3386
- }
3387
- const componentsToPropagate = [];
3388
- for (const slotDef of resolvedSlots) {
3389
- const slotContent = rootSlots[slotDef.id] ?? [];
3390
- componentsToPropagate.push(...slotContent);
3391
- }
3392
- if (componentsToPropagate.length === 0) {
3393
- continue;
3394
- }
3395
- let compositionModified = false;
3396
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
3397
- const instanceUpdates = [];
3398
- for (const { instance, instanceId } of instances) {
3399
- const instanceName = instance._id ?? instanceId;
3400
- const clonedComponents = this.deepCloneComponents(componentsToPropagate);
3401
- this.addComponentsToInstanceSlot(instance, targetSlot, clonedComponents);
3402
- compositionModified = true;
3403
- propagatedInstances++;
3404
- instanceUpdates.push(
3405
- `${targetComponentType} "${instanceName}": ${componentsToPropagate.length} component(s) \u2192 ${targetSlot}`
3406
- );
3407
- }
3408
- if (compositionModified) {
3409
- this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
3410
- for (const update of instanceUpdates) {
3411
- this.logger.detail(`\u2192 ${update}`);
3412
- }
3413
- if (!whatIf) {
3414
- await this.compositionService.saveComposition(filePath, composition);
3415
- }
3416
- modifiedCompositions++;
3417
- }
3418
- }
3419
- let modifiedSourceComponents = 0;
3420
- if (deleteSourceSlot) {
3421
- for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
3422
- let modifiedSource = { ...sourceComponent };
3423
- let sourceComponentModified = false;
3424
- for (const slotDef of resolvedSlots) {
3425
- const slotToDelete = this.componentService.findSlot(modifiedSource, slotDef.id, findOptions);
3426
- if (!slotToDelete) {
3427
- continue;
3428
- }
3429
- this.logger.action(whatIf, "DELETE", `Slot "${slotDef.id}" from ${sourceType}`);
3430
- modifiedSource = this.componentService.removeSlot(modifiedSource, slotDef.id, findOptions);
3431
- sourceComponentModified = true;
3432
- }
3433
- if (sourceComponentModified) {
3434
- modifiedSourceComponents++;
3435
- }
3436
- if (sourceComponentModified && !whatIf) {
3437
- await this.componentService.saveComponent(sourceFilePath, modifiedSource);
3438
- }
3439
- }
3440
- for (const { composition, filePath } of compositions) {
3441
- const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
3442
- let cleared = false;
3443
- for (const slotDef of resolvedSlots) {
3444
- if (composition.composition.slots?.[slotDef.id]?.length) {
3445
- composition.composition.slots[slotDef.id] = [];
3446
- cleared = true;
3447
- }
3448
- }
3449
- if (cleared) {
3450
- this.logger.action(whatIf, "CLEAR", `Root slots in composition/${relativePath}`);
3451
- this.logger.detail(`\u2192 Cleared: ${resolvedSlotIds.join(", ")}`);
3452
- if (!whatIf) {
3453
- await this.compositionService.saveComposition(filePath, composition);
3454
- }
3455
- }
3456
- }
3457
- }
3458
- return {
3459
- modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
3460
- modifiedCompositions,
3461
- propagatedInstances
3462
- };
3463
- }
3464
- mergeAllowedComponents(slots) {
3465
- const allowed = /* @__PURE__ */ new Set();
3466
- for (const slot of slots) {
3467
- for (const comp of slot.allowedComponents ?? []) {
3468
- allowed.add(comp);
3469
- }
3470
- }
3471
- return Array.from(allowed).sort();
3472
- }
3473
- deepCloneComponents(components) {
3474
- return JSON.parse(JSON.stringify(components));
3475
- }
3476
- addComponentsToInstanceSlot(instance, slotName, components) {
3477
- if (!instance.slots) {
3478
- instance.slots = {};
3479
- }
3480
- if (!instance.slots[slotName]) {
3481
- instance.slots[slotName] = [];
3482
- }
3483
- instance.slots[slotName].push(...components);
3484
- }
3485
- parsePipeSeparatedValues(value, strict) {
3486
- const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3487
- const normalized = [];
3488
- for (const entry of entries) {
3489
- const exists = normalized.some(
3490
- (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
3687
+ componentsDir: options.componentsDir,
3688
+ compositionsDir: options.compositionsDir,
3689
+ compositionPatternsDir: options.compositionPatternsDir,
3690
+ componentPatternsDir: options.componentPatternsDir,
3691
+ parentComponentType: options.parentComponentType,
3692
+ slot: options.slot,
3693
+ newComponentType: options.newComponentType,
3694
+ parameters: options.parameters,
3695
+ whatIf: options.whatIf ?? false,
3696
+ strict: options.strict ?? false
3697
+ });
3698
+ logger.success(
3699
+ `Added component: ${result.allowedComponentsUpdated ? "1 component definition updated, " : ""}${result.instancesAdded} instance(s) added across ${result.compositionsModified} composition(s), ${result.compositionPatternsModified} composition pattern(s), ${result.componentPatternsModified} component pattern(s)`
3491
3700
  );
3492
- if (!exists) {
3493
- normalized.push(entry);
3701
+ } catch (error) {
3702
+ if (error instanceof TransformError) {
3703
+ logger.error(error.message);
3704
+ process.exit(1);
3494
3705
  }
3706
+ throw error;
3495
3707
  }
3496
- return normalized;
3497
- }
3498
- };
3708
+ });
3709
+ return command;
3710
+ }
3499
3711
 
3500
- // src/cli/commands/propagate-root-component-slot.ts
3501
- function createPropagateRootComponentSlotCommand() {
3502
- const command = new Command9("propagate-root-component-slot");
3503
- command.description(
3504
- "Copies slot definitions from a composition type's root component to a target component type, then moves all component instances from that slot across all matching compositions."
3505
- ).option(
3506
- "--compositionType <type>",
3507
- "The composition type(s) to process. Supports pipe-separated lists (e.g., HomePage|LandingPage)"
3508
- ).option("--slot <slots>", "Pipe-separated list of slot names to copy from the source component").option(
3509
- "--targetComponentType <type>",
3510
- "The component type that will receive the copied slots"
3511
- ).option(
3512
- "--targetSlot <slot>",
3513
- "The slot name on the target component where the contents will be placed"
3514
- ).option(
3515
- "--deleteSourceSlot",
3516
- "Delete the original slots from the source component after propagation"
3517
- ).hook("preAction", (thisCommand) => {
3712
+ // src/cli/commands/add-component-pattern.ts
3713
+ import { Command as Command8 } from "commander";
3714
+ function createAddComponentPatternCommand() {
3715
+ const command = new Command8("add-component-pattern");
3716
+ command.description("Adds a component pattern instance to existing component slots across all compositions.").option("--parentComponentType <type>", "The component type that owns the slot").option("--slot <slotId>", "The slot ID where the component pattern will be added").option("--componentPatternId <id>", "The component pattern ID to add").hook("preAction", (thisCommand) => {
3518
3717
  const opts = thisCommand.opts();
3519
3718
  const requiredOptions = [
3520
- { name: "compositionType", flag: "--compositionType" },
3719
+ { name: "parentComponentType", flag: "--parentComponentType" },
3521
3720
  { name: "slot", flag: "--slot" },
3522
- { name: "targetComponentType", flag: "--targetComponentType" },
3523
- { name: "targetSlot", flag: "--targetSlot" }
3721
+ { name: "componentPatternId", flag: "--componentPatternId" }
3524
3722
  ];
3525
3723
  const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
3526
3724
  if (missing.length > 0) {
@@ -3531,37 +3729,29 @@ function createPropagateRootComponentSlotCommand() {
3531
3729
  const globalOpts = cmd.optsWithGlobals();
3532
3730
  const options = {
3533
3731
  ...globalOpts,
3534
- compositionType: opts.compositionType,
3732
+ parentComponentType: opts.parentComponentType,
3535
3733
  slot: opts.slot,
3536
- targetComponentType: opts.targetComponentType,
3537
- targetSlot: opts.targetSlot,
3538
- deleteSourceSlot: opts.deleteSourceSlot
3734
+ componentPatternId: opts.componentPatternId
3539
3735
  };
3540
3736
  const logger = new Logger();
3541
3737
  const fileSystem = new FileSystemService();
3542
3738
  const componentService = new ComponentService(fileSystem);
3543
- const compositionService = new CompositionService(fileSystem);
3544
- const propagator = new SlotPropagatorService(
3545
- fileSystem,
3546
- componentService,
3547
- compositionService,
3548
- logger
3549
- );
3739
+ const adder = new ComponentAdderService(fileSystem, componentService, logger);
3550
3740
  try {
3551
- const result = await propagator.propagate({
3741
+ const result = await adder.addComponentPattern({
3552
3742
  rootDir: options.rootDir,
3553
3743
  componentsDir: options.componentsDir,
3554
3744
  compositionsDir: options.compositionsDir,
3555
- compositionType: options.compositionType,
3745
+ compositionPatternsDir: options.compositionPatternsDir,
3746
+ componentPatternsDir: options.componentPatternsDir,
3747
+ parentComponentType: options.parentComponentType,
3556
3748
  slot: options.slot,
3557
- targetComponentType: options.targetComponentType,
3558
- targetSlot: options.targetSlot,
3749
+ componentPatternId: options.componentPatternId,
3559
3750
  whatIf: options.whatIf ?? false,
3560
- strict: options.strict ?? false,
3561
- deleteSourceSlot: options.deleteSourceSlot ?? false
3751
+ strict: options.strict ?? false
3562
3752
  });
3563
3753
  logger.success(
3564
- `Modified ${result.modifiedComponents} component(s), ${result.modifiedCompositions} composition(s)`
3754
+ `Added component pattern: ${result.allowedComponentsUpdated ? "1 component definition updated, " : ""}${result.instancesAdded} instance(s) added across ${result.compositionsModified} composition(s), ${result.compositionPatternsModified} composition pattern(s), ${result.componentPatternsModified} component pattern(s)`
3565
3755
  );
3566
3756
  } catch (error) {
3567
3757
  if (error instanceof TransformError) {
@@ -3574,473 +3764,310 @@ function createPropagateRootComponentSlotCommand() {
3574
3764
  return command;
3575
3765
  }
3576
3766
 
3577
- // src/cli/commands/convert-compositions-to-entries.ts
3578
- import { Command as Command10 } from "commander";
3767
+ // src/cli/commands/propagate-root-component-slot.ts
3768
+ import { Command as Command9 } from "commander";
3579
3769
 
3580
- // src/core/services/composition-converter.service.ts
3581
- import * as crypto from "crypto";
3582
- var CompositionConverterService = class {
3770
+ // src/core/services/slot-propagator.service.ts
3771
+ var SlotPropagatorService = class {
3583
3772
  constructor(fileSystem, componentService, compositionService, logger) {
3584
3773
  this.fileSystem = fileSystem;
3585
3774
  this.componentService = componentService;
3586
3775
  this.compositionService = compositionService;
3587
3776
  this.logger = logger;
3588
3777
  }
3589
- async convert(options) {
3778
+ async propagate(options) {
3590
3779
  const {
3591
3780
  rootDir,
3592
- compositionsDir,
3593
3781
  componentsDir,
3594
- contentTypesDir,
3595
- entriesDir,
3596
- compositionTypes,
3597
- flattenComponentIds,
3598
- whatIf,
3599
- strict
3600
- } = options;
3601
- const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
3602
- const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
3603
- const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
3604
- const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
3605
- let contentTypesWritten = 0;
3606
- let entriesFromCompositions = 0;
3607
- let entriesFromFlattened = 0;
3608
- let entriesReused = 0;
3609
- this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
3610
- if (flattenComponentIds.length > 0) {
3611
- this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
3612
- }
3613
- const sourceItemMap = flattenComponentIds.length > 0 ? await this.buildSourceItemMap(entriesDirFull) : /* @__PURE__ */ new Map();
3614
- if (sourceItemMap.size > 0) {
3615
- this.logger.info(`Found ${sourceItemMap.size} existing entry(ies) with sourceItem values`);
3616
- }
3617
- const compositionResults = await this.compositionService.findCompositionsByTypes(
3618
- compositionsDirFull,
3619
- compositionTypes,
3620
- { strict }
3621
- );
3622
- if (compositionResults.length === 0) {
3623
- this.logger.warn("No compositions found matching the specified types");
3624
- return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0, entriesReused: 0 };
3625
- }
3626
- this.logger.info(`Found ${compositionResults.length} composition(s)`);
3627
- const rootComponentTypes = /* @__PURE__ */ new Set();
3628
- for (const { composition } of compositionResults) {
3629
- rootComponentTypes.add(composition.composition.type);
3630
- }
3631
- const contentTypeMap = /* @__PURE__ */ new Map();
3632
- for (const rootType of rootComponentTypes) {
3633
- const { component } = await this.componentService.loadComponent(
3634
- componentsDirFull,
3635
- rootType,
3636
- { strict }
3637
- );
3638
- const contentType = this.generateContentType(component);
3639
- contentTypeMap.set(rootType, contentType);
3640
- }
3641
- const flattenContentTypeMap = /* @__PURE__ */ new Map();
3642
- const missingFlattenTypes = [];
3643
- const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
3644
- for (const flattenType of flattenComponentIds) {
3645
- const isRootType = [...rootComponentTypes].some(
3646
- (rt) => this.compareTypes(rt, flattenType, strict)
3647
- );
3648
- if (isRootType) {
3649
- continue;
3650
- }
3651
- try {
3652
- const { component } = await this.componentService.loadComponent(
3653
- componentsDirFull,
3654
- flattenType,
3655
- { strict }
3656
- );
3657
- const contentType = this.generateContentType(component);
3658
- flattenContentTypeMap.set(flattenType, contentType);
3659
- } catch (error) {
3660
- if (error instanceof ComponentNotFoundError) {
3661
- this.logger.info(`Flatten component type not found: ${flattenType}`);
3662
- missingFlattenTypes.push(flattenType);
3663
- continue;
3664
- }
3665
- throw error;
3666
- }
3667
- }
3668
- for (const { composition } of compositionResults) {
3669
- const comp = composition.composition;
3670
- const compositionId = comp._id;
3671
- const compositionName = comp._name ?? compositionId;
3672
- const compositionType = comp.type;
3673
- const entry = this.generateEntryFromComposition(composition);
3674
- const flattenedByType = /* @__PURE__ */ new Map();
3675
- if (flattenComponentIds.length > 0 && comp.slots) {
3676
- for (const flattenType of flattenComponentIds) {
3677
- if (this.compareTypes(flattenType, compositionType, strict)) {
3678
- this.logger.warn(
3679
- `Skipping flatten of "${flattenType}" \u2014 same as root component type`
3680
- );
3681
- continue;
3682
- }
3683
- const instances = this.findFlattenTargets(
3684
- comp.slots,
3685
- flattenType,
3686
- compositionId,
3687
- compositionName,
3688
- strict
3689
- );
3690
- if (instances.length > 0) {
3691
- flattenedByType.set(flattenType, instances);
3692
- if (missingFlattenTypes.includes(flattenType)) {
3693
- foundMissingFlattenTypes.add(flattenType);
3694
- }
3695
- }
3696
- }
3697
- }
3698
- const resolvedRefIds = /* @__PURE__ */ new Map();
3699
- for (const [flattenType, instances] of flattenedByType) {
3700
- const refIds = [];
3701
- for (const inst of instances) {
3702
- const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
3703
- refIds.push(existingId ?? inst.determinisiticId);
3704
- }
3705
- resolvedRefIds.set(flattenType, refIds);
3706
- }
3707
- for (const [flattenType] of flattenedByType) {
3708
- entry.entry.fields[flattenType] = {
3709
- type: "contentReference",
3710
- value: resolvedRefIds.get(flattenType)
3711
- };
3712
- }
3713
- if (flattenComponentIds.length > 0) {
3714
- this.transformContentReferences(entry);
3715
- }
3716
- const entryId = entry.entry._id;
3717
- const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${entryId}.json`);
3718
- this.logger.action(
3719
- whatIf,
3720
- "WRITE",
3721
- `${entriesDir}/${entryId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
3722
- );
3723
- if (!whatIf) {
3724
- await this.fileSystem.writeFile(entryFilePath, entry);
3725
- }
3726
- entriesFromCompositions++;
3727
- for (const [flattenType, instances] of flattenedByType) {
3728
- for (const inst of instances) {
3729
- const existingId = this.findExistingEntryBySourceItem(inst, sourceItemMap);
3730
- if (existingId) {
3731
- this.logger.info(
3732
- `Reusing existing entry ${existingId} for ${flattenType} (sourceItem match)`
3733
- );
3734
- entriesReused++;
3735
- continue;
3736
- }
3737
- const flatEntry = this.generateEntryFromFlattenedInstance(inst);
3738
- const flatEntryPath = this.fileSystem.joinPath(
3739
- entriesDirFull,
3740
- `${inst.determinisiticId}.json`
3741
- );
3742
- this.logger.action(
3743
- whatIf,
3744
- "WRITE",
3745
- `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
3746
- );
3747
- if (!whatIf) {
3748
- await this.fileSystem.writeFile(flatEntryPath, flatEntry);
3749
- }
3750
- entriesFromFlattened++;
3751
- }
3752
- }
3782
+ compositionsDir,
3783
+ compositionType,
3784
+ slot,
3785
+ targetComponentType,
3786
+ targetSlot,
3787
+ whatIf,
3788
+ strict,
3789
+ deleteSourceSlot
3790
+ } = options;
3791
+ const findOptions = { strict };
3792
+ const compositionTypes = this.parsePipeSeparatedValues(compositionType, strict);
3793
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
3794
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
3795
+ const sourceComponents = [];
3796
+ for (const sourceType of compositionTypes) {
3797
+ this.logger.info(`Loading component: ${sourceType}`);
3798
+ const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, sourceType, findOptions);
3799
+ sourceComponents.push({ sourceType, sourceFilePath, sourceComponent });
3753
3800
  }
3754
- if (flattenComponentIds.length > 0) {
3755
- for (const contentType of contentTypeMap.values()) {
3756
- for (const flattenType of flattenComponentIds) {
3757
- if (this.compareTypes(flattenType, contentType.id, strict)) {
3758
- continue;
3759
- }
3760
- if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
3761
- continue;
3762
- }
3763
- contentType.fields.push({
3764
- id: flattenType,
3765
- name: flattenType,
3766
- type: "contentReference",
3767
- typeConfig: {
3768
- isMulti: true,
3769
- allowedContentTypes: [flattenType]
3770
- },
3771
- localizable: false
3772
- });
3801
+ this.logger.info(`Loading component: ${targetComponentType}`);
3802
+ const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
3803
+ const slotNames = slot.split("|").map((s) => s.trim()).filter((s) => s.length > 0);
3804
+ const resolvedSlots = [];
3805
+ const resolvedSlotIds = [];
3806
+ for (const { sourceType, sourceComponent } of sourceComponents) {
3807
+ const notFound = [];
3808
+ for (const slotName of slotNames) {
3809
+ const slotDef = this.componentService.findSlot(sourceComponent, slotName, findOptions);
3810
+ if (!slotDef) {
3811
+ notFound.push(slotName);
3812
+ continue;
3813
+ }
3814
+ const exists = resolvedSlots.some(
3815
+ (existing) => strict ? existing.id === slotDef.id : existing.id.toLowerCase() === slotDef.id.toLowerCase()
3816
+ );
3817
+ if (!exists) {
3818
+ resolvedSlots.push(slotDef);
3819
+ resolvedSlotIds.push(slotDef.id);
3773
3820
  }
3774
3821
  }
3775
- }
3776
- for (const [typeName, contentType] of contentTypeMap) {
3777
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
3778
- const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
3779
- const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
3780
- const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
3781
- this.logger.action(
3782
- whatIf,
3783
- "WRITE",
3784
- `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
3785
- );
3786
- if (!whatIf) {
3787
- await this.fileSystem.writeFile(filePath, contentType);
3822
+ if (notFound.length > 0) {
3823
+ throw new PropertyNotFoundError(`Slot "${notFound.join(", ")}"`, sourceType);
3788
3824
  }
3789
- contentTypesWritten++;
3790
3825
  }
3791
- for (const [typeName, contentType] of flattenContentTypeMap) {
3792
- const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
3793
- this.logger.action(
3794
- whatIf,
3795
- "WRITE",
3796
- `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
3797
- );
3798
- if (!whatIf) {
3799
- await this.fileSystem.writeFile(filePath, contentType);
3826
+ this.logger.info(`Resolved slots: ${resolvedSlotIds.join(", ")}`);
3827
+ let modifiedComponent = { ...targetComponent };
3828
+ let componentModified = false;
3829
+ const existingSlot = this.componentService.findSlot(modifiedComponent, targetSlot, findOptions);
3830
+ if (!existingSlot) {
3831
+ const mergedAllowedComponents = this.mergeAllowedComponents(resolvedSlots);
3832
+ this.logger.action(whatIf, "CREATE", `Slot "${targetSlot}" on ${targetComponentType}`);
3833
+ modifiedComponent = this.componentService.addSlot(modifiedComponent, {
3834
+ id: targetSlot,
3835
+ name: targetSlot,
3836
+ allowedComponents: mergedAllowedComponents
3837
+ });
3838
+ componentModified = true;
3839
+ } else {
3840
+ const mergedAllowedComponents = this.mergeAllowedComponents([existingSlot, ...resolvedSlots]);
3841
+ const existingAllowed = existingSlot.allowedComponents ?? [];
3842
+ if (mergedAllowedComponents.length > existingAllowed.length) {
3843
+ this.logger.action(
3844
+ whatIf,
3845
+ "UPDATE",
3846
+ `Slot "${targetSlot}" allowedComponents on ${targetComponentType}`
3847
+ );
3848
+ modifiedComponent = this.componentService.updateSlotAllowedComponents(
3849
+ modifiedComponent,
3850
+ targetSlot,
3851
+ mergedAllowedComponents,
3852
+ findOptions
3853
+ );
3854
+ componentModified = true;
3855
+ } else {
3856
+ this.logger.info(`Slot "${targetSlot}" already exists on ${targetComponentType}`);
3800
3857
  }
3801
- contentTypesWritten++;
3802
3858
  }
3803
- const neverFoundMissingTypes = missingFlattenTypes.filter(
3804
- (type) => !foundMissingFlattenTypes.has(type)
3859
+ if (componentModified && !whatIf) {
3860
+ await this.componentService.saveComponent(targetFilePath, modifiedComponent);
3861
+ }
3862
+ const compositions = await this.compositionService.findCompositionsByTypes(
3863
+ fullCompositionsDir,
3864
+ compositionTypes,
3865
+ findOptions
3805
3866
  );
3806
- if (neverFoundMissingTypes.length > 0) {
3807
- this.logger.warn(
3808
- `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
3867
+ let modifiedCompositions = 0;
3868
+ let propagatedInstances = 0;
3869
+ for (const { composition, filePath } of compositions) {
3870
+ const rootSlots = composition.composition.slots ?? {};
3871
+ const instances = this.compositionService.findComponentInstances(
3872
+ composition,
3873
+ targetComponentType,
3874
+ findOptions
3809
3875
  );
3810
- }
3811
- return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened, entriesReused };
3812
- }
3813
- // --- Content Type Generation ---
3814
- generateContentType(component) {
3815
- const fields = [];
3816
- if (component.parameters) {
3817
- for (const param of component.parameters) {
3818
- fields.push(this.parameterToField(param));
3876
+ if (instances.length === 0) {
3877
+ continue;
3819
3878
  }
3820
- }
3821
- return {
3822
- id: component.id,
3823
- name: component.name,
3824
- fields
3825
- };
3826
- }
3827
- parameterToField(param) {
3828
- const field = {
3829
- id: param.id,
3830
- name: param.name,
3831
- type: param.type
3832
- };
3833
- if (param.helpText !== void 0) {
3834
- field.helpText = param.helpText;
3835
- }
3836
- if (param.localizable !== void 0) {
3837
- field.localizable = param.localizable;
3838
- }
3839
- if (param.typeConfig !== void 0) {
3840
- field.typeConfig = param.typeConfig;
3841
- }
3842
- return field;
3843
- }
3844
- // --- Entry Generation ---
3845
- generateEntryFromComposition(composition) {
3846
- const comp = composition.composition;
3847
- const compositionSpecificKeys = /* @__PURE__ */ new Set(["_id", "_name", "type", "parameters", "slots", "_overrides"]);
3848
- const extraRootProps = {};
3849
- for (const [key, value] of Object.entries(comp)) {
3850
- if (!compositionSpecificKeys.has(key) && value != null) {
3851
- extraRootProps[key] = value;
3879
+ const componentsToPropagate = [];
3880
+ for (const slotDef of resolvedSlots) {
3881
+ const slotContent = rootSlots[slotDef.id] ?? [];
3882
+ componentsToPropagate.push(...slotContent);
3852
3883
  }
3853
- }
3854
- const wrapperKeys = /* @__PURE__ */ new Set(["composition"]);
3855
- const extraWrapperProps = {};
3856
- for (const [key, value] of Object.entries(composition)) {
3857
- if (!wrapperKeys.has(key) && value != null) {
3858
- extraWrapperProps[key] = value;
3884
+ if (componentsToPropagate.length === 0) {
3885
+ continue;
3859
3886
  }
3860
- }
3861
- const entryId = computeGuidHash(`entry${comp._id}`);
3862
- const entryName = this.truncateName(comp._name ?? comp._id, 60);
3863
- return {
3864
- entry: {
3865
- _id: entryId,
3866
- _name: entryName,
3867
- type: comp.type,
3868
- fields: { ...comp.parameters ?? {} },
3869
- ...extraRootProps
3870
- },
3871
- ...extraWrapperProps
3872
- };
3873
- }
3874
- generateEntryFromFlattenedInstance(inst) {
3875
- const entryName = this.truncateName(
3876
- `${inst.componentType} (from ${inst.compositionName})`,
3877
- 60
3878
- );
3879
- return {
3880
- entry: {
3881
- _id: inst.determinisiticId,
3882
- _name: entryName,
3883
- type: inst.componentType,
3884
- fields: { ...inst.instance.parameters ?? {} }
3887
+ let compositionModified = false;
3888
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
3889
+ const instanceUpdates = [];
3890
+ for (const { instance, instanceId } of instances) {
3891
+ const instanceName = instance._id ?? instanceId;
3892
+ const clonedComponents = regenerateIds(componentsToPropagate, `${instanceName}.${targetSlot}`);
3893
+ this.addComponentsToInstanceSlot(instance, targetSlot, clonedComponents);
3894
+ compositionModified = true;
3895
+ propagatedInstances++;
3896
+ instanceUpdates.push(
3897
+ `${targetComponentType} "${instanceName}": ${componentsToPropagate.length} component(s) \u2192 ${targetSlot}`
3898
+ );
3885
3899
  }
3886
- };
3887
- }
3888
- // --- Flatten Tree Walking ---
3889
- findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
3890
- const results = [];
3891
- this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
3892
- return results;
3893
- }
3894
- walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
3895
- for (const [slotName, instances] of Object.entries(slots)) {
3896
- if (!Array.isArray(instances)) continue;
3897
- for (let i = 0; i < instances.length; i++) {
3898
- const instance = instances[i];
3899
- if (instance._pattern) {
3900
- continue;
3900
+ if (compositionModified) {
3901
+ this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
3902
+ for (const update of instanceUpdates) {
3903
+ this.logger.detail(`\u2192 ${update}`);
3904
+ }
3905
+ if (!whatIf) {
3906
+ await this.compositionService.saveComposition(filePath, composition);
3907
+ }
3908
+ modifiedCompositions++;
3909
+ }
3910
+ }
3911
+ let modifiedSourceComponents = 0;
3912
+ if (deleteSourceSlot) {
3913
+ for (const { sourceType, sourceFilePath, sourceComponent } of sourceComponents) {
3914
+ let modifiedSource = { ...sourceComponent };
3915
+ let sourceComponentModified = false;
3916
+ for (const slotDef of resolvedSlots) {
3917
+ const slotToDelete = this.componentService.findSlot(modifiedSource, slotDef.id, findOptions);
3918
+ if (!slotToDelete) {
3919
+ continue;
3920
+ }
3921
+ this.logger.action(whatIf, "DELETE", `Slot "${slotDef.id}" from ${sourceType}`);
3922
+ modifiedSource = this.componentService.removeSlot(modifiedSource, slotDef.id, findOptions);
3923
+ sourceComponentModified = true;
3901
3924
  }
3902
- const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
3903
- if (this.compareTypes(instance.type, targetType, strict)) {
3904
- const fullPath = `${compositionId}-${currentPath}`;
3905
- const deterministicId = computeGuidHash(fullPath);
3906
- results.push({
3907
- instance,
3908
- path: fullPath,
3909
- determinisiticId: deterministicId,
3910
- componentType: instance.type,
3911
- compositionId,
3912
- compositionName
3913
- });
3925
+ if (sourceComponentModified) {
3926
+ modifiedSourceComponents++;
3914
3927
  }
3915
- if (instance.slots) {
3916
- this.walkSlots(
3917
- instance.slots,
3918
- targetType,
3919
- compositionId,
3920
- compositionName,
3921
- currentPath,
3922
- results,
3923
- strict
3924
- );
3928
+ if (sourceComponentModified && !whatIf) {
3929
+ await this.componentService.saveComponent(sourceFilePath, modifiedSource);
3925
3930
  }
3926
3931
  }
3927
- }
3928
- }
3929
- // --- Content Reference Transformation ---
3930
- transformContentReferences(entry) {
3931
- const dataResources = {};
3932
- for (const [fieldName, field] of Object.entries(entry.entry.fields)) {
3933
- if (field.type === "contentReference" && Array.isArray(field.value)) {
3934
- const entryIds = field.value;
3935
- const resourceKey = `ref-${entry.entry._id}-${fieldName}`;
3936
- field.value = `\${#jptr:/${resourceKey}/entries}`;
3937
- dataResources[resourceKey] = {
3938
- type: "uniformContentInternalReference",
3939
- variables: {
3940
- locale: "${locale}",
3941
- entryIds: entryIds.join(",")
3932
+ for (const { composition, filePath } of compositions) {
3933
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
3934
+ let cleared = false;
3935
+ for (const slotDef of resolvedSlots) {
3936
+ if (composition.composition.slots?.[slotDef.id]?.length) {
3937
+ composition.composition.slots[slotDef.id] = [];
3938
+ cleared = true;
3942
3939
  }
3943
- };
3940
+ }
3941
+ if (cleared) {
3942
+ this.logger.action(whatIf, "CLEAR", `Root slots in composition/${relativePath}`);
3943
+ this.logger.detail(`\u2192 Cleared: ${resolvedSlotIds.join(", ")}`);
3944
+ if (!whatIf) {
3945
+ await this.compositionService.saveComposition(filePath, composition);
3946
+ }
3947
+ }
3944
3948
  }
3945
3949
  }
3946
- if (Object.keys(dataResources).length > 0) {
3947
- entry.entry._dataResources = {
3948
- ...entry.entry._dataResources ?? {},
3949
- ...dataResources
3950
- };
3951
- }
3950
+ return {
3951
+ modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
3952
+ modifiedCompositions,
3953
+ propagatedInstances
3954
+ };
3952
3955
  }
3953
- // --- Source Item Matching ---
3954
- async buildSourceItemMap(entriesDirFull) {
3955
- const sourceItemMap = /* @__PURE__ */ new Map();
3956
- const entryFiles = await this.fileSystem.findFiles(entriesDirFull, "*.json");
3957
- for (const filePath of entryFiles) {
3958
- try {
3959
- const entryData = await this.fileSystem.readFile(filePath);
3960
- const sourceItemField = entryData?.entry?.fields?.sourceItem;
3961
- if (sourceItemField?.value != null) {
3962
- sourceItemMap.set(String(sourceItemField.value), entryData.entry._id);
3963
- }
3964
- } catch {
3965
- continue;
3956
+ mergeAllowedComponents(slots) {
3957
+ const allowed = /* @__PURE__ */ new Set();
3958
+ for (const slot of slots) {
3959
+ for (const comp of slot.allowedComponents ?? []) {
3960
+ allowed.add(comp);
3966
3961
  }
3967
3962
  }
3968
- return sourceItemMap;
3963
+ return Array.from(allowed).sort();
3969
3964
  }
3970
- findExistingEntryBySourceItem(inst, sourceItemMap) {
3971
- const sourceItemParam = inst.instance.parameters?.sourceItem;
3972
- if (sourceItemParam?.value == null) {
3973
- return void 0;
3974
- }
3975
- return sourceItemMap.get(String(sourceItemParam.value));
3965
+ deepCloneComponents(components) {
3966
+ return JSON.parse(JSON.stringify(components));
3976
3967
  }
3977
- // --- Utilities ---
3978
- compareTypes(type1, type2, strict) {
3979
- if (strict) {
3980
- return type1 === type2;
3968
+ addComponentsToInstanceSlot(instance, slotName, components) {
3969
+ if (!instance.slots) {
3970
+ instance.slots = {};
3981
3971
  }
3982
- return type1.toLowerCase() === type2.toLowerCase();
3983
- }
3984
- truncate(str, maxLength) {
3985
- if (str.length <= maxLength) return str;
3986
- return str.substring(0, maxLength - 3) + "...";
3972
+ if (!instance.slots[slotName]) {
3973
+ instance.slots[slotName] = [];
3974
+ }
3975
+ instance.slots[slotName].push(...components);
3987
3976
  }
3988
- truncateName(name, maxLength) {
3989
- if (name.length <= maxLength) return name;
3990
- return name.substring(0, maxLength);
3977
+ parsePipeSeparatedValues(value, strict) {
3978
+ const entries = value.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3979
+ const normalized = [];
3980
+ for (const entry of entries) {
3981
+ const exists = normalized.some(
3982
+ (existing) => strict ? existing === entry : existing.toLowerCase() === entry.toLowerCase()
3983
+ );
3984
+ if (!exists) {
3985
+ normalized.push(entry);
3986
+ }
3987
+ }
3988
+ return normalized;
3991
3989
  }
3992
3990
  };
3993
- function computeGuidHash(guidOrSeed) {
3994
- let uuidStr;
3995
- if (isValidUuid(guidOrSeed)) {
3996
- uuidStr = formatAsBracedUuid(guidOrSeed);
3997
- } else {
3998
- const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
3999
- const hex = [
4000
- // First 4 bytes: little-endian in .NET Guid
4001
- hash[3].toString(16).padStart(2, "0"),
4002
- hash[2].toString(16).padStart(2, "0"),
4003
- hash[1].toString(16).padStart(2, "0"),
4004
- hash[0].toString(16).padStart(2, "0"),
4005
- "-",
4006
- // Bytes 4-5: little-endian
4007
- hash[5].toString(16).padStart(2, "0"),
4008
- hash[4].toString(16).padStart(2, "0"),
4009
- "-",
4010
- // Bytes 6-7: little-endian
4011
- hash[7].toString(16).padStart(2, "0"),
4012
- hash[6].toString(16).padStart(2, "0"),
4013
- "-",
4014
- // Bytes 8-9: big-endian
4015
- hash[8].toString(16).padStart(2, "0"),
4016
- hash[9].toString(16).padStart(2, "0"),
4017
- "-",
4018
- // Bytes 10-15: big-endian
4019
- hash[10].toString(16).padStart(2, "0"),
4020
- hash[11].toString(16).padStart(2, "0"),
4021
- hash[12].toString(16).padStart(2, "0"),
4022
- hash[13].toString(16).padStart(2, "0"),
4023
- hash[14].toString(16).padStart(2, "0"),
4024
- hash[15].toString(16).padStart(2, "0")
4025
- ].join("");
4026
- uuidStr = `{${hex}}`.toUpperCase();
4027
- }
4028
- const chars = uuidStr.split("");
4029
- chars[15] = "4";
4030
- const arr20 = ["8", "9", "A", "B"];
4031
- const hexVal = parseInt(chars[20], 16);
4032
- chars[20] = arr20[hexVal % 4];
4033
- return chars.join("").slice(1, -1).toLowerCase();
4034
- }
4035
- function isValidUuid(str) {
4036
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
4037
- }
4038
- function formatAsBracedUuid(uuid) {
4039
- const clean = uuid.replace(/[{}]/g, "").toUpperCase();
4040
- return `{${clean}}`;
3991
+
3992
+ // src/cli/commands/propagate-root-component-slot.ts
3993
+ function createPropagateRootComponentSlotCommand() {
3994
+ const command = new Command9("propagate-root-component-slot");
3995
+ command.description(
3996
+ "Copies slot definitions from a composition type's root component to a target component type, then moves all component instances from that slot across all matching compositions."
3997
+ ).option(
3998
+ "--compositionType <type>",
3999
+ "The composition type(s) to process. Supports pipe-separated lists (e.g., HomePage|LandingPage)"
4000
+ ).option("--slot <slots>", "Pipe-separated list of slot names to copy from the source component").option(
4001
+ "--targetComponentType <type>",
4002
+ "The component type that will receive the copied slots"
4003
+ ).option(
4004
+ "--targetSlot <slot>",
4005
+ "The slot name on the target component where the contents will be placed"
4006
+ ).option(
4007
+ "--deleteSourceSlot",
4008
+ "Delete the original slots from the source component after propagation"
4009
+ ).hook("preAction", (thisCommand) => {
4010
+ const opts = thisCommand.opts();
4011
+ const requiredOptions = [
4012
+ { name: "compositionType", flag: "--compositionType" },
4013
+ { name: "slot", flag: "--slot" },
4014
+ { name: "targetComponentType", flag: "--targetComponentType" },
4015
+ { name: "targetSlot", flag: "--targetSlot" }
4016
+ ];
4017
+ const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
4018
+ if (missing.length > 0) {
4019
+ console.error(`error: missing required options: ${missing.join(", ")}`);
4020
+ process.exit(1);
4021
+ }
4022
+ }).action(async (opts, cmd) => {
4023
+ const globalOpts = cmd.optsWithGlobals();
4024
+ const options = {
4025
+ ...globalOpts,
4026
+ compositionType: opts.compositionType,
4027
+ slot: opts.slot,
4028
+ targetComponentType: opts.targetComponentType,
4029
+ targetSlot: opts.targetSlot,
4030
+ deleteSourceSlot: opts.deleteSourceSlot
4031
+ };
4032
+ const logger = new Logger();
4033
+ const fileSystem = new FileSystemService();
4034
+ const componentService = new ComponentService(fileSystem);
4035
+ const compositionService = new CompositionService(fileSystem);
4036
+ const propagator = new SlotPropagatorService(
4037
+ fileSystem,
4038
+ componentService,
4039
+ compositionService,
4040
+ logger
4041
+ );
4042
+ try {
4043
+ const result = await propagator.propagate({
4044
+ rootDir: options.rootDir,
4045
+ componentsDir: options.componentsDir,
4046
+ compositionsDir: options.compositionsDir,
4047
+ compositionType: options.compositionType,
4048
+ slot: options.slot,
4049
+ targetComponentType: options.targetComponentType,
4050
+ targetSlot: options.targetSlot,
4051
+ whatIf: options.whatIf ?? false,
4052
+ strict: options.strict ?? false,
4053
+ deleteSourceSlot: options.deleteSourceSlot ?? false
4054
+ });
4055
+ logger.success(
4056
+ `Modified ${result.modifiedComponents} component(s), ${result.modifiedCompositions} composition(s)`
4057
+ );
4058
+ } catch (error) {
4059
+ if (error instanceof TransformError) {
4060
+ logger.error(error.message);
4061
+ process.exit(1);
4062
+ }
4063
+ throw error;
4064
+ }
4065
+ });
4066
+ return command;
4041
4067
  }
4042
4068
 
4043
4069
  // src/cli/commands/convert-compositions-to-entries.ts
4070
+ import { Command as Command10 } from "commander";
4044
4071
  function createConvertCompositionsToEntriesCommand() {
4045
4072
  const command = new Command10("convert-compositions-to-entries");
4046
4073
  command.description(
@@ -4649,7 +4676,7 @@ function createRemoveFieldCommand() {
4649
4676
  // package.json
4650
4677
  var package_default = {
4651
4678
  name: "@uniformdev/transformer",
4652
- version: "1.1.16",
4679
+ version: "1.1.17",
4653
4680
  description: "CLI tool for transforming Uniform.dev serialization files offline",
4654
4681
  type: "module",
4655
4682
  bin: {