@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 +1049 -1022
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1343 -1313
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -523,301 +523,793 @@ var CompositionService = class {
|
|
|
523
523
|
}
|
|
524
524
|
};
|
|
525
525
|
|
|
526
|
-
// src/core/services/
|
|
527
|
-
|
|
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
|
|
535
|
+
async convert(options) {
|
|
535
536
|
const {
|
|
536
537
|
rootDir,
|
|
537
|
-
componentsDir,
|
|
538
538
|
compositionsDir,
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
539
|
+
componentsDir,
|
|
540
|
+
contentTypesDir,
|
|
541
|
+
entriesDir,
|
|
542
|
+
compositionTypes,
|
|
543
|
+
flattenComponentIds,
|
|
543
544
|
whatIf,
|
|
544
|
-
strict
|
|
545
|
-
deleteSourceParameter
|
|
545
|
+
strict
|
|
546
546
|
} = options;
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
569
|
-
|
|
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
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
597
|
+
try {
|
|
598
|
+
const { component } = await this.componentService.loadComponent(
|
|
599
|
+
componentsDirFull,
|
|
600
|
+
flattenType,
|
|
601
|
+
{ strict }
|
|
575
602
|
);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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 (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
693
|
-
|
|
653
|
+
for (const [flattenType] of flattenedByType) {
|
|
654
|
+
entry.entry.fields[flattenType] = {
|
|
655
|
+
type: "contentReference",
|
|
656
|
+
value: resolvedRefIds.get(flattenType)
|
|
657
|
+
};
|
|
694
658
|
}
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
"
|
|
743
|
-
`${
|
|
690
|
+
"WRITE",
|
|
691
|
+
`${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
|
|
744
692
|
);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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 (!
|
|
784
|
-
|
|
732
|
+
if (!whatIf) {
|
|
733
|
+
await this.fileSystem.writeFile(filePath, contentType);
|
|
785
734
|
}
|
|
735
|
+
contentTypesWritten++;
|
|
786
736
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
798
|
-
|
|
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
|
-
|
|
801
|
-
|
|
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
|
-
|
|
804
|
-
|
|
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
|
-
|
|
807
|
-
const
|
|
808
|
-
|
|
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
|
-
|
|
811
|
-
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
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: "
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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
|
-
|
|
3493
|
-
|
|
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
|
-
|
|
3497
|
-
|
|
3498
|
-
}
|
|
3708
|
+
});
|
|
3709
|
+
return command;
|
|
3710
|
+
}
|
|
3499
3711
|
|
|
3500
|
-
// src/cli/commands/
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
command
|
|
3504
|
-
|
|
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: "
|
|
3719
|
+
{ name: "parentComponentType", flag: "--parentComponentType" },
|
|
3521
3720
|
{ name: "slot", flag: "--slot" },
|
|
3522
|
-
{ name: "
|
|
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
|
-
|
|
3732
|
+
parentComponentType: opts.parentComponentType,
|
|
3535
3733
|
slot: opts.slot,
|
|
3536
|
-
|
|
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
|
|
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
|
|
3741
|
+
const result = await adder.addComponentPattern({
|
|
3552
3742
|
rootDir: options.rootDir,
|
|
3553
3743
|
componentsDir: options.componentsDir,
|
|
3554
3744
|
compositionsDir: options.compositionsDir,
|
|
3555
|
-
|
|
3745
|
+
compositionPatternsDir: options.compositionPatternsDir,
|
|
3746
|
+
componentPatternsDir: options.componentPatternsDir,
|
|
3747
|
+
parentComponentType: options.parentComponentType,
|
|
3556
3748
|
slot: options.slot,
|
|
3557
|
-
|
|
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
|
-
`
|
|
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/
|
|
3578
|
-
import { Command as
|
|
3767
|
+
// src/cli/commands/propagate-root-component-slot.ts
|
|
3768
|
+
import { Command as Command9 } from "commander";
|
|
3579
3769
|
|
|
3580
|
-
// src/core/services/
|
|
3581
|
-
|
|
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
|
|
3778
|
+
async propagate(options) {
|
|
3590
3779
|
const {
|
|
3591
3780
|
rootDir,
|
|
3592
|
-
compositionsDir,
|
|
3593
3781
|
componentsDir,
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
const
|
|
3604
|
-
const
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
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
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
);
|
|
3798
|
-
|
|
3799
|
-
|
|
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
|
-
|
|
3804
|
-
|
|
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
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
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
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
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
|
-
|
|
3903
|
-
|
|
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 (
|
|
3916
|
-
this.
|
|
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
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
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
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
}
|
|
3950
|
+
return {
|
|
3951
|
+
modifiedComponents: (componentModified ? 1 : 0) + modifiedSourceComponents,
|
|
3952
|
+
modifiedCompositions,
|
|
3953
|
+
propagatedInstances
|
|
3954
|
+
};
|
|
3952
3955
|
}
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
const
|
|
3956
|
-
|
|
3957
|
-
|
|
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
|
|
3963
|
+
return Array.from(allowed).sort();
|
|
3969
3964
|
}
|
|
3970
|
-
|
|
3971
|
-
|
|
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
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
return type1 === type2;
|
|
3968
|
+
addComponentsToInstanceSlot(instance, slotName, components) {
|
|
3969
|
+
if (!instance.slots) {
|
|
3970
|
+
instance.slots = {};
|
|
3981
3971
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
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
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
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
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
"
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
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.
|
|
4679
|
+
version: "1.1.17",
|
|
4653
4680
|
description: "CLI tool for transforming Uniform.dev serialization files offline",
|
|
4654
4681
|
type: "module",
|
|
4655
4682
|
bin: {
|