@ikas/component-cli 1.4.0-beta.6 → 1.4.0-beta.61

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 +230 -114
  14. package/dist/commands/config.js.map +1 -1
  15. package/dist/commands/create-design-tokens.d.ts +7 -0
  16. package/dist/commands/create-design-tokens.d.ts.map +1 -0
  17. package/dist/commands/create-design-tokens.js +127 -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 +220 -25
  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 +49 -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 +3 -3
  112. package/dist/utils/component-helpers.d.ts.map +1 -1
  113. package/dist/utils/component-helpers.js +75 -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({
@@ -652,21 +777,30 @@ function removeProp(componentName, options) {
652
777
  remainingProps: component.props.map((p) => p.name),
653
778
  }));
654
779
  }
655
- function removeComponent(options) {
656
- const { config, configPath } = loadConfig();
657
- const pascalName = toPascalCase(options.name);
658
- const componentIndex = config.components.findIndex((c) => c.name === pascalName);
659
- if (componentIndex === -1) {
660
- console.log(JSON.stringify({
661
- success: false,
662
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
663
- }));
664
- process.exit(1);
780
+ /**
781
+ * Strip a deleted component's ID from every other component's COMPONENT/COMPONENT_LIST
782
+ * `filteredComponentIds` allowlist. Empty arrays are deleted to keep the field optional
783
+ * (mirrors the editor-side convention).
784
+ */
785
+ function pruneFilteredComponentIdRefs(config, removedId) {
786
+ for (const component of config.components) {
787
+ for (const prop of component.props || []) {
788
+ if (!prop.filteredComponentIds)
789
+ continue;
790
+ prop.filteredComponentIds = prop.filteredComponentIds.filter((id) => id !== removedId);
791
+ if (prop.filteredComponentIds.length === 0)
792
+ delete prop.filteredComponentIds;
793
+ }
665
794
  }
666
- const component = config.components[componentIndex];
795
+ }
796
+ function removeComponent(ref) {
797
+ const { config, configPath } = loadConfig();
798
+ const component = resolveComponent(config, ref);
799
+ const componentIndex = config.components.indexOf(component);
667
800
  const componentDir = path.resolve(process.cwd(), path.dirname(component.entry));
668
- // Remove component from config
801
+ // Remove component from config and strip orphaned references from remaining components
669
802
  config.components.splice(componentIndex, 1);
803
+ pruneFilteredComponentIdRefs(config, component.id);
670
804
  saveConfig(configPath, config);
671
805
  // Remove component directory
672
806
  if (fs.existsSync(componentDir)) {
@@ -676,21 +810,15 @@ function removeComponent(options) {
676
810
  updateBarrelExport(process.cwd(), getComponentNames(config));
677
811
  console.log(JSON.stringify({
678
812
  success: true,
679
- removedComponent: pascalName,
813
+ removedComponentId: component.id,
814
+ removedComponentName: component.name,
680
815
  removedDirectory: path.relative(process.cwd(), componentDir),
681
- remainingComponents: getComponentNames(config),
816
+ remainingComponents: config.components.map((c) => ({ id: c.id, name: c.name })),
682
817
  }));
683
818
  }
684
- function addPropGroup(componentName, options) {
819
+ function addPropGroup(ref, options) {
685
820
  const { config, configPath } = loadConfig();
686
- const component = findComponent(config, componentName);
687
- if (!component) {
688
- console.log(JSON.stringify({
689
- success: false,
690
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
691
- }));
692
- process.exit(1);
693
- }
821
+ const component = resolveComponent(config, ref);
694
822
  if (!component.propGroups)
695
823
  component.propGroups = [];
696
824
  // Check uniqueness
@@ -741,16 +869,9 @@ function addPropGroup(componentName, options) {
741
869
  },
742
870
  }));
743
871
  }
744
- function updatePropGroup(componentName, options) {
872
+ function updatePropGroup(ref, options) {
745
873
  const { config, configPath } = loadConfig();
746
- const component = findComponent(config, componentName);
747
- if (!component) {
748
- console.log(JSON.stringify({
749
- success: false,
750
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
751
- }));
752
- process.exit(1);
753
- }
874
+ const component = resolveComponent(config, ref);
754
875
  if (!component.propGroups || component.propGroups.length === 0) {
755
876
  console.log(JSON.stringify({
756
877
  success: false,
@@ -780,16 +901,9 @@ function updatePropGroup(componentName, options) {
780
901
  },
781
902
  }));
782
903
  }
783
- function removePropGroup(componentName, options) {
904
+ function removePropGroup(ref, options) {
784
905
  const { config, configPath } = loadConfig();
785
- const component = findComponent(config, componentName);
786
- if (!component) {
787
- console.log(JSON.stringify({
788
- success: false,
789
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
790
- }));
791
- process.exit(1);
792
- }
906
+ const component = resolveComponent(config, ref);
793
907
  if (!component.propGroups || component.propGroups.length === 0) {
794
908
  console.log(JSON.stringify({
795
909
  success: false,
@@ -825,16 +939,9 @@ function removePropGroup(componentName, options) {
825
939
  remainingPropGroups: component.propGroups.map(g => g.id),
826
940
  }));
827
941
  }
828
- function movePropGroup(componentName, options) {
942
+ function movePropGroup(ref, options) {
829
943
  const { config, configPath } = loadConfig();
830
- const component = findComponent(config, componentName);
831
- if (!component) {
832
- console.log(JSON.stringify({
833
- success: false,
834
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
835
- }));
836
- process.exit(1);
837
- }
944
+ const component = resolveComponent(config, ref);
838
945
  if (!component.propGroups || component.propGroups.length === 0) {
839
946
  console.log(JSON.stringify({
840
947
  success: false,
@@ -857,16 +964,9 @@ function movePropGroup(componentName, options) {
857
964
  parentGroupId: options.parent || null,
858
965
  }));
859
966
  }
860
- function updateComponent(options) {
967
+ function updateComponent(ref, options) {
861
968
  const { config, configPath } = loadConfig();
862
- const component = findComponent(config, options.name);
863
- if (!component) {
864
- console.log(JSON.stringify({
865
- success: false,
866
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
867
- }));
868
- process.exit(1);
869
- }
969
+ const component = resolveComponent(config, ref);
870
970
  if (options.isHeader !== undefined) {
871
971
  if (options.isHeader) {
872
972
  component.isHeader = true;
@@ -1119,14 +1219,16 @@ export function createConfigCommand() {
1119
1219
  .option("--type <type>", "Component type: section or component", "component")
1120
1220
  .option("--isHeader", "Mark this section as the store header (only for type: section)")
1121
1221
  .option("--isFooter", "Mark this section as the store footer (only for type: section)")
1122
- .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\"}]'")
1123
1224
  .action((options) => {
1124
1225
  addComponent(options.name, options);
1125
1226
  });
1126
1227
  config
1127
1228
  .command("add-prop")
1128
1229
  .description("Add a prop to a component")
1129
- .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)")
1130
1232
  .requiredOption("--name <name>", "Prop name (camelCase)")
1131
1233
  .requiredOption("--displayName <displayName>", "Display name in editor")
1132
1234
  .requiredOption("--type <type>", `Prop type: ${PROP_TYPES.join(", ")}`)
@@ -1139,12 +1241,13 @@ export function createConfigCommand() {
1139
1241
  .option("--filteredComponentIds <json>", "JSON array of component IDs to restrict selection (for COMPONENT/COMPONENT_LIST)")
1140
1242
  .option("--privateVarMap <json>", 'JSON object mapping variable keys to {id, typeId} (for COMPONENT/COMPONENT_LIST)')
1141
1243
  .action((options) => {
1142
- addProp(options.component, options);
1244
+ addProp({ id: options.componentId, name: options.component }, options);
1143
1245
  });
1144
1246
  config
1145
1247
  .command("update-prop")
1146
1248
  .description("Update a prop on a component")
1147
- .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)")
1148
1251
  .requiredOption("--prop <propName>", "Prop name to update")
1149
1252
  .option("--displayName <displayName>", "New display name")
1150
1253
  .option("--type <type>", "New prop type")
@@ -1157,76 +1260,89 @@ export function createConfigCommand() {
1157
1260
  .option("--filteredComponentIds <json>", "JSON array of component IDs (use 'none' to clear)")
1158
1261
  .option("--privateVarMap <json>", "JSON object mapping variable keys to {id, typeId} (use 'none' to clear)")
1159
1262
  .action((options) => {
1160
- updateProp(options.component, options);
1263
+ updateProp({ id: options.componentId, name: options.component }, options);
1161
1264
  });
1162
1265
  config
1163
1266
  .command("remove-prop")
1164
1267
  .description("Remove a prop from a component")
1165
- .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)")
1166
1270
  .requiredOption("--prop <propName>", "Prop name to remove")
1167
1271
  .action((options) => {
1168
- removeProp(options.component, options);
1272
+ removeProp({ id: options.componentId, name: options.component }, options);
1169
1273
  });
1170
1274
  config
1171
1275
  .command("update-component")
1172
- .description("Update a component's metadata (isHeader, isFooter)")
1173
- .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)")
1174
1279
  .option("--isHeader", "Mark as store header")
1175
1280
  .option("--no-isHeader", "Unmark as store header")
1176
1281
  .option("--isFooter", "Mark as store footer")
1177
1282
  .option("--no-isFooter", "Unmark as store footer")
1178
1283
  .action((options) => {
1179
- updateComponent(options);
1284
+ updateComponent({ id: options.id, name: options.name }, options);
1180
1285
  });
1181
1286
  config
1182
1287
  .command("remove-component")
1183
- .description("Remove a component from the project (deletes files)")
1184
- .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)")
1185
1291
  .action((options) => {
1186
- removeComponent(options);
1292
+ removeComponent({ id: options.id, name: options.name });
1187
1293
  });
1188
1294
  config
1189
1295
  .command("add-prop-group")
1190
1296
  .description("Add a prop group to a component")
1191
- .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)")
1192
1299
  .requiredOption("--id <id>", "Group ID (unique within component, kebab-case)")
1193
1300
  .requiredOption("--name <name>", "Display name in editor")
1194
1301
  .option("--description <description>", "Group description")
1195
1302
  .option("--parent <parentId>", "Parent group ID (for nesting, max 1 level)")
1196
1303
  .action((options) => {
1197
- addPropGroup(options.component, options);
1304
+ addPropGroup({ id: options.componentId, name: options.component }, options);
1198
1305
  });
1199
1306
  config
1200
1307
  .command("update-prop-group")
1201
1308
  .description("Update a prop group on a component")
1202
- .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)")
1203
1311
  .requiredOption("--id <id>", "Group ID to update")
1204
1312
  .option("--name <name>", "New display name")
1205
1313
  .option("--description <description>", "New description")
1206
1314
  .action((options) => {
1207
- updatePropGroup(options.component, options);
1315
+ updatePropGroup({ id: options.componentId, name: options.component }, options);
1208
1316
  });
1209
1317
  config
1210
1318
  .command("remove-prop-group")
1211
1319
  .description("Remove a prop group from a component (props become ungrouped)")
1212
- .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)")
1213
1322
  .requiredOption("--id <id>", "Group ID to remove")
1214
1323
  .action((options) => {
1215
- removePropGroup(options.component, options);
1324
+ removePropGroup({ id: options.componentId, name: options.component }, options);
1216
1325
  });
1217
1326
  config
1218
1327
  .command("move-prop-group")
1219
1328
  .description("Move a prop group to a different parent or position (for drag-and-drop reordering)")
1220
- .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)")
1221
1331
  .requiredOption("--id <id>", "Group ID to move")
1222
1332
  .option("--parent <parentId>", "Target parent group ID (omit to move to root)")
1223
1333
  .option("--index <index>", "Zero-based insertion index within the target parent (appends when omitted)", v => parseInt(v, 10))
1224
1334
  .action((options) => {
1225
- 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();
1226
1342
  });
1227
1343
  config
1228
1344
  .command("list")
1229
- .description("List all components and their props")
1345
+ .description("Alias for `list-components` (kept for backwards compatibility)")
1230
1346
  .action(() => {
1231
1347
  listComponents();
1232
1348
  });