@uniformdev/transformer 1.1.9 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -128,6 +128,10 @@ interface PropagateRootComponentSlotOptions extends GlobalOptions {
128
128
  targetSlot: string;
129
129
  deleteSourceSlot?: boolean;
130
130
  }
131
+ interface ConvertCompositionsToEntriesOptions extends GlobalOptions {
132
+ compositionTypes: string;
133
+ flattenComponentIds?: string;
134
+ }
131
135
 
132
136
  declare class TransformError extends Error {
133
137
  constructor(message: string);
@@ -389,4 +393,77 @@ declare class ComponentAdderService {
389
393
  private addComponentToNestedSlots;
390
394
  }
391
395
 
392
- export { type AddComponentOptions, type AddComponentPatternOptions, ComponentAdderService, ComponentAlreadyExistsError, type ComponentDefinition, type ComponentInstance, ComponentNotFoundError, ComponentRenamerService, ComponentService, type Composition, type CompositionOverrides, type CompositionPatternCandidatesOptions, type CompositionRoot, CompositionService, DuplicateIdError, FileNotFoundError, FileSystemService, type GlobalOptions, InvalidYamlError, Logger, type PackSerializationOptions, type Parameter, type ParameterValue, type PropagateRootComponentPropertyOptions, type PropagateRootComponentSlotOptions, PropertyNotFoundError, PropertyPropagatorService, type RenameComponentOptions, type RenameSlotOptions, SlotAlreadyExistsError, type SlotDefinition, SlotNotFoundError, SlotRenamerService, TransformError, type UnpackSerializationOptions };
396
+ interface ConvertCompositionsToEntriesInternalOptions {
397
+ rootDir: string;
398
+ compositionsDir: string;
399
+ componentsDir: string;
400
+ contentTypesDir: string;
401
+ entriesDir: string;
402
+ compositionTypes: string[];
403
+ flattenComponentIds: string[];
404
+ whatIf: boolean;
405
+ strict: boolean;
406
+ }
407
+ interface ContentTypeDefinition {
408
+ id: string;
409
+ name: string;
410
+ fields: ContentTypeField[];
411
+ }
412
+ interface ContentTypeField {
413
+ id: string;
414
+ name: string;
415
+ type: string;
416
+ helpText?: string;
417
+ localizable?: boolean;
418
+ typeConfig?: Record<string, unknown>;
419
+ }
420
+ interface EntryDefinition {
421
+ entry: {
422
+ _id: string;
423
+ _name: string;
424
+ type: string;
425
+ fields: Record<string, ParameterValue>;
426
+ };
427
+ }
428
+ interface FlattenedInstance {
429
+ instance: ComponentInstance;
430
+ path: string;
431
+ determinisiticId: string;
432
+ componentType: string;
433
+ compositionId: string;
434
+ compositionName: string;
435
+ }
436
+ interface ConvertResult {
437
+ contentTypesWritten: number;
438
+ entriesFromCompositions: number;
439
+ entriesFromFlattened: number;
440
+ }
441
+ declare class CompositionConverterService {
442
+ private fileSystem;
443
+ private componentService;
444
+ private compositionService;
445
+ private logger;
446
+ constructor(fileSystem: FileSystemService, componentService: ComponentService, compositionService: CompositionService, logger: Logger);
447
+ convert(options: ConvertCompositionsToEntriesInternalOptions): Promise<ConvertResult>;
448
+ generateContentType(component: ComponentDefinition): ContentTypeDefinition;
449
+ private parameterToField;
450
+ generateEntryFromComposition(composition: Composition): EntryDefinition;
451
+ generateEntryFromFlattenedInstance(inst: FlattenedInstance): EntryDefinition;
452
+ findFlattenTargets(slots: Record<string, ComponentInstance[]>, targetType: string, compositionId: string, compositionName: string, strict: boolean): FlattenedInstance[];
453
+ private walkSlots;
454
+ private compareTypes;
455
+ private truncate;
456
+ }
457
+ /**
458
+ * Computes a deterministic UUID v4 from a string seed.
459
+ * This is a TypeScript port of the C# ComputeGuidHash method.
460
+ *
461
+ * Algorithm:
462
+ * 1. If the input is already a valid UUID, parse it; otherwise MD5 hash the string
463
+ * 2. Format as UUID
464
+ * 3. Force position 15 to '4' (UUID v4 version nibble)
465
+ * 4. Force position 20 to one of '8','9','A','B' (UUID v4 variant)
466
+ */
467
+ declare function computeGuidHash(guidOrSeed: string): string;
468
+
469
+ export { type AddComponentOptions, type AddComponentPatternOptions, ComponentAdderService, ComponentAlreadyExistsError, type ComponentDefinition, type ComponentInstance, ComponentNotFoundError, ComponentRenamerService, ComponentService, type Composition, CompositionConverterService, type CompositionOverrides, type CompositionPatternCandidatesOptions, type CompositionRoot, CompositionService, type ConvertCompositionsToEntriesOptions, DuplicateIdError, FileNotFoundError, FileSystemService, type GlobalOptions, InvalidYamlError, Logger, type PackSerializationOptions, type Parameter, type ParameterValue, type PropagateRootComponentPropertyOptions, type PropagateRootComponentSlotOptions, PropertyNotFoundError, PropertyPropagatorService, type RenameComponentOptions, type RenameSlotOptions, SlotAlreadyExistsError, type SlotDefinition, SlotNotFoundError, SlotRenamerService, TransformError, type UnpackSerializationOptions, computeGuidHash };
package/dist/index.js CHANGED
@@ -1648,6 +1648,351 @@ var ComponentAdderService = class {
1648
1648
  }
1649
1649
  };
1650
1650
 
1651
+ // src/core/services/composition-converter.service.ts
1652
+ import * as crypto from "crypto";
1653
+ var CompositionConverterService = class {
1654
+ constructor(fileSystem, componentService, compositionService, logger) {
1655
+ this.fileSystem = fileSystem;
1656
+ this.componentService = componentService;
1657
+ this.compositionService = compositionService;
1658
+ this.logger = logger;
1659
+ }
1660
+ async convert(options) {
1661
+ const {
1662
+ rootDir,
1663
+ compositionsDir,
1664
+ componentsDir,
1665
+ contentTypesDir,
1666
+ entriesDir,
1667
+ compositionTypes,
1668
+ flattenComponentIds,
1669
+ whatIf,
1670
+ strict
1671
+ } = options;
1672
+ const compositionsDirFull = this.fileSystem.resolvePath(rootDir, compositionsDir);
1673
+ const componentsDirFull = this.fileSystem.resolvePath(rootDir, componentsDir);
1674
+ const contentTypesDirFull = this.fileSystem.resolvePath(rootDir, contentTypesDir);
1675
+ const entriesDirFull = this.fileSystem.resolvePath(rootDir, entriesDir);
1676
+ let contentTypesWritten = 0;
1677
+ let entriesFromCompositions = 0;
1678
+ let entriesFromFlattened = 0;
1679
+ this.logger.info(`Composition types: ${compositionTypes.join(", ")}`);
1680
+ if (flattenComponentIds.length > 0) {
1681
+ this.logger.info(`Flatten component types: ${flattenComponentIds.join(", ")}`);
1682
+ }
1683
+ const compositionResults = await this.compositionService.findCompositionsByTypes(
1684
+ compositionsDirFull,
1685
+ compositionTypes,
1686
+ { strict }
1687
+ );
1688
+ if (compositionResults.length === 0) {
1689
+ this.logger.warn("No compositions found matching the specified types");
1690
+ return { contentTypesWritten: 0, entriesFromCompositions: 0, entriesFromFlattened: 0 };
1691
+ }
1692
+ this.logger.info(`Found ${compositionResults.length} composition(s)`);
1693
+ const rootComponentTypes = /* @__PURE__ */ new Set();
1694
+ for (const { composition } of compositionResults) {
1695
+ rootComponentTypes.add(composition.composition.type);
1696
+ }
1697
+ const contentTypeMap = /* @__PURE__ */ new Map();
1698
+ for (const rootType of rootComponentTypes) {
1699
+ const { component } = await this.componentService.loadComponent(
1700
+ componentsDirFull,
1701
+ rootType,
1702
+ { strict }
1703
+ );
1704
+ const contentType = this.generateContentType(component);
1705
+ contentTypeMap.set(rootType, contentType);
1706
+ }
1707
+ const flattenContentTypeMap = /* @__PURE__ */ new Map();
1708
+ for (const flattenType of flattenComponentIds) {
1709
+ const isRootType = [...rootComponentTypes].some(
1710
+ (rt) => this.compareTypes(rt, flattenType, strict)
1711
+ );
1712
+ if (isRootType) {
1713
+ continue;
1714
+ }
1715
+ try {
1716
+ const { component } = await this.componentService.loadComponent(
1717
+ componentsDirFull,
1718
+ flattenType,
1719
+ { strict }
1720
+ );
1721
+ const contentType = this.generateContentType(component);
1722
+ flattenContentTypeMap.set(flattenType, contentType);
1723
+ } catch (error) {
1724
+ if (error instanceof ComponentNotFoundError) {
1725
+ throw new ComponentNotFoundError(flattenType, componentsDirFull);
1726
+ }
1727
+ throw error;
1728
+ }
1729
+ }
1730
+ if (flattenComponentIds.length > 0) {
1731
+ for (const contentType of contentTypeMap.values()) {
1732
+ for (const flattenType of flattenComponentIds) {
1733
+ if (this.compareTypes(flattenType, contentType.id, strict)) {
1734
+ continue;
1735
+ }
1736
+ contentType.fields.push({
1737
+ id: flattenType,
1738
+ name: flattenType,
1739
+ type: "contentReference",
1740
+ typeConfig: {
1741
+ isMulti: true,
1742
+ allowedContentTypes: [flattenType]
1743
+ },
1744
+ localizable: false
1745
+ });
1746
+ }
1747
+ }
1748
+ }
1749
+ for (const [typeName, contentType] of contentTypeMap) {
1750
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1751
+ const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
1752
+ const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
1753
+ const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
1754
+ this.logger.action(
1755
+ whatIf,
1756
+ "WRITE",
1757
+ `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
1758
+ );
1759
+ if (!whatIf) {
1760
+ await this.fileSystem.writeFile(filePath, contentType);
1761
+ }
1762
+ contentTypesWritten++;
1763
+ }
1764
+ for (const [typeName, contentType] of flattenContentTypeMap) {
1765
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1766
+ this.logger.action(
1767
+ whatIf,
1768
+ "WRITE",
1769
+ `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
1770
+ );
1771
+ if (!whatIf) {
1772
+ await this.fileSystem.writeFile(filePath, contentType);
1773
+ }
1774
+ contentTypesWritten++;
1775
+ }
1776
+ for (const { composition } of compositionResults) {
1777
+ const comp = composition.composition;
1778
+ const compositionId = comp._id;
1779
+ const compositionName = comp._name ?? compositionId;
1780
+ const compositionType = comp.type;
1781
+ const entry = this.generateEntryFromComposition(composition);
1782
+ const flattenedByType = /* @__PURE__ */ new Map();
1783
+ if (flattenComponentIds.length > 0 && comp.slots) {
1784
+ for (const flattenType of flattenComponentIds) {
1785
+ if (this.compareTypes(flattenType, compositionType, strict)) {
1786
+ this.logger.warn(
1787
+ `Skipping flatten of "${flattenType}" \u2014 same as root component type`
1788
+ );
1789
+ continue;
1790
+ }
1791
+ const instances = this.findFlattenTargets(
1792
+ comp.slots,
1793
+ flattenType,
1794
+ compositionId,
1795
+ compositionName,
1796
+ strict
1797
+ );
1798
+ if (instances.length > 0) {
1799
+ flattenedByType.set(flattenType, instances);
1800
+ }
1801
+ }
1802
+ }
1803
+ for (const [flattenType, instances] of flattenedByType) {
1804
+ entry.entry.fields[flattenType] = {
1805
+ type: "contentReference",
1806
+ value: instances.map((inst) => inst.determinisiticId)
1807
+ };
1808
+ }
1809
+ const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${compositionId}.json`);
1810
+ this.logger.action(
1811
+ whatIf,
1812
+ "WRITE",
1813
+ `${entriesDir}/${compositionId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
1814
+ );
1815
+ if (!whatIf) {
1816
+ await this.fileSystem.writeFile(entryFilePath, entry);
1817
+ }
1818
+ entriesFromCompositions++;
1819
+ for (const [flattenType, instances] of flattenedByType) {
1820
+ for (const inst of instances) {
1821
+ const flatEntry = this.generateEntryFromFlattenedInstance(inst);
1822
+ const flatEntryPath = this.fileSystem.joinPath(
1823
+ entriesDirFull,
1824
+ `${inst.determinisiticId}.json`
1825
+ );
1826
+ this.logger.action(
1827
+ whatIf,
1828
+ "WRITE",
1829
+ `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
1830
+ );
1831
+ if (!whatIf) {
1832
+ await this.fileSystem.writeFile(flatEntryPath, flatEntry);
1833
+ }
1834
+ entriesFromFlattened++;
1835
+ }
1836
+ }
1837
+ }
1838
+ return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened };
1839
+ }
1840
+ // --- Content Type Generation ---
1841
+ generateContentType(component) {
1842
+ const fields = [];
1843
+ if (component.parameters) {
1844
+ for (const param of component.parameters) {
1845
+ fields.push(this.parameterToField(param));
1846
+ }
1847
+ }
1848
+ return {
1849
+ id: component.id,
1850
+ name: component.name,
1851
+ fields
1852
+ };
1853
+ }
1854
+ parameterToField(param) {
1855
+ const field = {
1856
+ id: param.id,
1857
+ name: param.name,
1858
+ type: param.type
1859
+ };
1860
+ if (param.helpText !== void 0) {
1861
+ field.helpText = param.helpText;
1862
+ }
1863
+ if (param.localizable !== void 0) {
1864
+ field.localizable = param.localizable;
1865
+ }
1866
+ if (param.typeConfig !== void 0) {
1867
+ field.typeConfig = param.typeConfig;
1868
+ }
1869
+ return field;
1870
+ }
1871
+ // --- Entry Generation ---
1872
+ generateEntryFromComposition(composition) {
1873
+ const comp = composition.composition;
1874
+ return {
1875
+ entry: {
1876
+ _id: comp._id,
1877
+ _name: comp._name ?? comp._id,
1878
+ type: comp.type,
1879
+ fields: { ...comp.parameters ?? {} }
1880
+ }
1881
+ };
1882
+ }
1883
+ generateEntryFromFlattenedInstance(inst) {
1884
+ return {
1885
+ entry: {
1886
+ _id: inst.determinisiticId,
1887
+ _name: `${inst.componentType} (from ${inst.compositionName})`,
1888
+ type: inst.componentType,
1889
+ fields: { ...inst.instance.parameters ?? {} }
1890
+ }
1891
+ };
1892
+ }
1893
+ // --- Flatten Tree Walking ---
1894
+ findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
1895
+ const results = [];
1896
+ this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
1897
+ return results;
1898
+ }
1899
+ walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
1900
+ for (const [slotName, instances] of Object.entries(slots)) {
1901
+ if (!Array.isArray(instances)) continue;
1902
+ for (let i = 0; i < instances.length; i++) {
1903
+ const instance = instances[i];
1904
+ if (instance._pattern) {
1905
+ continue;
1906
+ }
1907
+ const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
1908
+ if (this.compareTypes(instance.type, targetType, strict)) {
1909
+ const fullPath = `${compositionId}-${currentPath}`;
1910
+ const deterministicId = computeGuidHash(fullPath);
1911
+ results.push({
1912
+ instance,
1913
+ path: fullPath,
1914
+ determinisiticId: deterministicId,
1915
+ componentType: instance.type,
1916
+ compositionId,
1917
+ compositionName
1918
+ });
1919
+ }
1920
+ if (instance.slots) {
1921
+ this.walkSlots(
1922
+ instance.slots,
1923
+ targetType,
1924
+ compositionId,
1925
+ compositionName,
1926
+ currentPath,
1927
+ results,
1928
+ strict
1929
+ );
1930
+ }
1931
+ }
1932
+ }
1933
+ }
1934
+ // --- Utilities ---
1935
+ compareTypes(type1, type2, strict) {
1936
+ if (strict) {
1937
+ return type1 === type2;
1938
+ }
1939
+ return type1.toLowerCase() === type2.toLowerCase();
1940
+ }
1941
+ truncate(str, maxLength) {
1942
+ if (str.length <= maxLength) return str;
1943
+ return str.substring(0, maxLength - 3) + "...";
1944
+ }
1945
+ };
1946
+ function computeGuidHash(guidOrSeed) {
1947
+ let uuidStr;
1948
+ if (isValidUuid(guidOrSeed)) {
1949
+ uuidStr = formatAsBracedUuid(guidOrSeed);
1950
+ } else {
1951
+ const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
1952
+ const hex = [
1953
+ // First 4 bytes: little-endian in .NET Guid
1954
+ hash[3].toString(16).padStart(2, "0"),
1955
+ hash[2].toString(16).padStart(2, "0"),
1956
+ hash[1].toString(16).padStart(2, "0"),
1957
+ hash[0].toString(16).padStart(2, "0"),
1958
+ "-",
1959
+ // Bytes 4-5: little-endian
1960
+ hash[5].toString(16).padStart(2, "0"),
1961
+ hash[4].toString(16).padStart(2, "0"),
1962
+ "-",
1963
+ // Bytes 6-7: little-endian
1964
+ hash[7].toString(16).padStart(2, "0"),
1965
+ hash[6].toString(16).padStart(2, "0"),
1966
+ "-",
1967
+ // Bytes 8-9: big-endian
1968
+ hash[8].toString(16).padStart(2, "0"),
1969
+ hash[9].toString(16).padStart(2, "0"),
1970
+ "-",
1971
+ // Bytes 10-15: big-endian
1972
+ hash[10].toString(16).padStart(2, "0"),
1973
+ hash[11].toString(16).padStart(2, "0"),
1974
+ hash[12].toString(16).padStart(2, "0"),
1975
+ hash[13].toString(16).padStart(2, "0"),
1976
+ hash[14].toString(16).padStart(2, "0"),
1977
+ hash[15].toString(16).padStart(2, "0")
1978
+ ].join("");
1979
+ uuidStr = `{${hex}}`.toUpperCase();
1980
+ }
1981
+ const chars = uuidStr.split("");
1982
+ chars[15] = "4";
1983
+ const arr20 = ["8", "9", "A", "B"];
1984
+ const hexVal = parseInt(chars[20], 16);
1985
+ chars[20] = arr20[hexVal % 4];
1986
+ return chars.join("").slice(1, -1).toLowerCase();
1987
+ }
1988
+ function isValidUuid(str) {
1989
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
1990
+ }
1991
+ function formatAsBracedUuid(uuid) {
1992
+ const clean = uuid.replace(/[{}]/g, "").toUpperCase();
1993
+ return `{${clean}}`;
1994
+ }
1995
+
1651
1996
  // src/cli/logger.ts
1652
1997
  import chalk from "chalk";
1653
1998
  var Logger = class {
@@ -1677,6 +2022,7 @@ export {
1677
2022
  ComponentNotFoundError,
1678
2023
  ComponentRenamerService,
1679
2024
  ComponentService,
2025
+ CompositionConverterService,
1680
2026
  CompositionService,
1681
2027
  DuplicateIdError,
1682
2028
  FileNotFoundError,
@@ -1688,6 +2034,7 @@ export {
1688
2034
  SlotAlreadyExistsError,
1689
2035
  SlotNotFoundError,
1690
2036
  SlotRenamerService,
1691
- TransformError
2037
+ TransformError,
2038
+ computeGuidHash
1692
2039
  };
1693
2040
  //# sourceMappingURL=index.js.map