@uniformdev/transformer 1.1.8 → 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/cli/index.js +651 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +348 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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";
|
|
@@ -900,7 +900,7 @@ var PatternAnalyzerService = class {
|
|
|
900
900
|
this.logger = logger;
|
|
901
901
|
}
|
|
902
902
|
async analyze(options) {
|
|
903
|
-
const { rootDir, compositionsDir, projectMapNodesDir, minGroupSize, depth, threshold } = options;
|
|
903
|
+
const { rootDir, compositionsDir, projectMapNodesDir, minGroupSize, depth, threshold, subset } = options;
|
|
904
904
|
const compositionsPath = this.fileSystem.resolvePath(rootDir, compositionsDir);
|
|
905
905
|
const projectMapNodesPath = this.fileSystem.resolvePath(rootDir, projectMapNodesDir);
|
|
906
906
|
const projectMapNodes = await this.loadProjectMapNodes(projectMapNodesPath);
|
|
@@ -926,7 +926,9 @@ var PatternAnalyzerService = class {
|
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
let groups;
|
|
929
|
-
if (
|
|
929
|
+
if (subset) {
|
|
930
|
+
groups = this.clusterBySubset(loadedCompositions, depth);
|
|
931
|
+
} else if (threshold === 0) {
|
|
930
932
|
const fingerprintGroups = /* @__PURE__ */ new Map();
|
|
931
933
|
for (const item of loadedCompositions) {
|
|
932
934
|
if (!fingerprintGroups.has(item.fingerprint)) {
|
|
@@ -960,34 +962,77 @@ var PatternAnalyzerService = class {
|
|
|
960
962
|
}
|
|
961
963
|
continue;
|
|
962
964
|
}
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
965
|
+
if (subset) {
|
|
966
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
967
|
+
for (const item of group) {
|
|
968
|
+
const id = item.composition.composition._id ?? "";
|
|
969
|
+
nodeCache.set(id, this.parseCompositionToNode(item.composition, depth));
|
|
970
|
+
}
|
|
971
|
+
group.sort((a, b) => {
|
|
972
|
+
const idA = a.composition.composition._id ?? "";
|
|
973
|
+
const idB = b.composition.composition._id ?? "";
|
|
974
|
+
const countA = this.countNodes(nodeCache.get(idA));
|
|
975
|
+
const countB = this.countNodes(nodeCache.get(idB));
|
|
976
|
+
if (countB !== countA) return countB - countA;
|
|
977
|
+
return idA.localeCompare(idB);
|
|
978
|
+
});
|
|
979
|
+
let mergedNode = nodeCache.get(group[0].composition.composition._id ?? "");
|
|
980
|
+
for (let i = 1; i < group.length; i++) {
|
|
981
|
+
const id = group[i].composition.composition._id ?? "";
|
|
982
|
+
mergedNode = this.mergeComponentNodes(mergedNode, nodeCache.get(id));
|
|
983
|
+
}
|
|
984
|
+
const firstComposition = group[0].composition;
|
|
985
|
+
const rootType = firstComposition.composition.type;
|
|
986
|
+
const firstName = this.getCompositionName(firstComposition);
|
|
987
|
+
rootTypeCounts.set(rootType, (rootTypeCounts.get(rootType) ?? 0) + 1);
|
|
988
|
+
const structureDescription = this.generateStructureDescriptionFromNode(mergedNode);
|
|
989
|
+
const compositions = group.map(({ composition, filePath }) => ({
|
|
990
|
+
id: composition.composition._id ?? "unknown",
|
|
991
|
+
name: this.getCompositionName(composition),
|
|
992
|
+
projectMapNodeSlug: this.resolveProjectMapSlug(composition, projectMapNodes),
|
|
993
|
+
filePath
|
|
994
|
+
}));
|
|
995
|
+
const fingerprint = group[0].fingerprint;
|
|
996
|
+
const patternGroup = {
|
|
997
|
+
name: rootType,
|
|
998
|
+
structureDescription,
|
|
999
|
+
fingerprint,
|
|
1000
|
+
compositions,
|
|
1001
|
+
_rootType: rootType,
|
|
1002
|
+
_firstName: firstName
|
|
1003
|
+
};
|
|
1004
|
+
patterns.push(patternGroup);
|
|
1005
|
+
patternCompositionsData.set(patternGroup, group.map((g) => g.composition));
|
|
1006
|
+
} else {
|
|
1007
|
+
group.sort((a, b) => {
|
|
1008
|
+
const idA = a.composition.composition._id ?? "";
|
|
1009
|
+
const idB = b.composition.composition._id ?? "";
|
|
1010
|
+
return idA.localeCompare(idB);
|
|
1011
|
+
});
|
|
1012
|
+
const firstComposition = group[0].composition;
|
|
1013
|
+
const rootType = firstComposition.composition.type;
|
|
1014
|
+
const firstName = this.getCompositionName(firstComposition);
|
|
1015
|
+
rootTypeCounts.set(rootType, (rootTypeCounts.get(rootType) ?? 0) + 1);
|
|
1016
|
+
const structureDescription = this.generateStructureDescription(firstComposition);
|
|
1017
|
+
const compositions = group.map(({ composition, filePath }) => ({
|
|
1018
|
+
id: composition.composition._id ?? "unknown",
|
|
1019
|
+
name: this.getCompositionName(composition),
|
|
1020
|
+
projectMapNodeSlug: this.resolveProjectMapSlug(composition, projectMapNodes),
|
|
1021
|
+
filePath
|
|
1022
|
+
}));
|
|
1023
|
+
const fingerprint = group[0].fingerprint;
|
|
1024
|
+
const patternGroup = {
|
|
1025
|
+
name: rootType,
|
|
1026
|
+
// Temporary name, will be updated below if disambiguation needed
|
|
1027
|
+
structureDescription,
|
|
1028
|
+
fingerprint,
|
|
1029
|
+
compositions,
|
|
1030
|
+
_rootType: rootType,
|
|
1031
|
+
_firstName: firstName
|
|
1032
|
+
};
|
|
1033
|
+
patterns.push(patternGroup);
|
|
1034
|
+
patternCompositionsData.set(patternGroup, group.map((g) => g.composition));
|
|
1035
|
+
}
|
|
991
1036
|
}
|
|
992
1037
|
for (const pattern of patterns) {
|
|
993
1038
|
const p = pattern;
|
|
@@ -1137,6 +1182,158 @@ var PatternAnalyzerService = class {
|
|
|
1137
1182
|
}
|
|
1138
1183
|
return distance;
|
|
1139
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Checks if `smaller` is a structural subset of `larger`.
|
|
1187
|
+
* Same root type required. For each slot in `smaller`, that slot must exist
|
|
1188
|
+
* in `larger` with at least the same components at the same positions.
|
|
1189
|
+
* `larger` may have extra slots or extra trailing components. Recursive.
|
|
1190
|
+
*/
|
|
1191
|
+
isSubsetOf(smaller, larger) {
|
|
1192
|
+
if (smaller.type !== larger.type) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
for (const [slotName, smallerChildren] of smaller.slots) {
|
|
1196
|
+
const largerChildren = larger.slots.get(slotName);
|
|
1197
|
+
if (!largerChildren) {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
if (smallerChildren.length > largerChildren.length) {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
for (let i = 0; i < smallerChildren.length; i++) {
|
|
1204
|
+
if (!this.isSubsetOf(smallerChildren[i], largerChildren[i])) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Counts total component instances in a tree (root + all descendants).
|
|
1213
|
+
*/
|
|
1214
|
+
countNodes(node) {
|
|
1215
|
+
let count = 1;
|
|
1216
|
+
for (const children of node.slots.values()) {
|
|
1217
|
+
for (const child of children) {
|
|
1218
|
+
count += this.countNodes(child);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return count;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Merges two ComponentNode trees into a combined superset structure.
|
|
1225
|
+
* Takes the union of all slots and the maximum components at each position.
|
|
1226
|
+
* Both nodes must have the same root type.
|
|
1227
|
+
*/
|
|
1228
|
+
mergeComponentNodes(a, b) {
|
|
1229
|
+
if (a.type !== b.type) {
|
|
1230
|
+
return a;
|
|
1231
|
+
}
|
|
1232
|
+
const mergedSlots = /* @__PURE__ */ new Map();
|
|
1233
|
+
const allSlotNames = /* @__PURE__ */ new Set([...a.slots.keys(), ...b.slots.keys()]);
|
|
1234
|
+
for (const slotName of allSlotNames) {
|
|
1235
|
+
const aChildren = a.slots.get(slotName) ?? [];
|
|
1236
|
+
const bChildren = b.slots.get(slotName) ?? [];
|
|
1237
|
+
const maxLen = Math.max(aChildren.length, bChildren.length);
|
|
1238
|
+
const mergedChildren = [];
|
|
1239
|
+
for (let i = 0; i < maxLen; i++) {
|
|
1240
|
+
const aChild = aChildren[i];
|
|
1241
|
+
const bChild = bChildren[i];
|
|
1242
|
+
if (aChild && bChild) {
|
|
1243
|
+
if (aChild.type === bChild.type) {
|
|
1244
|
+
mergedChildren.push(this.mergeComponentNodes(aChild, bChild));
|
|
1245
|
+
} else {
|
|
1246
|
+
mergedChildren.push(this.countNodes(aChild) >= this.countNodes(bChild) ? aChild : bChild);
|
|
1247
|
+
}
|
|
1248
|
+
} else {
|
|
1249
|
+
mergedChildren.push(aChild ?? bChild);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
mergedSlots.set(slotName, mergedChildren);
|
|
1253
|
+
}
|
|
1254
|
+
return { type: a.type, slots: mergedSlots };
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Clusters compositions using Union-Find based on subset relationship.
|
|
1258
|
+
* Two compositions are unioned if either is a structural subset of the other.
|
|
1259
|
+
*/
|
|
1260
|
+
clusterBySubset(compositions, depth) {
|
|
1261
|
+
const n = compositions.length;
|
|
1262
|
+
if (n === 0) return [];
|
|
1263
|
+
const parsedNodes = compositions.map(
|
|
1264
|
+
(item) => this.parseCompositionToNode(item.composition, depth)
|
|
1265
|
+
);
|
|
1266
|
+
const parent = Array.from({ length: n }, (_, i) => i);
|
|
1267
|
+
const rank = Array(n).fill(0);
|
|
1268
|
+
const find = (x) => {
|
|
1269
|
+
if (parent[x] !== x) {
|
|
1270
|
+
parent[x] = find(parent[x]);
|
|
1271
|
+
}
|
|
1272
|
+
return parent[x];
|
|
1273
|
+
};
|
|
1274
|
+
const union = (x, y) => {
|
|
1275
|
+
const rootX = find(x);
|
|
1276
|
+
const rootY = find(y);
|
|
1277
|
+
if (rootX !== rootY) {
|
|
1278
|
+
if (rank[rootX] < rank[rootY]) {
|
|
1279
|
+
parent[rootX] = rootY;
|
|
1280
|
+
} else if (rank[rootX] > rank[rootY]) {
|
|
1281
|
+
parent[rootY] = rootX;
|
|
1282
|
+
} else {
|
|
1283
|
+
parent[rootY] = rootX;
|
|
1284
|
+
rank[rootX]++;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
for (let i = 0; i < n; i++) {
|
|
1289
|
+
for (let j = i + 1; j < n; j++) {
|
|
1290
|
+
if (parsedNodes[i].type !== parsedNodes[j].type) {
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (this.isSubsetOf(parsedNodes[i], parsedNodes[j]) || this.isSubsetOf(parsedNodes[j], parsedNodes[i])) {
|
|
1294
|
+
union(i, j);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1299
|
+
for (let i = 0; i < n; i++) {
|
|
1300
|
+
const root = find(i);
|
|
1301
|
+
if (!groups.has(root)) {
|
|
1302
|
+
groups.set(root, []);
|
|
1303
|
+
}
|
|
1304
|
+
groups.get(root).push(compositions[i]);
|
|
1305
|
+
}
|
|
1306
|
+
return Array.from(groups.values());
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Generates a structure description from a ComponentNode tree (instead of a Composition).
|
|
1310
|
+
*/
|
|
1311
|
+
generateStructureDescriptionFromNode(node) {
|
|
1312
|
+
return this.generateNodeDescriptionFromNode(node, 0);
|
|
1313
|
+
}
|
|
1314
|
+
generateNodeDescriptionFromNode(node, depth) {
|
|
1315
|
+
if (node.slots.size === 0) {
|
|
1316
|
+
return node.type;
|
|
1317
|
+
}
|
|
1318
|
+
const sortedSlotNames = [...node.slots.keys()].sort();
|
|
1319
|
+
const slotDescriptions = sortedSlotNames.map((slotName) => {
|
|
1320
|
+
const children = node.slots.get(slotName);
|
|
1321
|
+
if (children.length === 0) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
const instanceTypes = children.map((child) => {
|
|
1325
|
+
if (child.slots.size > 0) {
|
|
1326
|
+
return this.generateNodeDescriptionFromNode(child, depth + 1);
|
|
1327
|
+
}
|
|
1328
|
+
return child.type;
|
|
1329
|
+
});
|
|
1330
|
+
return `${slotName}[${instanceTypes.join(", ")}]`;
|
|
1331
|
+
}).filter(Boolean);
|
|
1332
|
+
if (slotDescriptions.length === 0) {
|
|
1333
|
+
return node.type;
|
|
1334
|
+
}
|
|
1335
|
+
return `${node.type} > ${slotDescriptions.join(" > ")}`;
|
|
1336
|
+
}
|
|
1140
1337
|
/**
|
|
1141
1338
|
* Extracts all unique component types from a composition recursively.
|
|
1142
1339
|
*/
|
|
@@ -1586,7 +1783,7 @@ function createFindCompositionPatternCandidatesCommand() {
|
|
|
1586
1783
|
"--threshold <count>",
|
|
1587
1784
|
"Number of component differences allowed while still matching (0 = exact match)",
|
|
1588
1785
|
"0"
|
|
1589
|
-
).option("--format <format>", "Output format: text or json", "text").option("--brief", "Skip structure and composition details in text output").hook("preAction", (thisCommand) => {
|
|
1786
|
+
).option("--format <format>", "Output format: text or json", "text").option("--brief", "Skip structure and composition details in text output").option("--subset", "Group compositions where one structure is a subset of another", false).hook("preAction", (thisCommand) => {
|
|
1590
1787
|
const opts = thisCommand.opts();
|
|
1591
1788
|
const minGroupSize = parseInt(opts.minGroupSize, 10);
|
|
1592
1789
|
if (isNaN(minGroupSize) || minGroupSize < 2) {
|
|
@@ -1615,7 +1812,8 @@ function createFindCompositionPatternCandidatesCommand() {
|
|
|
1615
1812
|
depth: parseInt(opts.depth, 10),
|
|
1616
1813
|
threshold: parseInt(opts.threshold, 10),
|
|
1617
1814
|
format: opts.format,
|
|
1618
|
-
brief: opts.brief ?? false
|
|
1815
|
+
brief: opts.brief ?? false,
|
|
1816
|
+
subset: opts.subset ?? false
|
|
1619
1817
|
};
|
|
1620
1818
|
const logger = new Logger();
|
|
1621
1819
|
const fileSystem = new FileSystemService();
|
|
@@ -1630,7 +1828,8 @@ function createFindCompositionPatternCandidatesCommand() {
|
|
|
1630
1828
|
minGroupSize: options.minGroupSize,
|
|
1631
1829
|
depth: options.depth,
|
|
1632
1830
|
threshold: options.threshold,
|
|
1633
|
-
strict: options.strict ?? false
|
|
1831
|
+
strict: options.strict ?? false,
|
|
1832
|
+
subset: options.subset ?? false
|
|
1634
1833
|
});
|
|
1635
1834
|
if (options.format === "json") {
|
|
1636
1835
|
console.log(analyzer.formatJsonOutput(result));
|
|
@@ -3375,10 +3574,425 @@ function createPropagateRootComponentSlotCommand() {
|
|
|
3375
3574
|
return command;
|
|
3376
3575
|
}
|
|
3377
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
|
+
for (const flattenType of flattenComponentIds) {
|
|
3638
|
+
const isRootType = [...rootComponentTypes].some(
|
|
3639
|
+
(rt) => this.compareTypes(rt, flattenType, strict)
|
|
3640
|
+
);
|
|
3641
|
+
if (isRootType) {
|
|
3642
|
+
continue;
|
|
3643
|
+
}
|
|
3644
|
+
try {
|
|
3645
|
+
const { component } = await this.componentService.loadComponent(
|
|
3646
|
+
componentsDirFull,
|
|
3647
|
+
flattenType,
|
|
3648
|
+
{ strict }
|
|
3649
|
+
);
|
|
3650
|
+
const contentType = this.generateContentType(component);
|
|
3651
|
+
flattenContentTypeMap.set(flattenType, contentType);
|
|
3652
|
+
} catch (error) {
|
|
3653
|
+
if (error instanceof ComponentNotFoundError) {
|
|
3654
|
+
throw new ComponentNotFoundError(flattenType, componentsDirFull);
|
|
3655
|
+
}
|
|
3656
|
+
throw error;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
if (flattenComponentIds.length > 0) {
|
|
3660
|
+
for (const contentType of contentTypeMap.values()) {
|
|
3661
|
+
for (const flattenType of flattenComponentIds) {
|
|
3662
|
+
if (this.compareTypes(flattenType, contentType.id, strict)) {
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
contentType.fields.push({
|
|
3666
|
+
id: flattenType,
|
|
3667
|
+
name: flattenType,
|
|
3668
|
+
type: "contentReference",
|
|
3669
|
+
typeConfig: {
|
|
3670
|
+
isMulti: true,
|
|
3671
|
+
allowedContentTypes: [flattenType]
|
|
3672
|
+
},
|
|
3673
|
+
localizable: false
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
for (const [typeName, contentType] of contentTypeMap) {
|
|
3679
|
+
const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
|
|
3680
|
+
const fieldCount = contentType.fields.filter((f) => f.type !== "contentReference").length;
|
|
3681
|
+
const refCount = contentType.fields.filter((f) => f.type === "contentReference").length;
|
|
3682
|
+
const refInfo = refCount > 0 ? ` + ${refCount} reference(s)` : "";
|
|
3683
|
+
this.logger.action(
|
|
3684
|
+
whatIf,
|
|
3685
|
+
"WRITE",
|
|
3686
|
+
`${contentTypesDir}/${typeName}.json (${fieldCount} fields${refInfo})`
|
|
3687
|
+
);
|
|
3688
|
+
if (!whatIf) {
|
|
3689
|
+
await this.fileSystem.writeFile(filePath, contentType);
|
|
3690
|
+
}
|
|
3691
|
+
contentTypesWritten++;
|
|
3692
|
+
}
|
|
3693
|
+
for (const [typeName, contentType] of flattenContentTypeMap) {
|
|
3694
|
+
const filePath = this.fileSystem.joinPath(contentTypesDirFull, `${typeName}.json`);
|
|
3695
|
+
this.logger.action(
|
|
3696
|
+
whatIf,
|
|
3697
|
+
"WRITE",
|
|
3698
|
+
`${contentTypesDir}/${typeName}.json (${contentType.fields.length} fields)`
|
|
3699
|
+
);
|
|
3700
|
+
if (!whatIf) {
|
|
3701
|
+
await this.fileSystem.writeFile(filePath, contentType);
|
|
3702
|
+
}
|
|
3703
|
+
contentTypesWritten++;
|
|
3704
|
+
}
|
|
3705
|
+
for (const { composition } of compositionResults) {
|
|
3706
|
+
const comp = composition.composition;
|
|
3707
|
+
const compositionId = comp._id;
|
|
3708
|
+
const compositionName = comp._name ?? compositionId;
|
|
3709
|
+
const compositionType = comp.type;
|
|
3710
|
+
const entry = this.generateEntryFromComposition(composition);
|
|
3711
|
+
const flattenedByType = /* @__PURE__ */ new Map();
|
|
3712
|
+
if (flattenComponentIds.length > 0 && comp.slots) {
|
|
3713
|
+
for (const flattenType of flattenComponentIds) {
|
|
3714
|
+
if (this.compareTypes(flattenType, compositionType, strict)) {
|
|
3715
|
+
this.logger.warn(
|
|
3716
|
+
`Skipping flatten of "${flattenType}" \u2014 same as root component type`
|
|
3717
|
+
);
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
const instances = this.findFlattenTargets(
|
|
3721
|
+
comp.slots,
|
|
3722
|
+
flattenType,
|
|
3723
|
+
compositionId,
|
|
3724
|
+
compositionName,
|
|
3725
|
+
strict
|
|
3726
|
+
);
|
|
3727
|
+
if (instances.length > 0) {
|
|
3728
|
+
flattenedByType.set(flattenType, instances);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
for (const [flattenType, instances] of flattenedByType) {
|
|
3733
|
+
entry.entry.fields[flattenType] = {
|
|
3734
|
+
type: "contentReference",
|
|
3735
|
+
value: instances.map((inst) => inst.determinisiticId)
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
const entryFilePath = this.fileSystem.joinPath(entriesDirFull, `${compositionId}.json`);
|
|
3739
|
+
this.logger.action(
|
|
3740
|
+
whatIf,
|
|
3741
|
+
"WRITE",
|
|
3742
|
+
`${entriesDir}/${compositionId}.json (${compositionType}, "${this.truncate(compositionName, 50)}")`
|
|
3743
|
+
);
|
|
3744
|
+
if (!whatIf) {
|
|
3745
|
+
await this.fileSystem.writeFile(entryFilePath, entry);
|
|
3746
|
+
}
|
|
3747
|
+
entriesFromCompositions++;
|
|
3748
|
+
for (const [flattenType, instances] of flattenedByType) {
|
|
3749
|
+
for (const inst of instances) {
|
|
3750
|
+
const flatEntry = this.generateEntryFromFlattenedInstance(inst);
|
|
3751
|
+
const flatEntryPath = this.fileSystem.joinPath(
|
|
3752
|
+
entriesDirFull,
|
|
3753
|
+
`${inst.determinisiticId}.json`
|
|
3754
|
+
);
|
|
3755
|
+
this.logger.action(
|
|
3756
|
+
whatIf,
|
|
3757
|
+
"WRITE",
|
|
3758
|
+
`${entriesDir}/${inst.determinisiticId}.json (${flattenType} from "${this.truncate(compositionName, 50)}")`
|
|
3759
|
+
);
|
|
3760
|
+
if (!whatIf) {
|
|
3761
|
+
await this.fileSystem.writeFile(flatEntryPath, flatEntry);
|
|
3762
|
+
}
|
|
3763
|
+
entriesFromFlattened++;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
return { contentTypesWritten, entriesFromCompositions, entriesFromFlattened };
|
|
3768
|
+
}
|
|
3769
|
+
// --- Content Type Generation ---
|
|
3770
|
+
generateContentType(component) {
|
|
3771
|
+
const fields = [];
|
|
3772
|
+
if (component.parameters) {
|
|
3773
|
+
for (const param of component.parameters) {
|
|
3774
|
+
fields.push(this.parameterToField(param));
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
return {
|
|
3778
|
+
id: component.id,
|
|
3779
|
+
name: component.name,
|
|
3780
|
+
fields
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
parameterToField(param) {
|
|
3784
|
+
const field = {
|
|
3785
|
+
id: param.id,
|
|
3786
|
+
name: param.name,
|
|
3787
|
+
type: param.type
|
|
3788
|
+
};
|
|
3789
|
+
if (param.helpText !== void 0) {
|
|
3790
|
+
field.helpText = param.helpText;
|
|
3791
|
+
}
|
|
3792
|
+
if (param.localizable !== void 0) {
|
|
3793
|
+
field.localizable = param.localizable;
|
|
3794
|
+
}
|
|
3795
|
+
if (param.typeConfig !== void 0) {
|
|
3796
|
+
field.typeConfig = param.typeConfig;
|
|
3797
|
+
}
|
|
3798
|
+
return field;
|
|
3799
|
+
}
|
|
3800
|
+
// --- Entry Generation ---
|
|
3801
|
+
generateEntryFromComposition(composition) {
|
|
3802
|
+
const comp = composition.composition;
|
|
3803
|
+
return {
|
|
3804
|
+
entry: {
|
|
3805
|
+
_id: comp._id,
|
|
3806
|
+
_name: comp._name ?? comp._id,
|
|
3807
|
+
type: comp.type,
|
|
3808
|
+
fields: { ...comp.parameters ?? {} }
|
|
3809
|
+
}
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
generateEntryFromFlattenedInstance(inst) {
|
|
3813
|
+
return {
|
|
3814
|
+
entry: {
|
|
3815
|
+
_id: inst.determinisiticId,
|
|
3816
|
+
_name: `${inst.componentType} (from ${inst.compositionName})`,
|
|
3817
|
+
type: inst.componentType,
|
|
3818
|
+
fields: { ...inst.instance.parameters ?? {} }
|
|
3819
|
+
}
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
// --- Flatten Tree Walking ---
|
|
3823
|
+
findFlattenTargets(slots, targetType, compositionId, compositionName, strict) {
|
|
3824
|
+
const results = [];
|
|
3825
|
+
this.walkSlots(slots, targetType, compositionId, compositionName, "", results, strict);
|
|
3826
|
+
return results;
|
|
3827
|
+
}
|
|
3828
|
+
walkSlots(slots, targetType, compositionId, compositionName, pathPrefix, results, strict) {
|
|
3829
|
+
for (const [slotName, instances] of Object.entries(slots)) {
|
|
3830
|
+
if (!Array.isArray(instances)) continue;
|
|
3831
|
+
for (let i = 0; i < instances.length; i++) {
|
|
3832
|
+
const instance = instances[i];
|
|
3833
|
+
if (instance._pattern) {
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3836
|
+
const currentPath = pathPrefix ? `${pathPrefix}-${slotName}-[${i}]-${instance.type}` : `${slotName}-[${i}]-${instance.type}`;
|
|
3837
|
+
if (this.compareTypes(instance.type, targetType, strict)) {
|
|
3838
|
+
const fullPath = `${compositionId}-${currentPath}`;
|
|
3839
|
+
const deterministicId = computeGuidHash(fullPath);
|
|
3840
|
+
results.push({
|
|
3841
|
+
instance,
|
|
3842
|
+
path: fullPath,
|
|
3843
|
+
determinisiticId: deterministicId,
|
|
3844
|
+
componentType: instance.type,
|
|
3845
|
+
compositionId,
|
|
3846
|
+
compositionName
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
if (instance.slots) {
|
|
3850
|
+
this.walkSlots(
|
|
3851
|
+
instance.slots,
|
|
3852
|
+
targetType,
|
|
3853
|
+
compositionId,
|
|
3854
|
+
compositionName,
|
|
3855
|
+
currentPath,
|
|
3856
|
+
results,
|
|
3857
|
+
strict
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
// --- Utilities ---
|
|
3864
|
+
compareTypes(type1, type2, strict) {
|
|
3865
|
+
if (strict) {
|
|
3866
|
+
return type1 === type2;
|
|
3867
|
+
}
|
|
3868
|
+
return type1.toLowerCase() === type2.toLowerCase();
|
|
3869
|
+
}
|
|
3870
|
+
truncate(str, maxLength) {
|
|
3871
|
+
if (str.length <= maxLength) return str;
|
|
3872
|
+
return str.substring(0, maxLength - 3) + "...";
|
|
3873
|
+
}
|
|
3874
|
+
};
|
|
3875
|
+
function computeGuidHash(guidOrSeed) {
|
|
3876
|
+
let uuidStr;
|
|
3877
|
+
if (isValidUuid(guidOrSeed)) {
|
|
3878
|
+
uuidStr = formatAsBracedUuid(guidOrSeed);
|
|
3879
|
+
} else {
|
|
3880
|
+
const hash = crypto.createHash("md5").update(guidOrSeed, "utf-8").digest();
|
|
3881
|
+
const hex = [
|
|
3882
|
+
// First 4 bytes: little-endian in .NET Guid
|
|
3883
|
+
hash[3].toString(16).padStart(2, "0"),
|
|
3884
|
+
hash[2].toString(16).padStart(2, "0"),
|
|
3885
|
+
hash[1].toString(16).padStart(2, "0"),
|
|
3886
|
+
hash[0].toString(16).padStart(2, "0"),
|
|
3887
|
+
"-",
|
|
3888
|
+
// Bytes 4-5: little-endian
|
|
3889
|
+
hash[5].toString(16).padStart(2, "0"),
|
|
3890
|
+
hash[4].toString(16).padStart(2, "0"),
|
|
3891
|
+
"-",
|
|
3892
|
+
// Bytes 6-7: little-endian
|
|
3893
|
+
hash[7].toString(16).padStart(2, "0"),
|
|
3894
|
+
hash[6].toString(16).padStart(2, "0"),
|
|
3895
|
+
"-",
|
|
3896
|
+
// Bytes 8-9: big-endian
|
|
3897
|
+
hash[8].toString(16).padStart(2, "0"),
|
|
3898
|
+
hash[9].toString(16).padStart(2, "0"),
|
|
3899
|
+
"-",
|
|
3900
|
+
// Bytes 10-15: big-endian
|
|
3901
|
+
hash[10].toString(16).padStart(2, "0"),
|
|
3902
|
+
hash[11].toString(16).padStart(2, "0"),
|
|
3903
|
+
hash[12].toString(16).padStart(2, "0"),
|
|
3904
|
+
hash[13].toString(16).padStart(2, "0"),
|
|
3905
|
+
hash[14].toString(16).padStart(2, "0"),
|
|
3906
|
+
hash[15].toString(16).padStart(2, "0")
|
|
3907
|
+
].join("");
|
|
3908
|
+
uuidStr = `{${hex}}`.toUpperCase();
|
|
3909
|
+
}
|
|
3910
|
+
const chars = uuidStr.split("");
|
|
3911
|
+
chars[15] = "4";
|
|
3912
|
+
const arr20 = ["8", "9", "A", "B"];
|
|
3913
|
+
const hexVal = parseInt(chars[20], 16);
|
|
3914
|
+
chars[20] = arr20[hexVal % 4];
|
|
3915
|
+
return chars.join("").slice(1, -1).toLowerCase();
|
|
3916
|
+
}
|
|
3917
|
+
function isValidUuid(str) {
|
|
3918
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
|
|
3919
|
+
}
|
|
3920
|
+
function formatAsBracedUuid(uuid) {
|
|
3921
|
+
const clean = uuid.replace(/[{}]/g, "").toUpperCase();
|
|
3922
|
+
return `{${clean}}`;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
// src/cli/commands/convert-compositions-to-entries.ts
|
|
3926
|
+
function createConvertCompositionsToEntriesCommand() {
|
|
3927
|
+
const command = new Command10("convert-compositions-to-entries");
|
|
3928
|
+
command.description(
|
|
3929
|
+
"Converts compositions into content entries, optionally flattening nested components into separate referenced entries."
|
|
3930
|
+
).option(
|
|
3931
|
+
"--compositionTypes <types>",
|
|
3932
|
+
"Pipe-separated list of composition types to convert (e.g., ProductDetailPage|ArticlePage)"
|
|
3933
|
+
).option(
|
|
3934
|
+
"--flattenComponentIds <types>",
|
|
3935
|
+
"Pipe-separated list of component types to flatten into separate entries (e.g., DetailHero|ArticleDetail)"
|
|
3936
|
+
).hook("preAction", (thisCommand) => {
|
|
3937
|
+
const opts = thisCommand.opts();
|
|
3938
|
+
const requiredOptions = [
|
|
3939
|
+
{ name: "compositionTypes", flag: "--compositionTypes" }
|
|
3940
|
+
];
|
|
3941
|
+
const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
|
|
3942
|
+
if (missing.length > 0) {
|
|
3943
|
+
console.error(`error: missing required options: ${missing.join(", ")}`);
|
|
3944
|
+
process.exit(1);
|
|
3945
|
+
}
|
|
3946
|
+
}).action(async (opts, cmd) => {
|
|
3947
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3948
|
+
const options = {
|
|
3949
|
+
...globalOpts,
|
|
3950
|
+
compositionTypes: opts.compositionTypes,
|
|
3951
|
+
flattenComponentIds: opts.flattenComponentIds
|
|
3952
|
+
};
|
|
3953
|
+
const logger = new Logger();
|
|
3954
|
+
const fileSystem = new FileSystemService();
|
|
3955
|
+
const componentService = new ComponentService(fileSystem);
|
|
3956
|
+
const compositionService = new CompositionService(fileSystem);
|
|
3957
|
+
const converter = new CompositionConverterService(
|
|
3958
|
+
fileSystem,
|
|
3959
|
+
componentService,
|
|
3960
|
+
compositionService,
|
|
3961
|
+
logger
|
|
3962
|
+
);
|
|
3963
|
+
try {
|
|
3964
|
+
const compositionTypes = options.compositionTypes.split("|").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
3965
|
+
const flattenComponentIds = options.flattenComponentIds ? options.flattenComponentIds.split("|").map((t) => t.trim()).filter((t) => t.length > 0) : [];
|
|
3966
|
+
const result = await converter.convert({
|
|
3967
|
+
rootDir: options.rootDir,
|
|
3968
|
+
compositionsDir: options.compositionsDir,
|
|
3969
|
+
componentsDir: options.componentsDir,
|
|
3970
|
+
contentTypesDir: options.contentTypesDir,
|
|
3971
|
+
entriesDir: options.entriesDir,
|
|
3972
|
+
compositionTypes,
|
|
3973
|
+
flattenComponentIds,
|
|
3974
|
+
whatIf: options.whatIf ?? false,
|
|
3975
|
+
strict: options.strict ?? false
|
|
3976
|
+
});
|
|
3977
|
+
const flatInfo = result.entriesFromFlattened > 0 ? `, ${result.entriesFromFlattened} from flattened components` : "";
|
|
3978
|
+
logger.success(
|
|
3979
|
+
`${result.contentTypesWritten} content type(s), ${result.entriesFromCompositions} entry(ies) from compositions${flatInfo}`
|
|
3980
|
+
);
|
|
3981
|
+
} catch (error) {
|
|
3982
|
+
if (error instanceof TransformError) {
|
|
3983
|
+
logger.error(error.message);
|
|
3984
|
+
process.exit(1);
|
|
3985
|
+
}
|
|
3986
|
+
throw error;
|
|
3987
|
+
}
|
|
3988
|
+
});
|
|
3989
|
+
return command;
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3378
3992
|
// package.json
|
|
3379
3993
|
var package_default = {
|
|
3380
3994
|
name: "@uniformdev/transformer",
|
|
3381
|
-
version: "1.1.
|
|
3995
|
+
version: "1.1.10",
|
|
3382
3996
|
description: "CLI tool for transforming Uniform.dev serialization files offline",
|
|
3383
3997
|
type: "module",
|
|
3384
3998
|
bin: {
|
|
@@ -3447,7 +4061,7 @@ var package_default = {
|
|
|
3447
4061
|
};
|
|
3448
4062
|
|
|
3449
4063
|
// src/cli/index.ts
|
|
3450
|
-
var program = new
|
|
4064
|
+
var program = new Command11();
|
|
3451
4065
|
var appVersion = package_default.version;
|
|
3452
4066
|
console.error(`uniform-transform v${appVersion}`);
|
|
3453
4067
|
program.name("uniform-transform").description("CLI tool for transforming Uniform.dev serialization files offline").version(appVersion);
|
|
@@ -3469,5 +4083,6 @@ program.addCommand(createRenameComponentCommand());
|
|
|
3469
4083
|
program.addCommand(createAddComponentCommand());
|
|
3470
4084
|
program.addCommand(createAddComponentPatternCommand());
|
|
3471
4085
|
program.addCommand(createPropagateRootComponentSlotCommand());
|
|
4086
|
+
program.addCommand(createConvertCompositionsToEntriesCommand());
|
|
3472
4087
|
program.parse();
|
|
3473
4088
|
//# sourceMappingURL=index.js.map
|