@ikas/component-cli 1.4.0-beta.10 → 1.4.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add-sections-to-page.d.ts +3 -0
- package/dist/commands/add-sections-to-page.d.ts.map +1 -0
- package/dist/commands/add-sections-to-page.js +39 -0
- package/dist/commands/add-sections-to-page.js.map +1 -0
- package/dist/commands/add-to-page.d.ts +3 -0
- package/dist/commands/add-to-page.d.ts.map +1 -0
- package/dist/commands/add-to-page.js +41 -0
- package/dist/commands/add-to-page.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +10 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +5 -165
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +330 -44
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create-design-tokens.d.ts +24 -0
- package/dist/commands/create-design-tokens.d.ts.map +1 -0
- package/dist/commands/create-design-tokens.js +317 -0
- package/dist/commands/create-design-tokens.js.map +1 -0
- package/dist/commands/create-global-variable.d.ts +3 -0
- package/dist/commands/create-global-variable.d.ts.map +1 -0
- package/dist/commands/create-global-variable.js +53 -0
- package/dist/commands/create-global-variable.js.map +1 -0
- package/dist/commands/create-page.d.ts +3 -0
- package/dist/commands/create-page.d.ts.map +1 -0
- package/dist/commands/create-page.js +31 -0
- package/dist/commands/create-page.js.map +1 -0
- package/dist/commands/delete-theme-globals.d.ts +4 -0
- package/dist/commands/delete-theme-globals.d.ts.map +1 -0
- package/dist/commands/delete-theme-globals.js +48 -0
- package/dist/commands/delete-theme-globals.js.map +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +88 -13
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/get-component-props.d.ts +3 -0
- package/dist/commands/get-component-props.d.ts.map +1 -0
- package/dist/commands/get-component-props.js +32 -0
- package/dist/commands/get-component-props.js.map +1 -0
- package/dist/commands/get-page-by-type.d.ts +3 -0
- package/dist/commands/get-page-by-type.d.ts.map +1 -0
- package/dist/commands/get-page-by-type.js +25 -0
- package/dist/commands/get-page-by-type.js.map +1 -0
- package/dist/commands/get-section-values.d.ts +3 -0
- package/dist/commands/get-section-values.d.ts.map +1 -0
- package/dist/commands/get-section-values.js +39 -0
- package/dist/commands/get-section-values.js.map +1 -0
- package/dist/commands/import.d.ts +3 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +25 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/list-entities.d.ts +3 -0
- package/dist/commands/list-entities.d.ts.map +1 -0
- package/dist/commands/list-entities.js +32 -0
- package/dist/commands/list-entities.js.map +1 -0
- package/dist/commands/list-imported.d.ts +3 -0
- package/dist/commands/list-imported.d.ts.map +1 -0
- package/dist/commands/list-imported.js +25 -0
- package/dist/commands/list-imported.js.map +1 -0
- package/dist/commands/list-page-sections.d.ts +3 -0
- package/dist/commands/list-page-sections.d.ts.map +1 -0
- package/dist/commands/list-page-sections.js +25 -0
- package/dist/commands/list-page-sections.js.map +1 -0
- package/dist/commands/list-pages.d.ts +3 -0
- package/dist/commands/list-pages.d.ts.map +1 -0
- package/dist/commands/list-pages.js +21 -0
- package/dist/commands/list-pages.js.map +1 -0
- package/dist/commands/list-theme-globals.d.ts +3 -0
- package/dist/commands/list-theme-globals.d.ts.map +1 -0
- package/dist/commands/list-theme-globals.js +22 -0
- package/dist/commands/list-theme-globals.js.map +1 -0
- package/dist/commands/publish-theme.d.ts +3 -0
- package/dist/commands/publish-theme.d.ts.map +1 -0
- package/dist/commands/publish-theme.js +29 -0
- package/dist/commands/publish-theme.js.map +1 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +23 -2
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/search-products.d.ts +3 -0
- package/dist/commands/search-products.d.ts.map +1 -0
- package/dist/commands/search-products.js +40 -0
- package/dist/commands/search-products.js.map +1 -0
- package/dist/commands/update-global-variable.d.ts +3 -0
- package/dist/commands/update-global-variable.d.ts.map +1 -0
- package/dist/commands/update-global-variable.js +47 -0
- package/dist/commands/update-global-variable.js.map +1 -0
- package/dist/commands/update-page-sections.d.ts +3 -0
- package/dist/commands/update-page-sections.d.ts.map +1 -0
- package/dist/commands/update-page-sections.js +39 -0
- package/dist/commands/update-page-sections.js.map +1 -0
- package/dist/commands/update-section-prop.d.ts +3 -0
- package/dist/commands/update-section-prop.d.ts.map +1 -0
- package/dist/commands/update-section-prop.js +59 -0
- package/dist/commands/update-section-prop.js.map +1 -0
- package/dist/commands/upload-image.d.ts +3 -0
- package/dist/commands/upload-image.d.ts.map +1 -0
- package/dist/commands/upload-image.js +38 -0
- package/dist/commands/upload-image.js.map +1 -0
- package/dist/commands/upload-images.d.ts +3 -0
- package/dist/commands/upload-images.d.ts.map +1 -0
- package/dist/commands/upload-images.js +48 -0
- package/dist/commands/upload-images.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/compile.d.ts +4 -1
- package/dist/utils/compile.d.ts.map +1 -1
- package/dist/utils/compile.js +630 -102
- package/dist/utils/compile.js.map +1 -1
- package/dist/utils/component-helpers.d.ts +51 -3
- package/dist/utils/component-helpers.d.ts.map +1 -1
- package/dist/utils/component-helpers.js +139 -4
- package/dist/utils/component-helpers.js.map +1 -1
- package/dist/utils/editor-action-client.d.ts +28 -0
- package/dist/utils/editor-action-client.d.ts.map +1 -0
- package/dist/utils/editor-action-client.js +116 -0
- package/dist/utils/editor-action-client.js.map +1 -0
- package/dist/utils/forbid-deep-imports.d.ts +3 -0
- package/dist/utils/forbid-deep-imports.d.ts.map +1 -0
- package/dist/utils/forbid-deep-imports.js +54 -0
- package/dist/utils/forbid-deep-imports.js.map +1 -0
- package/dist/utils/forbid-external-packages.d.ts +16 -0
- package/dist/utils/forbid-external-packages.d.ts.map +1 -0
- package/dist/utils/forbid-external-packages.js +89 -0
- package/dist/utils/forbid-external-packages.js.map +1 -0
- package/dist/utils/load-image.d.ts +16 -0
- package/dist/utils/load-image.d.ts.map +1 -0
- package/dist/utils/load-image.js +50 -0
- package/dist/utils/load-image.js.map +1 -0
- package/dist/utils/websocket-server.d.ts +40 -2
- package/dist/utils/websocket-server.d.ts.map +1 -1
- package/dist/utils/websocket-server.js +59 -1
- package/dist/utils/websocket-server.js.map +1 -1
- package/package.json +1 -1
- package/templates/add/section.css +11 -0
- package/dist/commands/create.d.ts +0 -9
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -9
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/proxy.d.ts +0 -39
- package/dist/commands/proxy.d.ts.map +0 -1
- package/dist/commands/proxy.js +0 -212
- package/dist/commands/proxy.js.map +0 -1
package/dist/commands/config.js
CHANGED
|
@@ -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, validateFilteredComponentIds, } from "../utils/component-helpers.js";
|
|
4
|
+
import { PROP_TYPES, toPascalCase, generateTypesFile, generateGlobalTypesFile, collectUsedEnumIds, generateComponentFile, generateStylesFile, generateProjectId, isValidProjectId, 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)) {
|
|
@@ -31,7 +31,10 @@ function resolveComponent(config, ref) {
|
|
|
31
31
|
success: false,
|
|
32
32
|
error: "Component reference required: pass --id (preferred) or --name (exact PascalCase). " +
|
|
33
33
|
"Run `ikas-component config list-components` to see canonical ids.",
|
|
34
|
-
validComponents: config.components.map((c) => ({
|
|
34
|
+
validComponents: config.components.map((c) => ({
|
|
35
|
+
id: c.id,
|
|
36
|
+
name: c.name,
|
|
37
|
+
})),
|
|
35
38
|
}));
|
|
36
39
|
process.exit(1);
|
|
37
40
|
}
|
|
@@ -52,7 +55,10 @@ function resolveComponent(config, ref) {
|
|
|
52
55
|
? `Component not found: id="${id}".`
|
|
53
56
|
: `Component not found: name="${name}". Names are matched exactly (PascalCase, no normalization).`,
|
|
54
57
|
lookup: id ? { id } : { name },
|
|
55
|
-
validComponents: config.components.map((c) => ({
|
|
58
|
+
validComponents: config.components.map((c) => ({
|
|
59
|
+
id: c.id,
|
|
60
|
+
name: c.name,
|
|
61
|
+
})),
|
|
56
62
|
hint: "Use --id (preferred) or exact PascalCase --name. " +
|
|
57
63
|
"Run `ikas-component config list-components` to see canonical ids.",
|
|
58
64
|
}));
|
|
@@ -144,6 +150,19 @@ function camelCaseToDisplayName(name) {
|
|
|
144
150
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
145
151
|
.replace(/^./, (c) => c.toUpperCase());
|
|
146
152
|
}
|
|
153
|
+
const ALLOWED_PROP_FIELDS = new Set([
|
|
154
|
+
"name",
|
|
155
|
+
"displayName",
|
|
156
|
+
"type",
|
|
157
|
+
"required",
|
|
158
|
+
"description",
|
|
159
|
+
"defaultValue",
|
|
160
|
+
"groupId",
|
|
161
|
+
"typeId",
|
|
162
|
+
"enumTypeId",
|
|
163
|
+
"filteredComponentIds",
|
|
164
|
+
"privateVarMap",
|
|
165
|
+
]);
|
|
147
166
|
/**
|
|
148
167
|
* Parse and validate the --props JSON flag for add-component.
|
|
149
168
|
* Returns validated ComponentProp[] or exits with error.
|
|
@@ -171,6 +190,16 @@ async function parsePropsFlag(propsJson, config, configPath) {
|
|
|
171
190
|
const props = [];
|
|
172
191
|
for (const raw of rawProps) {
|
|
173
192
|
const r = raw;
|
|
193
|
+
const unknownFields = Object.keys(r).filter((k) => !ALLOWED_PROP_FIELDS.has(k));
|
|
194
|
+
if (unknownFields.length > 0) {
|
|
195
|
+
console.log(JSON.stringify({
|
|
196
|
+
success: false,
|
|
197
|
+
error: `Unknown field(s) in --props entry: ${unknownFields.join(", ")}. ` +
|
|
198
|
+
`Allowed: ${[...ALLOWED_PROP_FIELDS].join(", ")}. ` +
|
|
199
|
+
`Note: use "groupId" (not "group") and "defaultValue" (not "default") in --props JSON.`,
|
|
200
|
+
}));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
174
203
|
if (!r.name || typeof r.name !== "string") {
|
|
175
204
|
console.log(JSON.stringify({
|
|
176
205
|
success: false,
|
|
@@ -211,6 +240,24 @@ async function parsePropsFlag(propsJson, config, configPath) {
|
|
|
211
240
|
}));
|
|
212
241
|
process.exit(1);
|
|
213
242
|
}
|
|
243
|
+
// Validate LINK / LIST_OF_LINK defaultValue shape (reject JSON strings and legacy { href })
|
|
244
|
+
if ((propType === "LINK" || propType === "LIST_OF_LINK") &&
|
|
245
|
+
r.defaultValue !== undefined) {
|
|
246
|
+
const linkError = validateLinkDefaultValue(propType, r.defaultValue, r.name);
|
|
247
|
+
if (linkError) {
|
|
248
|
+
console.log(JSON.stringify({ success: false, error: linkError }));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Validate SVG / SVG_LIST defaultValue (reject malformed/oversized/unsafe SVG)
|
|
253
|
+
if ((propType === "SVG" || propType === "SVG_LIST") &&
|
|
254
|
+
r.defaultValue !== undefined) {
|
|
255
|
+
const svgError = validateSvgDefaultValue(propType, r.defaultValue, r.name);
|
|
256
|
+
if (svgError) {
|
|
257
|
+
console.log(JSON.stringify({ success: false, error: svgError }));
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
214
261
|
// Auto-generate displayName if omitted
|
|
215
262
|
const displayName = typeof r.displayName === "string" && r.displayName
|
|
216
263
|
? r.displayName
|
|
@@ -235,7 +282,10 @@ async function parsePropsFlag(propsJson, config, configPath) {
|
|
|
235
282
|
process.exit(1);
|
|
236
283
|
}
|
|
237
284
|
// Validate enum type exists (checks config then live dev server)
|
|
238
|
-
if (config &&
|
|
285
|
+
if (config &&
|
|
286
|
+
configPath &&
|
|
287
|
+
typeof r.enumTypeId === "string" &&
|
|
288
|
+
!r.enumTypeId.startsWith("@ikas/")) {
|
|
239
289
|
const resolved = await resolveEnumType(r.enumTypeId, config, configPath);
|
|
240
290
|
if (!resolved) {
|
|
241
291
|
console.log(JSON.stringify({
|
|
@@ -254,9 +304,7 @@ async function parsePropsFlag(propsJson, config, configPath) {
|
|
|
254
304
|
...(typeof r.description === "string" && r.description
|
|
255
305
|
? { description: r.description }
|
|
256
306
|
: {}),
|
|
257
|
-
...(r.defaultValue !== undefined
|
|
258
|
-
? { defaultValue: r.defaultValue }
|
|
259
|
-
: {}),
|
|
307
|
+
...(r.defaultValue !== undefined ? { defaultValue: r.defaultValue } : {}),
|
|
260
308
|
...(typeof r.groupId === "string" && r.groupId
|
|
261
309
|
? { groupId: r.groupId }
|
|
262
310
|
: {}),
|
|
@@ -266,11 +314,19 @@ async function parsePropsFlag(propsJson, config, configPath) {
|
|
|
266
314
|
...(propType === "ENUM" && typeof r.enumTypeId === "string"
|
|
267
315
|
? { enumTypeId: r.enumTypeId }
|
|
268
316
|
: {}),
|
|
269
|
-
...((propType === "COMPONENT" || propType === "COMPONENT_LIST") &&
|
|
270
|
-
|
|
317
|
+
...((propType === "COMPONENT" || propType === "COMPONENT_LIST") &&
|
|
318
|
+
r.filteredComponentIds &&
|
|
319
|
+
Array.isArray(r.filteredComponentIds)
|
|
320
|
+
? {
|
|
321
|
+
filteredComponentIds: assertKnownComponentIds(r.filteredComponentIds, config),
|
|
322
|
+
}
|
|
271
323
|
: {}),
|
|
272
|
-
...((propType === "COMPONENT" || propType === "COMPONENT_LIST") &&
|
|
273
|
-
|
|
324
|
+
...((propType === "COMPONENT" || propType === "COMPONENT_LIST") &&
|
|
325
|
+
r.privateVarMap &&
|
|
326
|
+
typeof r.privateVarMap === "object"
|
|
327
|
+
? {
|
|
328
|
+
privateVarMap: r.privateVarMap,
|
|
329
|
+
}
|
|
274
330
|
: {}),
|
|
275
331
|
};
|
|
276
332
|
props.push(prop);
|
|
@@ -302,6 +358,18 @@ async function addComponent(name, options) {
|
|
|
302
358
|
if (!config.projectId) {
|
|
303
359
|
config.projectId = generateProjectId();
|
|
304
360
|
}
|
|
361
|
+
else if (!isValidProjectId(config.projectId)) {
|
|
362
|
+
// A malformed projectId (hand-edited config) breaks the editor's republish
|
|
363
|
+
// stability: published/local id prefixes stop matching and every publish
|
|
364
|
+
// INSERTs duplicate design elements. Fail fast instead of corrupting data.
|
|
365
|
+
console.log(JSON.stringify({
|
|
366
|
+
success: false,
|
|
367
|
+
error: `Invalid projectId "${config.projectId}" in ikas.config.json. ` +
|
|
368
|
+
"projectId must be at least 6 alphanumeric characters (no '-' or '_'). " +
|
|
369
|
+
"Restore the original generated value — do not hand-edit it.",
|
|
370
|
+
}));
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
305
373
|
// Parse --props (and validate filteredComponentIds) BEFORE any filesystem side-effects
|
|
306
374
|
// so a bad payload doesn't leave an empty component directory behind.
|
|
307
375
|
const props = options.props
|
|
@@ -407,7 +475,9 @@ async function addProp(ref, options) {
|
|
|
407
475
|
}
|
|
408
476
|
// Validate that enumTypeId references an existing enum type
|
|
409
477
|
// (checks config.customTypes first, then falls back to the live dev server)
|
|
410
|
-
if (propType === "ENUM" &&
|
|
478
|
+
if (propType === "ENUM" &&
|
|
479
|
+
options.enumTypeId &&
|
|
480
|
+
!options.enumTypeId.startsWith("@ikas/")) {
|
|
411
481
|
const resolved = await resolveEnumType(options.enumTypeId, config, configPath);
|
|
412
482
|
if (!resolved) {
|
|
413
483
|
console.log(JSON.stringify({
|
|
@@ -437,32 +507,66 @@ async function addProp(ref, options) {
|
|
|
437
507
|
}
|
|
438
508
|
// Parse filteredComponentIds JSON for COMPONENT/COMPONENT_LIST
|
|
439
509
|
let parsedFilteredIds;
|
|
440
|
-
if (options.filteredComponentIds &&
|
|
510
|
+
if (options.filteredComponentIds &&
|
|
511
|
+
(propType === "COMPONENT" || propType === "COMPONENT_LIST")) {
|
|
441
512
|
try {
|
|
442
513
|
parsedFilteredIds = JSON.parse(options.filteredComponentIds);
|
|
443
514
|
if (!Array.isArray(parsedFilteredIds)) {
|
|
444
|
-
console.log(JSON.stringify({
|
|
515
|
+
console.log(JSON.stringify({
|
|
516
|
+
success: false,
|
|
517
|
+
error: "--filteredComponentIds must be a JSON array of strings.",
|
|
518
|
+
}));
|
|
445
519
|
process.exit(1);
|
|
446
520
|
}
|
|
447
521
|
}
|
|
448
522
|
catch {
|
|
449
|
-
console.log(JSON.stringify({
|
|
523
|
+
console.log(JSON.stringify({
|
|
524
|
+
success: false,
|
|
525
|
+
error: `Invalid --filteredComponentIds JSON: ${options.filteredComponentIds}`,
|
|
526
|
+
}));
|
|
450
527
|
process.exit(1);
|
|
451
528
|
}
|
|
452
529
|
assertKnownComponentIds(parsedFilteredIds, config);
|
|
453
530
|
}
|
|
454
531
|
// Parse privateVarMap JSON for COMPONENT/COMPONENT_LIST
|
|
455
532
|
let parsedPrivateVarMap;
|
|
456
|
-
if (options.privateVarMap &&
|
|
533
|
+
if (options.privateVarMap &&
|
|
534
|
+
(propType === "COMPONENT" || propType === "COMPONENT_LIST")) {
|
|
457
535
|
try {
|
|
458
536
|
parsedPrivateVarMap = JSON.parse(options.privateVarMap);
|
|
459
|
-
if (typeof parsedPrivateVarMap !== "object" ||
|
|
460
|
-
|
|
537
|
+
if (typeof parsedPrivateVarMap !== "object" ||
|
|
538
|
+
Array.isArray(parsedPrivateVarMap)) {
|
|
539
|
+
console.log(JSON.stringify({
|
|
540
|
+
success: false,
|
|
541
|
+
error: "--privateVarMap must be a JSON object mapping keys to {id, typeId}.",
|
|
542
|
+
}));
|
|
461
543
|
process.exit(1);
|
|
462
544
|
}
|
|
463
545
|
}
|
|
464
546
|
catch {
|
|
465
|
-
console.log(JSON.stringify({
|
|
547
|
+
console.log(JSON.stringify({
|
|
548
|
+
success: false,
|
|
549
|
+
error: `Invalid --privateVarMap JSON: ${options.privateVarMap}`,
|
|
550
|
+
}));
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const parsedDefaultValue = options.defaultValue !== undefined
|
|
555
|
+
? parseDefaultValue(options.defaultValue, propType)
|
|
556
|
+
: undefined;
|
|
557
|
+
if ((propType === "LINK" || propType === "LIST_OF_LINK") &&
|
|
558
|
+
parsedDefaultValue !== undefined) {
|
|
559
|
+
const linkError = validateLinkDefaultValue(propType, parsedDefaultValue, options.name);
|
|
560
|
+
if (linkError) {
|
|
561
|
+
console.log(JSON.stringify({ success: false, error: linkError }));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if ((propType === "SVG" || propType === "SVG_LIST") &&
|
|
566
|
+
parsedDefaultValue !== undefined) {
|
|
567
|
+
const svgError = validateSvgDefaultValue(propType, parsedDefaultValue, options.name);
|
|
568
|
+
if (svgError) {
|
|
569
|
+
console.log(JSON.stringify({ success: false, error: svgError }));
|
|
466
570
|
process.exit(1);
|
|
467
571
|
}
|
|
468
572
|
}
|
|
@@ -472,8 +576,8 @@ async function addProp(ref, options) {
|
|
|
472
576
|
type: propType,
|
|
473
577
|
required: options.required ?? false,
|
|
474
578
|
...(options.description ? { description: options.description } : {}),
|
|
475
|
-
...(
|
|
476
|
-
? { defaultValue:
|
|
579
|
+
...(parsedDefaultValue !== undefined
|
|
580
|
+
? { defaultValue: parsedDefaultValue }
|
|
477
581
|
: {}),
|
|
478
582
|
...(options.group ? { groupId: options.group } : {}),
|
|
479
583
|
...(options.typeId ? { typeId: options.typeId } : {}),
|
|
@@ -496,8 +600,12 @@ async function addProp(ref, options) {
|
|
|
496
600
|
required: newProp.required,
|
|
497
601
|
...(newProp.groupId ? { groupId: newProp.groupId } : {}),
|
|
498
602
|
...(newProp.enumTypeId ? { enumTypeId: newProp.enumTypeId } : {}),
|
|
499
|
-
...(newProp.filteredComponentIds
|
|
500
|
-
|
|
603
|
+
...(newProp.filteredComponentIds
|
|
604
|
+
? { filteredComponentIds: newProp.filteredComponentIds }
|
|
605
|
+
: {}),
|
|
606
|
+
...(newProp.privateVarMap
|
|
607
|
+
? { privateVarMap: newProp.privateVarMap }
|
|
608
|
+
: {}),
|
|
501
609
|
},
|
|
502
610
|
}));
|
|
503
611
|
}
|
|
@@ -507,10 +615,147 @@ function parseDefaultValue(value, propType) {
|
|
|
507
615
|
return Number(value);
|
|
508
616
|
case "BOOLEAN":
|
|
509
617
|
return value === "true";
|
|
618
|
+
case "LINK":
|
|
619
|
+
case "LIST_OF_LINK":
|
|
620
|
+
// The --default-value flag arrives as a string; LINK values must be stored as a
|
|
621
|
+
// typed object. Parse it here; if it is not valid JSON, return the raw string so
|
|
622
|
+
// validateLinkDefaultValue can report a precise error.
|
|
623
|
+
try {
|
|
624
|
+
return JSON.parse(value);
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
return value;
|
|
628
|
+
}
|
|
629
|
+
case "SVG_LIST":
|
|
630
|
+
// Same reasoning as LINK_LIST: SVG_LIST must be stored as an array of SVG strings.
|
|
631
|
+
// Parse it here; if it is not valid JSON, return the raw string so
|
|
632
|
+
// validateSvgDefaultValue can report a precise "must be an array" error.
|
|
633
|
+
try {
|
|
634
|
+
return JSON.parse(value);
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
return value;
|
|
638
|
+
}
|
|
510
639
|
default:
|
|
511
640
|
return value;
|
|
512
641
|
}
|
|
513
642
|
}
|
|
643
|
+
const VALID_LINK_TYPES = ["PAGE", "EXTERNAL", "FILE"];
|
|
644
|
+
const LINK_SHAPE_HELP = 'A link must be an object: { "linkType": "EXTERNAL", "label": "Text", "externalLink": "https://...", "subLinks": [] } ' +
|
|
645
|
+
'for external links, or { "linkType": "PAGE", "label": "Text", "pageType": "INDEX", "subLinks": [] } for store pages. ' +
|
|
646
|
+
"Do NOT pass a JSON string or a { label, href } shape.";
|
|
647
|
+
// Returns the index of the first array element that fails `fn`, as an error string, or null.
|
|
648
|
+
const firstError = (arr, fn) => arr.reduce((acc, item, index) => acc ?? fn(item, index), null);
|
|
649
|
+
const validateSingleLink = (link, propName, path) => {
|
|
650
|
+
const where = `defaultValue for prop "${propName}"${path}`;
|
|
651
|
+
if (typeof link === "string") {
|
|
652
|
+
return `${where} is a JSON string but must be an object. ${LINK_SHAPE_HELP}`;
|
|
653
|
+
}
|
|
654
|
+
if (!link || typeof link !== "object" || Array.isArray(link)) {
|
|
655
|
+
return `${where} must be a link object. ${LINK_SHAPE_HELP}`;
|
|
656
|
+
}
|
|
657
|
+
const l = link;
|
|
658
|
+
if ("href" in l && !("linkType" in l)) {
|
|
659
|
+
return `${where} uses a legacy { href } shape without "linkType". Use "linkType" + "externalLink"/"pageType" instead. ${LINK_SHAPE_HELP}`;
|
|
660
|
+
}
|
|
661
|
+
if (typeof l.linkType !== "string" ||
|
|
662
|
+
!VALID_LINK_TYPES.includes(l.linkType)) {
|
|
663
|
+
return `${where} must have a valid "linkType" (one of ${VALID_LINK_TYPES.join(", ")}). ${LINK_SHAPE_HELP}`;
|
|
664
|
+
}
|
|
665
|
+
if (l.subLinks !== undefined && !Array.isArray(l.subLinks)) {
|
|
666
|
+
return `${where}: "subLinks" must be an array (use [] when there are none).`;
|
|
667
|
+
}
|
|
668
|
+
if (Array.isArray(l.subLinks)) {
|
|
669
|
+
return firstError(l.subLinks, (sl, i) => validateSingleLink(sl, propName, `${path}.subLinks[${i}]`));
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
};
|
|
673
|
+
// Validates a LINK / LIST_OF_LINK defaultValue. Returns an error message, or null if valid.
|
|
674
|
+
// These defaults are a frequent source of bad data: agents tend to pass a JSON string or a
|
|
675
|
+
// legacy { label, href } shape. Rejecting them at authoring time keeps malformed link values
|
|
676
|
+
// out of published themes.
|
|
677
|
+
function validateLinkDefaultValue(propType, value, propName) {
|
|
678
|
+
if (value === undefined || value === null)
|
|
679
|
+
return null;
|
|
680
|
+
if (propType === "LINK") {
|
|
681
|
+
return validateSingleLink(value, propName, "");
|
|
682
|
+
}
|
|
683
|
+
// LIST_OF_LINK
|
|
684
|
+
if (typeof value === "string") {
|
|
685
|
+
return `defaultValue for prop "${propName}" is a JSON string but must be an object { "links": [...] }. ${LINK_SHAPE_HELP}`;
|
|
686
|
+
}
|
|
687
|
+
if (!value ||
|
|
688
|
+
typeof value !== "object" ||
|
|
689
|
+
Array.isArray(value) ||
|
|
690
|
+
!Array.isArray(value.links)) {
|
|
691
|
+
return `defaultValue for prop "${propName}" must be an object with a "links" array: { "links": [ <link>, ... ] }. ${LINK_SHAPE_HELP}`;
|
|
692
|
+
}
|
|
693
|
+
const links = value.links;
|
|
694
|
+
return firstError(links, (link, i) => validateSingleLink(link, propName, `.links[${i}]`));
|
|
695
|
+
}
|
|
696
|
+
const SVG_DEFAULT_HELP = "An SVG default must be a single well-formed <svg>…</svg> string ≤ 64 KB with no embedded base64 raster images. " +
|
|
697
|
+
"Tip: the editor uses each svg's `class` attribute as its display name — set class=\"my-icon\" to name it; " +
|
|
698
|
+
"SVG_LIST items with no class show as svg-1, svg-2, ….";
|
|
699
|
+
// Keep the cap and base64 pattern in sync with @ikas/editor-models validate-svg.ts (the
|
|
700
|
+
// authoritative validator). Duplicated here on purpose — see validateSvgShape below.
|
|
701
|
+
const SVG_MAX_SIZE_KB = 64;
|
|
702
|
+
const SVG_BASE64_RASTER_RE = /data:image\/[a-z0-9.+-]+;base64/i;
|
|
703
|
+
const SVG_SHAPE_RE = /^\s*(?:<\?xml[^>]*\?>\s*)?(?:<!DOCTYPE[^>]*>\s*)?<svg[\s>][\s\S]*<\/svg>\s*$/i;
|
|
704
|
+
// Count TOP-LEVEL <svg> roots without a DOM: scan svg open/close tags in order and track
|
|
705
|
+
// nesting depth. A root is any <svg> opened at depth 0. This matches the editor's DOM-based
|
|
706
|
+
// single-root check — it rejects siblings (`<svg/><svg/>`) while allowing a nested <svg>.
|
|
707
|
+
// Self-closing <svg .../> counts as a root but opens no nesting level.
|
|
708
|
+
function svgRootCount(input) {
|
|
709
|
+
const tags = input.match(/<svg\b[^>]*>|<\/svg\s*>/gi) || [];
|
|
710
|
+
return tags.reduce((acc, tag) => {
|
|
711
|
+
if (tag.startsWith("</"))
|
|
712
|
+
return { depth: Math.max(0, acc.depth - 1), roots: acc.roots };
|
|
713
|
+
const roots = acc.depth === 0 ? acc.roots + 1 : acc.roots;
|
|
714
|
+
const depth = /\/>\s*$/.test(tag) ? acc.depth : acc.depth + 1;
|
|
715
|
+
return { depth, roots };
|
|
716
|
+
}, { depth: 0, roots: 0 }).roots;
|
|
717
|
+
}
|
|
718
|
+
// Lightweight, dependency-free SVG structure check. This standalone CLI is publishable and
|
|
719
|
+
// must not depend on the private @ikas/editor-models package (nor pull a DOM/DOMPurify into a
|
|
720
|
+
// CLI), so it mirrors the RULES of the editor's validateSvg (size, base64, single <svg> root)
|
|
721
|
+
// for fast authoring feedback. The authoritative sanitize runs in the editor/upload path.
|
|
722
|
+
// Returns a rejection reason, or null if the shape is acceptable.
|
|
723
|
+
function validateSvgShape(input) {
|
|
724
|
+
if (typeof input !== "string" || input.trim() === "")
|
|
725
|
+
return "empty or not a string";
|
|
726
|
+
if (Buffer.byteLength(input, "utf8") > SVG_MAX_SIZE_KB * 1024)
|
|
727
|
+
return `exceeds ${SVG_MAX_SIZE_KB} KB`;
|
|
728
|
+
if (SVG_BASE64_RASTER_RE.test(input))
|
|
729
|
+
return "embedded base64 raster image not allowed";
|
|
730
|
+
if (!SVG_SHAPE_RE.test(input))
|
|
731
|
+
return "not a single-root <svg> document";
|
|
732
|
+
if (svgRootCount(input) !== 1)
|
|
733
|
+
return "must be exactly one <svg> root";
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
// Validates an SVG / SVG_LIST defaultValue. Returns an error message, or null if valid.
|
|
737
|
+
// Authoring an SVG default is a common mistake source for agents; rejecting here (with an
|
|
738
|
+
// instructive message) keeps malformed/oversized SVGs out of published components.
|
|
739
|
+
function validateSvgDefaultValue(propType, value, propName) {
|
|
740
|
+
if (value === undefined || value === null)
|
|
741
|
+
return null;
|
|
742
|
+
if (propType === "SVG") {
|
|
743
|
+
const reason = validateSvgShape(value);
|
|
744
|
+
return reason
|
|
745
|
+
? `defaultValue for prop "${propName}" is not a valid SVG: ${reason}. ${SVG_DEFAULT_HELP}`
|
|
746
|
+
: null;
|
|
747
|
+
}
|
|
748
|
+
// SVG_LIST
|
|
749
|
+
if (!Array.isArray(value)) {
|
|
750
|
+
return `defaultValue for prop "${propName}" must be an array of SVG strings: [ "<svg>…</svg>", ... ]. ${SVG_DEFAULT_HELP}`;
|
|
751
|
+
}
|
|
752
|
+
return firstError(value, (svg, i) => {
|
|
753
|
+
const reason = validateSvgShape(svg);
|
|
754
|
+
return reason
|
|
755
|
+
? `defaultValue[${i}] for prop "${propName}" is not a valid SVG: ${reason}. ${SVG_DEFAULT_HELP}`
|
|
756
|
+
: null;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
514
759
|
function updateProp(ref, options) {
|
|
515
760
|
const { config, configPath } = loadConfig();
|
|
516
761
|
const component = resolveComponent(config, ref);
|
|
@@ -544,7 +789,15 @@ function updateProp(ref, options) {
|
|
|
544
789
|
prop.description = options.description || undefined;
|
|
545
790
|
}
|
|
546
791
|
if (options.defaultValue !== undefined) {
|
|
547
|
-
|
|
792
|
+
const parsed = parseDefaultValue(options.defaultValue, prop.type);
|
|
793
|
+
if (prop.type === "LINK" || prop.type === "LIST_OF_LINK") {
|
|
794
|
+
const linkError = validateLinkDefaultValue(prop.type, parsed, prop.name);
|
|
795
|
+
if (linkError) {
|
|
796
|
+
console.log(JSON.stringify({ success: false, error: linkError }));
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
prop.defaultValue = parsed;
|
|
548
801
|
}
|
|
549
802
|
if (options.group !== undefined) {
|
|
550
803
|
if (options.group === "" || options.group === "none") {
|
|
@@ -586,14 +839,18 @@ function updateProp(ref, options) {
|
|
|
586
839
|
}
|
|
587
840
|
}
|
|
588
841
|
if (options.filteredComponentIds !== undefined) {
|
|
589
|
-
if (options.filteredComponentIds === "" ||
|
|
842
|
+
if (options.filteredComponentIds === "" ||
|
|
843
|
+
options.filteredComponentIds === "none") {
|
|
590
844
|
delete prop.filteredComponentIds;
|
|
591
845
|
}
|
|
592
846
|
else {
|
|
593
847
|
try {
|
|
594
848
|
const parsed = JSON.parse(options.filteredComponentIds);
|
|
595
849
|
if (!Array.isArray(parsed)) {
|
|
596
|
-
console.log(JSON.stringify({
|
|
850
|
+
console.log(JSON.stringify({
|
|
851
|
+
success: false,
|
|
852
|
+
error: "--filteredComponentIds must be a JSON array of strings.",
|
|
853
|
+
}));
|
|
597
854
|
process.exit(1);
|
|
598
855
|
}
|
|
599
856
|
assertKnownComponentIds(parsed, config);
|
|
@@ -601,7 +858,10 @@ function updateProp(ref, options) {
|
|
|
601
858
|
}
|
|
602
859
|
catch (err) {
|
|
603
860
|
if (err instanceof SyntaxError) {
|
|
604
|
-
console.log(JSON.stringify({
|
|
861
|
+
console.log(JSON.stringify({
|
|
862
|
+
success: false,
|
|
863
|
+
error: `Invalid --filteredComponentIds JSON: ${options.filteredComponentIds}`,
|
|
864
|
+
}));
|
|
605
865
|
process.exit(1);
|
|
606
866
|
}
|
|
607
867
|
throw err;
|
|
@@ -616,13 +876,19 @@ function updateProp(ref, options) {
|
|
|
616
876
|
try {
|
|
617
877
|
const parsed = JSON.parse(options.privateVarMap);
|
|
618
878
|
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
619
|
-
console.log(JSON.stringify({
|
|
879
|
+
console.log(JSON.stringify({
|
|
880
|
+
success: false,
|
|
881
|
+
error: "--privateVarMap must be a JSON object mapping keys to {id, typeId}.",
|
|
882
|
+
}));
|
|
620
883
|
process.exit(1);
|
|
621
884
|
}
|
|
622
885
|
prop.privateVarMap = parsed;
|
|
623
886
|
}
|
|
624
887
|
catch {
|
|
625
|
-
console.log(JSON.stringify({
|
|
888
|
+
console.log(JSON.stringify({
|
|
889
|
+
success: false,
|
|
890
|
+
error: `Invalid --privateVarMap JSON: ${options.privateVarMap}`,
|
|
891
|
+
}));
|
|
626
892
|
process.exit(1);
|
|
627
893
|
}
|
|
628
894
|
}
|
|
@@ -642,7 +908,9 @@ function updateProp(ref, options) {
|
|
|
642
908
|
...(prop.groupId ? { groupId: prop.groupId } : {}),
|
|
643
909
|
...(prop.typeId ? { typeId: prop.typeId } : {}),
|
|
644
910
|
...(prop.enumTypeId ? { enumTypeId: prop.enumTypeId } : {}),
|
|
645
|
-
...(prop.filteredComponentIds
|
|
911
|
+
...(prop.filteredComponentIds
|
|
912
|
+
? { filteredComponentIds: prop.filteredComponentIds }
|
|
913
|
+
: {}),
|
|
646
914
|
...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
|
|
647
915
|
},
|
|
648
916
|
}));
|
|
@@ -706,7 +974,10 @@ function removeComponent(ref) {
|
|
|
706
974
|
removedComponentId: component.id,
|
|
707
975
|
removedComponentName: component.name,
|
|
708
976
|
removedDirectory: path.relative(process.cwd(), componentDir),
|
|
709
|
-
remainingComponents: config.components.map((c) => ({
|
|
977
|
+
remainingComponents: config.components.map((c) => ({
|
|
978
|
+
id: c.id,
|
|
979
|
+
name: c.name,
|
|
980
|
+
})),
|
|
710
981
|
}));
|
|
711
982
|
}
|
|
712
983
|
function addPropGroup(ref, options) {
|
|
@@ -813,10 +1084,10 @@ function removePropGroup(ref, options) {
|
|
|
813
1084
|
process.exit(1);
|
|
814
1085
|
}
|
|
815
1086
|
if (found.parent) {
|
|
816
|
-
found.parent.children = found.parent.children.filter(c => c.id !== options.id);
|
|
1087
|
+
found.parent.children = found.parent.children.filter((c) => c.id !== options.id);
|
|
817
1088
|
}
|
|
818
1089
|
else {
|
|
819
|
-
component.propGroups = component.propGroups.filter(g => g.id !== options.id);
|
|
1090
|
+
component.propGroups = component.propGroups.filter((g) => g.id !== options.id);
|
|
820
1091
|
}
|
|
821
1092
|
// Clear groupId from props referencing the deleted group
|
|
822
1093
|
for (const prop of component.props) {
|
|
@@ -829,7 +1100,7 @@ function removePropGroup(ref, options) {
|
|
|
829
1100
|
success: true,
|
|
830
1101
|
componentName: component.name,
|
|
831
1102
|
removedPropGroup: options.id,
|
|
832
|
-
remainingPropGroups: component.propGroups.map(g => g.id),
|
|
1103
|
+
remainingPropGroups: component.propGroups.map((g) => g.id),
|
|
833
1104
|
}));
|
|
834
1105
|
}
|
|
835
1106
|
function movePropGroup(ref, options) {
|
|
@@ -846,7 +1117,10 @@ function movePropGroup(ref, options) {
|
|
|
846
1117
|
component.propGroups = movePropGroupInTree(component.propGroups, options.id, options.parent || null, options.index);
|
|
847
1118
|
}
|
|
848
1119
|
catch (e) {
|
|
849
|
-
console.log(JSON.stringify({
|
|
1120
|
+
console.log(JSON.stringify({
|
|
1121
|
+
success: false,
|
|
1122
|
+
error: e.message || "Failed to move prop group",
|
|
1123
|
+
}));
|
|
850
1124
|
process.exit(1);
|
|
851
1125
|
}
|
|
852
1126
|
saveConfig(configPath, config);
|
|
@@ -902,7 +1176,9 @@ function listComponents() {
|
|
|
902
1176
|
required: p.required,
|
|
903
1177
|
...(p.groupId ? { groupId: p.groupId } : {}),
|
|
904
1178
|
})),
|
|
905
|
-
...(c.propGroups && c.propGroups.length > 0
|
|
1179
|
+
...(c.propGroups && c.propGroups.length > 0
|
|
1180
|
+
? { propGroups: c.propGroups }
|
|
1181
|
+
: {}),
|
|
906
1182
|
}));
|
|
907
1183
|
console.log(JSON.stringify({
|
|
908
1184
|
success: true,
|
|
@@ -916,7 +1192,9 @@ function listComponents() {
|
|
|
916
1192
|
async function addEnum(options) {
|
|
917
1193
|
const { config, configPath } = loadConfig();
|
|
918
1194
|
// Validate name
|
|
919
|
-
if (!options.name ||
|
|
1195
|
+
if (!options.name ||
|
|
1196
|
+
typeof options.name !== "string" ||
|
|
1197
|
+
!options.name.trim()) {
|
|
920
1198
|
console.log(JSON.stringify({ success: false, error: "Enum name is required." }));
|
|
921
1199
|
process.exit(1);
|
|
922
1200
|
}
|
|
@@ -1030,7 +1308,7 @@ function updateEnum(options) {
|
|
|
1030
1308
|
Object.keys(enumOptions).length === 0) {
|
|
1031
1309
|
console.log(JSON.stringify({
|
|
1032
1310
|
success: false,
|
|
1033
|
-
error:
|
|
1311
|
+
error: "Options must be a non-empty JSON object.",
|
|
1034
1312
|
}));
|
|
1035
1313
|
process.exit(1);
|
|
1036
1314
|
}
|
|
@@ -1103,8 +1381,7 @@ function listEnums() {
|
|
|
1103
1381
|
}
|
|
1104
1382
|
// --- Command registration ---
|
|
1105
1383
|
export function createConfigCommand() {
|
|
1106
|
-
const config = new Command("config")
|
|
1107
|
-
.description("Non-interactive commands for managing ikas.config.json (designed for AI agents and scripting)");
|
|
1384
|
+
const config = new Command("config").description("Non-interactive commands for managing ikas.config.json (designed for AI agents and scripting)");
|
|
1108
1385
|
config
|
|
1109
1386
|
.command("add-component")
|
|
1110
1387
|
.description("Add a new component to the project")
|
|
@@ -1112,7 +1389,8 @@ export function createConfigCommand() {
|
|
|
1112
1389
|
.option("--type <type>", "Component type: section or component", "component")
|
|
1113
1390
|
.option("--isHeader", "Mark this section as the store header (only for type: section)")
|
|
1114
1391
|
.option("--isFooter", "Mark this section as the store footer (only for type: section)")
|
|
1115
|
-
.option("--props <json>", "JSON array of props
|
|
1392
|
+
.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. " +
|
|
1393
|
+
'Example: \'[{"name":"title","type":"TEXT","required":true,"defaultValue":"Hello","groupId":"basic"}]\'')
|
|
1116
1394
|
.action((options) => {
|
|
1117
1395
|
addComponent(options.name, options);
|
|
1118
1396
|
});
|
|
@@ -1131,7 +1409,7 @@ export function createConfigCommand() {
|
|
|
1131
1409
|
.option("--typeId <typeId>", "Type ID for TYPE props (required when type is TYPE)")
|
|
1132
1410
|
.option("--enumTypeId <enumTypeId>", "Enum type ID for ENUM props (required when type is ENUM)")
|
|
1133
1411
|
.option("--filteredComponentIds <json>", "JSON array of component IDs to restrict selection (for COMPONENT/COMPONENT_LIST)")
|
|
1134
|
-
.option("--privateVarMap <json>",
|
|
1412
|
+
.option("--privateVarMap <json>", "JSON object mapping variable keys to {id, typeId} (for COMPONENT/COMPONENT_LIST)")
|
|
1135
1413
|
.action((options) => {
|
|
1136
1414
|
addProp({ id: options.componentId, name: options.component }, options);
|
|
1137
1415
|
});
|
|
@@ -1222,7 +1500,7 @@ export function createConfigCommand() {
|
|
|
1222
1500
|
.option("--component <name>", "Component name — exact match (PascalCase, as stored in ikas.config.json)")
|
|
1223
1501
|
.requiredOption("--id <id>", "Group ID to move")
|
|
1224
1502
|
.option("--parent <parentId>", "Target parent group ID (omit to move to root)")
|
|
1225
|
-
.option("--index <index>", "Zero-based insertion index within the target parent (appends when omitted)", v => parseInt(v, 10))
|
|
1503
|
+
.option("--index <index>", "Zero-based insertion index within the target parent (appends when omitted)", (v) => parseInt(v, 10))
|
|
1226
1504
|
.action((options) => {
|
|
1227
1505
|
movePropGroup({ id: options.componentId, name: options.component }, options);
|
|
1228
1506
|
});
|
|
@@ -1318,7 +1596,15 @@ async function fetchLiveEditorEnumById(enumId) {
|
|
|
1318
1596
|
clearTimeout(timeout);
|
|
1319
1597
|
const types = message.payload?.types || [];
|
|
1320
1598
|
const match = types.find((t) => t.category === "enum" && t.id === enumId);
|
|
1321
|
-
finish(match
|
|
1599
|
+
finish(match
|
|
1600
|
+
? {
|
|
1601
|
+
id: match.id,
|
|
1602
|
+
name: match.name,
|
|
1603
|
+
...(match.enumOptions
|
|
1604
|
+
? { enumOptions: match.enumOptions }
|
|
1605
|
+
: {}),
|
|
1606
|
+
}
|
|
1607
|
+
: null);
|
|
1322
1608
|
}
|
|
1323
1609
|
}
|
|
1324
1610
|
catch {
|