@uniformdev/transformer 1.1.29 → 1.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command13 } from "commander";
4
+ import { Command as Command16 } from "commander";
5
5
 
6
6
  // src/cli/commands/propagate-root-component-property.ts
7
7
  import { Command } from "commander";
@@ -5019,10 +5019,737 @@ function createRemoveFieldCommand() {
5019
5019
  return command;
5020
5020
  }
5021
5021
 
5022
+ // src/cli/commands/add-component-parameter.ts
5023
+ import { Command as Command13 } from "commander";
5024
+
5025
+ // src/core/services/component-parameter-adder.service.ts
5026
+ var ComponentParameterAdderService = class {
5027
+ constructor(fileSystem, componentService, logger) {
5028
+ this.fileSystem = fileSystem;
5029
+ this.componentService = componentService;
5030
+ this.logger = logger;
5031
+ }
5032
+ async add(options) {
5033
+ const {
5034
+ rootDir,
5035
+ componentsDir,
5036
+ componentId,
5037
+ parameterId,
5038
+ parameterType,
5039
+ parameterConfig,
5040
+ groupId,
5041
+ whatIf,
5042
+ strict
5043
+ } = options;
5044
+ const findOptions = { strict };
5045
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
5046
+ this.logger.info(`Loading component: ${componentId}`);
5047
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentId, findOptions);
5048
+ let parsedConfig = {};
5049
+ if (parameterConfig) {
5050
+ try {
5051
+ parsedConfig = JSON.parse(parameterConfig);
5052
+ } catch (e) {
5053
+ throw new TransformError(
5054
+ `Invalid JSON in --parameterConfig: ${e instanceof Error ? e.message : String(e)}`
5055
+ );
5056
+ }
5057
+ }
5058
+ const { id: _ignoreId, type: _ignoreType, ...restConfig } = parsedConfig;
5059
+ const parameter = {
5060
+ id: parameterId,
5061
+ name: parsedConfig.name ?? parameterId,
5062
+ type: parameterType,
5063
+ ...restConfig
5064
+ };
5065
+ const existing = this.componentService.findParameter(component, parameterId, findOptions);
5066
+ const parameterReplaced = existing !== void 0;
5067
+ if (parameterReplaced) {
5068
+ this.logger.warn(
5069
+ `Parameter "${parameterId}" already exists on component "${component.id}" \u2014 replacing`
5070
+ );
5071
+ }
5072
+ this.logger.action(
5073
+ whatIf,
5074
+ parameterReplaced ? "UPDATE" : "ADD",
5075
+ `parameter "${parameterId}" to component/${this.fileSystem.getBasename(componentFilePath)}`
5076
+ );
5077
+ let updatedComponent = this.componentService.addParameterToComponent(
5078
+ component,
5079
+ parameter,
5080
+ findOptions
5081
+ );
5082
+ if (groupId) {
5083
+ const group = this.componentService.findParameter(updatedComponent, groupId, findOptions);
5084
+ if (!group || !this.componentService.isGroupParameter(group)) {
5085
+ throw new TransformError(
5086
+ `Group "${groupId}" not found on component "${component.id}"`
5087
+ );
5088
+ }
5089
+ updatedComponent = this.componentService.addParameterToGroup(
5090
+ updatedComponent,
5091
+ groupId,
5092
+ parameterId,
5093
+ findOptions
5094
+ );
5095
+ }
5096
+ if (!whatIf) {
5097
+ await this.componentService.saveComponent(componentFilePath, updatedComponent);
5098
+ }
5099
+ this.logger.info("");
5100
+ this.logger.info(`Summary: 1 component definition updated.`);
5101
+ return {
5102
+ componentDefinitionUpdated: true,
5103
+ parameterReplaced
5104
+ };
5105
+ }
5106
+ };
5107
+
5108
+ // src/cli/commands/add-component-parameter.ts
5109
+ function createAddComponentParameterCommand() {
5110
+ const command = new Command13("add-component-parameter");
5111
+ command.description(
5112
+ "Adds a new parameter definition to a component. If the parameter already exists it is replaced."
5113
+ ).option("--componentId <id>", "The ID of the component to add the parameter to").option("--parameterId <id>", "The ID of the new parameter").option("--parameterType <type>", "The type of the new parameter (e.g. text, checkbox, select)").option(
5114
+ "--parameterConfig <json>",
5115
+ "Optional JSON string with additional parameter properties (e.g. name, typeConfig, localizable)"
5116
+ ).option(
5117
+ "--groupId <id>",
5118
+ "Optional ID of an existing group parameter to register the new parameter under"
5119
+ ).hook("preAction", (thisCommand) => {
5120
+ const opts = thisCommand.opts();
5121
+ const requiredOptions = [
5122
+ { name: "componentId", flag: "--componentId" },
5123
+ { name: "parameterId", flag: "--parameterId" },
5124
+ { name: "parameterType", flag: "--parameterType" }
5125
+ ];
5126
+ const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
5127
+ if (missing.length > 0) {
5128
+ console.error(`error: missing required options: ${missing.join(", ")}`);
5129
+ process.exit(1);
5130
+ }
5131
+ }).action(async (opts, cmd) => {
5132
+ const globalOpts = cmd.optsWithGlobals();
5133
+ const options = {
5134
+ ...globalOpts,
5135
+ componentId: opts.componentId,
5136
+ parameterId: opts.parameterId,
5137
+ parameterType: opts.parameterType,
5138
+ parameterConfig: opts.parameterConfig,
5139
+ groupId: opts.groupId
5140
+ };
5141
+ const logger = new Logger();
5142
+ const fileSystem = new FileSystemService();
5143
+ const componentService = new ComponentService(fileSystem);
5144
+ const adder = new ComponentParameterAdderService(fileSystem, componentService, logger);
5145
+ try {
5146
+ const result = await adder.add({
5147
+ rootDir: options.rootDir,
5148
+ componentsDir: options.componentsDir,
5149
+ componentId: options.componentId,
5150
+ parameterId: options.parameterId,
5151
+ parameterType: options.parameterType,
5152
+ parameterConfig: options.parameterConfig,
5153
+ groupId: options.groupId,
5154
+ whatIf: options.whatIf ?? false,
5155
+ strict: options.strict ?? false
5156
+ });
5157
+ logger.success(
5158
+ `${result.parameterReplaced ? "Replaced" : "Added"} parameter "${options.parameterId}" on component "${options.componentId}"`
5159
+ );
5160
+ } catch (error) {
5161
+ if (error instanceof TransformError) {
5162
+ logger.error(error.message);
5163
+ process.exit(1);
5164
+ }
5165
+ throw error;
5166
+ }
5167
+ });
5168
+ return command;
5169
+ }
5170
+
5171
+ // src/cli/commands/add-contenttype-field.ts
5172
+ import { Command as Command14 } from "commander";
5173
+
5174
+ // src/core/services/contenttype-field-adder.service.ts
5175
+ var ContentTypeFieldAdderService = class {
5176
+ constructor(fileSystem, logger) {
5177
+ this.fileSystem = fileSystem;
5178
+ this.logger = logger;
5179
+ }
5180
+ compareIds(id1, id2, strict) {
5181
+ if (strict) {
5182
+ return id1 === id2;
5183
+ }
5184
+ return id1.toLowerCase() === id2.toLowerCase();
5185
+ }
5186
+ async loadContentType(contentTypesDir, contentTypeId, strict) {
5187
+ const jsonPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.json`);
5188
+ const yamlPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.yaml`);
5189
+ const ymlPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.yml`);
5190
+ if (await this.fileSystem.fileExists(jsonPath)) {
5191
+ const contentType = await this.fileSystem.readFile(jsonPath);
5192
+ return { contentType, filePath: jsonPath };
5193
+ }
5194
+ if (await this.fileSystem.fileExists(yamlPath)) {
5195
+ const contentType = await this.fileSystem.readFile(yamlPath);
5196
+ return { contentType, filePath: yamlPath };
5197
+ }
5198
+ if (await this.fileSystem.fileExists(ymlPath)) {
5199
+ const contentType = await this.fileSystem.readFile(ymlPath);
5200
+ return { contentType, filePath: ymlPath };
5201
+ }
5202
+ if (!strict) {
5203
+ const files = await this.fileSystem.findFiles(contentTypesDir, "*.{json,yaml,yml}");
5204
+ for (const filePath of files) {
5205
+ const basename2 = this.fileSystem.getBasename(filePath);
5206
+ const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
5207
+ if (nameWithoutExt.toLowerCase() === contentTypeId.toLowerCase()) {
5208
+ const contentType = await this.fileSystem.readFile(filePath);
5209
+ return { contentType, filePath };
5210
+ }
5211
+ }
5212
+ }
5213
+ throw new TransformError(`Content type not found: ${contentTypeId} (searched: ${contentTypesDir})`);
5214
+ }
5215
+ async add(options) {
5216
+ const {
5217
+ rootDir,
5218
+ contentTypesDir,
5219
+ contentTypeId,
5220
+ fieldId,
5221
+ fieldType,
5222
+ fieldConfig,
5223
+ whatIf,
5224
+ strict
5225
+ } = options;
5226
+ const fullContentTypesDir = this.fileSystem.resolvePath(rootDir, contentTypesDir);
5227
+ this.logger.info(`Loading content type: ${contentTypeId}`);
5228
+ const { contentType, filePath: contentTypeFilePath } = await this.loadContentType(fullContentTypesDir, contentTypeId, strict);
5229
+ let parsedConfig = {};
5230
+ if (fieldConfig) {
5231
+ try {
5232
+ parsedConfig = JSON.parse(fieldConfig);
5233
+ } catch (e) {
5234
+ throw new TransformError(
5235
+ `Invalid JSON in --fieldConfig: ${e instanceof Error ? e.message : String(e)}`
5236
+ );
5237
+ }
5238
+ }
5239
+ const { id: _ignoreId, type: _ignoreType, ...restConfig } = parsedConfig;
5240
+ const field = {
5241
+ id: fieldId,
5242
+ name: parsedConfig.name ?? fieldId,
5243
+ type: fieldType,
5244
+ ...restConfig
5245
+ };
5246
+ if (!contentType.fields) {
5247
+ contentType.fields = [];
5248
+ }
5249
+ const existingIndex = contentType.fields.findIndex(
5250
+ (f) => this.compareIds(f.id, fieldId, strict)
5251
+ );
5252
+ const fieldReplaced = existingIndex >= 0;
5253
+ if (fieldReplaced) {
5254
+ this.logger.warn(
5255
+ `Field "${fieldId}" already exists on content type "${contentType.id}" \u2014 replacing`
5256
+ );
5257
+ }
5258
+ this.logger.action(
5259
+ whatIf,
5260
+ fieldReplaced ? "UPDATE" : "ADD",
5261
+ `field "${fieldId}" to contentType/${this.fileSystem.getBasename(contentTypeFilePath)}`
5262
+ );
5263
+ if (fieldReplaced) {
5264
+ contentType.fields[existingIndex] = field;
5265
+ } else {
5266
+ contentType.fields.push(field);
5267
+ }
5268
+ if (!whatIf) {
5269
+ await this.fileSystem.writeFile(contentTypeFilePath, contentType);
5270
+ }
5271
+ this.logger.info("");
5272
+ this.logger.info(`Summary: 1 content type definition updated.`);
5273
+ return {
5274
+ contentTypeDefinitionUpdated: true,
5275
+ fieldReplaced
5276
+ };
5277
+ }
5278
+ };
5279
+
5280
+ // src/cli/commands/add-contenttype-field.ts
5281
+ function createAddContentTypeFieldCommand() {
5282
+ const command = new Command14("add-contenttype-field");
5283
+ command.description(
5284
+ "Adds a new field definition to a content type. If the field already exists it is replaced."
5285
+ ).option("--contentTypeId <id>", "The ID of the content type to add the field to").option("--fieldId <id>", "The ID of the new field").option("--fieldType <type>", "The type of the new field (e.g. text, richText, reference)").option(
5286
+ "--fieldConfig <json>",
5287
+ "Optional JSON string with additional field properties (e.g. name, typeConfig, localizable)"
5288
+ ).hook("preAction", (thisCommand) => {
5289
+ const opts = thisCommand.opts();
5290
+ const requiredOptions = [
5291
+ { name: "contentTypeId", flag: "--contentTypeId" },
5292
+ { name: "fieldId", flag: "--fieldId" },
5293
+ { name: "fieldType", flag: "--fieldType" }
5294
+ ];
5295
+ const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
5296
+ if (missing.length > 0) {
5297
+ console.error(`error: missing required options: ${missing.join(", ")}`);
5298
+ process.exit(1);
5299
+ }
5300
+ }).action(async (opts, cmd) => {
5301
+ const globalOpts = cmd.optsWithGlobals();
5302
+ const options = {
5303
+ ...globalOpts,
5304
+ contentTypeId: opts.contentTypeId,
5305
+ fieldId: opts.fieldId,
5306
+ fieldType: opts.fieldType,
5307
+ fieldConfig: opts.fieldConfig
5308
+ };
5309
+ const logger = new Logger();
5310
+ const fileSystem = new FileSystemService();
5311
+ const adder = new ContentTypeFieldAdderService(fileSystem, logger);
5312
+ try {
5313
+ const result = await adder.add({
5314
+ rootDir: options.rootDir,
5315
+ contentTypesDir: options.contentTypesDir,
5316
+ contentTypeId: options.contentTypeId,
5317
+ fieldId: options.fieldId,
5318
+ fieldType: options.fieldType,
5319
+ fieldConfig: options.fieldConfig,
5320
+ whatIf: options.whatIf ?? false,
5321
+ strict: options.strict ?? false
5322
+ });
5323
+ logger.success(
5324
+ `${result.fieldReplaced ? "Replaced" : "Added"} field "${options.fieldId}" on content type "${options.contentTypeId}"`
5325
+ );
5326
+ } catch (error) {
5327
+ if (error instanceof TransformError) {
5328
+ logger.error(error.message);
5329
+ process.exit(1);
5330
+ }
5331
+ throw error;
5332
+ }
5333
+ });
5334
+ return command;
5335
+ }
5336
+
5337
+ // src/cli/commands/flatten-block-field.ts
5338
+ import { Command as Command15 } from "commander";
5339
+
5340
+ // src/core/services/block-field-flattener.service.ts
5341
+ var BlockFieldFlattenerService = class {
5342
+ constructor(fileSystem, componentService, logger) {
5343
+ this.fileSystem = fileSystem;
5344
+ this.componentService = componentService;
5345
+ this.logger = logger;
5346
+ }
5347
+ compareIds(id1, id2, strict) {
5348
+ if (strict) return id1 === id2;
5349
+ return id1.toLowerCase() === id2.toLowerCase();
5350
+ }
5351
+ async loadContentType(contentTypesDir, contentTypeId, strict) {
5352
+ const jsonPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.json`);
5353
+ const yamlPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.yaml`);
5354
+ const ymlPath = this.fileSystem.joinPath(contentTypesDir, `${contentTypeId}.yml`);
5355
+ if (await this.fileSystem.fileExists(jsonPath)) {
5356
+ return { contentType: await this.fileSystem.readFile(jsonPath), filePath: jsonPath };
5357
+ }
5358
+ if (await this.fileSystem.fileExists(yamlPath)) {
5359
+ return { contentType: await this.fileSystem.readFile(yamlPath), filePath: yamlPath };
5360
+ }
5361
+ if (await this.fileSystem.fileExists(ymlPath)) {
5362
+ return { contentType: await this.fileSystem.readFile(ymlPath), filePath: ymlPath };
5363
+ }
5364
+ if (!strict) {
5365
+ const files = await this.fileSystem.findFiles(contentTypesDir, "*.{json,yaml,yml}");
5366
+ for (const filePath of files) {
5367
+ const basename2 = this.fileSystem.getBasename(filePath);
5368
+ const nameWithoutExt = basename2.replace(/\.(json|yaml|yml)$/i, "");
5369
+ if (nameWithoutExt.toLowerCase() === contentTypeId.toLowerCase()) {
5370
+ return { contentType: await this.fileSystem.readFile(filePath), filePath };
5371
+ }
5372
+ }
5373
+ }
5374
+ throw new TransformError(`Content type not found: ${contentTypeId} (searched: ${contentTypesDir})`);
5375
+ }
5376
+ async flatten(options) {
5377
+ const {
5378
+ rootDir,
5379
+ componentsDir,
5380
+ compositionsDir,
5381
+ compositionPatternsDir,
5382
+ componentPatternsDir,
5383
+ contentTypesDir,
5384
+ componentId,
5385
+ parameterId,
5386
+ whatIf,
5387
+ strict,
5388
+ excludeFields = []
5389
+ } = options;
5390
+ const findOptions = { strict };
5391
+ const fullComponentsDir = this.fileSystem.resolvePath(rootDir, componentsDir);
5392
+ const fullCompositionsDir = this.fileSystem.resolvePath(rootDir, compositionsDir);
5393
+ const fullCompositionPatternsDir = this.fileSystem.resolvePath(rootDir, compositionPatternsDir);
5394
+ const fullComponentPatternsDir = this.fileSystem.resolvePath(rootDir, componentPatternsDir);
5395
+ const fullContentTypesDir = this.fileSystem.resolvePath(rootDir, contentTypesDir);
5396
+ this.logger.info(`Loading component: ${componentId}`);
5397
+ const { component, filePath: componentFilePath } = await this.componentService.loadComponent(fullComponentsDir, componentId, findOptions);
5398
+ const blockParam = this.componentService.findParameter(component, parameterId, findOptions);
5399
+ if (!blockParam) {
5400
+ throw new PropertyNotFoundError(parameterId, componentId);
5401
+ }
5402
+ const allowedTypes = blockParam.typeConfig?.allowedTypes;
5403
+ if (!allowedTypes || allowedTypes.length === 0) {
5404
+ throw new TransformError(
5405
+ `Parameter "${parameterId}" on component "${componentId}" has no allowedTypes in typeConfig`
5406
+ );
5407
+ }
5408
+ if (allowedTypes.length > 1) {
5409
+ throw new TransformError(
5410
+ `Cannot flatten: parameter "${parameterId}" allows multiple block types: ${allowedTypes.join(", ")}`
5411
+ );
5412
+ }
5413
+ const blockContentTypeId = allowedTypes[0];
5414
+ this.logger.info(`Block content type: ${blockContentTypeId}`);
5415
+ const { contentType: blockContentType } = await this.loadContentType(fullContentTypesDir, blockContentTypeId, strict);
5416
+ const excludeSet = new Set(excludeFields.map((id) => id.toLowerCase()));
5417
+ const blockFields = (blockContentType.fields ?? []).filter(
5418
+ (f) => !excludeSet.has(f.id.toLowerCase())
5419
+ );
5420
+ const blockFieldIds = blockFields.map((f) => f.id);
5421
+ this.logger.info(
5422
+ `Block content type: ${blockContentTypeId} (${blockFields.length} field(s): ${blockFieldIds.join(", ")})`
5423
+ );
5424
+ for (const field of blockFields) {
5425
+ const existingParam = this.componentService.findParameter(component, field.id, findOptions);
5426
+ if (existingParam && !this.compareIds(existingParam.id, parameterId, strict)) {
5427
+ throw new TransformError(
5428
+ `Cannot flatten: field "${field.id}" from block type "${blockContentTypeId}" conflicts with existing parameter "${existingParam.id}" on component "${componentId}"`
5429
+ );
5430
+ }
5431
+ }
5432
+ this.logger.info("Validation pass: scanning compositions...");
5433
+ await this.validateDirectory(fullCompositionsDir, componentId, parameterId, strict, "composition");
5434
+ await this.validateDirectory(fullCompositionPatternsDir, componentId, parameterId, strict, "compositionPattern");
5435
+ await this.validateDirectory(fullComponentPatternsDir, componentId, parameterId, strict, "componentPattern");
5436
+ this.logger.action(
5437
+ whatIf,
5438
+ "REMOVE",
5439
+ `parameter "${parameterId}" from component/${this.fileSystem.getBasename(componentFilePath)}`
5440
+ );
5441
+ this.logger.action(
5442
+ whatIf,
5443
+ "ADD",
5444
+ `parameters "${blockFieldIds.join(", ")}" to component/${this.fileSystem.getBasename(componentFilePath)}`
5445
+ );
5446
+ let updatedComponent = this.componentService.removeParameter(component, parameterId, findOptions);
5447
+ updatedComponent = this.componentService.removeEmptyGroups(updatedComponent);
5448
+ for (const field of blockFields) {
5449
+ updatedComponent = this.componentService.addParameterToComponent(
5450
+ updatedComponent,
5451
+ { id: field.id, name: field.name, type: field.type, ...field.typeConfig ? { typeConfig: field.typeConfig } : {}, ...this.extraFieldProps(field) },
5452
+ findOptions
5453
+ );
5454
+ }
5455
+ if (!whatIf) {
5456
+ await this.componentService.saveComponent(componentFilePath, updatedComponent);
5457
+ }
5458
+ const compositionsResult = await this.flattenInDirectory(
5459
+ fullCompositionsDir,
5460
+ componentId,
5461
+ parameterId,
5462
+ whatIf,
5463
+ strict,
5464
+ "composition"
5465
+ );
5466
+ const compositionPatternsResult = await this.flattenInDirectory(
5467
+ fullCompositionPatternsDir,
5468
+ componentId,
5469
+ parameterId,
5470
+ whatIf,
5471
+ strict,
5472
+ "compositionPattern"
5473
+ );
5474
+ const componentPatternsResult = await this.flattenInDirectory(
5475
+ fullComponentPatternsDir,
5476
+ componentId,
5477
+ parameterId,
5478
+ whatIf,
5479
+ strict,
5480
+ "componentPattern"
5481
+ );
5482
+ const totalFiles = compositionsResult.filesModified + compositionPatternsResult.filesModified + componentPatternsResult.filesModified;
5483
+ const totalInstances = compositionsResult.instancesUpdated + compositionPatternsResult.instancesUpdated + componentPatternsResult.instancesUpdated;
5484
+ this.logger.info("");
5485
+ this.logger.info(
5486
+ `Summary: 1 component definition updated, ${totalFiles} file(s) (${totalInstances} instance(s)) updated.`
5487
+ );
5488
+ return {
5489
+ componentDefinitionUpdated: true,
5490
+ compositionsModified: compositionsResult.filesModified,
5491
+ compositionPatternsModified: compositionPatternsResult.filesModified,
5492
+ componentPatternsModified: componentPatternsResult.filesModified,
5493
+ instancesUpdated: totalInstances
5494
+ };
5495
+ }
5496
+ // Extract extra field props beyond id, name, type, typeConfig
5497
+ extraFieldProps(field) {
5498
+ const { id: _id, name: _name, type: _type, typeConfig: _tc, ...rest } = field;
5499
+ return rest;
5500
+ }
5501
+ async validateDirectory(directory, componentId, parameterId, strict, label) {
5502
+ let files;
5503
+ try {
5504
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
5505
+ } catch {
5506
+ return;
5507
+ }
5508
+ for (const filePath of files) {
5509
+ let composition;
5510
+ try {
5511
+ composition = await this.fileSystem.readFile(filePath);
5512
+ } catch {
5513
+ continue;
5514
+ }
5515
+ if (!composition?.composition) continue;
5516
+ this.validateBlocksInTree(composition.composition, componentId, parameterId, strict, filePath, label);
5517
+ this.validateBlocksInOverrides(composition, componentId, parameterId, strict, filePath, label);
5518
+ }
5519
+ }
5520
+ validateBlocksInTree(node, componentId, parameterId, strict, filePath, label) {
5521
+ if (this.compareIds(node.type, componentId, strict) && node.parameters) {
5522
+ const blockParamValue = this.findKeyInMap(node.parameters, parameterId, strict);
5523
+ if (blockParamValue) {
5524
+ const items = blockParamValue.value;
5525
+ if (Array.isArray(items) && items.length > 1) {
5526
+ throw new TransformError(
5527
+ `Cannot flatten: more than one block item in parameter "${parameterId}" of "${componentId}" instance "${node._id ?? "(root)"}" in file "${label}/${this.fileSystem.getBasename(filePath)}"`
5528
+ );
5529
+ }
5530
+ }
5531
+ }
5532
+ if (node.slots) {
5533
+ for (const slotInstances of Object.values(node.slots)) {
5534
+ if (!Array.isArray(slotInstances)) continue;
5535
+ for (const instance of slotInstances) {
5536
+ this.validateBlocksInTree(instance, componentId, parameterId, strict, filePath, label);
5537
+ }
5538
+ }
5539
+ }
5540
+ }
5541
+ validateBlocksInOverrides(composition, componentId, parameterId, strict, filePath, label) {
5542
+ if (!composition.composition._overrides) return;
5543
+ for (const [overrideId, override] of Object.entries(composition.composition._overrides)) {
5544
+ if (!override.parameters) continue;
5545
+ const blockParamValue = this.findKeyInMap(override.parameters, parameterId, strict);
5546
+ if (blockParamValue) {
5547
+ const items = blockParamValue.value;
5548
+ if (Array.isArray(items) && items.length > 1) {
5549
+ throw new TransformError(
5550
+ `Cannot flatten: more than one block item in _overrides["${overrideId}"] parameter "${parameterId}" in file "${label}/${this.fileSystem.getBasename(filePath)}"`
5551
+ );
5552
+ }
5553
+ }
5554
+ }
5555
+ }
5556
+ async flattenInDirectory(directory, componentId, parameterId, whatIf, strict, label) {
5557
+ let files;
5558
+ try {
5559
+ files = await this.fileSystem.findFiles(directory, "**/*.{json,yaml,yml}");
5560
+ } catch {
5561
+ return { filesModified: 0, instancesUpdated: 0 };
5562
+ }
5563
+ if (files.length === 0) return { filesModified: 0, instancesUpdated: 0 };
5564
+ let filesModified = 0;
5565
+ let instancesUpdated = 0;
5566
+ for (const filePath of files) {
5567
+ let composition;
5568
+ try {
5569
+ composition = await this.fileSystem.readFile(filePath);
5570
+ } catch {
5571
+ continue;
5572
+ }
5573
+ if (!composition?.composition) continue;
5574
+ const treeCount = this.flattenBlocksInTree(composition.composition, componentId, parameterId, strict);
5575
+ const overridesCount = this.flattenBlocksInOverrides(composition, componentId, parameterId, strict);
5576
+ const totalCount = treeCount + overridesCount;
5577
+ if (totalCount > 0) {
5578
+ const relativePath = filePath.replace(directory, "").replace(/^[/\\]/, "");
5579
+ this.logger.action(
5580
+ whatIf,
5581
+ "UPDATE",
5582
+ `${label}/${relativePath} (${totalCount} instance(s) of ${componentId})`
5583
+ );
5584
+ if (!whatIf) {
5585
+ await this.fileSystem.writeFile(filePath, composition);
5586
+ }
5587
+ filesModified++;
5588
+ instancesUpdated += totalCount;
5589
+ }
5590
+ }
5591
+ return { filesModified, instancesUpdated };
5592
+ }
5593
+ flattenBlocksInTree(node, componentId, parameterId, strict) {
5594
+ let count = 0;
5595
+ if (this.compareIds(node.type, componentId, strict) && node.parameters) {
5596
+ const matchingKey = this.findKeyName(node.parameters, parameterId, strict);
5597
+ if (matchingKey !== void 0) {
5598
+ const blockParamValue = node.parameters[matchingKey];
5599
+ const items = blockParamValue.value;
5600
+ if (Array.isArray(items) && items.length === 1) {
5601
+ const blockItem = items[0];
5602
+ for (const [fieldId, fieldValue] of Object.entries(blockItem.fields ?? {})) {
5603
+ node.parameters[fieldId] = fieldValue;
5604
+ }
5605
+ }
5606
+ delete node.parameters[matchingKey];
5607
+ count++;
5608
+ }
5609
+ }
5610
+ if (node.slots) {
5611
+ for (const slotInstances of Object.values(node.slots)) {
5612
+ if (!Array.isArray(slotInstances)) continue;
5613
+ for (const instance of slotInstances) {
5614
+ count += this.flattenBlocksInTree(instance, componentId, parameterId, strict);
5615
+ }
5616
+ }
5617
+ }
5618
+ return count;
5619
+ }
5620
+ flattenBlocksInOverrides(composition, componentId, parameterId, strict) {
5621
+ if (!composition.composition._overrides) return 0;
5622
+ let count = 0;
5623
+ if (this.compareIds(composition.composition.type, componentId, strict)) {
5624
+ count += this.flattenOverrideMap(composition.composition._overrides, parameterId, strict);
5625
+ }
5626
+ count += this.flattenOverridesForMatchingInstances(
5627
+ composition.composition,
5628
+ composition.composition._overrides,
5629
+ componentId,
5630
+ parameterId,
5631
+ strict
5632
+ );
5633
+ return count;
5634
+ }
5635
+ flattenOverrideMap(overrides, parameterId, strict) {
5636
+ let count = 0;
5637
+ for (const override of Object.values(overrides)) {
5638
+ if (!override.parameters) continue;
5639
+ const matchingKey = this.findKeyName(override.parameters, parameterId, strict);
5640
+ if (matchingKey === void 0) continue;
5641
+ const blockParamValue = override.parameters[matchingKey];
5642
+ const items = blockParamValue.value;
5643
+ if (Array.isArray(items) && items.length === 1) {
5644
+ const blockItem = items[0];
5645
+ for (const [fieldId, fieldValue] of Object.entries(blockItem.fields ?? {})) {
5646
+ override.parameters[fieldId] = fieldValue;
5647
+ }
5648
+ }
5649
+ delete override.parameters[matchingKey];
5650
+ count++;
5651
+ }
5652
+ return count;
5653
+ }
5654
+ flattenOverridesForMatchingInstances(node, overrides, componentId, parameterId, strict) {
5655
+ let count = 0;
5656
+ if (node.slots) {
5657
+ for (const slotInstances of Object.values(node.slots)) {
5658
+ if (!Array.isArray(slotInstances)) continue;
5659
+ for (const instance of slotInstances) {
5660
+ if (this.compareIds(instance.type, componentId, strict) && instance._id) {
5661
+ const override = overrides[instance._id];
5662
+ if (override?.parameters) {
5663
+ const matchingKey = this.findKeyName(override.parameters, parameterId, strict);
5664
+ if (matchingKey !== void 0) {
5665
+ const blockParamValue = override.parameters[matchingKey];
5666
+ const items = blockParamValue.value;
5667
+ if (Array.isArray(items) && items.length === 1) {
5668
+ const blockItem = items[0];
5669
+ for (const [fieldId, fieldValue] of Object.entries(blockItem.fields ?? {})) {
5670
+ override.parameters[fieldId] = fieldValue;
5671
+ }
5672
+ }
5673
+ delete override.parameters[matchingKey];
5674
+ count++;
5675
+ }
5676
+ }
5677
+ }
5678
+ count += this.flattenOverridesForMatchingInstances(instance, overrides, componentId, parameterId, strict);
5679
+ }
5680
+ }
5681
+ }
5682
+ return count;
5683
+ }
5684
+ findKeyName(map, key, strict) {
5685
+ return Object.keys(map).find((k) => this.compareIds(k, key, strict));
5686
+ }
5687
+ findKeyInMap(map, key, strict) {
5688
+ const k = this.findKeyName(map, key, strict);
5689
+ return k !== void 0 ? map[k] : void 0;
5690
+ }
5691
+ };
5692
+
5693
+ // src/cli/commands/flatten-block-field.ts
5694
+ function createFlattenBlockFieldCommand() {
5695
+ const command = new Command15("flatten-block-field");
5696
+ command.description(
5697
+ "Dissolves a $block parameter by lifting the block content type fields onto the component and extracting block item values into composition instances directly."
5698
+ ).option("--componentId <id>", "The ID of the component that owns the block parameter").option("--parameterId <id>", "The ID of the $block parameter to flatten").option("--excludeFields <ids>", "Comma-separated list of block content type field IDs to skip when flattening").hook("preAction", (thisCommand) => {
5699
+ const opts = thisCommand.opts();
5700
+ const requiredOptions = [
5701
+ { name: "componentId", flag: "--componentId" },
5702
+ { name: "parameterId", flag: "--parameterId" }
5703
+ ];
5704
+ const missing = requiredOptions.filter((opt) => !opts[opt.name]).map((opt) => opt.flag);
5705
+ if (missing.length > 0) {
5706
+ console.error(`error: missing required options: ${missing.join(", ")}`);
5707
+ process.exit(1);
5708
+ }
5709
+ }).action(async (opts, cmd) => {
5710
+ const globalOpts = cmd.optsWithGlobals();
5711
+ const options = {
5712
+ ...globalOpts,
5713
+ componentId: opts.componentId,
5714
+ parameterId: opts.parameterId,
5715
+ excludeFields: opts.excludeFields
5716
+ };
5717
+ const logger = new Logger();
5718
+ const fileSystem = new FileSystemService();
5719
+ const componentService = new ComponentService(fileSystem);
5720
+ const flattener = new BlockFieldFlattenerService(fileSystem, componentService, logger);
5721
+ try {
5722
+ const result = await flattener.flatten({
5723
+ rootDir: options.rootDir,
5724
+ componentsDir: options.componentsDir,
5725
+ compositionsDir: options.compositionsDir,
5726
+ compositionPatternsDir: options.compositionPatternsDir,
5727
+ componentPatternsDir: options.componentPatternsDir,
5728
+ contentTypesDir: options.contentTypesDir,
5729
+ componentId: options.componentId,
5730
+ parameterId: options.parameterId,
5731
+ whatIf: options.whatIf ?? false,
5732
+ strict: options.strict ?? false,
5733
+ excludeFields: options.excludeFields ? options.excludeFields.split(",").map((s) => s.trim()) : []
5734
+ });
5735
+ logger.success(
5736
+ `Flattened parameter "${options.parameterId}" on component "${options.componentId}": ${result.compositionsModified} composition(s), ${result.compositionPatternsModified} composition pattern(s), ${result.componentPatternsModified} component pattern(s) updated`
5737
+ );
5738
+ } catch (error) {
5739
+ if (error instanceof TransformError) {
5740
+ logger.error(error.message);
5741
+ process.exit(1);
5742
+ }
5743
+ throw error;
5744
+ }
5745
+ });
5746
+ return command;
5747
+ }
5748
+
5022
5749
  // package.json
5023
5750
  var package_default = {
5024
5751
  name: "@uniformdev/transformer",
5025
- version: "1.1.29",
5752
+ version: "1.1.31",
5026
5753
  description: "CLI tool for transforming Uniform.dev serialization files offline",
5027
5754
  type: "module",
5028
5755
  bin: {
@@ -5091,7 +5818,7 @@ var package_default = {
5091
5818
  };
5092
5819
 
5093
5820
  // src/cli/index.ts
5094
- var program = new Command13();
5821
+ var program = new Command16();
5095
5822
  var appVersion = package_default.version;
5096
5823
  console.error(`uniform-transform v${appVersion}`);
5097
5824
  program.name("uniform-transform").description("CLI tool for transforming Uniform.dev serialization files offline").version(appVersion);
@@ -5116,5 +5843,8 @@ program.addCommand(createPropagateRootComponentSlotCommand());
5116
5843
  program.addCommand(createConvertCompositionsToEntriesCommand());
5117
5844
  program.addCommand(createRemoveParameterCommand());
5118
5845
  program.addCommand(createRemoveFieldCommand());
5846
+ program.addCommand(createAddComponentParameterCommand());
5847
+ program.addCommand(createAddContentTypeFieldCommand());
5848
+ program.addCommand(createFlattenBlockFieldCommand());
5119
5849
  program.parse();
5120
5850
  //# sourceMappingURL=index.js.map