@deneb-ui/cli 2.0.39 → 2.0.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.39",
3
+ "version": "2.0.41",
4
4
  "description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
@@ -49,7 +49,7 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@babel/parser": "^7.28.0",
52
- "@deneb-ui/core": "^2.0.39",
52
+ "@deneb-ui/core": "^2.0.41",
53
53
  "adm-zip": "^0.6.0",
54
54
  "recast": "^0.23.11"
55
55
  },
@@ -123,9 +123,24 @@ export function buildTemplateValidationSiteData(
123
123
  return {
124
124
  project: {
125
125
  id: 'template-validation',
126
+ slug: 'template-validation',
126
127
  title: 'Template Validation',
127
128
  status: 'APPROVED',
128
129
  },
130
+ siteInstance: {
131
+ id: 'template-validation-instance',
132
+ slug: 'template-validation',
133
+ domain: 'template-validation.fivora.site',
134
+ subdomain: 'template-validation',
135
+ customDomain: null,
136
+ liveUrl: 'https://template-validation.fivora.site',
137
+ },
138
+ api: {
139
+ baseUrl: 'https://api.fivora.site',
140
+ catalogUrl: 'https://api.fivora.site/site-catalog/template-validation/live-data',
141
+ contactUrl: 'https://api.fivora.site/site-contact',
142
+ analyticsUrl: 'https://api.fivora.site/site-analytics/page-view',
143
+ },
129
144
  shop: {
130
145
  businessName: 'Template Validation Shop',
131
146
  description:
@@ -2278,3 +2278,261 @@ function singularize(label: string) {
2278
2278
 
2279
2279
  return label;
2280
2280
  }
2281
+
2282
+ /**
2283
+ * Coerces primitive values to their target schema type (e.g. string "25" -> number 25,
2284
+ * string "true" -> boolean true, number/boolean -> string for text fields).
2285
+ */
2286
+ export function coercePrimitiveValue(
2287
+ value: unknown,
2288
+ type: PrimitiveType,
2289
+ defaultValue?: unknown,
2290
+ ): unknown {
2291
+ if (value === null || value === undefined) {
2292
+ if (defaultValue !== undefined) return defaultValue;
2293
+ return value;
2294
+ }
2295
+
2296
+ if (type === 'number') {
2297
+ if (typeof value === 'number') {
2298
+ return Number.isFinite(value) ? value : (defaultValue ?? value);
2299
+ }
2300
+ if (typeof value === 'string') {
2301
+ const trimmed = value.trim();
2302
+ if (trimmed === '') {
2303
+ return defaultValue !== undefined ? defaultValue : null;
2304
+ }
2305
+ const num = Number(trimmed);
2306
+ if (!Number.isNaN(num)) {
2307
+ return num;
2308
+ }
2309
+ }
2310
+ if (defaultValue !== undefined && typeof defaultValue === 'number') {
2311
+ return defaultValue;
2312
+ }
2313
+ return value;
2314
+ }
2315
+
2316
+ if (type === 'boolean') {
2317
+ if (typeof value === 'boolean') {
2318
+ return value;
2319
+ }
2320
+ if (typeof value === 'string') {
2321
+ const trimmed = value.trim().toLowerCase();
2322
+ if (trimmed === 'true' || trimmed === '1') return true;
2323
+ if (trimmed === 'false' || trimmed === '0') return false;
2324
+ if (trimmed === '') {
2325
+ return defaultValue !== undefined ? defaultValue : false;
2326
+ }
2327
+ }
2328
+ if (typeof value === 'number') {
2329
+ if (value === 1) return true;
2330
+ if (value === 0) return false;
2331
+ }
2332
+ if (defaultValue !== undefined && typeof defaultValue === 'boolean') {
2333
+ return defaultValue;
2334
+ }
2335
+ return Boolean(value);
2336
+ }
2337
+
2338
+ if (
2339
+ type === 'text' ||
2340
+ type === 'textarea' ||
2341
+ type === 'email' ||
2342
+ type === 'tel' ||
2343
+ type === 'url' ||
2344
+ type === 'select' ||
2345
+ type === 'image'
2346
+ ) {
2347
+ if (typeof value === 'number' || typeof value === 'boolean') {
2348
+ return String(value);
2349
+ }
2350
+ }
2351
+
2352
+ return value;
2353
+ }
2354
+
2355
+ function coerceNodeValue(
2356
+ value: unknown,
2357
+ node: TemplateEditorSection | TemplateEditorField,
2358
+ defaultVal?: unknown,
2359
+ ): unknown {
2360
+ if (node.type === 'object') {
2361
+ if (!isPlainObject(value)) return value;
2362
+ const obj = { ...(value as Record<string, unknown>) };
2363
+ const defaultObj = isPlainObject(defaultVal)
2364
+ ? (defaultVal as Record<string, unknown>)
2365
+ : undefined;
2366
+ for (const field of node.fields ?? []) {
2367
+ if (Object.prototype.hasOwnProperty.call(obj, field.key)) {
2368
+ obj[field.key] = coerceNodeValue(
2369
+ obj[field.key],
2370
+ field,
2371
+ defaultObj ? defaultObj[field.key] : undefined,
2372
+ );
2373
+ }
2374
+ }
2375
+ return obj;
2376
+ }
2377
+
2378
+ if (node.type === 'list') {
2379
+ if (!Array.isArray(value)) return value;
2380
+ const defaultArr = Array.isArray(defaultVal) ? defaultVal : undefined;
2381
+ const sampleDefaultItem =
2382
+ defaultArr && defaultArr.length > 0 ? defaultArr[0] : undefined;
2383
+
2384
+ return value.map((item, index) => {
2385
+ const itemDefault =
2386
+ defaultArr && defaultArr[index] !== undefined
2387
+ ? defaultArr[index]
2388
+ : sampleDefaultItem;
2389
+
2390
+ if (node.fields && node.fields.length > 0) {
2391
+ if (!isPlainObject(item)) return item;
2392
+ const itemObj = { ...(item as Record<string, unknown>) };
2393
+ const itemDefaultObj = isPlainObject(itemDefault)
2394
+ ? (itemDefault as Record<string, unknown>)
2395
+ : undefined;
2396
+ for (const field of node.fields) {
2397
+ if (Object.prototype.hasOwnProperty.call(itemObj, field.key)) {
2398
+ itemObj[field.key] = coerceNodeValue(
2399
+ itemObj[field.key],
2400
+ field,
2401
+ itemDefaultObj ? itemDefaultObj[field.key] : undefined,
2402
+ );
2403
+ }
2404
+ }
2405
+ return itemObj;
2406
+ }
2407
+
2408
+ if (node.itemField) {
2409
+ return coercePrimitiveValue(item, node.itemField.type, itemDefault);
2410
+ }
2411
+
2412
+ return item;
2413
+ });
2414
+ }
2415
+
2416
+ return coercePrimitiveValue(
2417
+ value,
2418
+ node.type as PrimitiveType,
2419
+ defaultVal,
2420
+ );
2421
+ }
2422
+
2423
+ function coerceAtDottedPath(
2424
+ current: Record<string, unknown>,
2425
+ pathParts: string[],
2426
+ section: TemplateEditorSection,
2427
+ defaults?: Record<string, unknown> | null,
2428
+ ): Record<string, unknown> {
2429
+ const [head, ...rest] = pathParts;
2430
+ if (!head) return current;
2431
+
2432
+ if (rest.length === 0) {
2433
+ if (Object.prototype.hasOwnProperty.call(current, head)) {
2434
+ const defaultVal =
2435
+ defaults && isPlainObject(defaults) ? defaults[head] : undefined;
2436
+ current[head] = coerceNodeValue(current[head], section, defaultVal);
2437
+ }
2438
+ return current;
2439
+ }
2440
+
2441
+ if (isPlainObject(current[head])) {
2442
+ const nextDefaults =
2443
+ defaults && isPlainObject(defaults) && isPlainObject(defaults[head])
2444
+ ? (defaults[head] as Record<string, unknown>)
2445
+ : undefined;
2446
+ current[head] = coerceAtDottedPath(
2447
+ current[head] as Record<string, unknown>,
2448
+ rest,
2449
+ section,
2450
+ nextDefaults,
2451
+ );
2452
+ }
2453
+
2454
+ return current;
2455
+ }
2456
+
2457
+ function coerceViaDefaults(current: unknown, defaultVal: unknown): unknown {
2458
+ if (defaultVal === null || defaultVal === undefined) {
2459
+ return current;
2460
+ }
2461
+
2462
+ if (typeof defaultVal === 'number') {
2463
+ return coercePrimitiveValue(current, 'number', defaultVal);
2464
+ }
2465
+
2466
+ if (typeof defaultVal === 'boolean') {
2467
+ return coercePrimitiveValue(current, 'boolean', defaultVal);
2468
+ }
2469
+
2470
+ if (typeof defaultVal === 'string') {
2471
+ return coercePrimitiveValue(current, 'text', defaultVal);
2472
+ }
2473
+
2474
+ if (Array.isArray(defaultVal)) {
2475
+ if (!Array.isArray(current)) return current;
2476
+ if (defaultVal.length === 0) return current;
2477
+
2478
+ const sampleItem = defaultVal[0];
2479
+ return current.map((item, idx) => {
2480
+ const defItem =
2481
+ defaultVal[idx] !== undefined ? defaultVal[idx] : sampleItem;
2482
+ return coerceViaDefaults(item, defItem);
2483
+ });
2484
+ }
2485
+
2486
+ if (isPlainObject(defaultVal)) {
2487
+ if (!isPlainObject(current)) return current;
2488
+ const currentObj = { ...(current as Record<string, unknown>) };
2489
+ for (const [k, v] of Object.entries(currentObj)) {
2490
+ if (Object.prototype.hasOwnProperty.call(defaultVal, k)) {
2491
+ currentObj[k] = coerceViaDefaults(
2492
+ v,
2493
+ (defaultVal as Record<string, unknown>)[k],
2494
+ );
2495
+ }
2496
+ }
2497
+ return currentObj;
2498
+ }
2499
+
2500
+ return current;
2501
+ }
2502
+
2503
+ /**
2504
+ * Coerces website content values to their intended schema/default primitive types
2505
+ * (e.g. numeric strings like "25" to 25, boolean strings like "true" to true).
2506
+ * Ensures consistency between form submission, database storage, and template generation.
2507
+ */
2508
+ export function coerceContentTypes(
2509
+ content: Record<string, unknown>,
2510
+ schema?: TemplateEditorSchema | null,
2511
+ defaults?: Record<string, unknown> | null,
2512
+ ): Record<string, unknown> {
2513
+ if (!content || !isPlainObject(content)) {
2514
+ return content;
2515
+ }
2516
+
2517
+ let result = structuredClone(content);
2518
+
2519
+ // 1. Coerce via schema sections if schema is provided
2520
+ if (schema && Array.isArray(schema.sections) && schema.sections.length > 0) {
2521
+ for (const section of schema.sections) {
2522
+ const pathParts = section.path
2523
+ .split('.')
2524
+ .map((p) => p.trim())
2525
+ .filter(Boolean);
2526
+ if (pathParts.length === 0) continue;
2527
+
2528
+ result = coerceAtDottedPath(result, pathParts, section, defaults);
2529
+ }
2530
+ }
2531
+
2532
+ // 2. Coerce or reinforce via defaults if defaults is provided
2533
+ if (defaults && isPlainObject(defaults)) {
2534
+ result = coerceViaDefaults(result, defaults) as Record<string, unknown>;
2535
+ }
2536
+
2537
+ return result;
2538
+ }