@fmaplabs/meta-manifest 0.1.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.
@@ -0,0 +1,805 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cli/index.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ main: () => main,
25
+ parseArgs: () => parseArgs
26
+ });
27
+ module.exports = __toCommonJS(cli_exports);
28
+
29
+ // src/sync/client.ts
30
+ var PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {
31
+ metaobjectDefinitionByType(type: $type) {
32
+ id
33
+ name
34
+ type
35
+ description
36
+ displayNameKey
37
+ fieldDefinitions {
38
+ key
39
+ name
40
+ description
41
+ required
42
+ type { name }
43
+ validations { name value }
44
+ }
45
+ access { admin storefront }
46
+ capabilities {
47
+ publishable { enabled }
48
+ translatable { enabled }
49
+ renderable { enabled }
50
+ }
51
+ }
52
+ }`;
53
+ var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
54
+ metaobjectDefinitions(first: 50, after: $after) {
55
+ nodes {
56
+ id
57
+ name
58
+ type
59
+ fieldDefinitions {
60
+ key
61
+ name
62
+ description
63
+ required
64
+ type { name }
65
+ validations { name value }
66
+ }
67
+ }
68
+ pageInfo { hasNextPage endCursor }
69
+ }
70
+ }`;
71
+ var CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
72
+ metaobjectDefinitionCreate(definition: $definition) {
73
+ metaobjectDefinition { id type }
74
+ userErrors { field message code }
75
+ }
76
+ }`;
77
+ var UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {
78
+ metaobjectDefinitionUpdate(id: $id, definition: $definition) {
79
+ metaobjectDefinition { id type }
80
+ userErrors { field message code }
81
+ }
82
+ }`;
83
+ var SyncTransportError = class extends Error {
84
+ constructor(message, errors) {
85
+ super(message);
86
+ this.errors = errors;
87
+ this.name = "SyncTransportError";
88
+ }
89
+ errors;
90
+ };
91
+ async function execute(client, query, variables) {
92
+ const result = await client(query, variables ? { variables } : void 0);
93
+ if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {
94
+ throw new SyncTransportError("GraphQL request failed", result.errors);
95
+ }
96
+ return result.data;
97
+ }
98
+
99
+ // src/config.ts
100
+ var DEFAULT_API_VERSION = "2026-07";
101
+ function validateConfig(raw) {
102
+ const c = raw;
103
+ for (const key of ["shop", "accessToken", "schema"]) {
104
+ if (!c || typeof c[key] !== "string" || c[key] === "") {
105
+ throw new Error(`Invalid config: missing or empty "${key}".`);
106
+ }
107
+ }
108
+ return c;
109
+ }
110
+
111
+ // src/node/client.ts
112
+ function createAdminClient(opts) {
113
+ const version = opts.apiVersion ?? DEFAULT_API_VERSION;
114
+ const endpoint = `https://${opts.shop}/admin/api/${version}/graphql.json`;
115
+ return async (query, options) => {
116
+ let res;
117
+ try {
118
+ res = await fetch(endpoint, {
119
+ method: "POST",
120
+ headers: { "Content-Type": "application/json", "X-Shopify-Access-Token": opts.accessToken },
121
+ body: JSON.stringify({ query, variables: options?.variables })
122
+ });
123
+ } catch (cause) {
124
+ throw new SyncTransportError(`Request to ${opts.shop} failed`, cause);
125
+ }
126
+ if (!res.ok) {
127
+ throw new SyncTransportError(`Admin API returned HTTP ${res.status}`, await res.text().catch(() => null));
128
+ }
129
+ return res.json();
130
+ };
131
+ }
132
+
133
+ // src/cli/load-config.ts
134
+ var import_node_path = require("path");
135
+ var import_jiti = require("jiti");
136
+ var import_meta = {};
137
+ var jiti = (0, import_jiti.createJiti)(import_meta.url);
138
+ async function loadConfig(configPath = "meta-manifest.config.ts") {
139
+ const abs = (0, import_node_path.resolve)(process.cwd(), configPath);
140
+ const mod = await jiti.import(abs);
141
+ return validateConfig(mod.default);
142
+ }
143
+ async function loadSchemas(schemaPath) {
144
+ const abs = (0, import_node_path.resolve)(process.cwd(), schemaPath);
145
+ const mod = await jiti.import(abs);
146
+ if (!Array.isArray(mod.schemas)) {
147
+ throw new Error(`Schema module "${schemaPath}" must export a \`schemas\` array.`);
148
+ }
149
+ return mod.schemas;
150
+ }
151
+
152
+ // src/cli/init.ts
153
+ var import_node_fs = require("fs");
154
+ var import_node_path2 = require("path");
155
+ var CONFIG_TEMPLATE = `import { defineConfig } from "@fmaplabs/meta-manifest";
156
+
157
+ export default defineConfig({
158
+ shop: "my-store.myshopify.com",
159
+ accessToken: process.env.SHOPIFY_ADMIN_TOKEN!,
160
+ schema: "./src/schema.ts",
161
+ });
162
+ `;
163
+ var SCHEMA_TEMPLATE = `import { defineMetaobject, m } from "@fmaplabs/meta-manifest";
164
+
165
+ export const Author = defineMetaobject("author", {
166
+ name: "Author",
167
+ fields: {
168
+ name: m.text({ required: true, max: 120 }),
169
+ bio: m.multilineText(),
170
+ },
171
+ });
172
+
173
+ export const schemas = [Author];
174
+ `;
175
+ async function runInit(opts = {}) {
176
+ const cwd = opts.cwd ?? process.cwd();
177
+ const created = [];
178
+ const write = (rel, contents) => {
179
+ const abs = (0, import_node_path2.join)(cwd, rel);
180
+ if ((0, import_node_fs.existsSync)(abs)) return;
181
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(abs), { recursive: true });
182
+ (0, import_node_fs.writeFileSync)(abs, contents);
183
+ created.push(rel);
184
+ };
185
+ write("meta-manifest.config.ts", CONFIG_TEMPLATE);
186
+ write("src/schema.ts", SCHEMA_TEMPLATE);
187
+ if (created.length) {
188
+ 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`.");
190
+ } else {
191
+ console.log("Nothing to do \u2014 config and schema already exist.");
192
+ }
193
+ return { created };
194
+ }
195
+
196
+ // src/codegen.ts
197
+ var APP_PREFIX = "$app:";
198
+ var SIMPLE = {
199
+ single_line_text_field: "text",
200
+ multi_line_text_field: "multilineText",
201
+ number_integer: "integer",
202
+ number_decimal: "decimal",
203
+ boolean: "boolean",
204
+ date: "date",
205
+ date_time: "dateTime",
206
+ url: "url",
207
+ color: "color",
208
+ json: "json",
209
+ money: "money",
210
+ dimension: "dimension",
211
+ weight: "weight",
212
+ volume: "volume",
213
+ product_reference: "product",
214
+ variant_reference: "variant",
215
+ collection_reference: "collection",
216
+ page_reference: "page",
217
+ file_reference: "file"
218
+ };
219
+ function handleOf(type) {
220
+ return type.startsWith(APP_PREFIX) ? type.slice(APP_PREFIX.length) : type;
221
+ }
222
+ function identOf(type) {
223
+ return handleOf(type).split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
224
+ }
225
+ function v(validations, name) {
226
+ return validations.find((x) => x.name === name)?.value;
227
+ }
228
+ function refTarget(field) {
229
+ const single = v(field.validations, "metaobject_definition_type");
230
+ if (single) return single;
231
+ const many = v(field.validations, "metaobject_definition_types");
232
+ if (many) {
233
+ try {
234
+ const arr = JSON.parse(many);
235
+ if (Array.isArray(arr) && arr.length) return String(arr[0]);
236
+ } catch {
237
+ }
238
+ }
239
+ return void 0;
240
+ }
241
+ function optsLiteral(entries) {
242
+ return entries.length ? `{ ${entries.join(", ")} }` : "";
243
+ }
244
+ function scalarEntries(field, warnings, builder) {
245
+ const e = [];
246
+ if (field.required) e.push("required: true");
247
+ const num = (name, opt) => {
248
+ const val = v(field.validations, name);
249
+ if (val !== void 0) e.push(`${opt}: ${Number(val)}`);
250
+ };
251
+ const str = (name, opt) => {
252
+ const val = v(field.validations, name);
253
+ if (val !== void 0) e.push(`${opt}: ${JSON.stringify(val)}`);
254
+ };
255
+ const jsonArr = (name, opt) => {
256
+ const val = v(field.validations, name);
257
+ if (val !== void 0) {
258
+ try {
259
+ e.push(`${opt}: ${JSON.stringify(JSON.parse(val))}`);
260
+ } catch {
261
+ warnings.push(`could not parse "${name}" on field "${field.key}"`);
262
+ }
263
+ }
264
+ };
265
+ if (builder === "date" || builder === "dateTime") {
266
+ str("min", "min");
267
+ str("max", "max");
268
+ } else {
269
+ num("min", "min");
270
+ num("max", "max");
271
+ }
272
+ str("regex", "regex");
273
+ jsonArr("choices", "choices");
274
+ num("max_precision", "maxPrecision");
275
+ jsonArr("allowed_domains", "allowedDomains");
276
+ jsonArr("file_type_options", "accept");
277
+ return e;
278
+ }
279
+ function scalarCall(builder, field, warnings) {
280
+ const lit = optsLiteral(scalarEntries(field, warnings, builder));
281
+ return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
282
+ }
283
+ function fieldCall(field, typeToIdent, warnings) {
284
+ const type = field.type;
285
+ if (type === "rating") {
286
+ const min = v(field.validations, "min");
287
+ const max = v(field.validations, "max");
288
+ const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
289
+ if (field.required) e.unshift("required: true");
290
+ if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
291
+ return `m.rating(${optsLiteral(e)})`;
292
+ }
293
+ if (type === "metaobject_reference") {
294
+ const target = refTarget(field);
295
+ const ident = target ? typeToIdent.get(target) : void 0;
296
+ if (!ident) {
297
+ warnings.push(`unresolved reference on field "${field.key}"`);
298
+ return `m.json() /* TODO: unmapped reference */`;
299
+ }
300
+ return field.required ? `m.ref(() => ${ident}, { required: true })` : `m.ref(() => ${ident})`;
301
+ }
302
+ if (type.startsWith("list.")) {
303
+ const inner = type.slice("list.".length);
304
+ const listEntries = [];
305
+ if (field.required) listEntries.push("required: true");
306
+ const min = v(field.validations, "list.min");
307
+ const max = v(field.validations, "list.max");
308
+ if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
309
+ if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
310
+ const listOpts = optsLiteral(listEntries);
311
+ let innerCall;
312
+ if (inner === "metaobject_reference") {
313
+ const target = refTarget(field);
314
+ const ident = target ? typeToIdent.get(target) : void 0;
315
+ if (!ident) {
316
+ warnings.push(`unresolved list reference on field "${field.key}"`);
317
+ return `m.json() /* TODO: unmapped list reference */`;
318
+ }
319
+ innerCall = `m.ref(() => ${ident})`;
320
+ } else if (SIMPLE[inner]) {
321
+ innerCall = scalarCall(SIMPLE[inner], { ...field, required: false }, warnings);
322
+ } else {
323
+ warnings.push(`unmapped list element type "${inner}" on field "${field.key}"`);
324
+ return `m.json() /* TODO: unmapped list element ${inner} */`;
325
+ }
326
+ return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
327
+ }
328
+ if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
329
+ warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
330
+ return `m.json() /* TODO: unmapped type ${type} */`;
331
+ }
332
+ function defSource(def, typeToIdent, warnings) {
333
+ const ident = typeToIdent.get(def.type);
334
+ const handle = handleOf(def.type);
335
+ 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}
339
+ fields: {
340
+ ${fields}
341
+ },
342
+ });`;
343
+ }
344
+ function referencedTypes(def) {
345
+ const out = /* @__PURE__ */ new Set();
346
+ for (const f of def.fields) {
347
+ if (f.type === "metaobject_reference" || f.type === "list.metaobject_reference") {
348
+ const t = refTarget(f);
349
+ if (t) out.add(t);
350
+ }
351
+ }
352
+ return out;
353
+ }
354
+ function orderDefs(defs) {
355
+ const byType = new Map(defs.map((d) => [d.type, d]));
356
+ const deps = new Map(defs.map((d) => [d.type, referencedTypes(d)]));
357
+ const ordered = [];
358
+ const placed = /* @__PURE__ */ new Set();
359
+ let progress = true;
360
+ while (ordered.length < defs.length && progress) {
361
+ progress = false;
362
+ for (const d of defs) {
363
+ if (placed.has(d.type)) continue;
364
+ const unmet = [...deps.get(d.type) ?? []].filter((t) => byType.has(t) && !placed.has(t) && t !== d.type);
365
+ if (unmet.length === 0) {
366
+ ordered.push(d);
367
+ placed.add(d.type);
368
+ progress = true;
369
+ }
370
+ }
371
+ }
372
+ for (const d of defs) if (!placed.has(d.type)) ordered.push(d);
373
+ return ordered;
374
+ }
375
+ function generateSchemaSource(defs) {
376
+ const ordered = orderDefs(defs);
377
+ const typeToIdent = new Map(ordered.map((d) => [d.type, identOf(d.type)]));
378
+ const warnings = [];
379
+ const blocks = ordered.map((d) => defSource(d, typeToIdent, warnings));
380
+ const idents = ordered.map((d) => typeToIdent.get(d.type));
381
+ const header = `import { defineMetaobject, m } from "@fmaplabs/meta-manifest";`;
382
+ const body = blocks.join("\n\n");
383
+ const footer = `export const schemas = [${idents.join(", ")}];`;
384
+ for (const w of warnings) console.warn(`[meta-manifest] codegen: ${w}`);
385
+ return `${header}
386
+
387
+ ${body}
388
+
389
+ ${footer}
390
+ `;
391
+ }
392
+
393
+ // src/sync/diff.ts
394
+ function sameValidations(a, b) {
395
+ const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
396
+ return norm(a) === norm(b);
397
+ }
398
+ function diff(local, remote) {
399
+ const ops = [];
400
+ const remoteByType = new Map(remote.map((d) => [d.type, d]));
401
+ for (const localDef of local) {
402
+ const remoteDef = remoteByType.get(localDef.type);
403
+ if (!remoteDef) {
404
+ ops.push({ kind: "createDefinition", type: localDef.type, definition: localDef });
405
+ continue;
406
+ }
407
+ const remoteFields = new Map(remoteDef.fields.map((f) => [f.key, f]));
408
+ const localKeys = new Set(localDef.fields.map((f) => f.key));
409
+ for (const lf of localDef.fields) {
410
+ const rf = remoteFields.get(lf.key);
411
+ if (!rf) {
412
+ ops.push({ kind: "addField", type: localDef.type, field: lf });
413
+ continue;
414
+ }
415
+ if (rf.type !== lf.type) {
416
+ ops.push({ kind: "changeFieldType", type: localDef.type, key: lf.key, from: rf.type, to: lf.type, destructive: true });
417
+ continue;
418
+ }
419
+ const changes = {};
420
+ if (rf.required !== lf.required) changes.required = lf.required;
421
+ if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
422
+ if (Object.keys(changes).length) {
423
+ ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
424
+ }
425
+ }
426
+ for (const rf of remoteDef.fields) {
427
+ if (!localKeys.has(rf.key)) {
428
+ ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
429
+ }
430
+ }
431
+ }
432
+ return ops;
433
+ }
434
+
435
+ // src/sync/normalize.ts
436
+ function normalizeLocal(schema) {
437
+ const def = schema.toDefinitionInput();
438
+ return {
439
+ type: def.type,
440
+ name: def.name,
441
+ fields: def.fieldDefinitions.map((f) => ({
442
+ key: f.key,
443
+ type: f.type,
444
+ required: f.required,
445
+ validations: f.validations
446
+ }))
447
+ };
448
+ }
449
+ function normalizeRemote(def) {
450
+ return {
451
+ type: def.type,
452
+ name: def.name,
453
+ fields: def.fieldDefinitions.map((f) => ({
454
+ key: f.key,
455
+ type: typeof f.type === "string" ? f.type : f.type.name,
456
+ required: f.required,
457
+ validations: f.validations ?? []
458
+ }))
459
+ };
460
+ }
461
+
462
+ // src/sync/pull.ts
463
+ async function pull(client, types) {
464
+ const out = [];
465
+ for (const type of types) {
466
+ const data = await execute(client, PULL_DEFINITION_QUERY, { type });
467
+ const node = data.metaobjectDefinitionByType;
468
+ if (!node) continue;
469
+ out.push({
470
+ id: node.id,
471
+ type,
472
+ definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
473
+ });
474
+ }
475
+ return out;
476
+ }
477
+ function toCanonicalType(resolved) {
478
+ const m2 = /^app--\d+--(.+)$/.exec(resolved);
479
+ return m2 ? `$app:${m2[1]}` : null;
480
+ }
481
+ async function pullAll(client, opts = {}) {
482
+ const appOwnedOnly = opts.appOwnedOnly ?? true;
483
+ const out = [];
484
+ let after = null;
485
+ do {
486
+ const data = await execute(client, LIST_DEFINITIONS_QUERY, { after });
487
+ for (const node of data.metaobjectDefinitions.nodes) {
488
+ const canonical = toCanonicalType(node.type);
489
+ if (appOwnedOnly && !canonical) continue;
490
+ const type = canonical ?? node.type;
491
+ out.push({ id: node.id, type, definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions } });
492
+ }
493
+ after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
494
+ } while (after !== null);
495
+ return out;
496
+ }
497
+
498
+ // src/sync/push.ts
499
+ function fieldInputFor(defByType, type, key) {
500
+ return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
501
+ }
502
+ function referenceEdges(def) {
503
+ const out = [];
504
+ for (const field of def.fieldDefinitions) {
505
+ for (const v2 of field.validations) {
506
+ if (v2.name === "metaobject_definition_type") {
507
+ out.push(v2.value);
508
+ } else if (v2.name === "metaobject_definition_types") {
509
+ try {
510
+ const parsed = JSON.parse(v2.value);
511
+ if (Array.isArray(parsed)) {
512
+ for (const t of parsed) if (typeof t === "string") out.push(t);
513
+ }
514
+ } catch {
515
+ }
516
+ }
517
+ }
518
+ }
519
+ return out;
520
+ }
521
+ function topoSortCreates(types, deps) {
522
+ const remaining = /* @__PURE__ */ new Map();
523
+ const dependents = /* @__PURE__ */ new Map();
524
+ for (const t of types) {
525
+ const d = deps.get(t) ?? /* @__PURE__ */ new Set();
526
+ remaining.set(t, d.size);
527
+ for (const dep of d) {
528
+ const list2 = dependents.get(dep) ?? [];
529
+ list2.push(t);
530
+ dependents.set(dep, list2);
531
+ }
532
+ }
533
+ const queue = [];
534
+ for (const t of types) if ((remaining.get(t) ?? 0) === 0) queue.push(t);
535
+ const ordered = [];
536
+ while (queue.length) {
537
+ const t = queue.shift();
538
+ ordered.push(t);
539
+ for (const dependent of dependents.get(t) ?? []) {
540
+ const r = (remaining.get(dependent) ?? 0) - 1;
541
+ remaining.set(dependent, r);
542
+ if (r === 0) queue.push(dependent);
543
+ }
544
+ }
545
+ const orderedSet = new Set(ordered);
546
+ return { ordered, unordered: [...types].filter((t) => !orderedSet.has(t)) };
547
+ }
548
+ async function push(client, plan, sources, options) {
549
+ const allowDestructive = options?.allowDestructive ?? false;
550
+ const defByType = new Map(sources.definitions.map((d) => [d.type, d]));
551
+ const idByType = new Map(sources.remote.map((r) => [r.type, r.id]));
552
+ const indexed = plan.map((op, index) => ({ op, index }));
553
+ const createOps = indexed.filter((x) => x.op.kind === "createDefinition");
554
+ const otherOps = indexed.filter((x) => x.op.kind !== "createDefinition");
555
+ const createTypes = new Set(createOps.map((x) => x.op.type));
556
+ const deps = /* @__PURE__ */ new Map();
557
+ for (const { op } of createOps) {
558
+ const def = defByType.get(op.type);
559
+ const targets = def ? referenceEdges(def) : [];
560
+ deps.set(op.type, new Set(targets.filter((t) => createTypes.has(t) && t !== op.type)));
561
+ }
562
+ const { ordered, unordered } = topoSortCreates(createTypes, deps);
563
+ const orderedSet = new Set(ordered);
564
+ const cyclicTypes = new Set(unordered);
565
+ const createByType = new Map(createOps.map((x) => [x.op.type, x]));
566
+ const execOrder = [
567
+ ...ordered.map((t) => createByType.get(t)),
568
+ ...createOps.filter((x) => !orderedSet.has(x.op.type)),
569
+ ...otherOps
570
+ ];
571
+ const failedTypes = /* @__PURE__ */ new Set();
572
+ async function applyOp(op) {
573
+ if (op.kind === "createDefinition") {
574
+ if (cyclicTypes.has(op.type)) {
575
+ failedTypes.add(op.type);
576
+ return { op, status: "blocked", reason: "reference cycle \u2014 two-pass create deferred" };
577
+ }
578
+ for (const dep of deps.get(op.type) ?? []) {
579
+ if (failedTypes.has(dep)) {
580
+ failedTypes.add(op.type);
581
+ return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
582
+ }
583
+ }
584
+ const def = defByType.get(op.type);
585
+ if (!def) {
586
+ failedTypes.add(op.type);
587
+ return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
588
+ }
589
+ const data2 = await execute(client, CREATE_DEFINITION_MUTATION, { definition: def });
590
+ const payload2 = data2.metaobjectDefinitionCreate;
591
+ if (payload2.userErrors.length) {
592
+ failedTypes.add(op.type);
593
+ return { op, status: "failed", userErrors: payload2.userErrors };
594
+ }
595
+ const id2 = payload2.metaobjectDefinition?.id;
596
+ if (id2) idByType.set(op.type, id2);
597
+ return { op, status: "applied", id: id2 };
598
+ }
599
+ const destructive = op.kind === "removeField" || op.kind === "changeFieldType";
600
+ if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
601
+ if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
602
+ const id = idByType.get(op.type);
603
+ 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 } });
607
+ const payload = data.metaobjectDefinitionUpdate;
608
+ if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
609
+ return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
610
+ }
611
+ function fieldOpsFor(op) {
612
+ switch (op.kind) {
613
+ case "addField": {
614
+ const field = fieldInputFor(defByType, op.type, op.field.key);
615
+ return field ? [{ create: field }] : void 0;
616
+ }
617
+ case "updateField": {
618
+ const field = fieldInputFor(defByType, op.type, op.key);
619
+ if (!field) return void 0;
620
+ const update = {
621
+ key: field.key,
622
+ name: field.name,
623
+ required: field.required,
624
+ validations: field.validations
625
+ };
626
+ if (field.description != null) update.description = field.description;
627
+ return [{ update }];
628
+ }
629
+ case "removeField":
630
+ return [{ delete: { key: op.key } }];
631
+ case "changeFieldType": {
632
+ const field = fieldInputFor(defByType, op.type, op.key);
633
+ return field ? [{ delete: { key: op.key } }, { create: field }] : void 0;
634
+ }
635
+ default:
636
+ return void 0;
637
+ }
638
+ }
639
+ const results = new Array(plan.length);
640
+ for (const { op, index } of execOrder) results[index] = await applyOp(op);
641
+ const counts = { applied: 0, skipped: 0, blocked: 0, failed: 0 };
642
+ for (const r of results) counts[r.status]++;
643
+ return { results, counts, ok: counts.failed === 0 && counts.blocked === 0 };
644
+ }
645
+
646
+ // src/cli/plan.ts
647
+ async function planFor(client, schemas) {
648
+ const types = schemas.map((s) => s.type);
649
+ const localDefs = schemas.map(normalizeLocal);
650
+ const remote = await pull(client, types);
651
+ const plan = diff(localDefs, remote.map((r) => normalizeRemote(r.definition)));
652
+ return { plan, remote };
653
+ }
654
+
655
+ // src/cli/format.ts
656
+ function opTarget(op) {
657
+ if (op.kind === "addField") return `${op.type}.${op.field.key}`;
658
+ if ("key" in op) return `${op.type}.${op.key}`;
659
+ return op.type;
660
+ }
661
+ function isDestructive(op) {
662
+ return "destructive" in op && op.destructive === true;
663
+ }
664
+ function describeOp(op) {
665
+ return `${op.kind}: ${opTarget(op)}${isDestructive(op) ? " \xB7 destructive" : ""}`;
666
+ }
667
+ function describeResult(r) {
668
+ const head = `${r.op.kind}: ${opTarget(r.op)}`;
669
+ switch (r.status) {
670
+ case "applied":
671
+ return `\u2713 applied \u2014 ${head}`;
672
+ case "skipped":
673
+ return `\u2013 skipped (${r.reason}) \u2014 ${head}`;
674
+ case "blocked":
675
+ return `\u26A0 blocked (${r.reason}) \u2014 ${head}`;
676
+ case "failed":
677
+ return `\u2717 failed (${r.userErrors.map((e) => e.message).join("; ")}) \u2014 ${head}`;
678
+ }
679
+ }
680
+
681
+ // src/cli/diff.ts
682
+ async function runDiff(args) {
683
+ const { plan } = await planFor(args.client, args.schemas);
684
+ if (plan.length === 0) {
685
+ console.log("Everything is in sync \u2014 nothing to apply.");
686
+ } else {
687
+ console.log(`${plan.length} change${plan.length === 1 ? "" : "s"} would be applied:`);
688
+ for (const op of plan) console.log(` ${describeOp(op)}`);
689
+ }
690
+ return plan;
691
+ }
692
+
693
+ // src/cli/push.ts
694
+ async function runPush(args) {
695
+ const { plan, remote } = await planFor(args.client, args.schemas);
696
+ const definitions = args.schemas.map((s) => s.toDefinitionInput());
697
+ const result = await push(args.client, plan, { definitions, remote }, { allowDestructive: args.allowDestructive });
698
+ for (const r of result.results) console.log(` ${describeResult(r)}`);
699
+ console.log(
700
+ `applied ${result.counts.applied} \xB7 skipped ${result.counts.skipped} \xB7 blocked ${result.counts.blocked} \xB7 failed ${result.counts.failed}`
701
+ );
702
+ if (!args.allowDestructive && plan.some(isDestructive)) {
703
+ console.log("Some destructive changes were skipped. Re-run with --allow-destructive to apply them.");
704
+ }
705
+ return result;
706
+ }
707
+
708
+ // src/cli/pull.ts
709
+ var import_node_fs2 = require("fs");
710
+ var import_node_path3 = require("path");
711
+ async function maybeFormat(source) {
712
+ try {
713
+ const spec = "prettier";
714
+ const prettier = await import(spec);
715
+ return await prettier.format(source, { parser: "typescript" });
716
+ } catch {
717
+ return source;
718
+ }
719
+ }
720
+ async function runPull(args) {
721
+ const remote = await pullAll(args.client);
722
+ const defs = remote.map((r) => normalizeRemote(r.definition));
723
+ const source = await maybeFormat(generateSchemaSource(defs));
724
+ const abs = (0, import_node_path3.resolve)(process.cwd(), args.schemaPath);
725
+ if ((0, import_node_fs2.existsSync)(abs) && !args.force) {
726
+ console.warn(`Overwriting existing ${args.schemaPath}.`);
727
+ }
728
+ (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(abs), { recursive: true });
729
+ (0, import_node_fs2.writeFileSync)(abs, source);
730
+ console.log(`Wrote ${defs.length} definition${defs.length === 1 ? "" : "s"} to ${args.schemaPath}.`);
731
+ return { written: abs, count: defs.length };
732
+ }
733
+
734
+ // src/cli/index.ts
735
+ function parseArgs(argv) {
736
+ const args = { allowDestructive: false, force: false, help: false };
737
+ for (let i = 0; i < argv.length; i++) {
738
+ const a = argv[i];
739
+ if (a === "--help" || a === "-h") args.help = true;
740
+ else if (a === "--allow-destructive") args.allowDestructive = true;
741
+ else if (a === "--force") args.force = true;
742
+ else if (a === "--config") args.config = argv[++i];
743
+ else if (!a.startsWith("-") && !args.command) args.command = a;
744
+ }
745
+ return args;
746
+ }
747
+ var HELP = `meta-manifest \u2014 sync Shopify metaobject definitions
748
+
749
+ Usage: mm <command> [options]
750
+
751
+ Commands:
752
+ init Scaffold meta-manifest.config.ts + src/schema.ts
753
+ pull Enumerate remote definitions and write schema source
754
+ diff Show the changes a push would apply
755
+ push Apply local schema to the store
756
+
757
+ Options:
758
+ --config <path> Config file (default: meta-manifest.config.ts)
759
+ --allow-destructive Apply destructive changes on push
760
+ --force Overwrite schema on pull without warning
761
+ -h, --help Show this help`;
762
+ async function main(argv) {
763
+ const args = parseArgs(argv);
764
+ if (args.help || !args.command) {
765
+ console.log(HELP);
766
+ return args.command ? 0 : args.help ? 0 : 1;
767
+ }
768
+ try {
769
+ if (args.command === "init") {
770
+ await runInit();
771
+ return 0;
772
+ }
773
+ const config = await loadConfig(args.config);
774
+ const client = createAdminClient(config);
775
+ if (args.command === "pull") {
776
+ await runPull({ client, schemaPath: config.schema, force: args.force });
777
+ return 0;
778
+ }
779
+ const schemas = await loadSchemas(config.schema);
780
+ if (args.command === "diff") {
781
+ await runDiff({ client, schemas });
782
+ return 0;
783
+ }
784
+ if (args.command === "push") {
785
+ const result = await runPush({ client, schemas, allowDestructive: args.allowDestructive });
786
+ return result.ok ? 0 : 2;
787
+ }
788
+ console.error(`Unknown command: ${args.command}`);
789
+ console.log(HELP);
790
+ return 1;
791
+ } catch (err) {
792
+ if (err instanceof SyncTransportError) console.error(`Sync failed: Shopify rejected a request.`);
793
+ else console.error(err instanceof Error ? err.message : String(err));
794
+ return 1;
795
+ }
796
+ }
797
+ if (process.env.VITEST === void 0) {
798
+ main(process.argv.slice(2)).then((code) => process.exit(code));
799
+ }
800
+ // Annotate the CommonJS export names for ESM import in node:
801
+ 0 && (module.exports = {
802
+ main,
803
+ parseArgs
804
+ });
805
+ //# sourceMappingURL=index.cjs.map