@fmaplabs/meta-manifest 0.1.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 }
@@ -149,9 +161,19 @@ async function loadSchemas(schemaPath) {
149
161
  return mod.schemas;
150
162
  }
151
163
 
164
+ // src/cli/load-env.ts
165
+ var import_node_path2 = require("path");
166
+ function loadDotEnv(cwd = process.cwd()) {
167
+ try {
168
+ process.loadEnvFile((0, import_node_path2.resolve)(cwd, ".env"));
169
+ } catch (err) {
170
+ if (err.code !== "ENOENT") throw err;
171
+ }
172
+ }
173
+
152
174
  // src/cli/init.ts
153
175
  var import_node_fs = require("fs");
154
- var import_node_path2 = require("path");
176
+ var import_node_path3 = require("path");
155
177
  var CONFIG_TEMPLATE = `import { defineConfig } from "@fmaplabs/meta-manifest";
156
178
 
157
179
  export default defineConfig({
@@ -176,9 +198,9 @@ async function runInit(opts = {}) {
176
198
  const cwd = opts.cwd ?? process.cwd();
177
199
  const created = [];
178
200
  const write = (rel, contents) => {
179
- const abs = (0, import_node_path2.join)(cwd, rel);
201
+ const abs = (0, import_node_path3.join)(cwd, rel);
180
202
  if ((0, import_node_fs.existsSync)(abs)) return;
181
- (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(abs), { recursive: true });
203
+ (0, import_node_fs.mkdirSync)((0, import_node_path3.dirname)(abs), { recursive: true });
182
204
  (0, import_node_fs.writeFileSync)(abs, contents);
183
205
  created.push(rel);
184
206
  };
@@ -186,7 +208,7 @@ async function runInit(opts = {}) {
186
208
  write("src/schema.ts", SCHEMA_TEMPLATE);
187
209
  if (created.length) {
188
210
  console.log(`Created: ${created.join(", ")}`);
189
- console.log("Next: set SHOPIFY_ADMIN_TOKEN in your env, edit meta-manifest.config.ts, then run `mm diff`.");
211
+ console.log("Next: set SHOPIFY_ADMIN_TOKEN (export it or add it to .env), edit meta-manifest.config.ts, then run `mm diff`.");
190
212
  } else {
191
213
  console.log("Nothing to do \u2014 config and schema already exist.");
192
214
  }
@@ -195,6 +217,12 @@ async function runInit(opts = {}) {
195
217
 
196
218
  // src/codegen.ts
197
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
+ }
198
226
  var SIMPLE = {
199
227
  single_line_text_field: "text",
200
228
  multi_line_text_field: "multilineText",
@@ -276,8 +304,8 @@ function scalarEntries(field, warnings, builder) {
276
304
  jsonArr("file_type_options", "accept");
277
305
  return e;
278
306
  }
279
- function scalarCall(builder, field, warnings) {
280
- const lit = optsLiteral(scalarEntries(field, warnings, builder));
307
+ function scalarCall(builder, field, warnings, extra = []) {
308
+ const lit = optsLiteral([...scalarEntries(field, warnings, builder), ...extra]);
281
309
  return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
282
310
  }
283
311
  function fieldCall(field, typeToIdent, warnings) {
@@ -287,6 +315,7 @@ function fieldCall(field, typeToIdent, warnings) {
287
315
  const max = v(field.validations, "max");
288
316
  const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
289
317
  if (field.required) e.unshift("required: true");
318
+ e.push(...filterableEntry(field));
290
319
  if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
291
320
  return `m.rating(${optsLiteral(e)})`;
292
321
  }
@@ -297,7 +326,11 @@ function fieldCall(field, typeToIdent, warnings) {
297
326
  warnings.push(`unresolved reference on field "${field.key}"`);
298
327
  return `m.json() /* TODO: unmapped reference */`;
299
328
  }
300
- 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})`;
301
334
  }
302
335
  if (type.startsWith("list.")) {
303
336
  const inner = type.slice("list.".length);
@@ -307,6 +340,7 @@ function fieldCall(field, typeToIdent, warnings) {
307
340
  const max = v(field.validations, "list.max");
308
341
  if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
309
342
  if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
343
+ listEntries.push(...filterableEntry(field));
310
344
  const listOpts = optsLiteral(listEntries);
311
345
  let innerCall;
312
346
  if (inner === "metaobject_reference") {
@@ -325,17 +359,53 @@ function fieldCall(field, typeToIdent, warnings) {
325
359
  }
326
360
  return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
327
361
  }
328
- if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
362
+ if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings, filterableEntry(field));
329
363
  warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
330
364
  return `m.json() /* TODO: unmapped type ${type} */`;
331
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
+ }
332
404
  function defSource(def, typeToIdent, warnings) {
333
405
  const ident = typeToIdent.get(def.type);
334
406
  const handle = handleOf(def.type);
335
407
  const fields = def.fields.map((f) => ` ${f.key}: ${fieldCall(f, typeToIdent, warnings)},`).join("\n");
336
- const name = def.name ? `
337
- name: ${JSON.stringify(def.name)},` : "";
338
- return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${name}
408
+ return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${defConfigLines(def)}
339
409
  fields: {
340
410
  ${fields}
341
411
  },
@@ -395,6 +465,42 @@ function sameValidations(a, b) {
395
465
  const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
396
466
  return norm(a) === norm(b);
397
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
+ }
398
504
  function diff(local, remote) {
399
505
  const ops = [];
400
506
  const remoteByType = new Map(remote.map((d) => [d.type, d]));
@@ -418,6 +524,7 @@ function diff(local, remote) {
418
524
  }
419
525
  const changes = {};
420
526
  if (rf.required !== lf.required) changes.required = lf.required;
527
+ if (rf.filterable !== lf.filterable) changes.filterable = lf.filterable;
421
528
  if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
422
529
  if (Object.keys(changes).length) {
423
530
  ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
@@ -428,49 +535,176 @@ function diff(local, remote) {
428
535
  ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
429
536
  }
430
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
+ }
431
548
  }
432
549
  return ops;
433
550
  }
434
551
 
435
552
  // src/sync/normalize.ts
436
- function normalizeLocal(schema) {
437
- const def = schema.toDefinitionInput();
438
- 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 = {
439
566
  type: def.type,
440
567
  name: def.name,
568
+ capabilities: localCapabilities(def.capabilities),
441
569
  fields: def.fieldDefinitions.map((f) => ({
442
570
  key: f.key,
443
571
  type: f.type,
444
572
  required: f.required,
573
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
445
574
  validations: f.validations
446
575
  }))
447
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;
448
610
  }
449
611
  function normalizeRemote(def) {
450
- return {
612
+ const out = {
451
613
  type: def.type,
452
614
  name: def.name,
615
+ capabilities: remoteCapabilities(def.capabilities),
453
616
  fields: def.fieldDefinitions.map((f) => ({
454
617
  key: f.key,
455
618
  type: typeof f.type === "string" ? f.type : f.type.name,
456
619
  required: f.required,
620
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
457
621
  validations: f.validations ?? []
458
622
  }))
459
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
+ });
460
687
  }
461
688
 
462
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
+ }
463
701
  async function pull(client, types) {
464
702
  const out = [];
465
703
  for (const type of types) {
466
704
  const data = await execute(client, PULL_DEFINITION_QUERY, { type });
467
705
  const node = data.metaobjectDefinitionByType;
468
706
  if (!node) continue;
469
- out.push({
470
- id: node.id,
471
- type,
472
- definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
473
- });
707
+ out.push({ id: node.id, type, definition: toDefinition(type, node) });
474
708
  }
475
709
  return out;
476
710
  }
@@ -488,7 +722,7 @@ async function pullAll(client, opts = {}) {
488
722
  const canonical = toCanonicalType(node.type);
489
723
  if (appOwnedOnly && !canonical) continue;
490
724
  const type = canonical ?? node.type;
491
- 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) });
492
726
  }
493
727
  after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
494
728
  } while (after !== null);
@@ -496,6 +730,25 @@ async function pullAll(client, opts = {}) {
496
730
  }
497
731
 
498
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
+ }
499
752
  function fieldInputFor(defByType, type, key) {
500
753
  return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
501
754
  }
@@ -596,14 +849,22 @@ async function push(client, plan, sources, options) {
596
849
  if (id2) idByType.set(op.type, id2);
597
850
  return { op, status: "applied", id: id2 };
598
851
  }
599
- const destructive = op.kind === "removeField" || op.kind === "changeFieldType";
852
+ const destructive = "destructive" in op && op.destructive === true;
600
853
  if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
601
854
  if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
602
855
  const id = idByType.get(op.type);
603
856
  if (id == null) return { op, status: "blocked", reason: `no definition id for "${op.type}"` };
604
- const fieldDefinitions = fieldOpsFor(op);
605
- if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
606
- 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 });
607
868
  const payload = data.metaobjectDefinitionUpdate;
608
869
  if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
609
870
  return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
@@ -624,6 +885,9 @@ async function push(client, plan, sources, options) {
624
885
  validations: field.validations
625
886
  };
626
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
+ }
627
891
  return [{ update }];
628
892
  }
629
893
  case "removeField":
@@ -644,12 +908,31 @@ async function push(client, plan, sources, options) {
644
908
  }
645
909
 
646
910
  // src/cli/plan.ts
647
- async function planFor(client, schemas) {
648
- const types = schemas.map((s) => s.type);
649
- 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);
650
932
  const remote = await pull(client, types);
651
933
  const plan = diff(localDefs, remote.map((r) => normalizeRemote(r.definition)));
652
- return { plan, remote };
934
+ const warnings = await detectScopeFlips(client, definitions, plan);
935
+ return { plan, remote, definitions, warnings };
653
936
  }
654
937
 
655
938
  // src/cli/format.ts
@@ -680,7 +963,8 @@ function describeResult(r) {
680
963
 
681
964
  // src/cli/diff.ts
682
965
  async function runDiff(args) {
683
- 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}`);
684
968
  if (plan.length === 0) {
685
969
  console.log("Everything is in sync \u2014 nothing to apply.");
686
970
  } else {
@@ -692,8 +976,8 @@ async function runDiff(args) {
692
976
 
693
977
  // src/cli/push.ts
694
978
  async function runPush(args) {
695
- const { plan, remote } = await planFor(args.client, args.schemas);
696
- 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}`);
697
981
  const result = await push(args.client, plan, { definitions, remote }, { allowDestructive: args.allowDestructive });
698
982
  for (const r of result.results) console.log(` ${describeResult(r)}`);
699
983
  console.log(
@@ -707,7 +991,7 @@ async function runPush(args) {
707
991
 
708
992
  // src/cli/pull.ts
709
993
  var import_node_fs2 = require("fs");
710
- var import_node_path3 = require("path");
994
+ var import_node_path4 = require("path");
711
995
  async function maybeFormat(source) {
712
996
  try {
713
997
  const spec = "prettier";
@@ -721,11 +1005,11 @@ async function runPull(args) {
721
1005
  const remote = await pullAll(args.client);
722
1006
  const defs = remote.map((r) => normalizeRemote(r.definition));
723
1007
  const source = await maybeFormat(generateSchemaSource(defs));
724
- const abs = (0, import_node_path3.resolve)(process.cwd(), args.schemaPath);
1008
+ const abs = (0, import_node_path4.resolve)(process.cwd(), args.schemaPath);
725
1009
  if ((0, import_node_fs2.existsSync)(abs) && !args.force) {
726
1010
  console.warn(`Overwriting existing ${args.schemaPath}.`);
727
1011
  }
728
- (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(abs), { recursive: true });
1012
+ (0, import_node_fs2.mkdirSync)((0, import_node_path4.dirname)(abs), { recursive: true });
729
1013
  (0, import_node_fs2.writeFileSync)(abs, source);
730
1014
  console.log(`Wrote ${defs.length} definition${defs.length === 1 ? "" : "s"} to ${args.schemaPath}.`);
731
1015
  return { written: abs, count: defs.length };
@@ -770,6 +1054,7 @@ async function main(argv) {
770
1054
  await runInit();
771
1055
  return 0;
772
1056
  }
1057
+ loadDotEnv();
773
1058
  const config = await loadConfig(args.config);
774
1059
  const client = createAdminClient(config);
775
1060
  if (args.command === "pull") {
@@ -778,11 +1063,11 @@ async function main(argv) {
778
1063
  }
779
1064
  const schemas = await loadSchemas(config.schema);
780
1065
  if (args.command === "diff") {
781
- await runDiff({ client, schemas });
1066
+ await runDiff({ client, schemas, config });
782
1067
  return 0;
783
1068
  }
784
1069
  if (args.command === "push") {
785
- const result = await runPush({ client, schemas, allowDestructive: args.allowDestructive });
1070
+ const result = await runPush({ client, schemas, config, allowDestructive: args.allowDestructive });
786
1071
  return result.ok ? 0 : 2;
787
1072
  }
788
1073
  console.error(`Unknown command: ${args.command}`);