@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/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,369 @@ 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
+ const missingFlattenTypes = [];
1709
+ const foundMissingFlattenTypes = /* @__PURE__ */ new Set();
1710
+ for (const flattenType of flattenComponentIds) {
1711
+ const isRootType = [...rootComponentTypes].some(
1712
+ (rt) => this.compareTypes(rt, flattenType, strict)
1713
+ );
1714
+ if (isRootType) {
1715
+ continue;
1716
+ }
1717
+ try {
1718
+ const { component } = await this.componentService.loadComponent(
1719
+ componentsDirFull,
1720
+ flattenType,
1721
+ { strict }
1722
+ );
1723
+ const contentType = this.generateContentType(component);
1724
+ flattenContentTypeMap.set(flattenType, contentType);
1725
+ } catch (error) {
1726
+ if (error instanceof ComponentNotFoundError) {
1727
+ this.logger.info(`Flatten component type not found: ${flattenType}`);
1728
+ missingFlattenTypes.push(flattenType);
1729
+ continue;
1730
+ }
1731
+ throw error;
1732
+ }
1733
+ }
1734
+ for (const { composition } of compositionResults) {
1735
+ const comp = composition.composition;
1736
+ const compositionId = comp._id;
1737
+ const compositionName = comp._name ?? compositionId;
1738
+ const compositionType = comp.type;
1739
+ const entry = this.generateEntryFromComposition(composition);
1740
+ const flattenedByType = /* @__PURE__ */ new Map();
1741
+ if (flattenComponentIds.length > 0 && comp.slots) {
1742
+ for (const flattenType of flattenComponentIds) {
1743
+ if (this.compareTypes(flattenType, compositionType, strict)) {
1744
+ this.logger.warn(
1745
+ `Skipping flatten of "${flattenType}" \u2014 same as root component type`
1746
+ );
1747
+ continue;
1748
+ }
1749
+ const instances = this.findFlattenTargets(
1750
+ comp.slots,
1751
+ flattenType,
1752
+ compositionId,
1753
+ compositionName,
1754
+ strict
1755
+ );
1756
+ if (instances.length > 0) {
1757
+ flattenedByType.set(flattenType, instances);
1758
+ if (missingFlattenTypes.includes(flattenType)) {
1759
+ foundMissingFlattenTypes.add(flattenType);
1760
+ }
1761
+ }
1762
+ }
1763
+ }
1764
+ for (const [flattenType, instances] of flattenedByType) {
1765
+ entry.entry.fields[flattenType] = {
1766
+ type: "contentReference",
1767
+ value: instances.map((inst) => inst.determinisiticId)
1768
+ };
1769
+ }
1770
+ const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${compositionId}.json`);
1771
+ this.logger.action(
1772
+ whatIf,
1773
+ "WRITE",
1774
+ `${entriesDir}/${compositionId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
1775
+ );
1776
+ if (!whatIf) {
1777
+ await this.fileSystem.writeFile(entryFilePath, entry);
1778
+ }
1779
+ entriesFromCompositions++;
1780
+ for (const [flattenType, instances] of flattenedByType) {
1781
+ for (const inst of instances) {
1782
+ const flatEntry = this.generateEntryFromFlattenedInstance(inst);
1783
+ const flatEntryPath = this.fileSystem.joinPath(
1784
+ entriesDirFull,
1785
+ `${inst.determinisiticId}.json`
1786
+ );
1787
+ this.logger.action(
1788
+ whatIf,
1789
+ "WRITE",
1790
+ `${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
1791
+ );
1792
+ if (!whatIf) {
1793
+ await this.fileSystem.writeFile(flatEntryPath, flatEntry);
1794
+ }
1795
+ entriesFromFlattened++;
1796
+ }
1797
+ }
1798
+ }
1799
+ if (flattenComponentIds.length > 0) {
1800
+ for (const contentType of contentTypeMap.values()) {
1801
+ for (const flattenType of flattenComponentIds) {
1802
+ if (this.compareTypes(flattenType, contentType.id, strict)) {
1803
+ continue;
1804
+ }
1805
+ if (missingFlattenTypes.includes(flattenType) && !foundMissingFlattenTypes.has(flattenType)) {
1806
+ continue;
1807
+ }
1808
+ contentType.fields.push({
1809
+ id: flattenType,
1810
+ name: flattenType,
1811
+ type: "contentReference",
1812
+ typeConfig: {
1813
+ isMulti: true,
1814
+ allowedContentTypes: [flattenType]
1815
+ },
1816
+ localizable: false
1817
+ });
1818
+ }
1819
+ }
1820
+ }
1821
+ for (const [typeName, contentType] of contentTypeMap) {
1822
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1823
+ const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
1824
+ const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
1825
+ const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
1826
+ this.logger.action(
1827
+ whatIf,
1828
+ "WRITE",
1829
+ `${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
1830
+ );
1831
+ if (!whatIf) {
1832
+ await this.fileSystem.writeFile(filePath, contentType);
1833
+ }
1834
+ contentTypesWritten++;
1835
+ }
1836
+ for (const [typeName, contentType] of flattenContentTypeMap) {
1837
+ const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
1838
+ this.logger.action(
1839
+ whatIf,
1840
+ "WRITE",
1841
+ `${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
1842
+ );
1843
+ if (!whatIf) {
1844
+ await this.fileSystem.writeFile(filePath, contentType);
1845
+ }
1846
+ contentTypesWritten++;
1847
+ }
1848
+ const neverFoundMissingTypes = missingFlattenTypes.filter(
1849
+ (type) => !foundMissingFlattenTypes.has(type)
1850
+ );
1851
+ if (neverFoundMissingTypes.length > 0) {
1852
+ this.logger.warn(
1853
+ `Flatten component type(s) not found in any composition: ${neverFoundMissingTypes.join(", ")}`
1854
+ );
1855
+ }
1856
+ return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened };
1857
+ }
1858
+ // --- Content Type Generation ---
1859
+ generateContentType(component) {
1860
+ const fields = [];
1861
+ if (component.parameters) {
1862
+ for (const param of component.parameters) {
1863
+ fields.push(this.parameterToField(param));
1864
+ }
1865
+ }
1866
+ return {
1867
+ id: component.id,
1868
+ name: component.name,
1869
+ fields
1870
+ };
1871
+ }
1872
+ parameterToField(param) {
1873
+ const field = {
1874
+ id: param.id,
1875
+ name: param.name,
1876
+ type: param.type
1877
+ };
1878
+ if (param.helpText !== void 0) {
1879
+ field.helpText = param.helpText;
1880
+ }
1881
+ if (param.localizable !== void 0) {
1882
+ field.localizable = param.localizable;
1883
+ }
1884
+ if (param.typeConfig !== void 0) {
1885
+ field.typeConfig = param.typeConfig;
1886
+ }
1887
+ return field;
1888
+ }
1889
+ // --- Entry Generation ---
1890
+ generateEntryFromComposition(composition) {
1891
+ const comp = composition.composition;
1892
+ return {
1893
+ entry: {
1894
+ _id: comp._id,
1895
+ _name: comp._name ?? comp._id,
1896
+ type: comp.type,
1897
+ fields: { ...comp.parameters ?? {} }
1898
+ }
1899
+ };
1900
+ }
1901
+ generateEntryFromFlattenedInstance(inst) {
1902
+ return {
1903
+ entry: {
1904
+ _id: inst.determinisiticId,
1905
+ _name: `${inst.componentType} (from ${inst.compositionName})`,
1906
+ type: inst.componentType,
1907
+ fields: { ...inst.instance.parameters ?? {} }
1908
+ }
1909
+ };
1910
+ }
1911
+ // --- Flatten Tree Walking ---
1912
+ findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
1913
+ const results = [];
1914
+ this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
1915
+ return results;
1916
+ }
1917
+ walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
1918
+ for (const [slotName, instances] of Object.entries(slots)) {
1919
+ if (!Array.isArray(instances)) continue;
1920
+ for (let i = 0; i < instances.length; i++) {
1921
+ const instance = instances[i];
1922
+ if (instance._pattern) {
1923
+ continue;
1924
+ }
1925
+ const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
1926
+ if (this.compareTypes(instance.type, targetType, strict)) {
1927
+ const fullPath = `${compositionId}-${currentPath}`;
1928
+ const deterministicId = computeGuidHash(fullPath);
1929
+ results.push({
1930
+ instance,
1931
+ path: fullPath,
1932
+ determinisiticId: deterministicId,
1933
+ componentType: instance.type,
1934
+ compositionId,
1935
+ compositionName
1936
+ });
1937
+ }
1938
+ if (instance.slots) {
1939
+ this.walkSlots(
1940
+ instance.slots,
1941
+ targetType,
1942
+ compositionId,
1943
+ compositionName,
1944
+ currentPath,
1945
+ results,
1946
+ strict
1947
+ );
1948
+ }
1949
+ }
1950
+ }
1951
+ }
1952
+ // --- Utilities ---
1953
+ compareTypes(type1, type2, strict) {
1954
+ if (strict) {
1955
+ return type1 === type2;
1956
+ }
1957
+ return type1.toLowerCase() === type2.toLowerCase();
1958
+ }
1959
+ truncate(str, maxLength) {
1960
+ if (str.length <= maxLength) return str;
1961
+ return str.substring(0, maxLength - 3) + "...";
1962
+ }
1963
+ };
1964
+ function computeGuidHash(guidOrSeed) {
1965
+ let uuidStr;
1966
+ if (isValidUuid(guidOrSeed)) {
1967
+ uuidStr = formatAsBracedUuid(guidOrSeed);
1968
+ } else {
1969
+ const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
1970
+ const hex = [
1971
+ // First 4 bytes: little-endian in .NET Guid
1972
+ hash[3].toString(16).padStart(2, "0"),
1973
+ hash[2].toString(16).padStart(2, "0"),
1974
+ hash[1].toString(16).padStart(2, "0"),
1975
+ hash[0].toString(16).padStart(2, "0"),
1976
+ "-",
1977
+ // Bytes 4-5: little-endian
1978
+ hash[5].toString(16).padStart(2, "0"),
1979
+ hash[4].toString(16).padStart(2, "0"),
1980
+ "-",
1981
+ // Bytes 6-7: little-endian
1982
+ hash[7].toString(16).padStart(2, "0"),
1983
+ hash[6].toString(16).padStart(2, "0"),
1984
+ "-",
1985
+ // Bytes 8-9: big-endian
1986
+ hash[8].toString(16).padStart(2, "0"),
1987
+ hash[9].toString(16).padStart(2, "0"),
1988
+ "-",
1989
+ // Bytes 10-15: big-endian
1990
+ hash[10].toString(16).padStart(2, "0"),
1991
+ hash[11].toString(16).padStart(2, "0"),
1992
+ hash[12].toString(16).padStart(2, "0"),
1993
+ hash[13].toString(16).padStart(2, "0"),
1994
+ hash[14].toString(16).padStart(2, "0"),
1995
+ hash[15].toString(16).padStart(2, "0")
1996
+ ].join("");
1997
+ uuidStr = `{${hex}}`.toUpperCase();
1998
+ }
1999
+ const chars = uuidStr.split("");
2000
+ chars[15] = "4";
2001
+ const arr20 = ["8", "9", "A", "B"];
2002
+ const hexVal = parseInt(chars[20], 16);
2003
+ chars[20] = arr20[hexVal % 4];
2004
+ return chars.join("").slice(1, -1).toLowerCase();
2005
+ }
2006
+ function isValidUuid(str) {
2007
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
2008
+ }
2009
+ function formatAsBracedUuid(uuid) {
2010
+ const clean = uuid.replace(/[{}]/g, "").toUpperCase();
2011
+ return `{${clean}}`;
2012
+ }
2013
+
1651
2014
  // src/cli/logger.ts
1652
2015
  import chalk from "chalk";
1653
2016
  var Logger = class {
@@ -1677,6 +2040,7 @@ export {
1677
2040
  ComponentNotFoundError,
1678
2041
  ComponentRenamerService,
1679
2042
  ComponentService,
2043
+ CompositionConverterService,
1680
2044
  CompositionService,
1681
2045
  DuplicateIdError,
1682
2046
  FileNotFoundError,
@@ -1688,6 +2052,7 @@ export {
1688
2052
  SlotAlreadyExistsError,
1689
2053
  SlotNotFoundError,
1690
2054
  SlotRenamerService,
1691
- TransformError
2055
+ TransformError,
2056
+ computeGuidHash
1692
2057
  };
1693
2058
  //# sourceMappingURL=index.js.map