@fmaplabs/meta-manifest 0.2.0 → 0.3.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.
@@ -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",
@@ -286,8 +304,8 @@ function scalarEntries(field, warnings, builder) {
286
304
  jsonArr("file_type_options", "accept");
287
305
  return e;
288
306
  }
289
- function scalarCall(builder, field, warnings) {
290
- const lit = optsLiteral(scalarEntries(field, warnings, builder));
307
+ function scalarCall(builder, field, warnings, extra = []) {
308
+ const lit = optsLiteral([...scalarEntries(field, warnings, builder), ...extra]);
291
309
  return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
292
310
  }
293
311
  function fieldCall(field, typeToIdent, warnings) {
@@ -297,6 +315,7 @@ function fieldCall(field, typeToIdent, warnings) {
297
315
  const max = v(field.validations, "max");
298
316
  const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
299
317
  if (field.required) e.unshift("required: true");
318
+ e.push(...filterableEntry(field));
300
319
  if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
301
320
  return `m.rating(${optsLiteral(e)})`;
302
321
  }
@@ -307,7 +326,11 @@ function fieldCall(field, typeToIdent, warnings) {
307
326
  warnings.push(`unresolved reference on field "${field.key}"`);
308
327
  return `m.json() /* TODO: unmapped reference */`;
309
328
  }
310
- return field.required ? `m.ref(() => ${ident}, { required: true })` : `m.ref(() => ${ident})`;
329
+ const refEntries = [];
330
+ if (field.required) refEntries.push("required: true");
331
+ refEntries.push(...filterableEntry(field));
332
+ const opts = optsLiteral(refEntries);
333
+ return opts ? `m.ref(() => ${ident}, ${opts})` : `m.ref(() => ${ident})`;
311
334
  }
312
335
  if (type.startsWith("list.")) {
313
336
  const inner = type.slice("list.".length);
@@ -317,6 +340,7 @@ function fieldCall(field, typeToIdent, warnings) {
317
340
  const max = v(field.validations, "list.max");
318
341
  if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
319
342
  if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
343
+ listEntries.push(...filterableEntry(field));
320
344
  const listOpts = optsLiteral(listEntries);
321
345
  let innerCall;
322
346
  if (inner === "metaobject_reference") {
@@ -335,17 +359,53 @@ function fieldCall(field, typeToIdent, warnings) {
335
359
  }
336
360
  return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
337
361
  }
338
- if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
362
+ if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings, filterableEntry(field));
339
363
  warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
340
364
  return `m.json() /* TODO: unmapped type ${type} */`;
341
365
  }
366
+ function accessSource(access) {
367
+ if (!access) return "";
368
+ const parts = [];
369
+ if (access.admin === "MERCHANT_READ_WRITE") parts.push(`admin: "merchant_read_write"`);
370
+ if (access.storefront === "PUBLIC_READ") parts.push(`storefront: "public_read"`);
371
+ if (access.customerAccount === "READ") parts.push(`customerAccount: "read"`);
372
+ return parts.length ? `{ ${parts.join(", ")} }` : "";
373
+ }
374
+ function capabilitiesSource(caps) {
375
+ if (!caps) return "";
376
+ const parts = [];
377
+ if (caps.publishable?.enabled) parts.push(`publishable: true`);
378
+ if (caps.translatable?.enabled) parts.push(`translatable: true`);
379
+ if (caps.renderable?.enabled) {
380
+ const d = caps.renderable.data;
381
+ const dparts = [];
382
+ if (d?.metaTitleKey != null) dparts.push(`metaTitleKey: ${JSON.stringify(d.metaTitleKey)}`);
383
+ if (d?.metaDescriptionKey != null) dparts.push(`metaDescriptionKey: ${JSON.stringify(d.metaDescriptionKey)}`);
384
+ parts.push(dparts.length ? `renderable: { ${dparts.join(", ")} }` : `renderable: true`);
385
+ }
386
+ if (caps.onlineStore?.enabled && caps.onlineStore.data?.urlHandle != null) {
387
+ parts.push(`onlineStore: { urlHandle: ${JSON.stringify(caps.onlineStore.data.urlHandle)} }`);
388
+ }
389
+ return parts.length ? `{ ${parts.join(", ")} }` : "";
390
+ }
391
+ function defConfigLines(def) {
392
+ const lines = [];
393
+ if (isMerchant(def.type)) lines.push(` scope: "merchant",`);
394
+ if (def.name) lines.push(` name: ${JSON.stringify(def.name)},`);
395
+ if (def.description != null) lines.push(` description: ${JSON.stringify(def.description)},`);
396
+ if (def.displayNameKey != null) lines.push(` displayName: ${JSON.stringify(def.displayNameKey)},`);
397
+ const access = accessSource(def.access);
398
+ if (access) lines.push(` access: ${access},`);
399
+ const caps = capabilitiesSource(def.capabilities);
400
+ if (caps) lines.push(` capabilities: ${caps},`);
401
+ return lines.length ? `
402
+ ${lines.join("\n")}` : "";
403
+ }
342
404
  function defSource(def, typeToIdent, warnings) {
343
405
  const ident = typeToIdent.get(def.type);
344
406
  const handle = handleOf(def.type);
345
407
  const fields = def.fields.map((f) => ` ${f.key}: ${fieldCall(f, typeToIdent, warnings)},`).join("\n");
346
- const name = def.name ? `
347
- name: ${JSON.stringify(def.name)},` : "";
348
- return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${name}
408
+ return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${defConfigLines(def)}
349
409
  fields: {
350
410
  ${fields}
351
411
  },
@@ -405,6 +465,42 @@ function sameValidations(a, b) {
405
465
  const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
406
466
  return norm(a) === norm(b);
407
467
  }
468
+ var CAP_KEYS = ["publishable", "translatable", "renderable", "onlineStore"];
469
+ function accessChanged(local, remote) {
470
+ for (const key of ["admin", "storefront", "customerAccount"]) {
471
+ const lv = local[key];
472
+ if (lv != null && remote?.[key] !== lv) return true;
473
+ }
474
+ return false;
475
+ }
476
+ function capabilitiesChanged(local, remote) {
477
+ for (const key of CAP_KEYS) {
478
+ const lc = local[key];
479
+ if (!lc) continue;
480
+ const rc = remote?.[key];
481
+ if ((rc?.enabled ?? false) !== lc.enabled) return true;
482
+ const ldata = lc.data;
483
+ if (ldata) {
484
+ const rdata = rc?.data;
485
+ for (const [k, v2] of Object.entries(ldata)) {
486
+ if (v2 != null && rdata?.[k] !== v2) return true;
487
+ }
488
+ }
489
+ }
490
+ return false;
491
+ }
492
+ function disablesOnlineStore(local, remote) {
493
+ return local.onlineStore?.enabled === false && remote?.onlineStore?.enabled === true;
494
+ }
495
+ function definitionChanges(local, remote) {
496
+ const changes = [];
497
+ if (local.name != null && local.name !== remote.name) changes.push("name");
498
+ if (local.description != null && local.description !== remote.description) changes.push("description");
499
+ if (local.displayNameKey != null && local.displayNameKey !== remote.displayNameKey) changes.push("displayNameKey");
500
+ if (local.access && accessChanged(local.access, remote.access)) changes.push("access");
501
+ if (local.capabilities && capabilitiesChanged(local.capabilities, remote.capabilities)) changes.push("capabilities");
502
+ return changes;
503
+ }
408
504
  function diff(local, remote) {
409
505
  const ops = [];
410
506
  const remoteByType = new Map(remote.map((d) => [d.type, d]));
@@ -428,6 +524,7 @@ function diff(local, remote) {
428
524
  }
429
525
  const changes = {};
430
526
  if (rf.required !== lf.required) changes.required = lf.required;
527
+ if (rf.filterable !== lf.filterable) changes.filterable = lf.filterable;
431
528
  if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
432
529
  if (Object.keys(changes).length) {
433
530
  ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
@@ -438,49 +535,176 @@ function diff(local, remote) {
438
535
  ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
439
536
  }
440
537
  }
538
+ const metaChanges = definitionChanges(localDef, remoteDef);
539
+ if (metaChanges.length) {
540
+ const destructive = localDef.capabilities ? disablesOnlineStore(localDef.capabilities, remoteDef.capabilities) : false;
541
+ ops.push({
542
+ kind: "updateDefinition",
543
+ type: localDef.type,
544
+ changes: metaChanges,
545
+ ...destructive ? { destructive: true } : {}
546
+ });
547
+ }
441
548
  }
442
549
  return ops;
443
550
  }
444
551
 
445
552
  // src/sync/normalize.ts
446
- function normalizeLocal(schema) {
447
- const def = schema.toDefinitionInput();
448
- return {
553
+ function localCapabilities(caps) {
554
+ const out = {};
555
+ if (caps?.publishable) out.publishable = { enabled: caps.publishable.enabled };
556
+ if (caps?.translatable) out.translatable = { enabled: caps.translatable.enabled };
557
+ if (caps?.renderable) {
558
+ out.renderable = { enabled: caps.renderable.enabled };
559
+ if (caps.renderable.data) out.renderable.data = { ...caps.renderable.data };
560
+ }
561
+ out.onlineStore = caps?.onlineStore ? { enabled: caps.onlineStore.enabled, data: { urlHandle: caps.onlineStore.data?.urlHandle } } : { enabled: false };
562
+ return out;
563
+ }
564
+ function normalizeDefinition(def) {
565
+ const out = {
449
566
  type: def.type,
450
567
  name: def.name,
568
+ capabilities: localCapabilities(def.capabilities),
451
569
  fields: def.fieldDefinitions.map((f) => ({
452
570
  key: f.key,
453
571
  type: f.type,
454
572
  required: f.required,
573
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
455
574
  validations: f.validations
456
575
  }))
457
576
  };
577
+ if (def.description != null) out.description = def.description;
578
+ if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
579
+ if (def.access) out.access = { ...def.access };
580
+ return out;
581
+ }
582
+ function remoteAccess(access) {
583
+ if (!access) return void 0;
584
+ const out = {};
585
+ if (access.admin != null) out.admin = access.admin;
586
+ if (access.storefront != null) out.storefront = access.storefront;
587
+ if (access.customerAccount != null) out.customerAccount = access.customerAccount;
588
+ return out;
589
+ }
590
+ function remoteCapabilities(caps) {
591
+ const out = {};
592
+ if (!caps) return out;
593
+ if (caps.publishable) out.publishable = { enabled: !!caps.publishable.enabled };
594
+ if (caps.translatable) out.translatable = { enabled: !!caps.translatable.enabled };
595
+ if (caps.renderable) {
596
+ out.renderable = { enabled: !!caps.renderable.enabled };
597
+ const d = caps.renderable.data;
598
+ if (d) {
599
+ const data = {};
600
+ if (d.metaTitleKey != null) data.metaTitleKey = d.metaTitleKey;
601
+ if (d.metaDescriptionKey != null) data.metaDescriptionKey = d.metaDescriptionKey;
602
+ if (Object.keys(data).length) out.renderable.data = data;
603
+ }
604
+ }
605
+ if (caps.onlineStore) {
606
+ out.onlineStore = { enabled: !!caps.onlineStore.enabled };
607
+ if (caps.onlineStore.data?.urlHandle != null) out.onlineStore.data = { urlHandle: caps.onlineStore.data.urlHandle };
608
+ }
609
+ return out;
458
610
  }
459
611
  function normalizeRemote(def) {
460
- return {
612
+ const out = {
461
613
  type: def.type,
462
614
  name: def.name,
615
+ capabilities: remoteCapabilities(def.capabilities),
463
616
  fields: def.fieldDefinitions.map((f) => ({
464
617
  key: f.key,
465
618
  type: typeof f.type === "string" ? f.type : f.type.name,
466
619
  required: f.required,
620
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
467
621
  validations: f.validations ?? []
468
622
  }))
469
623
  };
624
+ if (def.description != null) out.description = def.description;
625
+ if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
626
+ const access = remoteAccess(def.access);
627
+ if (access) out.access = access;
628
+ return out;
629
+ }
630
+
631
+ // src/sync/resolve.ts
632
+ var APP_PREFIX2 = "$app:";
633
+ function effectiveScope(schema, config) {
634
+ return schema.config.scope ?? config.scope ?? "app";
635
+ }
636
+ function effectiveType(handle, scope) {
637
+ return scope === "merchant" ? handle : `${APP_PREFIX2}${handle}`;
638
+ }
639
+ function rewriteReference(v2, effByCanonical) {
640
+ if (v2.name === "metaobject_definition_type") {
641
+ const mapped = effByCanonical.get(v2.value);
642
+ return mapped ? { ...v2, value: mapped } : v2;
643
+ }
644
+ if (v2.name === "metaobject_definition_types") {
645
+ try {
646
+ const parsed = JSON.parse(v2.value);
647
+ if (Array.isArray(parsed)) {
648
+ const rewritten = parsed.map((t) => typeof t === "string" ? effByCanonical.get(t) ?? t : t);
649
+ return { ...v2, value: JSON.stringify(rewritten) };
650
+ }
651
+ } catch {
652
+ }
653
+ }
654
+ return v2;
655
+ }
656
+ function resolveAdmin(out, handle, scope, config) {
657
+ const explicitAdmin = out.access?.admin;
658
+ if (scope === "merchant") {
659
+ if (explicitAdmin != null) {
660
+ throw new Error(
661
+ `"${handle}" is merchant-scoped but sets access.admin; admin access is only valid on app-scoped metaobjects.`
662
+ );
663
+ }
664
+ return;
665
+ }
666
+ if (explicitAdmin == null) {
667
+ const admin = config.merchantEditable ? "MERCHANT_READ_WRITE" : "MERCHANT_READ";
668
+ out.access = { ...out.access, admin };
669
+ }
670
+ }
671
+ function resolveDefinitions(schemas, config = {}) {
672
+ const effByCanonical = /* @__PURE__ */ new Map();
673
+ for (const s of schemas) {
674
+ effByCanonical.set(`${APP_PREFIX2}${s.handle}`, effectiveType(s.handle, effectiveScope(s, config)));
675
+ }
676
+ return schemas.map((s) => {
677
+ const scope = effectiveScope(s, config);
678
+ const base = s.toDefinitionInput();
679
+ const fieldDefinitions = base.fieldDefinitions.map((f) => ({
680
+ ...f,
681
+ validations: f.validations.map((v2) => rewriteReference(v2, effByCanonical))
682
+ }));
683
+ const out = { ...base, type: effectiveType(s.handle, scope), fieldDefinitions };
684
+ resolveAdmin(out, s.handle, scope, config);
685
+ return out;
686
+ });
470
687
  }
471
688
 
472
689
  // src/sync/pull.ts
690
+ function toDefinition(type, node) {
691
+ return {
692
+ type,
693
+ name: node.name,
694
+ description: node.description,
695
+ displayNameKey: node.displayNameKey,
696
+ access: node.access,
697
+ capabilities: node.capabilities,
698
+ fieldDefinitions: node.fieldDefinitions
699
+ };
700
+ }
473
701
  async function pull(client, types) {
474
702
  const out = [];
475
703
  for (const type of types) {
476
704
  const data = await execute(client, PULL_DEFINITION_QUERY, { type });
477
705
  const node = data.metaobjectDefinitionByType;
478
706
  if (!node) continue;
479
- out.push({
480
- id: node.id,
481
- type,
482
- definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
483
- });
707
+ out.push({ id: node.id, type, definition: toDefinition(type, node) });
484
708
  }
485
709
  return out;
486
710
  }
@@ -498,7 +722,7 @@ async function pullAll(client, opts = {}) {
498
722
  const canonical = toCanonicalType(node.type);
499
723
  if (appOwnedOnly && !canonical) continue;
500
724
  const type = canonical ?? node.type;
501
- out.push({ id: node.id, type, definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions } });
725
+ out.push({ id: node.id, type, definition: toDefinition(type, node) });
502
726
  }
503
727
  after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
504
728
  } while (after !== null);
@@ -506,6 +730,25 @@ async function pullAll(client, opts = {}) {
506
730
  }
507
731
 
508
732
  // src/sync/push.ts
733
+ function definitionUpdateFor(def, changes) {
734
+ const out = {};
735
+ for (const c of changes) {
736
+ if (c === "name") out.name = def.name;
737
+ else if (c === "description") out.description = def.description;
738
+ else if (c === "displayNameKey") out.displayNameKey = def.displayNameKey;
739
+ else if (c === "access") out.access = def.access;
740
+ else if (c === "capabilities") {
741
+ const caps = def.capabilities;
742
+ const payload = {};
743
+ if (caps?.publishable) payload.publishable = caps.publishable;
744
+ if (caps?.translatable) payload.translatable = caps.translatable;
745
+ if (caps?.renderable) payload.renderable = caps.renderable;
746
+ payload.onlineStore = caps?.onlineStore ?? { enabled: false };
747
+ out.capabilities = payload;
748
+ }
749
+ }
750
+ return out;
751
+ }
509
752
  function fieldInputFor(defByType, type, key) {
510
753
  return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
511
754
  }
@@ -606,14 +849,22 @@ async function push(client, plan, sources, options) {
606
849
  if (id2) idByType.set(op.type, id2);
607
850
  return { op, status: "applied", id: id2 };
608
851
  }
609
- const destructive = op.kind === "removeField" || op.kind === "changeFieldType";
852
+ const destructive = "destructive" in op && op.destructive === true;
610
853
  if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
611
854
  if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
612
855
  const id = idByType.get(op.type);
613
856
  if (id == null) return { op, status: "blocked", reason: `no definition id for "${op.type}"` };
614
- const fieldDefinitions = fieldOpsFor(op);
615
- if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
616
- const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition: { fieldDefinitions } });
857
+ let definition;
858
+ if (op.kind === "updateDefinition") {
859
+ const def = defByType.get(op.type);
860
+ if (!def) return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
861
+ definition = definitionUpdateFor(def, op.changes);
862
+ } else {
863
+ const fieldDefinitions = fieldOpsFor(op);
864
+ if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
865
+ definition = { fieldDefinitions };
866
+ }
867
+ const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
617
868
  const payload = data.metaobjectDefinitionUpdate;
618
869
  if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
619
870
  return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
@@ -634,6 +885,9 @@ async function push(client, plan, sources, options) {
634
885
  validations: field.validations
635
886
  };
636
887
  if (field.description != null) update.description = field.description;
888
+ if ("filterable" in op.changes) {
889
+ update.capabilities = { adminFilterable: { enabled: op.changes.filterable ?? false } };
890
+ }
637
891
  return [{ update }];
638
892
  }
639
893
  case "removeField":
@@ -654,12 +908,31 @@ async function push(client, plan, sources, options) {
654
908
  }
655
909
 
656
910
  // src/cli/plan.ts
657
- async function planFor(client, schemas) {
658
- const types = schemas.map((s) => s.type);
659
- const localDefs = schemas.map(normalizeLocal);
911
+ var APP_PREFIX3 = "$app:";
912
+ async function detectScopeFlips(client, definitions, plan) {
913
+ const merchant = definitions.filter((d) => !d.type.startsWith(APP_PREFIX3));
914
+ if (merchant.length === 0) return [];
915
+ const created = new Set(plan.filter((op) => op.kind === "createDefinition").map((op) => op.type));
916
+ const shadows = await pull(client, merchant.map((d) => `${APP_PREFIX3}${d.type}`));
917
+ const shadowTypes = new Set(shadows.map((s) => s.type));
918
+ const warnings = [];
919
+ for (const d of merchant) {
920
+ if (created.has(d.type) && shadowTypes.has(`${APP_PREFIX3}${d.type}`)) {
921
+ warnings.push(
922
+ `"${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.`
923
+ );
924
+ }
925
+ }
926
+ return warnings;
927
+ }
928
+ async function planFor(client, schemas, config = {}) {
929
+ const definitions = resolveDefinitions(schemas, config);
930
+ const types = definitions.map((d) => d.type);
931
+ const localDefs = definitions.map(normalizeDefinition);
660
932
  const remote = await pull(client, types);
661
933
  const plan = diff(localDefs, remote.map((r) => normalizeRemote(r.definition)));
662
- return { plan, remote };
934
+ const warnings = await detectScopeFlips(client, definitions, plan);
935
+ return { plan, remote, definitions, warnings };
663
936
  }
664
937
 
665
938
  // src/cli/format.ts
@@ -690,7 +963,8 @@ function describeResult(r) {
690
963
 
691
964
  // src/cli/diff.ts
692
965
  async function runDiff(args) {
693
- const { plan } = await planFor(args.client, args.schemas);
966
+ const { plan, warnings } = await planFor(args.client, args.schemas, args.config);
967
+ for (const w of warnings) console.warn(`Warning: ${w}`);
694
968
  if (plan.length === 0) {
695
969
  console.log("Everything is in sync \u2014 nothing to apply.");
696
970
  } else {
@@ -702,8 +976,8 @@ async function runDiff(args) {
702
976
 
703
977
  // src/cli/push.ts
704
978
  async function runPush(args) {
705
- const { plan, remote } = await planFor(args.client, args.schemas);
706
- const definitions = args.schemas.map((s) => s.toDefinitionInput());
979
+ const { plan, remote, definitions, warnings } = await planFor(args.client, args.schemas, args.config);
980
+ for (const w of warnings) console.warn(`Warning: ${w}`);
707
981
  const result = await push(args.client, plan, { definitions, remote }, { allowDestructive: args.allowDestructive });
708
982
  for (const r of result.results) console.log(` ${describeResult(r)}`);
709
983
  console.log(
@@ -789,11 +1063,11 @@ async function main(argv) {
789
1063
  }
790
1064
  const schemas = await loadSchemas(config.schema);
791
1065
  if (args.command === "diff") {
792
- await runDiff({ client, schemas });
1066
+ await runDiff({ client, schemas, config });
793
1067
  return 0;
794
1068
  }
795
1069
  if (args.command === "push") {
796
- const result = await runPush({ client, schemas, allowDestructive: args.allowDestructive });
1070
+ const result = await runPush({ client, schemas, config, allowDestructive: args.allowDestructive });
797
1071
  return result.ok ? 0 : 2;
798
1072
  }
799
1073
  console.error(`Unknown command: ${args.command}`);