@uniformdev/transformer 1.1.9 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command10 } from "commander";
4
+ import { Command as Command11 } from "commander";
5
5
 
6
6
  // src/cli/commands/propagate-root-component-property.ts
7
7
  import { Command } from "commander";
@@ -3574,10 +3574,443 @@ function createPropagateRootComponentSlotCommand() {
3574
3574
  return command;
3575
3575
  }
3576
3576
 
3577
+ // src/cli/commands/convert-compositions-to-entries.ts
3578
+ import { Command as Command10 } from "commander";
3579
+
3580
+ // src/core/services/composition-converter.service.ts
3581
+ import * as crypto from "crypto";
3582
+ var CompositionConverterService = class {
3583
+ constructor(fileSystem, componentService, compositionService, logger) {
3584
+ this.fileSystem = fileSystem;
3585
+ this.componentService = componentService;
3586
+ this.compositionService = compositionService;
3587
+ this.logger = logger;
3588
+ }
3589
+ async convert(options) {
3590
+ const {
3591
+ rootDir,
3592
+ compositionsDir,
3593
+ 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
+ this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
3609
+ if (flattenComponentIds.length > 0) {
3610
+ this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
3611
+ }
3612
+ const compositionResults = await this.compositionService.findCompositionsByTypes(
3613
+ compositionsDirFull,
3614
+ compositionTypes,
3615
+ { strict }
3616
+ );
3617
+ if (compositionResults.length === 0) {
3618
+ this.logger.warn("No compositions found matching the specified types");
3619
+ return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0 };
3620
+ }
3621
+ this.logger.info(`Found ${compositionResults.length} composition(s)`);
3622
+ const rootComponentTypes = /* @__PURE__ */ new Set();
3623
+ for (const { composition } of compositionResults) {
3624
+ rootComponentTypes.add(composition.composition.type);
3625
+ }
3626
+ const contentTypeMap = /* @__PURE__ */ new Map();
3627
+ for (const rootType of rootComponentTypes) {
3628
+ const { component } = await this.componentService.loadComponent(
3629
+ componentsDirFull,
3630
+ rootType,
3631
+ { strict }
3632
+ );
3633
+ const contentType = this.generateContentType(component);
3634
+ contentTypeMap.set(rootType, contentType);
3635
+ }
3636
+ const flattenContentTypeMap = /* @__PURE__ */ new Map();
3637
+ const missingFlattenTypes = [];
3638
+ const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
3639
+ for (const flattenType of flattenComponentIds) {
3640
+ const isRootType = [...rootComponentTypes].some(
3641
+ (rt) => this.compareTypes(rt, flattenType, strict)
3642
+ );
3643
+ if (isRootType) {
3644
+ continue;
3645
+ }
3646
+ try {
3647
+ const { component } = await this.componentService.loadComponent(
3648
+ componentsDirFull,
3649
+ flattenType,
3650
+ { strict }
3651
+ );
3652
+ const contentType = this.generateContentType(component);
3653
+ flattenContentTypeMap.set(flattenType, contentType);
3654
+ } catch (error) {
3655
+ if (error instanceof ComponentNotFoundError) {
3656
+ this.logger.info(`Flatten component type not found: ${flattenType}`);
3657
+ missingFlattenTypes.push(flattenType);
3658
+ continue;
3659
+ }
3660
+ throw error;
3661
+ }
3662
+ }
3663
+ for (const { composition } of compositionResults) {
3664
+ const comp = composition.composition;
3665
+ const compositionId = comp._id;
3666
+ const compositionName = comp._name ?? compositionId;
3667
+ const compositionType = comp.type;
3668
+ const entry = this.generateEntryFromComposition(composition);
3669
+ const flattenedByType = /* @__PURE__ */ new Map();
3670
+ if (flattenComponentIds.length > 0 && comp.slots) {
3671
+ for (const flattenType of flattenComponentIds) {
3672
+ if (this.compareTypes(flattenType, compositionType, strict)) {
3673
+ this.logger.warn(
3674
+ `Skipping flatten of "${flattenType}" \u2014 same as root component type`
3675
+ );
3676
+ continue;
3677
+ }
3678
+ const instances = this.findFlattenTargets(
3679
+ comp.slots,
3680
+ flattenType,
3681
+ compositionId,
3682
+ compositionName,
3683
+ strict
3684
+ );
3685
+ if (instances.length > 0) {
3686
+ flattenedByType.set(flattenType, instances);
3687
+ if (missingFlattenTypes.includes(flattenType)) {
3688
+ foundMissingFlattenTypes.add(flattenType);
3689
+ }
3690
+ }
3691
+ }
3692
+ }
3693
+ for (const [flattenType, instances] of flattenedByType) {
3694
+ entry.entry.fields[flattenType] = {
3695
+ type: "contentReference",
3696
+ value: instances.map((inst) => inst.determinisiticId)
3697
+ };
3698
+ }
3699
+ const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${compositionId}.json`);
3700
+ this.logger.action(
3701
+ whatIf,
3702
+ "WRITE",
3703
+ `${entriesDir}/${compositionId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
3704
+ );
3705
+ if (!whatIf) {
3706
+ await this.fileSystem.writeFile(entryFilePath, entry);
3707
+ }
3708
+ entriesFromCompositions++;
3709
+ for (const [flattenType, instances] of flattenedByType) {
3710
+ for (const inst of instances) {
3711
+ const flatEntry = this.generateEntryFromFlattenedInstance(inst);
3712
+ const flatEntryPath = this.fileSystem.joinPath(
3713
+ entriesDirFull,
3714
+ `${inst.determinisiticId}.json`
3715
+ );
3716
+ this.logger.action(
3717
+ whatIf,
3718
+ "WRITE",
3719
+ `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
3720
+ );
3721
+ if (!whatIf) {
3722
+ await this.fileSystem.writeFile(flatEntryPath, flatEntry);
3723
+ }
3724
+ entriesFromFlattened++;
3725
+ }
3726
+ }
3727
+ }
3728
+ if (flattenComponentIds.length > 0) {
3729
+ for (const contentType of contentTypeMap.values()) {
3730
+ for (const flattenType of flattenComponentIds) {
3731
+ if (this.compareTypes(flattenType, contentType.id, strict)) {
3732
+ continue;
3733
+ }
3734
+ if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
3735
+ continue;
3736
+ }
3737
+ contentType.fields.push({
3738
+ id: flattenType,
3739
+ name: flattenType,
3740
+ type: "contentReference",
3741
+ typeConfig: {
3742
+ isMulti: true,
3743
+ allowedContentTypes: [flattenType]
3744
+ },
3745
+ localizable: false
3746
+ });
3747
+ }
3748
+ }
3749
+ }
3750
+ for (const [typeName, contentType] of contentTypeMap) {
3751
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
3752
+ const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
3753
+ const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
3754
+ const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
3755
+ this.logger.action(
3756
+ whatIf,
3757
+ "WRITE",
3758
+ `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
3759
+ );
3760
+ if (!whatIf) {
3761
+ await this.fileSystem.writeFile(filePath, contentType);
3762
+ }
3763
+ contentTypesWritten++;
3764
+ }
3765
+ for (const [typeName, contentType] of flattenContentTypeMap) {
3766
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
3767
+ this.logger.action(
3768
+ whatIf,
3769
+ "WRITE",
3770
+ `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
3771
+ );
3772
+ if (!whatIf) {
3773
+ await this.fileSystem.writeFile(filePath, contentType);
3774
+ }
3775
+ contentTypesWritten++;
3776
+ }
3777
+ const neverFoundMissingTypes = missingFlattenTypes.filter(
3778
+ (type) => !foundMissingFlattenTypes.has(type)
3779
+ );
3780
+ if (neverFoundMissingTypes.length > 0) {
3781
+ this.logger.warn(
3782
+ `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
3783
+ );
3784
+ }
3785
+ return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened };
3786
+ }
3787
+ // --- Content Type Generation ---
3788
+ generateContentType(component) {
3789
+ const fields = [];
3790
+ if (component.parameters) {
3791
+ for (const param of component.parameters) {
3792
+ fields.push(this.parameterToField(param));
3793
+ }
3794
+ }
3795
+ return {
3796
+ id: component.id,
3797
+ name: component.name,
3798
+ fields
3799
+ };
3800
+ }
3801
+ parameterToField(param) {
3802
+ const field = {
3803
+ id: param.id,
3804
+ name: param.name,
3805
+ type: param.type
3806
+ };
3807
+ if (param.helpText !== void 0) {
3808
+ field.helpText = param.helpText;
3809
+ }
3810
+ if (param.localizable !== void 0) {
3811
+ field.localizable = param.localizable;
3812
+ }
3813
+ if (param.typeConfig !== void 0) {
3814
+ field.typeConfig = param.typeConfig;
3815
+ }
3816
+ return field;
3817
+ }
3818
+ // --- Entry Generation ---
3819
+ generateEntryFromComposition(composition) {
3820
+ const comp = composition.composition;
3821
+ return {
3822
+ entry: {
3823
+ _id: comp._id,
3824
+ _name: comp._name ?? comp._id,
3825
+ type: comp.type,
3826
+ fields: { ...comp.parameters ?? {} }
3827
+ }
3828
+ };
3829
+ }
3830
+ generateEntryFromFlattenedInstance(inst) {
3831
+ return {
3832
+ entry: {
3833
+ _id: inst.determinisiticId,
3834
+ _name: `${inst.componentType} (from ${inst.compositionName})`,
3835
+ type: inst.componentType,
3836
+ fields: { ...inst.instance.parameters ?? {} }
3837
+ }
3838
+ };
3839
+ }
3840
+ // --- Flatten Tree Walking ---
3841
+ findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
3842
+ const results = [];
3843
+ this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
3844
+ return results;
3845
+ }
3846
+ walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
3847
+ for (const [slotName, instances] of Object.entries(slots)) {
3848
+ if (!Array.isArray(instances)) continue;
3849
+ for (let i = 0; i < instances.length; i++) {
3850
+ const instance = instances[i];
3851
+ if (instance._pattern) {
3852
+ continue;
3853
+ }
3854
+ const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
3855
+ if (this.compareTypes(instance.type, targetType, strict)) {
3856
+ const fullPath = `${compositionId}-${currentPath}`;
3857
+ const deterministicId = computeGuidHash(fullPath);
3858
+ results.push({
3859
+ instance,
3860
+ path: fullPath,
3861
+ determinisiticId: deterministicId,
3862
+ componentType: instance.type,
3863
+ compositionId,
3864
+ compositionName
3865
+ });
3866
+ }
3867
+ if (instance.slots) {
3868
+ this.walkSlots(
3869
+ instance.slots,
3870
+ targetType,
3871
+ compositionId,
3872
+ compositionName,
3873
+ currentPath,
3874
+ results,
3875
+ strict
3876
+ );
3877
+ }
3878
+ }
3879
+ }
3880
+ }
3881
+ // --- Utilities ---
3882
+ compareTypes(type1, type2, strict) {
3883
+ if (strict) {
3884
+ return type1 === type2;
3885
+ }
3886
+ return type1.toLowerCase() === type2.toLowerCase();
3887
+ }
3888
+ truncate(str, maxLength) {
3889
+ if (str.length <= maxLength) return str;
3890
+ return str.substring(0, maxLength - 3) + "...";
3891
+ }
3892
+ };
3893
+ function computeGuidHash(guidOrSeed) {
3894
+ let uuidStr;
3895
+ if (isValidUuid(guidOrSeed)) {
3896
+ uuidStr = formatAsBracedUuid(guidOrSeed);
3897
+ } else {
3898
+ const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
3899
+ const hex = [
3900
+ // First 4 bytes: little-endian in .NET Guid
3901
+ hash[3].toString(16).padStart(2, "0"),
3902
+ hash[2].toString(16).padStart(2, "0"),
3903
+ hash[1].toString(16).padStart(2, "0"),
3904
+ hash[0].toString(16).padStart(2, "0"),
3905
+ "-",
3906
+ // Bytes 4-5: little-endian
3907
+ hash[5].toString(16).padStart(2, "0"),
3908
+ hash[4].toString(16).padStart(2, "0"),
3909
+ "-",
3910
+ // Bytes 6-7: little-endian
3911
+ hash[7].toString(16).padStart(2, "0"),
3912
+ hash[6].toString(16).padStart(2, "0"),
3913
+ "-",
3914
+ // Bytes 8-9: big-endian
3915
+ hash[8].toString(16).padStart(2, "0"),
3916
+ hash[9].toString(16).padStart(2, "0"),
3917
+ "-",
3918
+ // Bytes 10-15: big-endian
3919
+ hash[10].toString(16).padStart(2, "0"),
3920
+ hash[11].toString(16).padStart(2, "0"),
3921
+ hash[12].toString(16).padStart(2, "0"),
3922
+ hash[13].toString(16).padStart(2, "0"),
3923
+ hash[14].toString(16).padStart(2, "0"),
3924
+ hash[15].toString(16).padStart(2, "0")
3925
+ ].join("");
3926
+ uuidStr = `{${hex}}`.toUpperCase();
3927
+ }
3928
+ const chars = uuidStr.split("");
3929
+ chars[15] = "4";
3930
+ const arr20 = ["8", "9", "A", "B"];
3931
+ const hexVal = parseInt(chars[20], 16);
3932
+ chars[20] = arr20[hexVal % 4];
3933
+ return chars.join("").slice(1, -1).toLowerCase();
3934
+ }
3935
+ function isValidUuid(str) {
3936
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
3937
+ }
3938
+ function formatAsBracedUuid(uuid) {
3939
+ const clean = uuid.replace(/[{}]/g, "").toUpperCase();
3940
+ return `{${clean}}`;
3941
+ }
3942
+
3943
+ // src/cli/commands/convert-compositions-to-entries.ts
3944
+ function createConvertCompositionsToEntriesCommand() {
3945
+ const command = new Command10("convert-compositions-to-entries");
3946
+ command.description(
3947
+ "Converts compositions into content entries, optionally flattening nested components into separate referenced entries."
3948
+ ).option(
3949
+ "--compositionTypes <types>",
3950
+ "Pipe-separated list of composition types to convert (e.g., ProductDetailPage|ArticlePage)"
3951
+ ).option(
3952
+ "--flattenComponentIds <types>",
3953
+ "Pipe-separated list of component types to flatten into separate entries (e.g., DetailHero|ArticleDetail)"
3954
+ ).hook("preAction", (thisCommand) => {
3955
+ const opts = thisCommand.opts();
3956
+ const requiredOptions = [
3957
+ { name: "compositionTypes", flag: "--compositionTypes" }
3958
+ ];
3959
+ const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
3960
+ if (missing.length > 0) {
3961
+ console.error(`error: missing required options: ${missing.join(", ")}`);
3962
+ process.exit(1);
3963
+ }
3964
+ }).action(async (opts, cmd) => {
3965
+ const globalOpts = cmd.optsWithGlobals();
3966
+ const options = {
3967
+ ...globalOpts,
3968
+ compositionTypes: opts.compositionTypes,
3969
+ flattenComponentIds: opts.flattenComponentIds
3970
+ };
3971
+ const logger = new Logger();
3972
+ const fileSystem = new FileSystemService();
3973
+ const componentService = new ComponentService(fileSystem);
3974
+ const compositionService = new CompositionService(fileSystem);
3975
+ const converter = new CompositionConverterService(
3976
+ fileSystem,
3977
+ componentService,
3978
+ compositionService,
3979
+ logger
3980
+ );
3981
+ try {
3982
+ const compositionTypes = options.compositionTypes.split("|").map((t) => t.trim()).filter((t) => t.length > 0);
3983
+ const flattenComponentIds = options.flattenComponentIds ? options.flattenComponentIds.split("|").map((t) => t.trim()).filter((t) => t.length > 0) : [];
3984
+ const result = await converter.convert({
3985
+ rootDir: options.rootDir,
3986
+ compositionsDir: options.compositionsDir,
3987
+ componentsDir: options.componentsDir,
3988
+ contentTypesDir: options.contentTypesDir,
3989
+ entriesDir: options.entriesDir,
3990
+ compositionTypes,
3991
+ flattenComponentIds,
3992
+ whatIf: options.whatIf ?? false,
3993
+ strict: options.strict ?? false
3994
+ });
3995
+ const flatInfo = result.entriesFromFlattened > 0 ? `, ${result.entriesFromFlattened} from flattened components` : "";
3996
+ logger.success(
3997
+ `${result.contentTypesWritten} content type(s), ${result.entriesFromCompositions} entry(ies) from compositions${flatInfo}`
3998
+ );
3999
+ } catch (error) {
4000
+ if (error instanceof TransformError) {
4001
+ logger.error(error.message);
4002
+ process.exit(1);
4003
+ }
4004
+ throw error;
4005
+ }
4006
+ });
4007
+ return command;
4008
+ }
4009
+
3577
4010
  // package.json
3578
4011
  var package_default = {
3579
4012
  name: "@uniformdev/transformer",
3580
- version: "1.1.9",
4013
+ version: "1.1.11",
3581
4014
  description: "CLI tool for transforming Uniform.dev serialization files offline",
3582
4015
  type: "module",
3583
4016
  bin: {
@@ -3646,7 +4079,7 @@ var package_default = {
3646
4079
  };
3647
4080
 
3648
4081
  // src/cli/index.ts
3649
- var program = new Command10();
4082
+ var program = new Command11();
3650
4083
  var appVersion = package_default.version;
3651
4084
  console.error(`uniform-transform v${appVersion}`);
3652
4085
  program.name("uniform-transform").description("CLI tool for transforming Uniform.dev serialization files offline").version(appVersion);
@@ -3668,5 +4101,6 @@ program.addCommand(createRenameComponentCommand());
3668
4101
  program.addCommand(createAddComponentCommand());
3669
4102
  program.addCommand(createAddComponentPatternCommand());
3670
4103
  program.addCommand(createPropagateRootComponentSlotCommand());
4104
+ program.addCommand(createConvertCompositionsToEntriesCommand());
3671
4105
  program.parse();
3672
4106
  //# sourceMappingURL=index.js.map