@fmaplabs/meta-manifest 0.2.0 → 0.4.0
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/README.md +59 -3
- package/dist/{chunk-PFU5VAO7.js → chunk-AUR2YQCW.js} +2 -2
- package/dist/{chunk-GH5DXHS5.js → chunk-OEJJXMYC.js} +461 -97
- package/dist/chunk-OEJJXMYC.js.map +1 -0
- package/dist/{chunk-3R6VQ3Z3.js → chunk-VSJUGUH7.js} +15 -3
- package/dist/chunk-VSJUGUH7.js.map +1 -0
- package/dist/cli/index.cjs +434 -75
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +35 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +475 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -38
- package/dist/index.d.ts +180 -38
- package/dist/index.js +6 -2
- package/dist/node/client.cjs.map +1 -1
- package/dist/node/client.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3R6VQ3Z3.js.map +0 -1
- package/dist/chunk-GH5DXHS5.js.map +0 -1
- /package/dist/{chunk-PFU5VAO7.js.map → chunk-AUR2YQCW.js.map} +0 -0
package/dist/cli/index.cjs
CHANGED
|
@@ -41,12 +41,14 @@ var PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {
|
|
|
41
41
|
required
|
|
42
42
|
type { name }
|
|
43
43
|
validations { name value }
|
|
44
|
+
capabilities { adminFilterable { enabled } }
|
|
44
45
|
}
|
|
45
|
-
access { admin storefront }
|
|
46
|
+
access { admin storefront customerAccount }
|
|
46
47
|
capabilities {
|
|
47
48
|
publishable { enabled }
|
|
48
49
|
translatable { enabled }
|
|
49
|
-
renderable { enabled }
|
|
50
|
+
renderable { enabled data { metaTitleKey metaDescriptionKey } }
|
|
51
|
+
onlineStore { enabled data { urlHandle } }
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
}`;
|
|
@@ -56,6 +58,8 @@ var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
|
|
|
56
58
|
id
|
|
57
59
|
name
|
|
58
60
|
type
|
|
61
|
+
description
|
|
62
|
+
displayNameKey
|
|
59
63
|
fieldDefinitions {
|
|
60
64
|
key
|
|
61
65
|
name
|
|
@@ -63,6 +67,14 @@ var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
|
|
|
63
67
|
required
|
|
64
68
|
type { name }
|
|
65
69
|
validations { name value }
|
|
70
|
+
capabilities { adminFilterable { enabled } }
|
|
71
|
+
}
|
|
72
|
+
access { admin storefront customerAccount }
|
|
73
|
+
capabilities {
|
|
74
|
+
publishable { enabled }
|
|
75
|
+
translatable { enabled }
|
|
76
|
+
renderable { enabled data { metaTitleKey metaDescriptionKey } }
|
|
77
|
+
onlineStore { enabled data { urlHandle } }
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
pageInfo { hasNextPage endCursor }
|
|
@@ -205,6 +217,12 @@ async function runInit(opts = {}) {
|
|
|
205
217
|
|
|
206
218
|
// src/codegen.ts
|
|
207
219
|
var APP_PREFIX = "$app:";
|
|
220
|
+
function isMerchant(type) {
|
|
221
|
+
return !type.startsWith(APP_PREFIX);
|
|
222
|
+
}
|
|
223
|
+
function filterableEntry(field) {
|
|
224
|
+
return field.filterable ? ["filterable: true"] : [];
|
|
225
|
+
}
|
|
208
226
|
var SIMPLE = {
|
|
209
227
|
single_line_text_field: "text",
|
|
210
228
|
multi_line_text_field: "multilineText",
|
|
@@ -248,6 +266,27 @@ function refTarget(field) {
|
|
|
248
266
|
}
|
|
249
267
|
return void 0;
|
|
250
268
|
}
|
|
269
|
+
function refTargets(field) {
|
|
270
|
+
const many = v(field.validations, "metaobject_definition_types");
|
|
271
|
+
if (many) {
|
|
272
|
+
try {
|
|
273
|
+
const arr = JSON.parse(many);
|
|
274
|
+
if (Array.isArray(arr)) return arr.map(String);
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const single = v(field.validations, "metaobject_definition_type");
|
|
279
|
+
return single ? [single] : [];
|
|
280
|
+
}
|
|
281
|
+
function mixedTargetsLiteral(field, typeToIdent, warnings) {
|
|
282
|
+
const targets = refTargets(field);
|
|
283
|
+
const idents = targets.map((t) => typeToIdent.get(t));
|
|
284
|
+
if (!targets.length || idents.some((i) => !i)) {
|
|
285
|
+
warnings.push(`unresolved mixed reference on field "${field.key}"`);
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
return `[${idents.map((i) => `() => ${i}`).join(", ")}]`;
|
|
289
|
+
}
|
|
251
290
|
function optsLiteral(entries) {
|
|
252
291
|
return entries.length ? `{ ${entries.join(", ")} }` : "";
|
|
253
292
|
}
|
|
@@ -286,8 +325,8 @@ function scalarEntries(field, warnings, builder) {
|
|
|
286
325
|
jsonArr("file_type_options", "accept");
|
|
287
326
|
return e;
|
|
288
327
|
}
|
|
289
|
-
function scalarCall(builder, field, warnings) {
|
|
290
|
-
const lit = optsLiteral(scalarEntries(field, warnings, builder));
|
|
328
|
+
function scalarCall(builder, field, warnings, extra = []) {
|
|
329
|
+
const lit = optsLiteral([...scalarEntries(field, warnings, builder), ...extra]);
|
|
291
330
|
return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
|
|
292
331
|
}
|
|
293
332
|
function fieldCall(field, typeToIdent, warnings) {
|
|
@@ -297,6 +336,7 @@ function fieldCall(field, typeToIdent, warnings) {
|
|
|
297
336
|
const max = v(field.validations, "max");
|
|
298
337
|
const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
|
|
299
338
|
if (field.required) e.unshift("required: true");
|
|
339
|
+
e.push(...filterableEntry(field));
|
|
300
340
|
if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
|
|
301
341
|
return `m.rating(${optsLiteral(e)})`;
|
|
302
342
|
}
|
|
@@ -307,7 +347,20 @@ function fieldCall(field, typeToIdent, warnings) {
|
|
|
307
347
|
warnings.push(`unresolved reference on field "${field.key}"`);
|
|
308
348
|
return `m.json() /* TODO: unmapped reference */`;
|
|
309
349
|
}
|
|
310
|
-
|
|
350
|
+
const refEntries = [];
|
|
351
|
+
if (field.required) refEntries.push("required: true");
|
|
352
|
+
refEntries.push(...filterableEntry(field));
|
|
353
|
+
const opts = optsLiteral(refEntries);
|
|
354
|
+
return opts ? `m.ref(() => ${ident}, ${opts})` : `m.ref(() => ${ident})`;
|
|
355
|
+
}
|
|
356
|
+
if (type === "mixed_reference") {
|
|
357
|
+
const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
|
|
358
|
+
if (!arr) return `m.json() /* TODO: unmapped mixed reference */`;
|
|
359
|
+
const refEntries = [];
|
|
360
|
+
if (field.required) refEntries.push("required: true");
|
|
361
|
+
refEntries.push(...filterableEntry(field));
|
|
362
|
+
const opts = optsLiteral(refEntries);
|
|
363
|
+
return opts ? `m.mixedRef(${arr}, ${opts})` : `m.mixedRef(${arr})`;
|
|
311
364
|
}
|
|
312
365
|
if (type.startsWith("list.")) {
|
|
313
366
|
const inner = type.slice("list.".length);
|
|
@@ -317,6 +370,7 @@ function fieldCall(field, typeToIdent, warnings) {
|
|
|
317
370
|
const max = v(field.validations, "list.max");
|
|
318
371
|
if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
|
|
319
372
|
if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
|
|
373
|
+
listEntries.push(...filterableEntry(field));
|
|
320
374
|
const listOpts = optsLiteral(listEntries);
|
|
321
375
|
let innerCall;
|
|
322
376
|
if (inner === "metaobject_reference") {
|
|
@@ -327,6 +381,10 @@ function fieldCall(field, typeToIdent, warnings) {
|
|
|
327
381
|
return `m.json() /* TODO: unmapped list reference */`;
|
|
328
382
|
}
|
|
329
383
|
innerCall = `m.ref(() => ${ident})`;
|
|
384
|
+
} else if (inner === "mixed_reference") {
|
|
385
|
+
const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
|
|
386
|
+
if (!arr) return `m.json() /* TODO: unmapped list mixed reference */`;
|
|
387
|
+
innerCall = `m.mixedRef(${arr})`;
|
|
330
388
|
} else if (SIMPLE[inner]) {
|
|
331
389
|
innerCall = scalarCall(SIMPLE[inner], { ...field, required: false }, warnings);
|
|
332
390
|
} else {
|
|
@@ -335,17 +393,53 @@ function fieldCall(field, typeToIdent, warnings) {
|
|
|
335
393
|
}
|
|
336
394
|
return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
|
|
337
395
|
}
|
|
338
|
-
if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
|
|
396
|
+
if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings, filterableEntry(field));
|
|
339
397
|
warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
|
|
340
398
|
return `m.json() /* TODO: unmapped type ${type} */`;
|
|
341
399
|
}
|
|
400
|
+
function accessSource(access) {
|
|
401
|
+
if (!access) return "";
|
|
402
|
+
const parts = [];
|
|
403
|
+
if (access.admin === "MERCHANT_READ_WRITE") parts.push(`admin: "merchant_read_write"`);
|
|
404
|
+
if (access.storefront === "PUBLIC_READ") parts.push(`storefront: "public_read"`);
|
|
405
|
+
if (access.customerAccount === "READ") parts.push(`customerAccount: "read"`);
|
|
406
|
+
return parts.length ? `{ ${parts.join(", ")} }` : "";
|
|
407
|
+
}
|
|
408
|
+
function capabilitiesSource(caps) {
|
|
409
|
+
if (!caps) return "";
|
|
410
|
+
const parts = [];
|
|
411
|
+
if (caps.publishable?.enabled) parts.push(`publishable: true`);
|
|
412
|
+
if (caps.translatable?.enabled) parts.push(`translatable: true`);
|
|
413
|
+
if (caps.renderable?.enabled) {
|
|
414
|
+
const d = caps.renderable.data;
|
|
415
|
+
const dparts = [];
|
|
416
|
+
if (d?.metaTitleKey != null) dparts.push(`metaTitleKey: ${JSON.stringify(d.metaTitleKey)}`);
|
|
417
|
+
if (d?.metaDescriptionKey != null) dparts.push(`metaDescriptionKey: ${JSON.stringify(d.metaDescriptionKey)}`);
|
|
418
|
+
parts.push(dparts.length ? `renderable: { ${dparts.join(", ")} }` : `renderable: true`);
|
|
419
|
+
}
|
|
420
|
+
if (caps.onlineStore?.enabled && caps.onlineStore.data?.urlHandle != null) {
|
|
421
|
+
parts.push(`onlineStore: { urlHandle: ${JSON.stringify(caps.onlineStore.data.urlHandle)} }`);
|
|
422
|
+
}
|
|
423
|
+
return parts.length ? `{ ${parts.join(", ")} }` : "";
|
|
424
|
+
}
|
|
425
|
+
function defConfigLines(def) {
|
|
426
|
+
const lines = [];
|
|
427
|
+
if (isMerchant(def.type)) lines.push(` scope: "merchant",`);
|
|
428
|
+
if (def.name) lines.push(` name: ${JSON.stringify(def.name)},`);
|
|
429
|
+
if (def.description != null) lines.push(` description: ${JSON.stringify(def.description)},`);
|
|
430
|
+
if (def.displayNameKey != null) lines.push(` displayName: ${JSON.stringify(def.displayNameKey)},`);
|
|
431
|
+
const access = accessSource(def.access);
|
|
432
|
+
if (access) lines.push(` access: ${access},`);
|
|
433
|
+
const caps = capabilitiesSource(def.capabilities);
|
|
434
|
+
if (caps) lines.push(` capabilities: ${caps},`);
|
|
435
|
+
return lines.length ? `
|
|
436
|
+
${lines.join("\n")}` : "";
|
|
437
|
+
}
|
|
342
438
|
function defSource(def, typeToIdent, warnings) {
|
|
343
439
|
const ident = typeToIdent.get(def.type);
|
|
344
440
|
const handle = handleOf(def.type);
|
|
345
441
|
const fields = def.fields.map((f) => ` ${f.key}: ${fieldCall(f, typeToIdent, warnings)},`).join("\n");
|
|
346
|
-
const
|
|
347
|
-
name: ${JSON.stringify(def.name)},` : "";
|
|
348
|
-
return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${name}
|
|
442
|
+
return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${defConfigLines(def)}
|
|
349
443
|
fields: {
|
|
350
444
|
${fields}
|
|
351
445
|
},
|
|
@@ -357,6 +451,8 @@ function referencedTypes(def) {
|
|
|
357
451
|
if (f.type === "metaobject_reference" || f.type === "list.metaobject_reference") {
|
|
358
452
|
const t = refTarget(f);
|
|
359
453
|
if (t) out.add(t);
|
|
454
|
+
} else if (f.type === "mixed_reference" || f.type === "list.mixed_reference") {
|
|
455
|
+
for (const t of refTargets(f)) out.add(t);
|
|
360
456
|
}
|
|
361
457
|
}
|
|
362
458
|
return out;
|
|
@@ -405,6 +501,42 @@ function sameValidations(a, b) {
|
|
|
405
501
|
const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
|
|
406
502
|
return norm(a) === norm(b);
|
|
407
503
|
}
|
|
504
|
+
var CAP_KEYS = ["publishable", "translatable", "renderable", "onlineStore"];
|
|
505
|
+
function accessChanged(local, remote) {
|
|
506
|
+
for (const key of ["admin", "storefront", "customerAccount"]) {
|
|
507
|
+
const lv = local[key];
|
|
508
|
+
if (lv != null && remote?.[key] !== lv) return true;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
function capabilitiesChanged(local, remote) {
|
|
513
|
+
for (const key of CAP_KEYS) {
|
|
514
|
+
const lc = local[key];
|
|
515
|
+
if (!lc) continue;
|
|
516
|
+
const rc = remote?.[key];
|
|
517
|
+
if ((rc?.enabled ?? false) !== lc.enabled) return true;
|
|
518
|
+
const ldata = lc.data;
|
|
519
|
+
if (ldata) {
|
|
520
|
+
const rdata = rc?.data;
|
|
521
|
+
for (const [k, v2] of Object.entries(ldata)) {
|
|
522
|
+
if (v2 != null && rdata?.[k] !== v2) return true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
function disablesOnlineStore(local, remote) {
|
|
529
|
+
return local.onlineStore?.enabled === false && remote?.onlineStore?.enabled === true;
|
|
530
|
+
}
|
|
531
|
+
function definitionChanges(local, remote) {
|
|
532
|
+
const changes = [];
|
|
533
|
+
if (local.name != null && local.name !== remote.name) changes.push("name");
|
|
534
|
+
if (local.description != null && local.description !== remote.description) changes.push("description");
|
|
535
|
+
if (local.displayNameKey != null && local.displayNameKey !== remote.displayNameKey) changes.push("displayNameKey");
|
|
536
|
+
if (local.access && accessChanged(local.access, remote.access)) changes.push("access");
|
|
537
|
+
if (local.capabilities && capabilitiesChanged(local.capabilities, remote.capabilities)) changes.push("capabilities");
|
|
538
|
+
return changes;
|
|
539
|
+
}
|
|
408
540
|
function diff(local, remote) {
|
|
409
541
|
const ops = [];
|
|
410
542
|
const remoteByType = new Map(remote.map((d) => [d.type, d]));
|
|
@@ -428,6 +560,7 @@ function diff(local, remote) {
|
|
|
428
560
|
}
|
|
429
561
|
const changes = {};
|
|
430
562
|
if (rf.required !== lf.required) changes.required = lf.required;
|
|
563
|
+
if (rf.filterable !== lf.filterable) changes.filterable = lf.filterable;
|
|
431
564
|
if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
|
|
432
565
|
if (Object.keys(changes).length) {
|
|
433
566
|
ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
|
|
@@ -438,49 +571,176 @@ function diff(local, remote) {
|
|
|
438
571
|
ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
|
|
439
572
|
}
|
|
440
573
|
}
|
|
574
|
+
const metaChanges = definitionChanges(localDef, remoteDef);
|
|
575
|
+
if (metaChanges.length) {
|
|
576
|
+
const destructive = localDef.capabilities ? disablesOnlineStore(localDef.capabilities, remoteDef.capabilities) : false;
|
|
577
|
+
ops.push({
|
|
578
|
+
kind: "updateDefinition",
|
|
579
|
+
type: localDef.type,
|
|
580
|
+
changes: metaChanges,
|
|
581
|
+
...destructive ? { destructive: true } : {}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
441
584
|
}
|
|
442
585
|
return ops;
|
|
443
586
|
}
|
|
444
587
|
|
|
445
588
|
// src/sync/normalize.ts
|
|
446
|
-
function
|
|
447
|
-
const
|
|
448
|
-
|
|
589
|
+
function localCapabilities(caps) {
|
|
590
|
+
const out = {};
|
|
591
|
+
if (caps?.publishable) out.publishable = { enabled: caps.publishable.enabled };
|
|
592
|
+
if (caps?.translatable) out.translatable = { enabled: caps.translatable.enabled };
|
|
593
|
+
if (caps?.renderable) {
|
|
594
|
+
out.renderable = { enabled: caps.renderable.enabled };
|
|
595
|
+
if (caps.renderable.data) out.renderable.data = { ...caps.renderable.data };
|
|
596
|
+
}
|
|
597
|
+
out.onlineStore = caps?.onlineStore ? { enabled: caps.onlineStore.enabled, data: { urlHandle: caps.onlineStore.data?.urlHandle } } : { enabled: false };
|
|
598
|
+
return out;
|
|
599
|
+
}
|
|
600
|
+
function normalizeDefinition(def) {
|
|
601
|
+
const out = {
|
|
449
602
|
type: def.type,
|
|
450
603
|
name: def.name,
|
|
604
|
+
capabilities: localCapabilities(def.capabilities),
|
|
451
605
|
fields: def.fieldDefinitions.map((f) => ({
|
|
452
606
|
key: f.key,
|
|
453
607
|
type: f.type,
|
|
454
608
|
required: f.required,
|
|
609
|
+
filterable: f.capabilities?.adminFilterable?.enabled ?? false,
|
|
455
610
|
validations: f.validations
|
|
456
611
|
}))
|
|
457
612
|
};
|
|
613
|
+
if (def.description != null) out.description = def.description;
|
|
614
|
+
if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
|
|
615
|
+
if (def.access) out.access = { ...def.access };
|
|
616
|
+
return out;
|
|
617
|
+
}
|
|
618
|
+
function remoteAccess(access) {
|
|
619
|
+
if (!access) return void 0;
|
|
620
|
+
const out = {};
|
|
621
|
+
if (access.admin != null) out.admin = access.admin;
|
|
622
|
+
if (access.storefront != null) out.storefront = access.storefront;
|
|
623
|
+
if (access.customerAccount != null) out.customerAccount = access.customerAccount;
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
function remoteCapabilities(caps) {
|
|
627
|
+
const out = {};
|
|
628
|
+
if (!caps) return out;
|
|
629
|
+
if (caps.publishable) out.publishable = { enabled: !!caps.publishable.enabled };
|
|
630
|
+
if (caps.translatable) out.translatable = { enabled: !!caps.translatable.enabled };
|
|
631
|
+
if (caps.renderable) {
|
|
632
|
+
out.renderable = { enabled: !!caps.renderable.enabled };
|
|
633
|
+
const d = caps.renderable.data;
|
|
634
|
+
if (d) {
|
|
635
|
+
const data = {};
|
|
636
|
+
if (d.metaTitleKey != null) data.metaTitleKey = d.metaTitleKey;
|
|
637
|
+
if (d.metaDescriptionKey != null) data.metaDescriptionKey = d.metaDescriptionKey;
|
|
638
|
+
if (Object.keys(data).length) out.renderable.data = data;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (caps.onlineStore) {
|
|
642
|
+
out.onlineStore = { enabled: !!caps.onlineStore.enabled };
|
|
643
|
+
if (caps.onlineStore.data?.urlHandle != null) out.onlineStore.data = { urlHandle: caps.onlineStore.data.urlHandle };
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
458
646
|
}
|
|
459
647
|
function normalizeRemote(def) {
|
|
460
|
-
|
|
648
|
+
const out = {
|
|
461
649
|
type: def.type,
|
|
462
650
|
name: def.name,
|
|
651
|
+
capabilities: remoteCapabilities(def.capabilities),
|
|
463
652
|
fields: def.fieldDefinitions.map((f) => ({
|
|
464
653
|
key: f.key,
|
|
465
654
|
type: typeof f.type === "string" ? f.type : f.type.name,
|
|
466
655
|
required: f.required,
|
|
656
|
+
filterable: f.capabilities?.adminFilterable?.enabled ?? false,
|
|
467
657
|
validations: f.validations ?? []
|
|
468
658
|
}))
|
|
469
659
|
};
|
|
660
|
+
if (def.description != null) out.description = def.description;
|
|
661
|
+
if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
|
|
662
|
+
const access = remoteAccess(def.access);
|
|
663
|
+
if (access) out.access = access;
|
|
664
|
+
return out;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/sync/resolve.ts
|
|
668
|
+
var APP_PREFIX2 = "$app:";
|
|
669
|
+
function effectiveScope(schema, config) {
|
|
670
|
+
return schema.config.scope ?? config.scope ?? "app";
|
|
671
|
+
}
|
|
672
|
+
function effectiveType(handle, scope) {
|
|
673
|
+
return scope === "merchant" ? handle : `${APP_PREFIX2}${handle}`;
|
|
674
|
+
}
|
|
675
|
+
function rewriteReference(v2, effByCanonical) {
|
|
676
|
+
if (v2.name === "metaobject_definition_type") {
|
|
677
|
+
const mapped = effByCanonical.get(v2.value);
|
|
678
|
+
return mapped ? { ...v2, value: mapped } : v2;
|
|
679
|
+
}
|
|
680
|
+
if (v2.name === "metaobject_definition_types") {
|
|
681
|
+
try {
|
|
682
|
+
const parsed = JSON.parse(v2.value);
|
|
683
|
+
if (Array.isArray(parsed)) {
|
|
684
|
+
const rewritten = parsed.map((t) => typeof t === "string" ? effByCanonical.get(t) ?? t : t);
|
|
685
|
+
return { ...v2, value: JSON.stringify(rewritten) };
|
|
686
|
+
}
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return v2;
|
|
691
|
+
}
|
|
692
|
+
function resolveAdmin(out, handle, scope, config) {
|
|
693
|
+
const explicitAdmin = out.access?.admin;
|
|
694
|
+
if (scope === "merchant") {
|
|
695
|
+
if (explicitAdmin != null) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
`"${handle}" is merchant-scoped but sets access.admin; admin access is only valid on app-scoped metaobjects.`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (explicitAdmin == null) {
|
|
703
|
+
const admin = config.merchantEditable ? "MERCHANT_READ_WRITE" : "MERCHANT_READ";
|
|
704
|
+
out.access = { ...out.access, admin };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function resolveDefinitions(schemas, config = {}) {
|
|
708
|
+
const effByCanonical = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const s of schemas) {
|
|
710
|
+
effByCanonical.set(`${APP_PREFIX2}${s.handle}`, effectiveType(s.handle, effectiveScope(s, config)));
|
|
711
|
+
}
|
|
712
|
+
return schemas.map((s) => {
|
|
713
|
+
const scope = effectiveScope(s, config);
|
|
714
|
+
const base = s.toDefinitionInput();
|
|
715
|
+
const fieldDefinitions = base.fieldDefinitions.map((f) => ({
|
|
716
|
+
...f,
|
|
717
|
+
validations: f.validations.map((v2) => rewriteReference(v2, effByCanonical))
|
|
718
|
+
}));
|
|
719
|
+
const out = { ...base, type: effectiveType(s.handle, scope), fieldDefinitions };
|
|
720
|
+
resolveAdmin(out, s.handle, scope, config);
|
|
721
|
+
return out;
|
|
722
|
+
});
|
|
470
723
|
}
|
|
471
724
|
|
|
472
725
|
// src/sync/pull.ts
|
|
726
|
+
function toDefinition(type, node) {
|
|
727
|
+
return {
|
|
728
|
+
type,
|
|
729
|
+
name: node.name,
|
|
730
|
+
description: node.description,
|
|
731
|
+
displayNameKey: node.displayNameKey,
|
|
732
|
+
access: node.access,
|
|
733
|
+
capabilities: node.capabilities,
|
|
734
|
+
fieldDefinitions: node.fieldDefinitions
|
|
735
|
+
};
|
|
736
|
+
}
|
|
473
737
|
async function pull(client, types) {
|
|
474
738
|
const out = [];
|
|
475
739
|
for (const type of types) {
|
|
476
740
|
const data = await execute(client, PULL_DEFINITION_QUERY, { type });
|
|
477
741
|
const node = data.metaobjectDefinitionByType;
|
|
478
742
|
if (!node) continue;
|
|
479
|
-
out.push({
|
|
480
|
-
id: node.id,
|
|
481
|
-
type,
|
|
482
|
-
definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
|
|
483
|
-
});
|
|
743
|
+
out.push({ id: node.id, type, definition: toDefinition(type, node) });
|
|
484
744
|
}
|
|
485
745
|
return out;
|
|
486
746
|
}
|
|
@@ -498,7 +758,7 @@ async function pullAll(client, opts = {}) {
|
|
|
498
758
|
const canonical = toCanonicalType(node.type);
|
|
499
759
|
if (appOwnedOnly && !canonical) continue;
|
|
500
760
|
const type = canonical ?? node.type;
|
|
501
|
-
out.push({ id: node.id, type, definition:
|
|
761
|
+
out.push({ id: node.id, type, definition: toDefinition(type, node) });
|
|
502
762
|
}
|
|
503
763
|
after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
|
|
504
764
|
} while (after !== null);
|
|
@@ -506,28 +766,57 @@ async function pullAll(client, opts = {}) {
|
|
|
506
766
|
}
|
|
507
767
|
|
|
508
768
|
// src/sync/push.ts
|
|
769
|
+
function definitionUpdateFor(def, changes) {
|
|
770
|
+
const out = {};
|
|
771
|
+
for (const c of changes) {
|
|
772
|
+
if (c === "name") out.name = def.name;
|
|
773
|
+
else if (c === "description") out.description = def.description;
|
|
774
|
+
else if (c === "displayNameKey") out.displayNameKey = def.displayNameKey;
|
|
775
|
+
else if (c === "access") out.access = def.access;
|
|
776
|
+
else if (c === "capabilities") {
|
|
777
|
+
const caps = def.capabilities;
|
|
778
|
+
const payload = {};
|
|
779
|
+
if (caps?.publishable) payload.publishable = caps.publishable;
|
|
780
|
+
if (caps?.translatable) payload.translatable = caps.translatable;
|
|
781
|
+
if (caps?.renderable) payload.renderable = caps.renderable;
|
|
782
|
+
payload.onlineStore = caps?.onlineStore ?? { enabled: false };
|
|
783
|
+
out.capabilities = payload;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
}
|
|
509
788
|
function fieldInputFor(defByType, type, key) {
|
|
510
789
|
return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
|
|
511
790
|
}
|
|
512
|
-
function
|
|
791
|
+
function fieldRefTargets(field) {
|
|
513
792
|
const out = [];
|
|
514
|
-
for (const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (
|
|
522
|
-
for (const t of parsed) if (typeof t === "string") out.push(t);
|
|
523
|
-
}
|
|
524
|
-
} catch {
|
|
793
|
+
for (const v2 of field.validations) {
|
|
794
|
+
if (v2.name === "metaobject_definition_type") {
|
|
795
|
+
out.push(v2.value);
|
|
796
|
+
} else if (v2.name === "metaobject_definition_types") {
|
|
797
|
+
try {
|
|
798
|
+
const parsed = JSON.parse(v2.value);
|
|
799
|
+
if (Array.isArray(parsed)) {
|
|
800
|
+
for (const t of parsed) if (typeof t === "string") out.push(t);
|
|
525
801
|
}
|
|
802
|
+
} catch {
|
|
526
803
|
}
|
|
527
804
|
}
|
|
528
805
|
}
|
|
529
806
|
return out;
|
|
530
807
|
}
|
|
808
|
+
function referenceEdges(def) {
|
|
809
|
+
return def.fieldDefinitions.flatMap(fieldRefTargets);
|
|
810
|
+
}
|
|
811
|
+
function splitCyclicFields(def, cyclicTypes) {
|
|
812
|
+
const pass1 = [];
|
|
813
|
+
const deferred = [];
|
|
814
|
+
for (const f of def.fieldDefinitions) {
|
|
815
|
+
const breaksCycle = fieldRefTargets(f).some((t) => cyclicTypes.has(t) && t !== def.type);
|
|
816
|
+
(breaksCycle ? deferred : pass1).push(f);
|
|
817
|
+
}
|
|
818
|
+
return { pass1, deferred };
|
|
819
|
+
}
|
|
531
820
|
function topoSortCreates(types, deps) {
|
|
532
821
|
const remaining = /* @__PURE__ */ new Map();
|
|
533
822
|
const dependents = /* @__PURE__ */ new Map();
|
|
@@ -570,50 +859,51 @@ async function push(client, plan, sources, options) {
|
|
|
570
859
|
deps.set(op.type, new Set(targets.filter((t) => createTypes.has(t) && t !== op.type)));
|
|
571
860
|
}
|
|
572
861
|
const { ordered, unordered } = topoSortCreates(createTypes, deps);
|
|
573
|
-
const orderedSet = new Set(ordered);
|
|
574
862
|
const cyclicTypes = new Set(unordered);
|
|
575
863
|
const createByType = new Map(createOps.map((x) => [x.op.type, x]));
|
|
576
|
-
const execOrder = [
|
|
577
|
-
...ordered.map((t) => createByType.get(t)),
|
|
578
|
-
...createOps.filter((x) => !orderedSet.has(x.op.type)),
|
|
579
|
-
...otherOps
|
|
580
|
-
];
|
|
581
864
|
const failedTypes = /* @__PURE__ */ new Set();
|
|
582
|
-
async function
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (
|
|
596
|
-
failedTypes.add(op.type);
|
|
597
|
-
return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
|
|
598
|
-
}
|
|
599
|
-
const data2 = await execute(client, CREATE_DEFINITION_MUTATION, { definition: def });
|
|
600
|
-
const payload2 = data2.metaobjectDefinitionCreate;
|
|
601
|
-
if (payload2.userErrors.length) {
|
|
865
|
+
async function createDefinition(op, definition) {
|
|
866
|
+
const data = await execute(client, CREATE_DEFINITION_MUTATION, { definition });
|
|
867
|
+
const payload = data.metaobjectDefinitionCreate;
|
|
868
|
+
if (payload.userErrors.length) {
|
|
869
|
+
failedTypes.add(op.type);
|
|
870
|
+
return { op, status: "failed", userErrors: payload.userErrors };
|
|
871
|
+
}
|
|
872
|
+
const id = payload.metaobjectDefinition?.id;
|
|
873
|
+
if (id) idByType.set(op.type, id);
|
|
874
|
+
return { op, status: "applied", id };
|
|
875
|
+
}
|
|
876
|
+
async function applyAcyclicCreate(op) {
|
|
877
|
+
for (const dep of deps.get(op.type) ?? []) {
|
|
878
|
+
if (failedTypes.has(dep)) {
|
|
602
879
|
failedTypes.add(op.type);
|
|
603
|
-
return { op, status: "
|
|
880
|
+
return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
|
|
604
881
|
}
|
|
605
|
-
const id2 = payload2.metaobjectDefinition?.id;
|
|
606
|
-
if (id2) idByType.set(op.type, id2);
|
|
607
|
-
return { op, status: "applied", id: id2 };
|
|
608
882
|
}
|
|
609
|
-
const
|
|
883
|
+
const def = defByType.get(op.type);
|
|
884
|
+
if (!def) {
|
|
885
|
+
failedTypes.add(op.type);
|
|
886
|
+
return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
|
|
887
|
+
}
|
|
888
|
+
return createDefinition(op, def);
|
|
889
|
+
}
|
|
890
|
+
async function applyFieldOp(op) {
|
|
891
|
+
const destructive = "destructive" in op && op.destructive === true;
|
|
610
892
|
if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
|
|
611
893
|
if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
|
|
612
894
|
const id = idByType.get(op.type);
|
|
613
895
|
if (id == null) return { op, status: "blocked", reason: `no definition id for "${op.type}"` };
|
|
614
|
-
|
|
615
|
-
if (
|
|
616
|
-
|
|
896
|
+
let definition;
|
|
897
|
+
if (op.kind === "updateDefinition") {
|
|
898
|
+
const def = defByType.get(op.type);
|
|
899
|
+
if (!def) return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
|
|
900
|
+
definition = definitionUpdateFor(def, op.changes);
|
|
901
|
+
} else {
|
|
902
|
+
const fieldDefinitions = fieldOpsFor(op);
|
|
903
|
+
if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
|
|
904
|
+
definition = { fieldDefinitions };
|
|
905
|
+
}
|
|
906
|
+
const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
|
|
617
907
|
const payload = data.metaobjectDefinitionUpdate;
|
|
618
908
|
if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
|
|
619
909
|
return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
|
|
@@ -634,6 +924,9 @@ async function push(client, plan, sources, options) {
|
|
|
634
924
|
validations: field.validations
|
|
635
925
|
};
|
|
636
926
|
if (field.description != null) update.description = field.description;
|
|
927
|
+
if ("filterable" in op.changes) {
|
|
928
|
+
update.capabilities = { adminFilterable: { enabled: op.changes.filterable ?? false } };
|
|
929
|
+
}
|
|
637
930
|
return [{ update }];
|
|
638
931
|
}
|
|
639
932
|
case "removeField":
|
|
@@ -647,19 +940,84 @@ async function push(client, plan, sources, options) {
|
|
|
647
940
|
}
|
|
648
941
|
}
|
|
649
942
|
const results = new Array(plan.length);
|
|
650
|
-
for (const
|
|
943
|
+
for (const type of ordered) {
|
|
944
|
+
const entry = createByType.get(type);
|
|
945
|
+
results[entry.index] = await applyAcyclicCreate(entry.op);
|
|
946
|
+
}
|
|
947
|
+
const cyclicCreates = createOps.filter((x) => cyclicTypes.has(x.op.type));
|
|
948
|
+
const deferredByType = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const { op, index } of cyclicCreates) {
|
|
950
|
+
const def = defByType.get(op.type);
|
|
951
|
+
if (!def) {
|
|
952
|
+
failedTypes.add(op.type);
|
|
953
|
+
results[index] = { op, status: "blocked", reason: `no definition input for "${op.type}"` };
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
const failedDep = [...deps.get(op.type) ?? []].find((d) => !cyclicTypes.has(d) && failedTypes.has(d));
|
|
957
|
+
if (failedDep) {
|
|
958
|
+
failedTypes.add(op.type);
|
|
959
|
+
results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedDep}" was not created` };
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
const { pass1, deferred } = splitCyclicFields(def, cyclicTypes);
|
|
963
|
+
deferredByType.set(op.type, deferred);
|
|
964
|
+
results[index] = await createDefinition(op, { ...def, fieldDefinitions: pass1 });
|
|
965
|
+
}
|
|
966
|
+
for (const { op, index } of cyclicCreates) {
|
|
967
|
+
if (results[index]?.status !== "applied") continue;
|
|
968
|
+
const deferred = deferredByType.get(op.type) ?? [];
|
|
969
|
+
if (!deferred.length) continue;
|
|
970
|
+
const failedTarget = deferred.flatMap(fieldRefTargets).find((t) => failedTypes.has(t));
|
|
971
|
+
if (failedTarget) {
|
|
972
|
+
failedTypes.add(op.type);
|
|
973
|
+
results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedTarget}" was not created` };
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
const id = idByType.get(op.type);
|
|
977
|
+
if (id == null) {
|
|
978
|
+
results[index] = { op, status: "blocked", reason: `no definition id for "${op.type}"` };
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const definition = { fieldDefinitions: deferred.map((f) => ({ create: f })) };
|
|
982
|
+
const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
|
|
983
|
+
const payload = data.metaobjectDefinitionUpdate;
|
|
984
|
+
if (payload.userErrors.length) {
|
|
985
|
+
failedTypes.add(op.type);
|
|
986
|
+
results[index] = { op, status: "failed", userErrors: payload.userErrors };
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
for (const { op, index } of otherOps) results[index] = await applyFieldOp(op);
|
|
651
990
|
const counts = { applied: 0, skipped: 0, blocked: 0, failed: 0 };
|
|
652
991
|
for (const r of results) counts[r.status]++;
|
|
653
992
|
return { results, counts, ok: counts.failed === 0 && counts.blocked === 0 };
|
|
654
993
|
}
|
|
655
994
|
|
|
656
995
|
// src/cli/plan.ts
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const
|
|
996
|
+
var APP_PREFIX3 = "$app:";
|
|
997
|
+
async function detectScopeFlips(client, definitions, plan) {
|
|
998
|
+
const merchant = definitions.filter((d) => !d.type.startsWith(APP_PREFIX3));
|
|
999
|
+
if (merchant.length === 0) return [];
|
|
1000
|
+
const created = new Set(plan.filter((op) => op.kind === "createDefinition").map((op) => op.type));
|
|
1001
|
+
const shadows = await pull(client, merchant.map((d) => `${APP_PREFIX3}${d.type}`));
|
|
1002
|
+
const shadowTypes = new Set(shadows.map((s) => s.type));
|
|
1003
|
+
const warnings = [];
|
|
1004
|
+
for (const d of merchant) {
|
|
1005
|
+
if (created.has(d.type) && shadowTypes.has(`${APP_PREFIX3}${d.type}`)) {
|
|
1006
|
+
warnings.push(
|
|
1007
|
+
`"${d.type}" is merchant-scoped but an app-owned "${APP_PREFIX3}${d.type}" already exists remotely. Scope changes are not migrated (type is immutable): a new merchant-owned definition will be created and the app-owned one left orphaned. Migrate manually \u2014 see docs/SYNC.md.`
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return warnings;
|
|
1012
|
+
}
|
|
1013
|
+
async function planFor(client, schemas, config = {}) {
|
|
1014
|
+
const definitions = resolveDefinitions(schemas, config);
|
|
1015
|
+
const types = definitions.map((d) => d.type);
|
|
1016
|
+
const localDefs = definitions.map(normalizeDefinition);
|
|
660
1017
|
const remote = await pull(client, types);
|
|
661
1018
|
const plan = diff(localDefs, remote.map((r) => normalizeRemote(r.definition)));
|
|
662
|
-
|
|
1019
|
+
const warnings = await detectScopeFlips(client, definitions, plan);
|
|
1020
|
+
return { plan, remote, definitions, warnings };
|
|
663
1021
|
}
|
|
664
1022
|
|
|
665
1023
|
// src/cli/format.ts
|
|
@@ -690,7 +1048,8 @@ function describeResult(r) {
|
|
|
690
1048
|
|
|
691
1049
|
// src/cli/diff.ts
|
|
692
1050
|
async function runDiff(args) {
|
|
693
|
-
const { plan } = await planFor(args.client, args.schemas);
|
|
1051
|
+
const { plan, warnings } = await planFor(args.client, args.schemas, args.config);
|
|
1052
|
+
for (const w of warnings) console.warn(`Warning: ${w}`);
|
|
694
1053
|
if (plan.length === 0) {
|
|
695
1054
|
console.log("Everything is in sync \u2014 nothing to apply.");
|
|
696
1055
|
} else {
|
|
@@ -702,8 +1061,8 @@ async function runDiff(args) {
|
|
|
702
1061
|
|
|
703
1062
|
// src/cli/push.ts
|
|
704
1063
|
async function runPush(args) {
|
|
705
|
-
const { plan, remote } = await planFor(args.client, args.schemas);
|
|
706
|
-
const
|
|
1064
|
+
const { plan, remote, definitions, warnings } = await planFor(args.client, args.schemas, args.config);
|
|
1065
|
+
for (const w of warnings) console.warn(`Warning: ${w}`);
|
|
707
1066
|
const result = await push(args.client, plan, { definitions, remote }, { allowDestructive: args.allowDestructive });
|
|
708
1067
|
for (const r of result.results) console.log(` ${describeResult(r)}`);
|
|
709
1068
|
console.log(
|
|
@@ -789,11 +1148,11 @@ async function main(argv) {
|
|
|
789
1148
|
}
|
|
790
1149
|
const schemas = await loadSchemas(config.schema);
|
|
791
1150
|
if (args.command === "diff") {
|
|
792
|
-
await runDiff({ client, schemas });
|
|
1151
|
+
await runDiff({ client, schemas, config });
|
|
793
1152
|
return 0;
|
|
794
1153
|
}
|
|
795
1154
|
if (args.command === "push") {
|
|
796
|
-
const result = await runPush({ client, schemas, allowDestructive: args.allowDestructive });
|
|
1155
|
+
const result = await runPush({ client, schemas, config, allowDestructive: args.allowDestructive });
|
|
797
1156
|
return result.ok ? 0 : 2;
|
|
798
1157
|
}
|
|
799
1158
|
console.error(`Unknown command: ${args.command}`);
|