@ikas/component-cli 1.4.0-beta.7 → 1.4.0-beta.70

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.
Files changed (131) hide show
  1. package/dist/commands/add-sections-to-page.d.ts +3 -0
  2. package/dist/commands/add-sections-to-page.d.ts.map +1 -0
  3. package/dist/commands/add-sections-to-page.js +39 -0
  4. package/dist/commands/add-sections-to-page.js.map +1 -0
  5. package/dist/commands/add-to-page.d.ts +3 -0
  6. package/dist/commands/add-to-page.d.ts.map +1 -0
  7. package/dist/commands/add-to-page.js +41 -0
  8. package/dist/commands/add-to-page.js.map +1 -0
  9. package/dist/commands/build.d.ts.map +1 -1
  10. package/dist/commands/build.js +5 -165
  11. package/dist/commands/build.js.map +1 -1
  12. package/dist/commands/config.d.ts.map +1 -1
  13. package/dist/commands/config.js +212 -113
  14. package/dist/commands/config.js.map +1 -1
  15. package/dist/commands/create-design-tokens.d.ts +9 -0
  16. package/dist/commands/create-design-tokens.d.ts.map +1 -0
  17. package/dist/commands/create-design-tokens.js +178 -0
  18. package/dist/commands/create-design-tokens.js.map +1 -0
  19. package/dist/commands/create-global-variable.d.ts +3 -0
  20. package/dist/commands/create-global-variable.d.ts.map +1 -0
  21. package/dist/commands/create-global-variable.js +53 -0
  22. package/dist/commands/create-global-variable.js.map +1 -0
  23. package/dist/commands/create-page.d.ts +3 -0
  24. package/dist/commands/create-page.d.ts.map +1 -0
  25. package/dist/commands/create-page.js +31 -0
  26. package/dist/commands/create-page.js.map +1 -0
  27. package/dist/commands/create.d.ts +3 -8
  28. package/dist/commands/create.d.ts.map +1 -1
  29. package/dist/commands/create.js +175 -8
  30. package/dist/commands/create.js.map +1 -1
  31. package/dist/commands/delete-theme-globals.d.ts +4 -0
  32. package/dist/commands/delete-theme-globals.d.ts.map +1 -0
  33. package/dist/commands/delete-theme-globals.js +48 -0
  34. package/dist/commands/delete-theme-globals.js.map +1 -0
  35. package/dist/commands/dev.d.ts.map +1 -1
  36. package/dist/commands/dev.js +217 -31
  37. package/dist/commands/dev.js.map +1 -1
  38. package/dist/commands/get-component-props.d.ts +3 -0
  39. package/dist/commands/get-component-props.d.ts.map +1 -0
  40. package/dist/commands/get-component-props.js +32 -0
  41. package/dist/commands/get-component-props.js.map +1 -0
  42. package/dist/commands/get-page-by-type.d.ts +3 -0
  43. package/dist/commands/get-page-by-type.d.ts.map +1 -0
  44. package/dist/commands/get-page-by-type.js +25 -0
  45. package/dist/commands/get-page-by-type.js.map +1 -0
  46. package/dist/commands/get-section-values.d.ts +3 -0
  47. package/dist/commands/get-section-values.d.ts.map +1 -0
  48. package/dist/commands/get-section-values.js +39 -0
  49. package/dist/commands/get-section-values.js.map +1 -0
  50. package/dist/commands/import.d.ts +3 -0
  51. package/dist/commands/import.d.ts.map +1 -0
  52. package/dist/commands/import.js +25 -0
  53. package/dist/commands/import.js.map +1 -0
  54. package/dist/commands/list-entities.d.ts +3 -0
  55. package/dist/commands/list-entities.d.ts.map +1 -0
  56. package/dist/commands/list-entities.js +32 -0
  57. package/dist/commands/list-entities.js.map +1 -0
  58. package/dist/commands/list-imported.d.ts +3 -0
  59. package/dist/commands/list-imported.d.ts.map +1 -0
  60. package/dist/commands/list-imported.js +25 -0
  61. package/dist/commands/list-imported.js.map +1 -0
  62. package/dist/commands/list-page-sections.d.ts +3 -0
  63. package/dist/commands/list-page-sections.d.ts.map +1 -0
  64. package/dist/commands/list-page-sections.js +25 -0
  65. package/dist/commands/list-page-sections.js.map +1 -0
  66. package/dist/commands/list-pages.d.ts +3 -0
  67. package/dist/commands/list-pages.d.ts.map +1 -0
  68. package/dist/commands/list-pages.js +21 -0
  69. package/dist/commands/list-pages.js.map +1 -0
  70. package/dist/commands/list-theme-globals.d.ts +3 -0
  71. package/dist/commands/list-theme-globals.d.ts.map +1 -0
  72. package/dist/commands/list-theme-globals.js +22 -0
  73. package/dist/commands/list-theme-globals.js.map +1 -0
  74. package/dist/commands/publish-theme.d.ts +3 -0
  75. package/dist/commands/publish-theme.d.ts.map +1 -0
  76. package/dist/commands/publish-theme.js +29 -0
  77. package/dist/commands/publish-theme.js.map +1 -0
  78. package/dist/commands/search-products.d.ts +3 -0
  79. package/dist/commands/search-products.d.ts.map +1 -0
  80. package/dist/commands/search-products.js +40 -0
  81. package/dist/commands/search-products.js.map +1 -0
  82. package/dist/commands/update-global-variable.d.ts +3 -0
  83. package/dist/commands/update-global-variable.d.ts.map +1 -0
  84. package/dist/commands/update-global-variable.js +47 -0
  85. package/dist/commands/update-global-variable.js.map +1 -0
  86. package/dist/commands/update-page-sections.d.ts +3 -0
  87. package/dist/commands/update-page-sections.d.ts.map +1 -0
  88. package/dist/commands/update-page-sections.js +39 -0
  89. package/dist/commands/update-page-sections.js.map +1 -0
  90. package/dist/commands/update-section-prop.d.ts +3 -0
  91. package/dist/commands/update-section-prop.d.ts.map +1 -0
  92. package/dist/commands/update-section-prop.js +59 -0
  93. package/dist/commands/update-section-prop.js.map +1 -0
  94. package/dist/commands/upload-image.d.ts +3 -0
  95. package/dist/commands/upload-image.d.ts.map +1 -0
  96. package/dist/commands/upload-image.js +38 -0
  97. package/dist/commands/upload-image.js.map +1 -0
  98. package/dist/commands/upload-images.d.ts +3 -0
  99. package/dist/commands/upload-images.d.ts.map +1 -0
  100. package/dist/commands/upload-images.js +48 -0
  101. package/dist/commands/upload-images.js.map +1 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +50 -0
  104. package/dist/index.js.map +1 -1
  105. package/dist/types.d.ts +11 -1
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/utils/compile.d.ts +4 -1
  108. package/dist/utils/compile.d.ts.map +1 -1
  109. package/dist/utils/compile.js +517 -48
  110. package/dist/utils/compile.js.map +1 -1
  111. package/dist/utils/component-helpers.d.ts +21 -3
  112. package/dist/utils/component-helpers.d.ts.map +1 -1
  113. package/dist/utils/component-helpers.js +111 -4
  114. package/dist/utils/component-helpers.js.map +1 -1
  115. package/dist/utils/editor-action-client.d.ts +28 -0
  116. package/dist/utils/editor-action-client.d.ts.map +1 -0
  117. package/dist/utils/editor-action-client.js +116 -0
  118. package/dist/utils/editor-action-client.js.map +1 -0
  119. package/dist/utils/load-image.d.ts +16 -0
  120. package/dist/utils/load-image.d.ts.map +1 -0
  121. package/dist/utils/load-image.js +50 -0
  122. package/dist/utils/load-image.js.map +1 -0
  123. package/dist/utils/websocket-server.d.ts +52 -2
  124. package/dist/utils/websocket-server.d.ts.map +1 -1
  125. package/dist/utils/websocket-server.js +59 -1
  126. package/dist/utils/websocket-server.js.map +1 -1
  127. package/package.json +1 -1
  128. package/dist/commands/proxy.d.ts +0 -39
  129. package/dist/commands/proxy.d.ts.map +0 -1
  130. package/dist/commands/proxy.js +0 -212
  131. package/dist/commands/proxy.js.map +0 -1
@@ -17,9 +17,48 @@ function loadConfig() {
17
17
  function saveConfig(configPath, config) {
18
18
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
19
19
  }
20
- function findComponent(config, name) {
21
- const pascalName = toPascalCase(name);
22
- return config.components.find((c) => c.name === pascalName);
20
+ /**
21
+ * Resolve a component by canonical id (preferred) or exact PascalCase name.
22
+ * No silent normalization: pass --id from `config list-components`, or pass --name
23
+ * exactly as stored in ikas.config.json. Exits with a structured JSON error
24
+ * (listing valid {id, name} pairs) if zero/both/missing.
25
+ */
26
+ function resolveComponent(config, ref) {
27
+ const id = ref.id?.trim() || undefined;
28
+ const name = ref.name?.trim() || undefined;
29
+ if (!id && !name) {
30
+ console.log(JSON.stringify({
31
+ success: false,
32
+ error: "Component reference required: pass --id (preferred) or --name (exact PascalCase). " +
33
+ "Run `ikas-component config list-components` to see canonical ids.",
34
+ validComponents: config.components.map((c) => ({ id: c.id, name: c.name })),
35
+ }));
36
+ process.exit(1);
37
+ }
38
+ if (id && name) {
39
+ console.log(JSON.stringify({
40
+ success: false,
41
+ error: "Specify only one of --id or --name, not both.",
42
+ }));
43
+ process.exit(1);
44
+ }
45
+ const found = id
46
+ ? config.components.find((c) => c.id === id)
47
+ : config.components.find((c) => c.name === name);
48
+ if (!found) {
49
+ console.log(JSON.stringify({
50
+ success: false,
51
+ error: id
52
+ ? `Component not found: id="${id}".`
53
+ : `Component not found: name="${name}". Names are matched exactly (PascalCase, no normalization).`,
54
+ lookup: id ? { id } : { name },
55
+ validComponents: config.components.map((c) => ({ id: c.id, name: c.name })),
56
+ hint: "Use --id (preferred) or exact PascalCase --name. " +
57
+ "Run `ikas-component config list-components` to see canonical ids.",
58
+ }));
59
+ process.exit(1);
60
+ }
61
+ return found;
23
62
  }
24
63
  function getComponentNames(config) {
25
64
  return config.components.map((c) => c.name);
@@ -105,6 +144,19 @@ function camelCaseToDisplayName(name) {
105
144
  .replace(/([a-z])([A-Z])/g, "$1 $2")
106
145
  .replace(/^./, (c) => c.toUpperCase());
107
146
  }
147
+ const ALLOWED_PROP_FIELDS = new Set([
148
+ "name",
149
+ "displayName",
150
+ "type",
151
+ "required",
152
+ "description",
153
+ "defaultValue",
154
+ "groupId",
155
+ "typeId",
156
+ "enumTypeId",
157
+ "filteredComponentIds",
158
+ "privateVarMap",
159
+ ]);
108
160
  /**
109
161
  * Parse and validate the --props JSON flag for add-component.
110
162
  * Returns validated ComponentProp[] or exits with error.
@@ -132,6 +184,16 @@ async function parsePropsFlag(propsJson, config, configPath) {
132
184
  const props = [];
133
185
  for (const raw of rawProps) {
134
186
  const r = raw;
187
+ const unknownFields = Object.keys(r).filter((k) => !ALLOWED_PROP_FIELDS.has(k));
188
+ if (unknownFields.length > 0) {
189
+ console.log(JSON.stringify({
190
+ success: false,
191
+ error: `Unknown field(s) in --props entry: ${unknownFields.join(", ")}. ` +
192
+ `Allowed: ${[...ALLOWED_PROP_FIELDS].join(", ")}. ` +
193
+ `Note: use "groupId" (not "group") and "defaultValue" (not "default") in --props JSON.`,
194
+ }));
195
+ process.exit(1);
196
+ }
135
197
  if (!r.name || typeof r.name !== "string") {
136
198
  console.log(JSON.stringify({
137
199
  success: false,
@@ -172,6 +234,14 @@ async function parsePropsFlag(propsJson, config, configPath) {
172
234
  }));
173
235
  process.exit(1);
174
236
  }
237
+ // Validate LINK / LIST_OF_LINK defaultValue shape (reject JSON strings and legacy { href })
238
+ if ((propType === "LINK" || propType === "LIST_OF_LINK") && r.defaultValue !== undefined) {
239
+ const linkError = validateLinkDefaultValue(propType, r.defaultValue, r.name);
240
+ if (linkError) {
241
+ console.log(JSON.stringify({ success: false, error: linkError }));
242
+ process.exit(1);
243
+ }
244
+ }
175
245
  // Auto-generate displayName if omitted
176
246
  const displayName = typeof r.displayName === "string" && r.displayName
177
247
  ? r.displayName
@@ -322,16 +392,9 @@ async function addComponent(name, options) {
322
392
  ],
323
393
  }));
324
394
  }
325
- async function addProp(componentName, options) {
395
+ async function addProp(ref, options) {
326
396
  const { config, configPath } = loadConfig();
327
- const component = findComponent(config, componentName);
328
- if (!component) {
329
- console.log(JSON.stringify({
330
- success: false,
331
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
332
- }));
333
- process.exit(1);
334
- }
397
+ const component = resolveComponent(config, ref);
335
398
  // Validate prop type
336
399
  const propType = options.type.toUpperCase();
337
400
  if (!PROP_TYPES.includes(propType)) {
@@ -434,14 +497,22 @@ async function addProp(componentName, options) {
434
497
  process.exit(1);
435
498
  }
436
499
  }
500
+ const parsedDefaultValue = options.defaultValue !== undefined ? parseDefaultValue(options.defaultValue, propType) : undefined;
501
+ if ((propType === "LINK" || propType === "LIST_OF_LINK") && parsedDefaultValue !== undefined) {
502
+ const linkError = validateLinkDefaultValue(propType, parsedDefaultValue, options.name);
503
+ if (linkError) {
504
+ console.log(JSON.stringify({ success: false, error: linkError }));
505
+ process.exit(1);
506
+ }
507
+ }
437
508
  const newProp = {
438
509
  name: options.name,
439
510
  displayName: options.displayName,
440
511
  type: propType,
441
512
  required: options.required ?? false,
442
513
  ...(options.description ? { description: options.description } : {}),
443
- ...(options.defaultValue !== undefined
444
- ? { defaultValue: parseDefaultValue(options.defaultValue, propType) }
514
+ ...(parsedDefaultValue !== undefined
515
+ ? { defaultValue: parsedDefaultValue }
445
516
  : {}),
446
517
  ...(options.group ? { groupId: options.group } : {}),
447
518
  ...(options.typeId ? { typeId: options.typeId } : {}),
@@ -475,20 +546,73 @@ function parseDefaultValue(value, propType) {
475
546
  return Number(value);
476
547
  case "BOOLEAN":
477
548
  return value === "true";
549
+ case "LINK":
550
+ case "LIST_OF_LINK":
551
+ // The --default-value flag arrives as a string; LINK values must be stored as a
552
+ // typed object. Parse it here; if it is not valid JSON, return the raw string so
553
+ // validateLinkDefaultValue can report a precise error.
554
+ try {
555
+ return JSON.parse(value);
556
+ }
557
+ catch {
558
+ return value;
559
+ }
478
560
  default:
479
561
  return value;
480
562
  }
481
563
  }
482
- function updateProp(componentName, options) {
564
+ const VALID_LINK_TYPES = ["PAGE", "EXTERNAL", "FILE"];
565
+ const LINK_SHAPE_HELP = 'A link must be an object: { "linkType": "EXTERNAL", "label": "Text", "externalLink": "https://...", "subLinks": [] } ' +
566
+ 'for external links, or { "linkType": "PAGE", "label": "Text", "pageType": "INDEX", "subLinks": [] } for store pages. ' +
567
+ "Do NOT pass a JSON string or a { label, href } shape.";
568
+ // Returns the index of the first array element that fails `fn`, as an error string, or null.
569
+ const firstError = (arr, fn) => arr.reduce((acc, item, index) => acc ?? fn(item, index), null);
570
+ const validateSingleLink = (link, propName, path) => {
571
+ const where = `defaultValue for prop "${propName}"${path}`;
572
+ if (typeof link === "string") {
573
+ return `${where} is a JSON string but must be an object. ${LINK_SHAPE_HELP}`;
574
+ }
575
+ if (!link || typeof link !== "object" || Array.isArray(link)) {
576
+ return `${where} must be a link object. ${LINK_SHAPE_HELP}`;
577
+ }
578
+ const l = link;
579
+ if ("href" in l && !("linkType" in l)) {
580
+ return `${where} uses a legacy { href } shape without "linkType". Use "linkType" + "externalLink"/"pageType" instead. ${LINK_SHAPE_HELP}`;
581
+ }
582
+ if (typeof l.linkType !== "string" || !VALID_LINK_TYPES.includes(l.linkType)) {
583
+ return `${where} must have a valid "linkType" (one of ${VALID_LINK_TYPES.join(", ")}). ${LINK_SHAPE_HELP}`;
584
+ }
585
+ if (l.subLinks !== undefined && !Array.isArray(l.subLinks)) {
586
+ return `${where}: "subLinks" must be an array (use [] when there are none).`;
587
+ }
588
+ if (Array.isArray(l.subLinks)) {
589
+ return firstError(l.subLinks, (sl, i) => validateSingleLink(sl, propName, `${path}.subLinks[${i}]`));
590
+ }
591
+ return null;
592
+ };
593
+ // Validates a LINK / LIST_OF_LINK defaultValue. Returns an error message, or null if valid.
594
+ // These defaults are a frequent source of bad data: agents tend to pass a JSON string or a
595
+ // legacy { label, href } shape. Rejecting them at authoring time keeps malformed link values
596
+ // out of published themes.
597
+ function validateLinkDefaultValue(propType, value, propName) {
598
+ if (value === undefined || value === null)
599
+ return null;
600
+ if (propType === "LINK") {
601
+ return validateSingleLink(value, propName, "");
602
+ }
603
+ // LIST_OF_LINK
604
+ if (typeof value === "string") {
605
+ return `defaultValue for prop "${propName}" is a JSON string but must be an object { "links": [...] }. ${LINK_SHAPE_HELP}`;
606
+ }
607
+ if (!value || typeof value !== "object" || Array.isArray(value) || !Array.isArray(value.links)) {
608
+ return `defaultValue for prop "${propName}" must be an object with a "links" array: { "links": [ <link>, ... ] }. ${LINK_SHAPE_HELP}`;
609
+ }
610
+ const links = value.links;
611
+ return firstError(links, (link, i) => validateSingleLink(link, propName, `.links[${i}]`));
612
+ }
613
+ function updateProp(ref, options) {
483
614
  const { config, configPath } = loadConfig();
484
- const component = findComponent(config, componentName);
485
- if (!component) {
486
- console.log(JSON.stringify({
487
- success: false,
488
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
489
- }));
490
- process.exit(1);
491
- }
615
+ const component = resolveComponent(config, ref);
492
616
  const propIndex = component.props.findIndex((p) => p.name === options.prop);
493
617
  if (propIndex === -1) {
494
618
  console.log(JSON.stringify({
@@ -519,7 +643,15 @@ function updateProp(componentName, options) {
519
643
  prop.description = options.description || undefined;
520
644
  }
521
645
  if (options.defaultValue !== undefined) {
522
- prop.defaultValue = parseDefaultValue(options.defaultValue, prop.type);
646
+ const parsed = parseDefaultValue(options.defaultValue, prop.type);
647
+ if (prop.type === "LINK" || prop.type === "LIST_OF_LINK") {
648
+ const linkError = validateLinkDefaultValue(prop.type, parsed, prop.name);
649
+ if (linkError) {
650
+ console.log(JSON.stringify({ success: false, error: linkError }));
651
+ process.exit(1);
652
+ }
653
+ }
654
+ prop.defaultValue = parsed;
523
655
  }
524
656
  if (options.group !== undefined) {
525
657
  if (options.group === "" || options.group === "none") {
@@ -622,16 +754,9 @@ function updateProp(componentName, options) {
622
754
  },
623
755
  }));
624
756
  }
625
- function removeProp(componentName, options) {
757
+ function removeProp(ref, options) {
626
758
  const { config, configPath } = loadConfig();
627
- const component = findComponent(config, componentName);
628
- if (!component) {
629
- console.log(JSON.stringify({
630
- success: false,
631
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
632
- }));
633
- process.exit(1);
634
- }
759
+ const component = resolveComponent(config, ref);
635
760
  const propIndex = component.props.findIndex((p) => p.name === options.prop);
636
761
  if (propIndex === -1) {
637
762
  console.log(JSON.stringify({
@@ -668,18 +793,10 @@ function pruneFilteredComponentIdRefs(config, removedId) {
668
793
  }
669
794
  }
670
795
  }
671
- function removeComponent(options) {
796
+ function removeComponent(ref) {
672
797
  const { config, configPath } = loadConfig();
673
- const pascalName = toPascalCase(options.name);
674
- const componentIndex = config.components.findIndex((c) => c.name === pascalName);
675
- if (componentIndex === -1) {
676
- console.log(JSON.stringify({
677
- success: false,
678
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
679
- }));
680
- process.exit(1);
681
- }
682
- const component = config.components[componentIndex];
798
+ const component = resolveComponent(config, ref);
799
+ const componentIndex = config.components.indexOf(component);
683
800
  const componentDir = path.resolve(process.cwd(), path.dirname(component.entry));
684
801
  // Remove component from config and strip orphaned references from remaining components
685
802
  config.components.splice(componentIndex, 1);
@@ -693,21 +810,15 @@ function removeComponent(options) {
693
810
  updateBarrelExport(process.cwd(), getComponentNames(config));
694
811
  console.log(JSON.stringify({
695
812
  success: true,
696
- removedComponent: pascalName,
813
+ removedComponentId: component.id,
814
+ removedComponentName: component.name,
697
815
  removedDirectory: path.relative(process.cwd(), componentDir),
698
- remainingComponents: getComponentNames(config),
816
+ remainingComponents: config.components.map((c) => ({ id: c.id, name: c.name })),
699
817
  }));
700
818
  }
701
- function addPropGroup(componentName, options) {
819
+ function addPropGroup(ref, options) {
702
820
  const { config, configPath } = loadConfig();
703
- const component = findComponent(config, componentName);
704
- if (!component) {
705
- console.log(JSON.stringify({
706
- success: false,
707
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
708
- }));
709
- process.exit(1);
710
- }
821
+ const component = resolveComponent(config, ref);
711
822
  if (!component.propGroups)
712
823
  component.propGroups = [];
713
824
  // Check uniqueness
@@ -758,16 +869,9 @@ function addPropGroup(componentName, options) {
758
869
  },
759
870
  }));
760
871
  }
761
- function updatePropGroup(componentName, options) {
872
+ function updatePropGroup(ref, options) {
762
873
  const { config, configPath } = loadConfig();
763
- const component = findComponent(config, componentName);
764
- if (!component) {
765
- console.log(JSON.stringify({
766
- success: false,
767
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
768
- }));
769
- process.exit(1);
770
- }
874
+ const component = resolveComponent(config, ref);
771
875
  if (!component.propGroups || component.propGroups.length === 0) {
772
876
  console.log(JSON.stringify({
773
877
  success: false,
@@ -797,16 +901,9 @@ function updatePropGroup(componentName, options) {
797
901
  },
798
902
  }));
799
903
  }
800
- function removePropGroup(componentName, options) {
904
+ function removePropGroup(ref, options) {
801
905
  const { config, configPath } = loadConfig();
802
- const component = findComponent(config, componentName);
803
- if (!component) {
804
- console.log(JSON.stringify({
805
- success: false,
806
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
807
- }));
808
- process.exit(1);
809
- }
906
+ const component = resolveComponent(config, ref);
810
907
  if (!component.propGroups || component.propGroups.length === 0) {
811
908
  console.log(JSON.stringify({
812
909
  success: false,
@@ -842,16 +939,9 @@ function removePropGroup(componentName, options) {
842
939
  remainingPropGroups: component.propGroups.map(g => g.id),
843
940
  }));
844
941
  }
845
- function movePropGroup(componentName, options) {
942
+ function movePropGroup(ref, options) {
846
943
  const { config, configPath } = loadConfig();
847
- const component = findComponent(config, componentName);
848
- if (!component) {
849
- console.log(JSON.stringify({
850
- success: false,
851
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
852
- }));
853
- process.exit(1);
854
- }
944
+ const component = resolveComponent(config, ref);
855
945
  if (!component.propGroups || component.propGroups.length === 0) {
856
946
  console.log(JSON.stringify({
857
947
  success: false,
@@ -874,16 +964,9 @@ function movePropGroup(componentName, options) {
874
964
  parentGroupId: options.parent || null,
875
965
  }));
876
966
  }
877
- function updateComponent(options) {
967
+ function updateComponent(ref, options) {
878
968
  const { config, configPath } = loadConfig();
879
- const component = findComponent(config, options.name);
880
- if (!component) {
881
- console.log(JSON.stringify({
882
- success: false,
883
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
884
- }));
885
- process.exit(1);
886
- }
969
+ const component = resolveComponent(config, ref);
887
970
  if (options.isHeader !== undefined) {
888
971
  if (options.isHeader) {
889
972
  component.isHeader = true;
@@ -1136,14 +1219,16 @@ export function createConfigCommand() {
1136
1219
  .option("--type <type>", "Component type: section or component", "component")
1137
1220
  .option("--isHeader", "Mark this section as the store header (only for type: section)")
1138
1221
  .option("--isFooter", "Mark this section as the store footer (only for type: section)")
1139
- .option("--props <json>", "JSON array of props, e.g. '[{\"name\":\"title\",\"type\":\"TEXT\",\"required\":true}]'. Each prop needs name+type; displayName is auto-generated from name if omitted.")
1222
+ .option("--props <json>", "JSON array of props. Required per entry: name, type. Optional: displayName (auto from name), required, description, defaultValue, groupId, typeId (TYPE props), enumTypeId (ENUM props), filteredComponentIds, privateVarMap. " +
1223
+ "Example: '[{\"name\":\"title\",\"type\":\"TEXT\",\"required\":true,\"defaultValue\":\"Hello\",\"groupId\":\"basic\"}]'")
1140
1224
  .action((options) => {
1141
1225
  addComponent(options.name, options);
1142
1226
  });
1143
1227
  config
1144
1228
  .command("add-prop")
1145
1229
  .description("Add a prop to a component")
1146
- .requiredOption("--component <name>", "Component name")
1230
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1231
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1147
1232
  .requiredOption("--name <name>", "Prop name (camelCase)")
1148
1233
  .requiredOption("--displayName <displayName>", "Display name in editor")
1149
1234
  .requiredOption("--type <type>", `Prop type: ${PROP_TYPES.join(", ")}`)
@@ -1156,12 +1241,13 @@ export function createConfigCommand() {
1156
1241
  .option("--filteredComponentIds <json>", "JSON array of component IDs to restrict selection (for COMPONENT/COMPONENT_LIST)")
1157
1242
  .option("--privateVarMap <json>", 'JSON object mapping variable keys to {id, typeId} (for COMPONENT/COMPONENT_LIST)')
1158
1243
  .action((options) => {
1159
- addProp(options.component, options);
1244
+ addProp({ id: options.componentId, name: options.component }, options);
1160
1245
  });
1161
1246
  config
1162
1247
  .command("update-prop")
1163
1248
  .description("Update a prop on a component")
1164
- .requiredOption("--component <name>", "Component name")
1249
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1250
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1165
1251
  .requiredOption("--prop <propName>", "Prop name to update")
1166
1252
  .option("--displayName <displayName>", "New display name")
1167
1253
  .option("--type <type>", "New prop type")
@@ -1174,76 +1260,89 @@ export function createConfigCommand() {
1174
1260
  .option("--filteredComponentIds <json>", "JSON array of component IDs (use 'none' to clear)")
1175
1261
  .option("--privateVarMap <json>", "JSON object mapping variable keys to {id, typeId} (use 'none' to clear)")
1176
1262
  .action((options) => {
1177
- updateProp(options.component, options);
1263
+ updateProp({ id: options.componentId, name: options.component }, options);
1178
1264
  });
1179
1265
  config
1180
1266
  .command("remove-prop")
1181
1267
  .description("Remove a prop from a component")
1182
- .requiredOption("--component <name>", "Component name")
1268
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1269
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1183
1270
  .requiredOption("--prop <propName>", "Prop name to remove")
1184
1271
  .action((options) => {
1185
- removeProp(options.component, options);
1272
+ removeProp({ id: options.componentId, name: options.component }, options);
1186
1273
  });
1187
1274
  config
1188
1275
  .command("update-component")
1189
- .description("Update a component's metadata (isHeader, isFooter)")
1190
- .requiredOption("--name <name>", "Component name")
1276
+ .description("Update a component's metadata (isHeader, isFooter). Identify by --id (preferred) or exact --name.")
1277
+ .option("--id <id>", "Component id (preferred; from `config list-components`)")
1278
+ .option("--name <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1191
1279
  .option("--isHeader", "Mark as store header")
1192
1280
  .option("--no-isHeader", "Unmark as store header")
1193
1281
  .option("--isFooter", "Mark as store footer")
1194
1282
  .option("--no-isFooter", "Unmark as store footer")
1195
1283
  .action((options) => {
1196
- updateComponent(options);
1284
+ updateComponent({ id: options.id, name: options.name }, options);
1197
1285
  });
1198
1286
  config
1199
1287
  .command("remove-component")
1200
- .description("Remove a component from the project (deletes files)")
1201
- .requiredOption("--name <name>", "Component name to remove")
1288
+ .description("Remove a component from the project (deletes files). Identify by --id (preferred) or exact --name.")
1289
+ .option("--id <id>", "Component id (preferred; from `config list-components`)")
1290
+ .option("--name <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1202
1291
  .action((options) => {
1203
- removeComponent(options);
1292
+ removeComponent({ id: options.id, name: options.name });
1204
1293
  });
1205
1294
  config
1206
1295
  .command("add-prop-group")
1207
1296
  .description("Add a prop group to a component")
1208
- .requiredOption("--component <name>", "Component name")
1297
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1298
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1209
1299
  .requiredOption("--id <id>", "Group ID (unique within component, kebab-case)")
1210
1300
  .requiredOption("--name <name>", "Display name in editor")
1211
1301
  .option("--description <description>", "Group description")
1212
1302
  .option("--parent <parentId>", "Parent group ID (for nesting, max 1 level)")
1213
1303
  .action((options) => {
1214
- addPropGroup(options.component, options);
1304
+ addPropGroup({ id: options.componentId, name: options.component }, options);
1215
1305
  });
1216
1306
  config
1217
1307
  .command("update-prop-group")
1218
1308
  .description("Update a prop group on a component")
1219
- .requiredOption("--component <name>", "Component name")
1309
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1310
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1220
1311
  .requiredOption("--id <id>", "Group ID to update")
1221
1312
  .option("--name <name>", "New display name")
1222
1313
  .option("--description <description>", "New description")
1223
1314
  .action((options) => {
1224
- updatePropGroup(options.component, options);
1315
+ updatePropGroup({ id: options.componentId, name: options.component }, options);
1225
1316
  });
1226
1317
  config
1227
1318
  .command("remove-prop-group")
1228
1319
  .description("Remove a prop group from a component (props become ungrouped)")
1229
- .requiredOption("--component <name>", "Component name")
1320
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1321
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1230
1322
  .requiredOption("--id <id>", "Group ID to remove")
1231
1323
  .action((options) => {
1232
- removePropGroup(options.component, options);
1324
+ removePropGroup({ id: options.componentId, name: options.component }, options);
1233
1325
  });
1234
1326
  config
1235
1327
  .command("move-prop-group")
1236
1328
  .description("Move a prop group to a different parent or position (for drag-and-drop reordering)")
1237
- .requiredOption("--component <name>", "Component name")
1329
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1330
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1238
1331
  .requiredOption("--id <id>", "Group ID to move")
1239
1332
  .option("--parent <parentId>", "Target parent group ID (omit to move to root)")
1240
1333
  .option("--index <index>", "Zero-based insertion index within the target parent (appends when omitted)", v => parseInt(v, 10))
1241
1334
  .action((options) => {
1242
- movePropGroup(options.component, options);
1335
+ movePropGroup({ id: options.componentId, name: options.component }, options);
1336
+ });
1337
+ config
1338
+ .command("list-components")
1339
+ .description("List all components and their props (with canonical ids for use with --id flags)")
1340
+ .action(() => {
1341
+ listComponents();
1243
1342
  });
1244
1343
  config
1245
1344
  .command("list")
1246
- .description("List all components and their props")
1345
+ .description("Alias for `list-components` (kept for backwards compatibility)")
1247
1346
  .action(() => {
1248
1347
  listComponents();
1249
1348
  });