@uniformdev/transformer 1.0.0

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.js ADDED
@@ -0,0 +1,1116 @@
1
+ // src/core/errors.ts
2
+ var TransformError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "TransformError";
6
+ }
7
+ };
8
+ var ComponentNotFoundError = class extends TransformError {
9
+ constructor(componentType, path2) {
10
+ const pathInfo = path2 ? ` (searched: ${path2})` : "";
11
+ super(`Component not found: ${componentType}${pathInfo}`);
12
+ this.name = "ComponentNotFoundError";
13
+ }
14
+ };
15
+ var PropertyNotFoundError = class extends TransformError {
16
+ constructor(propertyName, componentType) {
17
+ super(`Property "${propertyName}" not found on component "${componentType}"`);
18
+ this.name = "PropertyNotFoundError";
19
+ }
20
+ };
21
+ var InvalidYamlError = class extends TransformError {
22
+ constructor(filePath, details) {
23
+ const detailsInfo = details ? `: ${details}` : "";
24
+ super(`Invalid YAML file: ${filePath}${detailsInfo}`);
25
+ this.name = "InvalidYamlError";
26
+ }
27
+ };
28
+ var FileNotFoundError = class extends TransformError {
29
+ constructor(filePath) {
30
+ super(`File not found: ${filePath}`);
31
+ this.name = "FileNotFoundError";
32
+ }
33
+ };
34
+ var DuplicateIdError = class extends TransformError {
35
+ constructor(id, arrayProperty, filePath) {
36
+ super(`Duplicate id "${id}" in array "${arrayProperty}" of file: ${filePath}`);
37
+ this.name = "DuplicateIdError";
38
+ }
39
+ };
40
+ var ComponentAlreadyExistsError = class extends TransformError {
41
+ constructor(componentType, path2) {
42
+ const pathInfo = path2 ? ` (searched: ${path2})` : "";
43
+ super(`Component type "${componentType}" already exists${pathInfo}`);
44
+ this.name = "ComponentAlreadyExistsError";
45
+ }
46
+ };
47
+ var SlotNotFoundError = class extends TransformError {
48
+ constructor(slotId, componentType) {
49
+ super(`Slot "${slotId}" does not exist on component "${componentType}"`);
50
+ this.name = "SlotNotFoundError";
51
+ }
52
+ };
53
+ var SlotAlreadyExistsError = class extends TransformError {
54
+ constructor(slotId, componentType) {
55
+ super(`Slot "${slotId}" already exists on component "${componentType}"`);
56
+ this.name = "SlotAlreadyExistsError";
57
+ }
58
+ };
59
+
60
+ // src/core/services/file-system.service.ts
61
+ import * as fs from "fs";
62
+ import * as path from "path";
63
+ import { glob } from "glob";
64
+ import * as YAML from "yaml";
65
+ var FileSystemService = class {
66
+ isJsonFile(filePath) {
67
+ return filePath.toLowerCase().endsWith(".json");
68
+ }
69
+ async readFile(filePath) {
70
+ if (!fs.existsSync(filePath)) {
71
+ throw new FileNotFoundError(filePath);
72
+ }
73
+ try {
74
+ const content = fs.readFileSync(filePath, "utf-8");
75
+ if (this.isJsonFile(filePath)) {
76
+ return JSON.parse(content);
77
+ }
78
+ return YAML.parse(content);
79
+ } catch (error) {
80
+ if (error instanceof FileNotFoundError) {
81
+ throw error;
82
+ }
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ throw new InvalidYamlError(filePath, message);
85
+ }
86
+ }
87
+ async writeFile(filePath, data) {
88
+ const dir = path.dirname(filePath);
89
+ if (!fs.existsSync(dir)) {
90
+ fs.mkdirSync(dir, { recursive: true });
91
+ }
92
+ let content;
93
+ if (this.isJsonFile(filePath)) {
94
+ content = JSON.stringify(data, null, 2);
95
+ } else {
96
+ content = YAML.stringify(data, {
97
+ lineWidth: 0,
98
+ defaultKeyType: "PLAIN",
99
+ defaultStringType: "QUOTE_DOUBLE"
100
+ });
101
+ }
102
+ fs.writeFileSync(filePath, content, "utf-8");
103
+ }
104
+ async readYamlFile(filePath) {
105
+ return this.readFile(filePath);
106
+ }
107
+ async writeYamlFile(filePath, data) {
108
+ return this.writeFile(filePath, data);
109
+ }
110
+ async findFiles(directory, pattern) {
111
+ const fullPattern = path.join(directory, pattern).replace(/\\/g, "/");
112
+ return glob(fullPattern);
113
+ }
114
+ async fileExists(filePath) {
115
+ return fs.existsSync(filePath);
116
+ }
117
+ resolvePath(...segments) {
118
+ return path.resolve(...segments);
119
+ }
120
+ joinPath(...segments) {
121
+ return path.join(...segments);
122
+ }
123
+ getBasename(filePath, ext) {
124
+ return path.basename(filePath, ext);
125
+ }
126
+ async renameFile(oldPath, newPath) {
127
+ fs.renameSync(oldPath, newPath);
128
+ }
129
+ deleteFile(filePath) {
130
+ fs.unlinkSync(filePath);
131
+ }
132
+ getExtension(filePath) {
133
+ return path.extname(filePath);
134
+ }
135
+ getDirname(filePath) {
136
+ return path.dirname(filePath);
137
+ }
138
+ };
139
+
140
+ // src/core/services/component.service.ts
141
+ var ComponentService = class {
142
+ constructor(fileSystem) {
143
+ this.fileSystem = fileSystem;
144
+ }
145
+ compareIds(id1, id2, strict) {
146
+ if (strict) {
147
+ return id1 === id2;
148
+ }
149
+ return id1.toLowerCase() === id2.toLowerCase();
150
+ }
151
+ async loadComponent(componentsDir, componentType, options = {}) {
152
+ const { strict = false } = options;
153
+ const jsonPath = this.fileSystem.joinPath(componentsDir, `${componentType}.json`);
154
+ const yamlPath = this.fileSystem.joinPath(componentsDir, `${componentType}.yaml`);
155
+ const ymlPath = this.fileSystem.joinPath(componentsDir, `${componentType}.yml`);
156
+ if (await this.fileSystem.fileExists(jsonPath)) {
157
+ const component = await this.fileSystem.readFile(jsonPath);
158
+ return { component, filePath: jsonPath };
159
+ }
160
+ if (await this.fileSystem.fileExists(yamlPath)) {
161
+ const component = await this.fileSystem.readFile(yamlPath);
162
+ return { component, filePath: yamlPath };
163
+ }
164
+ if (await this.fileSystem.fileExists(ymlPath)) {
165
+ const component = await this.fileSystem.readFile(ymlPath);
166
+ return { component, filePath: ymlPath };
167
+ }
168
+ if (!strict) {
169
+ const files = await this.fileSystem.findFiles(componentsDir, "*.{json,yaml,yml}");
170
+ for (const filePath of files) {
171
+ const basename2 = this.fileSystem.getBasename(filePath);
172
+ const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
173
+ if (nameWithoutExt.toLowerCase() === componentType.toLowerCase()) {
174
+ const component = await this.fileSystem.readFile(filePath);
175
+ return { component, filePath };
176
+ }
177
+ }
178
+ }
179
+ throw new ComponentNotFoundError(componentType, componentsDir);
180
+ }
181
+ async saveComponent(filePath, component) {
182
+ await this.fileSystem.writeFile(filePath, component);
183
+ }
184
+ findParameter(component, parameterName, options = {}) {
185
+ const { strict = false } = options;
186
+ return component.parameters?.find((p) => this.compareIds(p.id, parameterName, strict));
187
+ }
188
+ isGroupParameter(parameter) {
189
+ return parameter.type === "group";
190
+ }
191
+ resolveGroupParameters(component, groupParameter, options = {}) {
192
+ if (!this.isGroupParameter(groupParameter)) {
193
+ return [groupParameter];
194
+ }
195
+ const childrenIds = groupParameter.typeConfig?.childrenParams ?? [];
196
+ const resolved = [];
197
+ for (const childId of childrenIds) {
198
+ const childParam = this.findParameter(component, childId, options);
199
+ if (childParam) {
200
+ resolved.push(childParam);
201
+ }
202
+ }
203
+ return resolved;
204
+ }
205
+ resolveProperties(component, propertyNames, options = {}) {
206
+ const parameters = [];
207
+ const notFound = [];
208
+ const seenIds = /* @__PURE__ */ new Set();
209
+ for (const name of propertyNames) {
210
+ const param = this.findParameter(component, name, options);
211
+ if (!param) {
212
+ notFound.push(name);
213
+ continue;
214
+ }
215
+ if (this.isGroupParameter(param)) {
216
+ const groupParams = this.resolveGroupParameters(component, param, options);
217
+ for (const gp of groupParams) {
218
+ if (!seenIds.has(gp.id)) {
219
+ seenIds.add(gp.id);
220
+ parameters.push(gp);
221
+ }
222
+ }
223
+ } else {
224
+ if (!seenIds.has(param.id)) {
225
+ seenIds.add(param.id);
226
+ parameters.push(param);
227
+ }
228
+ }
229
+ }
230
+ return { parameters, notFound };
231
+ }
232
+ addParameterToComponent(component, parameter, options = {}) {
233
+ const { strict = false } = options;
234
+ if (!component.parameters) {
235
+ component.parameters = [];
236
+ }
237
+ const existingIndex = component.parameters.findIndex(
238
+ (p) => this.compareIds(p.id, parameter.id, strict)
239
+ );
240
+ if (existingIndex >= 0) {
241
+ component.parameters[existingIndex] = { ...parameter };
242
+ } else {
243
+ component.parameters.push({ ...parameter });
244
+ }
245
+ return component;
246
+ }
247
+ ensureGroupExists(component, groupId, groupName, options = {}) {
248
+ const { strict = false } = options;
249
+ if (!component.parameters) {
250
+ component.parameters = [];
251
+ }
252
+ const existing = component.parameters.find((p) => this.compareIds(p.id, groupId, strict));
253
+ if (existing) {
254
+ return component;
255
+ }
256
+ const groupParam = {
257
+ id: groupId,
258
+ name: groupName ?? groupId,
259
+ type: "group",
260
+ typeConfig: {
261
+ childrenParams: []
262
+ }
263
+ };
264
+ component.parameters.push(groupParam);
265
+ return component;
266
+ }
267
+ addParameterToGroup(component, groupId, parameterId, options = {}) {
268
+ const { strict = false } = options;
269
+ const group = component.parameters?.find((p) => this.compareIds(p.id, groupId, strict));
270
+ if (!group || !this.isGroupParameter(group)) {
271
+ throw new PropertyNotFoundError(groupId, component.id);
272
+ }
273
+ if (!group.typeConfig) {
274
+ group.typeConfig = { childrenParams: [] };
275
+ }
276
+ if (!group.typeConfig.childrenParams) {
277
+ group.typeConfig.childrenParams = [];
278
+ }
279
+ const alreadyExists = group.typeConfig.childrenParams.some(
280
+ (id) => this.compareIds(id, parameterId, strict)
281
+ );
282
+ if (!alreadyExists) {
283
+ group.typeConfig.childrenParams.push(parameterId);
284
+ }
285
+ return component;
286
+ }
287
+ removeParameter(component, parameterId, options = {}) {
288
+ const { strict = false } = options;
289
+ if (!component.parameters) {
290
+ return component;
291
+ }
292
+ component.parameters = component.parameters.filter(
293
+ (p) => !this.compareIds(p.id, parameterId, strict)
294
+ );
295
+ for (const param of component.parameters) {
296
+ if (this.isGroupParameter(param) && param.typeConfig?.childrenParams) {
297
+ param.typeConfig.childrenParams = param.typeConfig.childrenParams.filter(
298
+ (id) => !this.compareIds(id, parameterId, strict)
299
+ );
300
+ }
301
+ }
302
+ return component;
303
+ }
304
+ removeEmptyGroups(component) {
305
+ if (!component.parameters) {
306
+ return component;
307
+ }
308
+ component.parameters = component.parameters.filter((p) => {
309
+ if (!this.isGroupParameter(p)) {
310
+ return true;
311
+ }
312
+ const children = p.typeConfig?.childrenParams ?? [];
313
+ return children.length > 0;
314
+ });
315
+ return component;
316
+ }
317
+ };
318
+
319
+ // src/core/services/composition.service.ts
320
+ var CompositionService = class {
321
+ constructor(fileSystem) {
322
+ this.fileSystem = fileSystem;
323
+ }
324
+ compareTypes(type1, type2, strict) {
325
+ if (strict) {
326
+ return type1 === type2;
327
+ }
328
+ return type1.toLowerCase() === type2.toLowerCase();
329
+ }
330
+ async loadComposition(filePath) {
331
+ return this.fileSystem.readFile(filePath);
332
+ }
333
+ async saveComposition(filePath, composition) {
334
+ await this.fileSystem.writeFile(filePath, composition);
335
+ }
336
+ async findCompositionsByType(compositionsDir, compositionType, options = {}) {
337
+ const { strict = false } = options;
338
+ const files = await this.fileSystem.findFiles(compositionsDir, "**/*.{json,yaml,yml}");
339
+ const results = [];
340
+ for (const filePath of files) {
341
+ try {
342
+ const composition = await this.loadComposition(filePath);
343
+ if (composition.composition?.type && this.compareTypes(composition.composition.type, compositionType, strict)) {
344
+ results.push({ composition, filePath });
345
+ }
346
+ } catch {
347
+ }
348
+ }
349
+ return results;
350
+ }
351
+ findComponentInstances(composition, componentType, options = {}) {
352
+ const { strict = false } = options;
353
+ const results = [];
354
+ this.searchSlots(composition.composition.slots ?? {}, componentType, [], results, strict);
355
+ return results;
356
+ }
357
+ searchSlots(slots, componentType, currentPath, results, strict) {
358
+ for (const [slotName, instances] of Object.entries(slots)) {
359
+ if (!Array.isArray(instances)) continue;
360
+ for (let i = 0; i < instances.length; i++) {
361
+ const instance = instances[i];
362
+ const instancePath = [...currentPath, slotName, String(i)];
363
+ if (this.compareTypes(instance.type, componentType, strict)) {
364
+ const instanceId = instance._id ?? `${componentType}-${results.length}`;
365
+ results.push({
366
+ instance,
367
+ instanceId,
368
+ path: instancePath
369
+ });
370
+ }
371
+ if (instance.slots) {
372
+ this.searchSlots(instance.slots, componentType, instancePath, results, strict);
373
+ }
374
+ }
375
+ }
376
+ }
377
+ getRootOverrides(composition) {
378
+ const rootId = composition.composition._id;
379
+ const overrides = composition.composition._overrides?.[rootId]?.parameters;
380
+ if (overrides && Object.keys(overrides).length > 0) {
381
+ return overrides;
382
+ }
383
+ return composition.composition.parameters ?? {};
384
+ }
385
+ getInstanceOverrides(composition, instanceId) {
386
+ return composition.composition._overrides?.[instanceId]?.parameters ?? {};
387
+ }
388
+ setInstanceOverride(composition, instanceId, parameterId, value) {
389
+ if (!composition.composition._overrides) {
390
+ composition.composition._overrides = {};
391
+ }
392
+ if (!composition.composition._overrides[instanceId]) {
393
+ composition.composition._overrides[instanceId] = {};
394
+ }
395
+ if (!composition.composition._overrides[instanceId].parameters) {
396
+ composition.composition._overrides[instanceId].parameters = {};
397
+ }
398
+ composition.composition._overrides[instanceId].parameters[parameterId] = value;
399
+ return composition;
400
+ }
401
+ setInstanceOverrides(composition, instanceId, parameters) {
402
+ for (const [paramId, value] of Object.entries(parameters)) {
403
+ this.setInstanceOverride(composition, instanceId, paramId, value);
404
+ }
405
+ return composition;
406
+ }
407
+ /**
408
+ * Sets parameters directly on a component instance (preferred approach).
409
+ * This modifies the instance object in-place.
410
+ */
411
+ setInstanceParameters(instance, parameters) {
412
+ if (!instance.parameters) {
413
+ instance.parameters = {};
414
+ }
415
+ for (const [paramId, value] of Object.entries(parameters)) {
416
+ instance.parameters[paramId] = value;
417
+ }
418
+ }
419
+ /**
420
+ * Deletes parameters from root overrides.
421
+ * Returns true if any parameters were deleted.
422
+ */
423
+ deleteRootOverrides(composition, parameterIds, options = {}) {
424
+ const { strict = false } = options;
425
+ const rootId = composition.composition._id;
426
+ let deleted = false;
427
+ const overrides = composition.composition._overrides?.[rootId]?.parameters;
428
+ if (overrides) {
429
+ for (const paramId of parameterIds) {
430
+ for (const key of Object.keys(overrides)) {
431
+ if (strict ? key === paramId : key.toLowerCase() === paramId.toLowerCase()) {
432
+ delete overrides[key];
433
+ deleted = true;
434
+ }
435
+ }
436
+ }
437
+ }
438
+ const rootParams = composition.composition.parameters;
439
+ if (rootParams) {
440
+ for (const paramId of parameterIds) {
441
+ for (const key of Object.keys(rootParams)) {
442
+ if (strict ? key === paramId : key.toLowerCase() === paramId.toLowerCase()) {
443
+ delete rootParams[key];
444
+ deleted = true;
445
+ }
446
+ }
447
+ }
448
+ }
449
+ return deleted;
450
+ }
451
+ };
452
+
453
+ // src/core/services/property-propagator.service.ts
454
+ var PropertyPropagatorService = class {
455
+ constructor(fileSystem, componentService, compositionService, logger) {
456
+ this.fileSystem = fileSystem;
457
+ this.componentService = componentService;
458
+ this.compositionService = compositionService;
459
+ this.logger = logger;
460
+ }
461
+ async propagate(options) {
462
+ const {
463
+ rootDir,
464
+ componentsDir,
465
+ compositionsDir,
466
+ compositionType,
467
+ property,
468
+ targetComponentType,
469
+ targetGroup,
470
+ whatIf,
471
+ strict,
472
+ deleteSourceParameter
473
+ } = options;
474
+ const findOptions = { strict };
475
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
476
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
477
+ this.logger.info(`Loading component: ${compositionType}`);
478
+ const { component: sourceComponent, filePath: sourceFilePath } = await this.componentService.loadComponent(fullComponentsDir, compositionType, findOptions);
479
+ this.logger.info(`Loading component: ${targetComponentType}`);
480
+ const { component: targetComponent, filePath: targetFilePath } = await this.componentService.loadComponent(fullComponentsDir, targetComponentType, findOptions);
481
+ const propertyNames = property.split("|").map((p) => p.trim());
482
+ const { parameters: resolvedParams, notFound } = this.componentService.resolveProperties(
483
+ sourceComponent,
484
+ propertyNames,
485
+ findOptions
486
+ );
487
+ if (notFound.length > 0) {
488
+ throw new PropertyNotFoundError(notFound.join(", "), compositionType);
489
+ }
490
+ const resolvedNames = resolvedParams.map((p) => p.id);
491
+ const groupSources = propertyNames.filter((name) => {
492
+ const param = this.componentService.findParameter(sourceComponent, name, findOptions);
493
+ return param && this.componentService.isGroupParameter(param);
494
+ });
495
+ if (groupSources.length > 0) {
496
+ this.logger.info(
497
+ `Resolved properties: ${resolvedNames.join(", ")} (from ${groupSources.join(", ")})`
498
+ );
499
+ } else {
500
+ this.logger.info(`Resolved properties: ${resolvedNames.join(", ")}`);
501
+ }
502
+ let modifiedComponent = { ...targetComponent };
503
+ let componentModified = false;
504
+ const existingGroup = this.componentService.findParameter(
505
+ modifiedComponent,
506
+ targetGroup,
507
+ findOptions
508
+ );
509
+ if (!existingGroup) {
510
+ this.logger.action(whatIf, "CREATE", `Group "${targetGroup}" on ${targetComponentType}`);
511
+ modifiedComponent = this.componentService.ensureGroupExists(
512
+ modifiedComponent,
513
+ targetGroup,
514
+ void 0,
515
+ findOptions
516
+ );
517
+ componentModified = true;
518
+ }
519
+ for (const param of resolvedParams) {
520
+ const existingParam = this.componentService.findParameter(
521
+ modifiedComponent,
522
+ param.id,
523
+ findOptions
524
+ );
525
+ if (!existingParam) {
526
+ this.logger.action(
527
+ whatIf,
528
+ "COPY",
529
+ `Parameter "${param.id}" \u2192 ${targetComponentType}.${targetGroup}`
530
+ );
531
+ modifiedComponent = this.componentService.addParameterToComponent(
532
+ modifiedComponent,
533
+ param,
534
+ findOptions
535
+ );
536
+ modifiedComponent = this.componentService.addParameterToGroup(
537
+ modifiedComponent,
538
+ targetGroup,
539
+ param.id,
540
+ findOptions
541
+ );
542
+ componentModified = true;
543
+ } else {
544
+ this.logger.info(`Parameter "${param.id}" already exists on ${targetComponentType}`);
545
+ const group = this.componentService.findParameter(
546
+ modifiedComponent,
547
+ targetGroup,
548
+ findOptions
549
+ );
550
+ if (group && this.componentService.isGroupParameter(group)) {
551
+ const isInGroup = group.typeConfig?.childrenParams?.some(
552
+ (id) => strict ? id === param.id : id.toLowerCase() === param.id.toLowerCase()
553
+ );
554
+ if (!isInGroup) {
555
+ modifiedComponent = this.componentService.addParameterToGroup(
556
+ modifiedComponent,
557
+ targetGroup,
558
+ param.id,
559
+ findOptions
560
+ );
561
+ componentModified = true;
562
+ }
563
+ }
564
+ }
565
+ }
566
+ if (componentModified && !whatIf) {
567
+ await this.componentService.saveComponent(targetFilePath, modifiedComponent);
568
+ }
569
+ const compositions = await this.compositionService.findCompositionsByType(
570
+ fullCompositionsDir,
571
+ compositionType,
572
+ findOptions
573
+ );
574
+ let modifiedCompositions = 0;
575
+ let propagatedInstances = 0;
576
+ for (const { composition, filePath } of compositions) {
577
+ const rootOverrides = this.compositionService.getRootOverrides(composition);
578
+ const instances = this.compositionService.findComponentInstances(
579
+ composition,
580
+ targetComponentType,
581
+ findOptions
582
+ );
583
+ if (instances.length === 0) {
584
+ continue;
585
+ }
586
+ const valuesToPropagate = {};
587
+ for (const param of resolvedParams) {
588
+ if (rootOverrides[param.id]) {
589
+ valuesToPropagate[param.id] = rootOverrides[param.id];
590
+ }
591
+ }
592
+ if (Object.keys(valuesToPropagate).length === 0) {
593
+ continue;
594
+ }
595
+ let compositionModified = false;
596
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
597
+ const instanceUpdates = [];
598
+ for (const { instance, instanceId } of instances) {
599
+ const instanceName = instance._id ?? instanceId;
600
+ this.compositionService.setInstanceParameters(instance, valuesToPropagate);
601
+ compositionModified = true;
602
+ propagatedInstances++;
603
+ instanceUpdates.push(
604
+ `${targetComponentType} "${instanceName}": ${Object.keys(valuesToPropagate).join(", ")}`
605
+ );
606
+ }
607
+ if (compositionModified) {
608
+ this.logger.action(whatIf, "UPDATE", `composition/${relativePath}`);
609
+ for (const update of instanceUpdates) {
610
+ this.logger.detail(`\u2192 ${update}`);
611
+ }
612
+ if (!whatIf) {
613
+ await this.compositionService.saveComposition(filePath, composition);
614
+ }
615
+ modifiedCompositions++;
616
+ }
617
+ }
618
+ let sourceComponentModified = false;
619
+ if (deleteSourceParameter) {
620
+ let modifiedSource = { ...sourceComponent };
621
+ for (const param of resolvedParams) {
622
+ this.logger.action(whatIf, "DELETE", `Parameter "${param.id}" from ${compositionType}`);
623
+ modifiedSource = this.componentService.removeParameter(modifiedSource, param.id, findOptions);
624
+ sourceComponentModified = true;
625
+ }
626
+ const beforeGroupCount = modifiedSource.parameters?.filter(
627
+ (p) => this.componentService.isGroupParameter(p)
628
+ ).length ?? 0;
629
+ modifiedSource = this.componentService.removeEmptyGroups(modifiedSource);
630
+ const afterGroupCount = modifiedSource.parameters?.filter(
631
+ (p) => this.componentService.isGroupParameter(p)
632
+ ).length ?? 0;
633
+ if (afterGroupCount < beforeGroupCount) {
634
+ const removedCount = beforeGroupCount - afterGroupCount;
635
+ this.logger.action(
636
+ whatIf,
637
+ "DELETE",
638
+ `${removedCount} empty group(s) from ${compositionType}`
639
+ );
640
+ }
641
+ if (sourceComponentModified && !whatIf) {
642
+ await this.componentService.saveComponent(sourceFilePath, modifiedSource);
643
+ }
644
+ for (const { composition, filePath } of compositions) {
645
+ const relativePath = filePath.replace(fullCompositionsDir, "").replace(/^[/\\]/, "");
646
+ const deleted = this.compositionService.deleteRootOverrides(
647
+ composition,
648
+ resolvedNames,
649
+ findOptions
650
+ );
651
+ if (deleted) {
652
+ this.logger.action(whatIf, "DELETE", `Root overrides from composition/${relativePath}`);
653
+ this.logger.detail(`\u2192 Removed: ${resolvedNames.join(", ")}`);
654
+ if (!whatIf) {
655
+ await this.compositionService.saveComposition(filePath, composition);
656
+ }
657
+ }
658
+ }
659
+ }
660
+ return {
661
+ modifiedComponents: (componentModified ? 1 : 0) + (sourceComponentModified ? 1 : 0),
662
+ modifiedCompositions,
663
+ propagatedInstances
664
+ };
665
+ }
666
+ };
667
+
668
+ // src/core/services/slot-renamer.service.ts
669
+ var SlotRenamerService = class {
670
+ constructor(fileSystem, componentService, logger) {
671
+ this.fileSystem = fileSystem;
672
+ this.componentService = componentService;
673
+ this.logger = logger;
674
+ }
675
+ compareIds(id1, id2, strict) {
676
+ if (strict) {
677
+ return id1 === id2;
678
+ }
679
+ return id1.toLowerCase() === id2.toLowerCase();
680
+ }
681
+ async rename(options) {
682
+ const {
683
+ rootDir,
684
+ componentsDir,
685
+ compositionsDir,
686
+ compositionPatternsDir,
687
+ componentPatternsDir,
688
+ componentType,
689
+ slotId,
690
+ newSlotId,
691
+ newSlotName,
692
+ whatIf,
693
+ strict
694
+ } = options;
695
+ const findOptions = { strict };
696
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
697
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
698
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
699
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
700
+ if (this.compareIds(slotId, newSlotId, strict)) {
701
+ throw new TransformError("New slot ID is the same as the current slot ID");
702
+ }
703
+ this.logger.info(`Loading component: ${componentType}`);
704
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
705
+ if (!component.slots || component.slots.length === 0) {
706
+ throw new SlotNotFoundError(slotId, componentType);
707
+ }
708
+ const slotIndex = component.slots.findIndex(
709
+ (s) => this.compareIds(s.id, slotId, strict)
710
+ );
711
+ if (slotIndex === -1) {
712
+ throw new SlotNotFoundError(slotId, componentType);
713
+ }
714
+ const existingNewSlot = component.slots.find(
715
+ (s) => this.compareIds(s.id, newSlotId, strict)
716
+ );
717
+ if (existingNewSlot) {
718
+ throw new SlotAlreadyExistsError(newSlotId, componentType);
719
+ }
720
+ this.logger.action(
721
+ whatIf,
722
+ "UPDATE",
723
+ `Slot "${slotId}" \u2192 "${newSlotId}" in component/${this.fileSystem.getBasename(componentFilePath)}`
724
+ );
725
+ component.slots[slotIndex].id = newSlotId;
726
+ if (newSlotName !== void 0) {
727
+ component.slots[slotIndex].name = newSlotName;
728
+ }
729
+ if (!whatIf) {
730
+ await this.componentService.saveComponent(componentFilePath, component);
731
+ }
732
+ const compositionsResult = await this.renameSlotInDirectory(
733
+ fullCompositionsDir,
734
+ componentType,
735
+ slotId,
736
+ newSlotId,
737
+ whatIf,
738
+ strict,
739
+ "composition"
740
+ );
741
+ const compositionPatternsResult = await this.renameSlotInDirectory(
742
+ fullCompositionPatternsDir,
743
+ componentType,
744
+ slotId,
745
+ newSlotId,
746
+ whatIf,
747
+ strict,
748
+ "compositionPattern"
749
+ );
750
+ const componentPatternsResult = await this.renameSlotInDirectory(
751
+ fullComponentPatternsDir,
752
+ componentType,
753
+ slotId,
754
+ newSlotId,
755
+ whatIf,
756
+ strict,
757
+ "componentPattern"
758
+ );
759
+ const totalFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
760
+ const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
761
+ this.logger.info("");
762
+ this.logger.info(
763
+ `Summary: 1 component definition, ${totalFiles} file(s) (${totalInstances} instance(s)) updated.`
764
+ );
765
+ return {
766
+ componentDefinitionUpdated: true,
767
+ compositionsModified: compositionsResult.filesModified,
768
+ compositionPatternsModified: compositionPatternsResult.filesModified,
769
+ componentPatternsModified: componentPatternsResult.filesModified,
770
+ instancesRenamed: totalInstances
771
+ };
772
+ }
773
+ async renameSlotInDirectory(directory, componentType, oldSlotId, newSlotId, whatIf, strict, label) {
774
+ let files;
775
+ try {
776
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
777
+ } catch {
778
+ return { filesModified: 0, instancesRenamed: 0 };
779
+ }
780
+ if (files.length === 0) {
781
+ return { filesModified: 0, instancesRenamed: 0 };
782
+ }
783
+ let filesModified = 0;
784
+ let instancesRenamed = 0;
785
+ for (const filePath of files) {
786
+ let composition;
787
+ try {
788
+ composition = await this.fileSystem.readFile(filePath);
789
+ } catch {
790
+ continue;
791
+ }
792
+ if (!composition?.composition) {
793
+ continue;
794
+ }
795
+ const count = this.renameSlotInTree(
796
+ composition.composition,
797
+ componentType,
798
+ oldSlotId,
799
+ newSlotId,
800
+ strict
801
+ );
802
+ if (count > 0) {
803
+ const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
804
+ this.logger.action(
805
+ whatIf,
806
+ "UPDATE",
807
+ `${label}/${relativePath} (${count} instance(s) of ${componentType})`
808
+ );
809
+ if (!whatIf) {
810
+ await this.fileSystem.writeFile(filePath, composition);
811
+ }
812
+ filesModified++;
813
+ instancesRenamed += count;
814
+ }
815
+ }
816
+ return { filesModified, instancesRenamed };
817
+ }
818
+ renameSlotInTree(node, componentType, oldSlotId, newSlotId, strict) {
819
+ let count = 0;
820
+ if (this.compareIds(node.type, componentType, strict) && node.slots) {
821
+ const result = this.renameSlotKey(node.slots, oldSlotId, newSlotId, strict);
822
+ if (result.renamed) {
823
+ node.slots = result.slots;
824
+ count++;
825
+ }
826
+ }
827
+ if (node.slots) {
828
+ for (const slotInstances of Object.values(node.slots)) {
829
+ if (!Array.isArray(slotInstances)) continue;
830
+ for (const instance of slotInstances) {
831
+ count += this.renameSlotInTree(instance, componentType, oldSlotId, newSlotId, strict);
832
+ }
833
+ }
834
+ }
835
+ return count;
836
+ }
837
+ renameSlotKey(slots, oldSlotId, newSlotId, strict) {
838
+ const matchingKey = Object.keys(slots).find(
839
+ (key) => this.compareIds(key, oldSlotId, strict)
840
+ );
841
+ if (!matchingKey) {
842
+ return { renamed: false, slots };
843
+ }
844
+ const newSlots = {};
845
+ for (const key of Object.keys(slots)) {
846
+ if (key === matchingKey) {
847
+ newSlots[newSlotId] = slots[key];
848
+ } else {
849
+ newSlots[key] = slots[key];
850
+ }
851
+ }
852
+ return { renamed: true, slots: newSlots };
853
+ }
854
+ };
855
+
856
+ // src/core/services/component-renamer.service.ts
857
+ var ComponentRenamerService = class {
858
+ constructor(fileSystem, componentService, logger) {
859
+ this.fileSystem = fileSystem;
860
+ this.componentService = componentService;
861
+ this.logger = logger;
862
+ }
863
+ compareIds(id1, id2, strict) {
864
+ if (strict) {
865
+ return id1 === id2;
866
+ }
867
+ return id1.toLowerCase() === id2.toLowerCase();
868
+ }
869
+ async rename(options) {
870
+ const {
871
+ rootDir,
872
+ componentsDir,
873
+ compositionsDir,
874
+ compositionPatternsDir,
875
+ componentPatternsDir,
876
+ componentType,
877
+ newComponentType,
878
+ newComponentName,
879
+ whatIf,
880
+ strict
881
+ } = options;
882
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
883
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
884
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
885
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
886
+ const findOptions = { strict };
887
+ if (this.compareIds(componentType, newComponentType, strict)) {
888
+ throw new TransformError("New component type is the same as the current component type");
889
+ }
890
+ this.logger.info(`Loading component: ${componentType}`);
891
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentType, findOptions);
892
+ let targetExists = false;
893
+ try {
894
+ await this.componentService.loadComponent(fullComponentsDir, newComponentType, findOptions);
895
+ targetExists = true;
896
+ } catch (error) {
897
+ if (!(error instanceof ComponentNotFoundError)) {
898
+ throw error;
899
+ }
900
+ }
901
+ if (targetExists) {
902
+ throw new ComponentAlreadyExistsError(newComponentType, fullComponentsDir);
903
+ }
904
+ this.logger.action(
905
+ whatIf,
906
+ "UPDATE",
907
+ `Component ID "${componentType}" \u2192 "${newComponentType}" in component/${this.fileSystem.getBasename(componentFilePath)}`
908
+ );
909
+ component.id = newComponentType;
910
+ if (newComponentName !== void 0) {
911
+ component.name = newComponentName;
912
+ }
913
+ if (!whatIf) {
914
+ await this.componentService.saveComponent(componentFilePath, component);
915
+ }
916
+ const ext = this.fileSystem.getExtension(componentFilePath);
917
+ const dir = this.fileSystem.getDirname(componentFilePath);
918
+ const newFilePath = this.fileSystem.joinPath(dir, `${newComponentType}${ext}`);
919
+ let fileRenamed = false;
920
+ if (componentFilePath !== newFilePath) {
921
+ this.logger.action(
922
+ whatIf,
923
+ "RENAME",
924
+ `component/${this.fileSystem.getBasename(componentFilePath)} \u2192 component/${this.fileSystem.getBasename(newFilePath)}`
925
+ );
926
+ if (!whatIf) {
927
+ await this.fileSystem.renameFile(componentFilePath, newFilePath);
928
+ }
929
+ fileRenamed = true;
930
+ }
931
+ const allowedComponentsUpdated = await this.updateAllowedComponents(
932
+ fullComponentsDir,
933
+ componentType,
934
+ newComponentType,
935
+ componentFilePath,
936
+ whatIf,
937
+ strict
938
+ );
939
+ const compositionsResult = await this.renameTypeInDirectory(
940
+ fullCompositionsDir,
941
+ componentType,
942
+ newComponentType,
943
+ whatIf,
944
+ strict,
945
+ "composition"
946
+ );
947
+ const compositionPatternsResult = await this.renameTypeInDirectory(
948
+ fullCompositionPatternsDir,
949
+ componentType,
950
+ newComponentType,
951
+ whatIf,
952
+ strict,
953
+ "compositionPattern"
954
+ );
955
+ const componentPatternsResult = await this.renameTypeInDirectory(
956
+ fullComponentPatternsDir,
957
+ componentType,
958
+ newComponentType,
959
+ whatIf,
960
+ strict,
961
+ "componentPattern"
962
+ );
963
+ const totalCompositionFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
964
+ const totalInstances = compositionsResult.instancesRenamed + compositionPatternsResult.instancesRenamed + componentPatternsResult.instancesRenamed;
965
+ this.logger.info("");
966
+ this.logger.info(
967
+ `Summary: 1 component renamed, ${allowedComponentsUpdated} component(s) with allowedComponents updated, ${totalCompositionFiles} composition file(s) (${totalInstances} instance(s)) updated.`
968
+ );
969
+ return {
970
+ componentRenamed: true,
971
+ fileRenamed,
972
+ allowedComponentsUpdated,
973
+ compositionsModified: compositionsResult.filesModified,
974
+ compositionPatternsModified: compositionPatternsResult.filesModified,
975
+ componentPatternsModified: componentPatternsResult.filesModified,
976
+ instancesRenamed: totalInstances
977
+ };
978
+ }
979
+ async updateAllowedComponents(componentsDir, oldType, newType, sourceFilePath, whatIf, strict) {
980
+ let files;
981
+ try {
982
+ files = await this.fileSystem.findFiles(componentsDir, "*.{json,yaml,yml}");
983
+ } catch {
984
+ return 0;
985
+ }
986
+ let updatedCount = 0;
987
+ for (const filePath of files) {
988
+ if (filePath === sourceFilePath) continue;
989
+ let comp;
990
+ try {
991
+ comp = await this.fileSystem.readFile(filePath);
992
+ } catch {
993
+ continue;
994
+ }
995
+ if (!comp.slots || comp.slots.length === 0) continue;
996
+ let fileModified = false;
997
+ for (const slot of comp.slots) {
998
+ if (!slot.allowedComponents) continue;
999
+ for (let i = 0; i < slot.allowedComponents.length; i++) {
1000
+ if (this.compareIds(slot.allowedComponents[i], oldType, strict)) {
1001
+ slot.allowedComponents[i] = newType;
1002
+ fileModified = true;
1003
+ }
1004
+ }
1005
+ }
1006
+ if (fileModified) {
1007
+ this.logger.action(
1008
+ whatIf,
1009
+ "UPDATE",
1010
+ `allowedComponents in component/${this.fileSystem.getBasename(filePath)}: ${oldType} \u2192 ${newType}`
1011
+ );
1012
+ if (!whatIf) {
1013
+ await this.fileSystem.writeFile(filePath, comp);
1014
+ }
1015
+ updatedCount++;
1016
+ }
1017
+ }
1018
+ return updatedCount;
1019
+ }
1020
+ async renameTypeInDirectory(directory, oldType, newType, whatIf, strict, label) {
1021
+ let files;
1022
+ try {
1023
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
1024
+ } catch {
1025
+ return { filesModified: 0, instancesRenamed: 0 };
1026
+ }
1027
+ if (files.length === 0) {
1028
+ return { filesModified: 0, instancesRenamed: 0 };
1029
+ }
1030
+ let filesModified = 0;
1031
+ let instancesRenamed = 0;
1032
+ for (const filePath of files) {
1033
+ let composition;
1034
+ try {
1035
+ composition = await this.fileSystem.readFile(filePath);
1036
+ } catch {
1037
+ continue;
1038
+ }
1039
+ if (!composition?.composition) continue;
1040
+ const count = this.renameTypeInTree(composition.composition, oldType, newType, strict);
1041
+ if (count > 0) {
1042
+ const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
1043
+ this.logger.action(
1044
+ whatIf,
1045
+ "UPDATE",
1046
+ `${label}/${relativePath} (${count} instance(s))`
1047
+ );
1048
+ if (!whatIf) {
1049
+ await this.fileSystem.writeFile(filePath, composition);
1050
+ }
1051
+ filesModified++;
1052
+ instancesRenamed += count;
1053
+ }
1054
+ }
1055
+ return { filesModified, instancesRenamed };
1056
+ }
1057
+ renameTypeInTree(node, oldType, newType, strict) {
1058
+ let count = 0;
1059
+ if (this.compareIds(node.type, oldType, strict)) {
1060
+ node.type = newType;
1061
+ count++;
1062
+ }
1063
+ if (node.slots) {
1064
+ for (const slotInstances of Object.values(node.slots)) {
1065
+ if (!Array.isArray(slotInstances)) continue;
1066
+ for (const instance of slotInstances) {
1067
+ count += this.renameTypeInTree(instance, oldType, newType, strict);
1068
+ }
1069
+ }
1070
+ }
1071
+ return count;
1072
+ }
1073
+ };
1074
+
1075
+ // src/cli/logger.ts
1076
+ import chalk from "chalk";
1077
+ var Logger = class {
1078
+ info(message) {
1079
+ console.log(`${chalk.blue("[INFO]")} ${message}`);
1080
+ }
1081
+ success(message) {
1082
+ console.log(`${chalk.green("[DONE]")} ${message}`);
1083
+ }
1084
+ error(message) {
1085
+ console.log(`${chalk.red("[ERROR]")} ${message}`);
1086
+ }
1087
+ warn(message) {
1088
+ console.log(`${chalk.yellow("[WARN]")} ${message}`);
1089
+ }
1090
+ action(whatIf, actionType, message) {
1091
+ const prefix = whatIf ? chalk.yellow("[WOULD]") : chalk.green(`[${actionType}]`);
1092
+ console.log(`${prefix} ${message}`);
1093
+ }
1094
+ detail(message) {
1095
+ console.log(` ${chalk.gray(message)}`);
1096
+ }
1097
+ };
1098
+ export {
1099
+ ComponentAlreadyExistsError,
1100
+ ComponentNotFoundError,
1101
+ ComponentRenamerService,
1102
+ ComponentService,
1103
+ CompositionService,
1104
+ DuplicateIdError,
1105
+ FileNotFoundError,
1106
+ FileSystemService,
1107
+ InvalidYamlError,
1108
+ Logger,
1109
+ PropertyNotFoundError,
1110
+ PropertyPropagatorService,
1111
+ SlotAlreadyExistsError,
1112
+ SlotNotFoundError,
1113
+ SlotRenamerService,
1114
+ TransformError
1115
+ };
1116
+ //# sourceMappingURL=index.js.map