@ikas/component-cli 1.4.0-beta.2 → 1.4.0-beta.20

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 (59) hide show
  1. package/dist/commands/add-to-page.d.ts +3 -0
  2. package/dist/commands/add-to-page.d.ts.map +1 -0
  3. package/dist/commands/add-to-page.js +41 -0
  4. package/dist/commands/add-to-page.js.map +1 -0
  5. package/dist/commands/config.d.ts.map +1 -1
  6. package/dist/commands/config.js +185 -120
  7. package/dist/commands/config.js.map +1 -1
  8. package/dist/commands/create.d.ts +3 -8
  9. package/dist/commands/create.d.ts.map +1 -1
  10. package/dist/commands/create.js +227 -8
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/dev.d.ts.map +1 -1
  13. package/dist/commands/dev.js +230 -24
  14. package/dist/commands/dev.js.map +1 -1
  15. package/dist/commands/import.d.ts +3 -0
  16. package/dist/commands/import.d.ts.map +1 -0
  17. package/dist/commands/import.js +25 -0
  18. package/dist/commands/import.js.map +1 -0
  19. package/dist/commands/list-imported.d.ts +3 -0
  20. package/dist/commands/list-imported.d.ts.map +1 -0
  21. package/dist/commands/list-imported.js +25 -0
  22. package/dist/commands/list-imported.js.map +1 -0
  23. package/dist/commands/list-page-sections.d.ts +3 -0
  24. package/dist/commands/list-page-sections.d.ts.map +1 -0
  25. package/dist/commands/list-page-sections.js +25 -0
  26. package/dist/commands/list-page-sections.js.map +1 -0
  27. package/dist/commands/list-pages.d.ts +3 -0
  28. package/dist/commands/list-pages.d.ts.map +1 -0
  29. package/dist/commands/list-pages.js +21 -0
  30. package/dist/commands/list-pages.js.map +1 -0
  31. package/dist/commands/proxy.d.ts.map +1 -1
  32. package/dist/commands/proxy.js +4 -6
  33. package/dist/commands/proxy.js.map +1 -1
  34. package/dist/commands/update-section-prop.d.ts +3 -0
  35. package/dist/commands/update-section-prop.d.ts.map +1 -0
  36. package/dist/commands/update-section-prop.js +59 -0
  37. package/dist/commands/update-section-prop.js.map +1 -0
  38. package/dist/commands/upload-image.d.ts +3 -0
  39. package/dist/commands/upload-image.d.ts.map +1 -0
  40. package/dist/commands/upload-image.js +79 -0
  41. package/dist/commands/upload-image.js.map +1 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +14 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/types.d.ts +1 -1
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/utils/component-helpers.d.ts +14 -1
  48. package/dist/utils/component-helpers.d.ts.map +1 -1
  49. package/dist/utils/component-helpers.js +16 -0
  50. package/dist/utils/component-helpers.js.map +1 -1
  51. package/dist/utils/editor-action-client.d.ts +28 -0
  52. package/dist/utils/editor-action-client.d.ts.map +1 -0
  53. package/dist/utils/editor-action-client.js +101 -0
  54. package/dist/utils/editor-action-client.js.map +1 -0
  55. package/dist/utils/websocket-server.d.ts +48 -0
  56. package/dist/utils/websocket-server.d.ts.map +1 -1
  57. package/dist/utils/websocket-server.js +58 -0
  58. package/dist/utils/websocket-server.js.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createAddToPageCommand(): Command;
3
+ //# sourceMappingURL=add-to-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-to-page.d.ts","sourceRoot":"","sources":["../../src/commands/add-to-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6CpC,wBAAgB,sBAAsB,IAAI,OAAO,CAYhD"}
@@ -0,0 +1,41 @@
1
+ import { Command } from "commander";
2
+ import { printErrorAndExit, printResultAndExit, runEditorAction, } from "../utils/editor-action-client.js";
3
+ async function runAddToPage(options) {
4
+ if (!options.componentId || typeof options.componentId !== "string") {
5
+ printErrorAndExit(new Error("--component-id is required"));
6
+ }
7
+ if (!options.pageId || typeof options.pageId !== "string") {
8
+ printErrorAndExit(new Error("--page-id is required (use `ikas-component list-pages` to discover ids)"));
9
+ }
10
+ let index;
11
+ if (options.index !== undefined) {
12
+ index = parseInt(options.index, 10);
13
+ if (Number.isNaN(index) || index < 0) {
14
+ printErrorAndExit(new Error("--index must be a non-negative integer"));
15
+ }
16
+ }
17
+ const port = options.port ? parseInt(options.port, 10) : undefined;
18
+ try {
19
+ const result = await runEditorAction("add-section-to-page", {
20
+ componentId: options.componentId,
21
+ pageId: options.pageId,
22
+ ...(typeof index === "number" ? { index } : {}),
23
+ }, port ? { port } : {});
24
+ printResultAndExit(result);
25
+ }
26
+ catch (e) {
27
+ printErrorAndExit(e);
28
+ }
29
+ }
30
+ export function createAddToPageCommand() {
31
+ const cmd = new Command("add-to-page");
32
+ cmd
33
+ .description("Place a section-type code component on a page. Requires the component to be imported first (see `ikas-component import`).")
34
+ .requiredOption("--component-id <id>", "Code component id (from `ikas-component list-imported`)")
35
+ .requiredOption("--page-id <id>", "Page id (from `ikas-component list-pages`)")
36
+ .option("--index <n>", "Zero-based insertion index in the page; appends when omitted")
37
+ .option("--port <port>", "Dev server WebSocket port", "5201")
38
+ .action(runAddToPage);
39
+ return cmd;
40
+ }
41
+ //# sourceMappingURL=add-to-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-to-page.js","sourceRoot":"","sources":["../../src/commands/add-to-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,kCAAkC,CAAC;AAS1C,KAAK,UAAU,YAAY,CAAC,OAAyB;IACnD,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpE,iBAAiB,CAAC,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1D,iBAAiB,CAAC,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC,CAAC;IAC1G,CAAC;IACD,IAAI,KAAyB,CAAC;IAC9B,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrC,iBAAiB,CAAC,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAClC,qBAAqB,EACrB;YACE,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,EACD,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CACrB,CAAC;QACF,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IACvC,GAAG;SACA,WAAW,CACV,2HAA2H,CAC5H;SACA,cAAc,CAAC,qBAAqB,EAAE,yDAAyD,CAAC;SAChG,cAAc,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;SAC9E,MAAM,CAAC,aAAa,EAAE,8DAA8D,CAAC;SACrF,MAAM,CAAC,eAAe,EAAE,2BAA2B,EAAE,MAAM,CAAC;SAC5D,MAAM,CAAC,YAAY,CAAC,CAAC;IACxB,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA28CpC,wBAAgB,mBAAmB,IAAI,OAAO,CAuL7C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAw/CpC,wBAAgB,mBAAmB,IAAI,OAAO,CA2M7C"}
@@ -1,7 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
- import { PROP_TYPES, toPascalCase, generateTypesFile, generateGlobalTypesFile, collectUsedEnumIds, generateComponentFile, generateStylesFile, generateProjectId, generateComponentId, generateUniqueId, updateBarrelExport, findPropGroup, collectPropGroupIds, movePropGroupInTree, } from "../utils/component-helpers.js";
4
+ import { PROP_TYPES, toPascalCase, generateTypesFile, generateGlobalTypesFile, collectUsedEnumIds, generateComponentFile, generateStylesFile, generateProjectId, generateComponentId, generateUniqueId, updateBarrelExport, findPropGroup, collectPropGroupIds, movePropGroupInTree, validateFilteredComponentIds, } from "../utils/component-helpers.js";
5
5
  function loadConfig() {
6
6
  const configPath = path.resolve(process.cwd(), "ikas.config.json");
7
7
  if (!fs.existsSync(configPath)) {
@@ -17,13 +17,79 @@ 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);
26
65
  }
66
+ /**
67
+ * Emit a structured JSON error for unknown filteredComponentIds entries and exit non-zero.
68
+ * Lists valid `{ id, name }` pairs so the LLM/caller can recover.
69
+ */
70
+ function emitFilteredComponentIdsError(unknown, components) {
71
+ console.log(JSON.stringify({
72
+ success: false,
73
+ error: `Unknown filteredComponentIds: ${unknown.join(", ")}. ` +
74
+ `Component ids are opaque and cannot be derived from names — they must come from the ` +
75
+ `componentId returned by 'config add-component' or from 'config list'.`,
76
+ validComponents: components.map((c) => ({ id: c.id, name: c.name })),
77
+ }));
78
+ process.exit(1);
79
+ }
80
+ /**
81
+ * Validate `input` ids against `config.components` and return the same array on success.
82
+ * Exits with a JSON error if any id is unknown. Use at every write site that accepts
83
+ * filteredComponentIds from user/LLM input.
84
+ */
85
+ function assertKnownComponentIds(input, config) {
86
+ const components = config?.components ?? [];
87
+ const { unknown } = validateFilteredComponentIds(input, components);
88
+ if (unknown.length > 0) {
89
+ emitFilteredComponentIdsError(unknown, components);
90
+ }
91
+ return input;
92
+ }
27
93
  function regenerateTypes(component, componentType, config) {
28
94
  const componentDir = path.resolve(process.cwd(), path.dirname(component.entry));
29
95
  const typesPath = path.join(componentDir, "types.ts");
@@ -78,6 +144,19 @@ function camelCaseToDisplayName(name) {
78
144
  .replace(/([a-z])([A-Z])/g, "$1 $2")
79
145
  .replace(/^./, (c) => c.toUpperCase());
80
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
+ ]);
81
160
  /**
82
161
  * Parse and validate the --props JSON flag for add-component.
83
162
  * Returns validated ComponentProp[] or exits with error.
@@ -105,6 +184,16 @@ async function parsePropsFlag(propsJson, config, configPath) {
105
184
  const props = [];
106
185
  for (const raw of rawProps) {
107
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
+ }
108
197
  if (!r.name || typeof r.name !== "string") {
109
198
  console.log(JSON.stringify({
110
199
  success: false,
@@ -201,7 +290,7 @@ async function parsePropsFlag(propsJson, config, configPath) {
201
290
  ? { enumTypeId: r.enumTypeId }
202
291
  : {}),
203
292
  ...((propType === "COMPONENT" || propType === "COMPONENT_LIST") && r.filteredComponentIds && Array.isArray(r.filteredComponentIds)
204
- ? { filteredComponentIds: r.filteredComponentIds }
293
+ ? { filteredComponentIds: assertKnownComponentIds(r.filteredComponentIds, config) }
205
294
  : {}),
206
295
  ...((propType === "COMPONENT" || propType === "COMPONENT_LIST") && r.privateVarMap && typeof r.privateVarMap === "object"
207
296
  ? { privateVarMap: r.privateVarMap }
@@ -236,14 +325,15 @@ async function addComponent(name, options) {
236
325
  if (!config.projectId) {
237
326
  config.projectId = generateProjectId();
238
327
  }
328
+ // Parse --props (and validate filteredComponentIds) BEFORE any filesystem side-effects
329
+ // so a bad payload doesn't leave an empty component directory behind.
330
+ const props = options.props
331
+ ? await parsePropsFlag(options.props, config, configPath)
332
+ : [];
239
333
  const componentId = generateComponentId(config.projectId);
240
334
  const componentDir = path.resolve(process.cwd(), `src/components/${pascalName}`);
241
335
  // Create directory
242
336
  fs.mkdirSync(componentDir, { recursive: true });
243
- // Parse --props if provided, otherwise empty
244
- const props = options.props
245
- ? await parsePropsFlag(options.props, config, configPath)
246
- : [];
247
337
  // Auto-create prop groups from groupId references in props
248
338
  const referencedGroupIds = new Set();
249
339
  for (const prop of props) {
@@ -294,16 +384,9 @@ async function addComponent(name, options) {
294
384
  ],
295
385
  }));
296
386
  }
297
- async function addProp(componentName, options) {
387
+ async function addProp(ref, options) {
298
388
  const { config, configPath } = loadConfig();
299
- const component = findComponent(config, componentName);
300
- if (!component) {
301
- console.log(JSON.stringify({
302
- success: false,
303
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
304
- }));
305
- process.exit(1);
306
- }
389
+ const component = resolveComponent(config, ref);
307
390
  // Validate prop type
308
391
  const propType = options.type.toUpperCase();
309
392
  if (!PROP_TYPES.includes(propType)) {
@@ -389,6 +472,7 @@ async function addProp(componentName, options) {
389
472
  console.log(JSON.stringify({ success: false, error: `Invalid --filteredComponentIds JSON: ${options.filteredComponentIds}` }));
390
473
  process.exit(1);
391
474
  }
475
+ assertKnownComponentIds(parsedFilteredIds, config);
392
476
  }
393
477
  // Parse privateVarMap JSON for COMPONENT/COMPONENT_LIST
394
478
  let parsedPrivateVarMap;
@@ -450,16 +534,9 @@ function parseDefaultValue(value, propType) {
450
534
  return value;
451
535
  }
452
536
  }
453
- function updateProp(componentName, options) {
537
+ function updateProp(ref, options) {
454
538
  const { config, configPath } = loadConfig();
455
- const component = findComponent(config, componentName);
456
- if (!component) {
457
- console.log(JSON.stringify({
458
- success: false,
459
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
460
- }));
461
- process.exit(1);
462
- }
539
+ const component = resolveComponent(config, ref);
463
540
  const propIndex = component.props.findIndex((p) => p.name === options.prop);
464
541
  if (propIndex === -1) {
465
542
  console.log(JSON.stringify({
@@ -542,11 +619,15 @@ function updateProp(componentName, options) {
542
619
  console.log(JSON.stringify({ success: false, error: "--filteredComponentIds must be a JSON array of strings." }));
543
620
  process.exit(1);
544
621
  }
622
+ assertKnownComponentIds(parsed, config);
545
623
  prop.filteredComponentIds = parsed;
546
624
  }
547
- catch {
548
- console.log(JSON.stringify({ success: false, error: `Invalid --filteredComponentIds JSON: ${options.filteredComponentIds}` }));
549
- process.exit(1);
625
+ catch (err) {
626
+ if (err instanceof SyntaxError) {
627
+ console.log(JSON.stringify({ success: false, error: `Invalid --filteredComponentIds JSON: ${options.filteredComponentIds}` }));
628
+ process.exit(1);
629
+ }
630
+ throw err;
550
631
  }
551
632
  }
552
633
  }
@@ -589,16 +670,9 @@ function updateProp(componentName, options) {
589
670
  },
590
671
  }));
591
672
  }
592
- function removeProp(componentName, options) {
673
+ function removeProp(ref, options) {
593
674
  const { config, configPath } = loadConfig();
594
- const component = findComponent(config, componentName);
595
- if (!component) {
596
- console.log(JSON.stringify({
597
- success: false,
598
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
599
- }));
600
- process.exit(1);
601
- }
675
+ const component = resolveComponent(config, ref);
602
676
  const propIndex = component.props.findIndex((p) => p.name === options.prop);
603
677
  if (propIndex === -1) {
604
678
  console.log(JSON.stringify({
@@ -619,21 +693,30 @@ function removeProp(componentName, options) {
619
693
  remainingProps: component.props.map((p) => p.name),
620
694
  }));
621
695
  }
622
- function removeComponent(options) {
623
- const { config, configPath } = loadConfig();
624
- const pascalName = toPascalCase(options.name);
625
- const componentIndex = config.components.findIndex((c) => c.name === pascalName);
626
- if (componentIndex === -1) {
627
- console.log(JSON.stringify({
628
- success: false,
629
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
630
- }));
631
- process.exit(1);
696
+ /**
697
+ * Strip a deleted component's ID from every other component's COMPONENT/COMPONENT_LIST
698
+ * `filteredComponentIds` allowlist. Empty arrays are deleted to keep the field optional
699
+ * (mirrors the editor-side convention).
700
+ */
701
+ function pruneFilteredComponentIdRefs(config, removedId) {
702
+ for (const component of config.components) {
703
+ for (const prop of component.props || []) {
704
+ if (!prop.filteredComponentIds)
705
+ continue;
706
+ prop.filteredComponentIds = prop.filteredComponentIds.filter((id) => id !== removedId);
707
+ if (prop.filteredComponentIds.length === 0)
708
+ delete prop.filteredComponentIds;
709
+ }
632
710
  }
633
- const component = config.components[componentIndex];
711
+ }
712
+ function removeComponent(ref) {
713
+ const { config, configPath } = loadConfig();
714
+ const component = resolveComponent(config, ref);
715
+ const componentIndex = config.components.indexOf(component);
634
716
  const componentDir = path.resolve(process.cwd(), path.dirname(component.entry));
635
- // Remove component from config
717
+ // Remove component from config and strip orphaned references from remaining components
636
718
  config.components.splice(componentIndex, 1);
719
+ pruneFilteredComponentIdRefs(config, component.id);
637
720
  saveConfig(configPath, config);
638
721
  // Remove component directory
639
722
  if (fs.existsSync(componentDir)) {
@@ -643,21 +726,15 @@ function removeComponent(options) {
643
726
  updateBarrelExport(process.cwd(), getComponentNames(config));
644
727
  console.log(JSON.stringify({
645
728
  success: true,
646
- removedComponent: pascalName,
729
+ removedComponentId: component.id,
730
+ removedComponentName: component.name,
647
731
  removedDirectory: path.relative(process.cwd(), componentDir),
648
- remainingComponents: getComponentNames(config),
732
+ remainingComponents: config.components.map((c) => ({ id: c.id, name: c.name })),
649
733
  }));
650
734
  }
651
- function addPropGroup(componentName, options) {
735
+ function addPropGroup(ref, options) {
652
736
  const { config, configPath } = loadConfig();
653
- const component = findComponent(config, componentName);
654
- if (!component) {
655
- console.log(JSON.stringify({
656
- success: false,
657
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
658
- }));
659
- process.exit(1);
660
- }
737
+ const component = resolveComponent(config, ref);
661
738
  if (!component.propGroups)
662
739
  component.propGroups = [];
663
740
  // Check uniqueness
@@ -708,16 +785,9 @@ function addPropGroup(componentName, options) {
708
785
  },
709
786
  }));
710
787
  }
711
- function updatePropGroup(componentName, options) {
788
+ function updatePropGroup(ref, options) {
712
789
  const { config, configPath } = loadConfig();
713
- const component = findComponent(config, componentName);
714
- if (!component) {
715
- console.log(JSON.stringify({
716
- success: false,
717
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
718
- }));
719
- process.exit(1);
720
- }
790
+ const component = resolveComponent(config, ref);
721
791
  if (!component.propGroups || component.propGroups.length === 0) {
722
792
  console.log(JSON.stringify({
723
793
  success: false,
@@ -747,16 +817,9 @@ function updatePropGroup(componentName, options) {
747
817
  },
748
818
  }));
749
819
  }
750
- function removePropGroup(componentName, options) {
820
+ function removePropGroup(ref, options) {
751
821
  const { config, configPath } = loadConfig();
752
- const component = findComponent(config, componentName);
753
- if (!component) {
754
- console.log(JSON.stringify({
755
- success: false,
756
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
757
- }));
758
- process.exit(1);
759
- }
822
+ const component = resolveComponent(config, ref);
760
823
  if (!component.propGroups || component.propGroups.length === 0) {
761
824
  console.log(JSON.stringify({
762
825
  success: false,
@@ -792,16 +855,9 @@ function removePropGroup(componentName, options) {
792
855
  remainingPropGroups: component.propGroups.map(g => g.id),
793
856
  }));
794
857
  }
795
- function movePropGroup(componentName, options) {
858
+ function movePropGroup(ref, options) {
796
859
  const { config, configPath } = loadConfig();
797
- const component = findComponent(config, componentName);
798
- if (!component) {
799
- console.log(JSON.stringify({
800
- success: false,
801
- error: `Component "${componentName}" not found. Available: ${getComponentNames(config).join(", ")}`,
802
- }));
803
- process.exit(1);
804
- }
860
+ const component = resolveComponent(config, ref);
805
861
  if (!component.propGroups || component.propGroups.length === 0) {
806
862
  console.log(JSON.stringify({
807
863
  success: false,
@@ -824,16 +880,9 @@ function movePropGroup(componentName, options) {
824
880
  parentGroupId: options.parent || null,
825
881
  }));
826
882
  }
827
- function updateComponent(options) {
883
+ function updateComponent(ref, options) {
828
884
  const { config, configPath } = loadConfig();
829
- const component = findComponent(config, options.name);
830
- if (!component) {
831
- console.log(JSON.stringify({
832
- success: false,
833
- error: `Component "${options.name}" not found. Available: ${getComponentNames(config).join(", ")}`,
834
- }));
835
- process.exit(1);
836
- }
885
+ const component = resolveComponent(config, ref);
837
886
  if (options.isHeader !== undefined) {
838
887
  if (options.isHeader) {
839
888
  component.isHeader = true;
@@ -1086,14 +1135,16 @@ export function createConfigCommand() {
1086
1135
  .option("--type <type>", "Component type: section or component", "component")
1087
1136
  .option("--isHeader", "Mark this section as the store header (only for type: section)")
1088
1137
  .option("--isFooter", "Mark this section as the store footer (only for type: section)")
1089
- .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.")
1138
+ .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. " +
1139
+ "Example: '[{\"name\":\"title\",\"type\":\"TEXT\",\"required\":true,\"defaultValue\":\"Hello\",\"groupId\":\"basic\"}]'")
1090
1140
  .action((options) => {
1091
1141
  addComponent(options.name, options);
1092
1142
  });
1093
1143
  config
1094
1144
  .command("add-prop")
1095
1145
  .description("Add a prop to a component")
1096
- .requiredOption("--component <name>", "Component name")
1146
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1147
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1097
1148
  .requiredOption("--name <name>", "Prop name (camelCase)")
1098
1149
  .requiredOption("--displayName <displayName>", "Display name in editor")
1099
1150
  .requiredOption("--type <type>", `Prop type: ${PROP_TYPES.join(", ")}`)
@@ -1106,12 +1157,13 @@ export function createConfigCommand() {
1106
1157
  .option("--filteredComponentIds <json>", "JSON array of component IDs to restrict selection (for COMPONENT/COMPONENT_LIST)")
1107
1158
  .option("--privateVarMap <json>", 'JSON object mapping variable keys to {id, typeId} (for COMPONENT/COMPONENT_LIST)')
1108
1159
  .action((options) => {
1109
- addProp(options.component, options);
1160
+ addProp({ id: options.componentId, name: options.component }, options);
1110
1161
  });
1111
1162
  config
1112
1163
  .command("update-prop")
1113
1164
  .description("Update a prop on a component")
1114
- .requiredOption("--component <name>", "Component name")
1165
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1166
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1115
1167
  .requiredOption("--prop <propName>", "Prop name to update")
1116
1168
  .option("--displayName <displayName>", "New display name")
1117
1169
  .option("--type <type>", "New prop type")
@@ -1124,76 +1176,89 @@ export function createConfigCommand() {
1124
1176
  .option("--filteredComponentIds <json>", "JSON array of component IDs (use 'none' to clear)")
1125
1177
  .option("--privateVarMap <json>", "JSON object mapping variable keys to {id, typeId} (use 'none' to clear)")
1126
1178
  .action((options) => {
1127
- updateProp(options.component, options);
1179
+ updateProp({ id: options.componentId, name: options.component }, options);
1128
1180
  });
1129
1181
  config
1130
1182
  .command("remove-prop")
1131
1183
  .description("Remove a prop from a component")
1132
- .requiredOption("--component <name>", "Component name")
1184
+ .option("--component-id <id>", "Component id (preferred; from `config list-components`)")
1185
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1133
1186
  .requiredOption("--prop <propName>", "Prop name to remove")
1134
1187
  .action((options) => {
1135
- removeProp(options.component, options);
1188
+ removeProp({ id: options.componentId, name: options.component }, options);
1136
1189
  });
1137
1190
  config
1138
1191
  .command("update-component")
1139
- .description("Update a component's metadata (isHeader, isFooter)")
1140
- .requiredOption("--name <name>", "Component name")
1192
+ .description("Update a component's metadata (isHeader, isFooter). Identify by --id (preferred) or exact --name.")
1193
+ .option("--id <id>", "Component id (preferred; from `config list-components`)")
1194
+ .option("--name <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1141
1195
  .option("--isHeader", "Mark as store header")
1142
1196
  .option("--no-isHeader", "Unmark as store header")
1143
1197
  .option("--isFooter", "Mark as store footer")
1144
1198
  .option("--no-isFooter", "Unmark as store footer")
1145
1199
  .action((options) => {
1146
- updateComponent(options);
1200
+ updateComponent({ id: options.id, name: options.name }, options);
1147
1201
  });
1148
1202
  config
1149
1203
  .command("remove-component")
1150
- .description("Remove a component from the project (deletes files)")
1151
- .requiredOption("--name <name>", "Component name to remove")
1204
+ .description("Remove a component from the project (deletes files). Identify by --id (preferred) or exact --name.")
1205
+ .option("--id <id>", "Component id (preferred; from `config list-components`)")
1206
+ .option("--name <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1152
1207
  .action((options) => {
1153
- removeComponent(options);
1208
+ removeComponent({ id: options.id, name: options.name });
1154
1209
  });
1155
1210
  config
1156
1211
  .command("add-prop-group")
1157
1212
  .description("Add a prop group to a component")
1158
- .requiredOption("--component <name>", "Component name")
1213
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1214
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1159
1215
  .requiredOption("--id <id>", "Group ID (unique within component, kebab-case)")
1160
1216
  .requiredOption("--name <name>", "Display name in editor")
1161
1217
  .option("--description <description>", "Group description")
1162
1218
  .option("--parent <parentId>", "Parent group ID (for nesting, max 1 level)")
1163
1219
  .action((options) => {
1164
- addPropGroup(options.component, options);
1220
+ addPropGroup({ id: options.componentId, name: options.component }, options);
1165
1221
  });
1166
1222
  config
1167
1223
  .command("update-prop-group")
1168
1224
  .description("Update a prop group on a component")
1169
- .requiredOption("--component <name>", "Component name")
1225
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1226
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1170
1227
  .requiredOption("--id <id>", "Group ID to update")
1171
1228
  .option("--name <name>", "New display name")
1172
1229
  .option("--description <description>", "New description")
1173
1230
  .action((options) => {
1174
- updatePropGroup(options.component, options);
1231
+ updatePropGroup({ id: options.componentId, name: options.component }, options);
1175
1232
  });
1176
1233
  config
1177
1234
  .command("remove-prop-group")
1178
1235
  .description("Remove a prop group from a component (props become ungrouped)")
1179
- .requiredOption("--component <name>", "Component name")
1236
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1237
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1180
1238
  .requiredOption("--id <id>", "Group ID to remove")
1181
1239
  .action((options) => {
1182
- removePropGroup(options.component, options);
1240
+ removePropGroup({ id: options.componentId, name: options.component }, options);
1183
1241
  });
1184
1242
  config
1185
1243
  .command("move-prop-group")
1186
1244
  .description("Move a prop group to a different parent or position (for drag-and-drop reordering)")
1187
- .requiredOption("--component <name>", "Component name")
1245
+ .option("--component-id <componentId>", "Component id (preferred; from `config list-components`)")
1246
+ .option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
1188
1247
  .requiredOption("--id <id>", "Group ID to move")
1189
1248
  .option("--parent <parentId>", "Target parent group ID (omit to move to root)")
1190
1249
  .option("--index <index>", "Zero-based insertion index within the target parent (appends when omitted)", v => parseInt(v, 10))
1191
1250
  .action((options) => {
1192
- movePropGroup(options.component, options);
1251
+ movePropGroup({ id: options.componentId, name: options.component }, options);
1252
+ });
1253
+ config
1254
+ .command("list-components")
1255
+ .description("List all components and their props (with canonical ids for use with --id flags)")
1256
+ .action(() => {
1257
+ listComponents();
1193
1258
  });
1194
1259
  config
1195
1260
  .command("list")
1196
- .description("List all components and their props")
1261
+ .description("Alias for `list-components` (kept for backwards compatibility)")
1197
1262
  .action(() => {
1198
1263
  listComponents();
1199
1264
  });