shop-cli 0.1.0 → 0.1.2

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.
Files changed (50) hide show
  1. package/dist/cli/approvalRequired.d.ts +30 -0
  2. package/dist/cli/approvalRequired.js +87 -0
  3. package/dist/cli/errors.d.ts +4 -1
  4. package/dist/cli/errors.js +3 -1
  5. package/dist/cli/gid.d.ts +1 -1
  6. package/dist/cli/help/registry.js +1013 -221
  7. package/dist/cli/help/render.d.ts +1 -0
  8. package/dist/cli/help/render.js +47 -0
  9. package/dist/cli/parse-command.d.ts +18 -0
  10. package/dist/cli/parse-command.js +109 -0
  11. package/dist/cli/router.js +3 -0
  12. package/dist/cli/suggest.d.ts +5 -0
  13. package/dist/cli/suggest.js +88 -0
  14. package/dist/cli/verbs/_shared.d.ts +1 -1
  15. package/dist/cli/verbs/_shared.js +4 -3
  16. package/dist/cli/verbs/catalogs.js +1 -1
  17. package/dist/cli/verbs/checkout-branding.js +2 -2
  18. package/dist/cli/verbs/collections.js +147 -12
  19. package/dist/cli/verbs/companies.js +2 -2
  20. package/dist/cli/verbs/company-contacts.js +8 -4
  21. package/dist/cli/verbs/company-locations.js +1 -1
  22. package/dist/cli/verbs/customers.js +156 -19
  23. package/dist/cli/verbs/discounts-automatic.js +36 -10
  24. package/dist/cli/verbs/discounts-code.js +27 -10
  25. package/dist/cli/verbs/draft-orders.js +49 -3
  26. package/dist/cli/verbs/files.js +171 -55
  27. package/dist/cli/verbs/fulfillment-orders.js +19 -4
  28. package/dist/cli/verbs/fulfillments.js +8 -2
  29. package/dist/cli/verbs/graphql.js +2 -0
  30. package/dist/cli/verbs/inventory.js +7 -1
  31. package/dist/cli/verbs/marketing-activities.js +1 -1
  32. package/dist/cli/verbs/markets.js +37 -4
  33. package/dist/cli/verbs/metaobjects.js +10 -1
  34. package/dist/cli/verbs/orders.js +80 -8
  35. package/dist/cli/verbs/product-variants.js +67 -16
  36. package/dist/cli/verbs/products.js +1263 -211
  37. package/dist/cli/verbs/selling-plan-groups.js +32 -10
  38. package/dist/cli/verbs/subscription-contracts.js +1 -0
  39. package/dist/cli/verbs/themes.js +18 -0
  40. package/dist/cli/verbs/url-redirects.js +64 -6
  41. package/dist/cli/workflows/files/stagedUploads.d.ts +3 -3
  42. package/dist/cli/workflows/files/stagedUploads.js +71 -19
  43. package/dist/cli/workflows/files/stdinFile.d.ts +7 -0
  44. package/dist/cli/workflows/files/stdinFile.js +65 -0
  45. package/dist/cli/workflows/files/urlDownloads.d.ts +14 -0
  46. package/dist/cli/workflows/files/urlDownloads.js +114 -0
  47. package/dist/cli/workflows/files/waitForReady.d.ts +20 -0
  48. package/dist/cli/workflows/files/waitForReady.js +114 -0
  49. package/dist/cli.js +115 -29
  50. package/package.json +3 -2
@@ -23,6 +23,7 @@ __export(products_exports, {
23
23
  module.exports = __toCommonJS(products_exports);
24
24
  var import_errors = require("../errors");
25
25
  var import_gid = require("../gid");
26
+ var import_render = require("../help/render");
26
27
  var import_input = require("../input");
27
28
  var import_output = require("../output");
28
29
  var import_computedFields = require("../output/computedFields");
@@ -30,18 +31,48 @@ var import_router = require("../router");
30
31
  var import_select = require("../selection/select");
31
32
  var import_userErrors = require("../userErrors");
32
33
  var import_stagedUploads = require("../workflows/files/stagedUploads");
34
+ var import_waitForReady = require("../workflows/files/waitForReady");
35
+ var import_stdinFile = require("../workflows/files/stdinFile");
33
36
  var import_metafieldsUpsert = require("../workflows/products/metafieldsUpsert");
34
37
  var import_publishablePublish = require("../workflows/products/publishablePublish");
35
38
  var import_resolvePublicationId = require("../workflows/publications/resolvePublicationId");
36
39
  var import_shared = require("./_shared");
37
- const productMediaSelection = {
40
+ const requireProductIdForSubverb = (args) => {
41
+ if (args.id !== void 0) {
42
+ throw new import_errors.CliError("Unknown flag --id, did you mean --product-id?", 2);
43
+ }
44
+ return (0, import_shared.requireGidFlag)(args["product-id"], "--product-id", "Product");
45
+ };
46
+ const requireProductIdForRootVerb = (args) => {
47
+ const rawId = args.id;
48
+ const rawProductId = args["product-id"];
49
+ const hasId = typeof rawId === "string" && rawId.length > 0;
50
+ const hasProductId = typeof rawProductId === "string" && rawProductId.length > 0;
51
+ if (!hasId && !hasProductId) {
52
+ throw new import_errors.CliError("Missing --id", 2);
53
+ }
54
+ if (hasId && hasProductId) {
55
+ const a = (0, import_gid.coerceGid)(rawId, "Product");
56
+ const b = (0, import_gid.coerceGid)(rawProductId, "Product");
57
+ if (a !== b) throw new import_errors.CliError("Conflicting --id and --product-id", 2);
58
+ return a;
59
+ }
60
+ if (hasProductId) {
61
+ return (0, import_shared.requireGidFlag)(rawProductId, "--product-id", "Product");
62
+ }
63
+ return (0, import_shared.requireId)(rawId, "Product");
64
+ };
65
+ const productMediaSummarySelection = {
38
66
  id: true,
39
67
  mediaContentType: true,
40
68
  status: true,
41
69
  alt: true,
42
- preview: { status: true, image: { url: true } },
70
+ preview: { status: true, image: { url: true } }
71
+ };
72
+ const productMediaSelection = {
43
73
  mediaErrors: { code: true, message: true },
44
- mediaWarnings: { code: true, message: true }
74
+ mediaWarnings: { code: true, message: true },
75
+ ...productMediaSummarySelection
45
76
  };
46
77
  const productSummarySelection = {
47
78
  id: true,
@@ -63,18 +94,96 @@ const productFullSelectionForGet = {
63
94
  ...productFullSelection,
64
95
  ...import_computedFields.computedPublicationsSelection
65
96
  };
97
+ const productTagsSummarySelection = {
98
+ ...productSummarySelection,
99
+ tags: true
100
+ };
101
+ const productOptionSelection = {
102
+ id: true,
103
+ name: true,
104
+ position: true,
105
+ values: true
106
+ };
107
+ const productOptionListSummarySelection = {
108
+ id: true,
109
+ name: true,
110
+ position: true,
111
+ values: true
112
+ };
113
+ const productOptionListFullSelection = {
114
+ ...productOptionListSummarySelection,
115
+ optionValues: { id: true, name: true, hasVariants: true }
116
+ };
117
+ const getProductOptionListSelection = (view) => {
118
+ if (view === "ids") return { id: true };
119
+ if (view === "full") return productOptionListFullSelection;
120
+ if (view === "raw") return {};
121
+ return productOptionListSummarySelection;
122
+ };
123
+ const productOptionsSummarySelection = {
124
+ ...productSummarySelection,
125
+ options: {
126
+ __args: { first: 100 },
127
+ ...productOptionSelection
128
+ }
129
+ };
130
+ const productOptionsFullSelection = {
131
+ ...productFullSelection,
132
+ options: {
133
+ __args: { first: 100 },
134
+ ...productOptionSelection
135
+ }
136
+ };
137
+ const productVariantSummarySelection = {
138
+ id: true,
139
+ displayName: true,
140
+ sku: true,
141
+ price: true,
142
+ availableForSale: true
143
+ };
144
+ const productVariantFullSelection = {
145
+ ...productVariantSummarySelection,
146
+ barcode: true,
147
+ compareAtPrice: true,
148
+ inventoryQuantity: true,
149
+ product: { id: true, title: true },
150
+ inventoryItem: { id: true }
151
+ };
152
+ const getProductVariantSelection = (view) => {
153
+ if (view === "ids") return { id: true };
154
+ if (view === "full") return productVariantFullSelection;
155
+ if (view === "raw") return {};
156
+ return productVariantSummarySelection;
157
+ };
66
158
  const getProductSelection = (view) => {
67
159
  if (view === "ids") return { id: true };
68
160
  if (view === "full") return productFullSelection;
69
161
  if (view === "raw") return {};
70
162
  return productSummarySelection;
71
163
  };
164
+ const getProductSelectionForTags = (view) => {
165
+ if (view === "ids") return { id: true };
166
+ if (view === "full") return productFullSelection;
167
+ if (view === "raw") return {};
168
+ return productTagsSummarySelection;
169
+ };
170
+ const getProductSelectionForOptions = (view) => {
171
+ if (view === "ids") return { id: true };
172
+ if (view === "full") return productOptionsFullSelection;
173
+ if (view === "raw") return {};
174
+ return productOptionsSummarySelection;
175
+ };
72
176
  const getProductSelectionForGet = (view) => {
73
177
  if (view === "ids") return { id: true };
74
178
  if (view === "full") return productFullSelectionForGet;
75
179
  if (view === "raw") return {};
76
180
  return productSummarySelectionForGet;
77
181
  };
182
+ const getProductMediaSelection = (view) => {
183
+ if (view === "ids") return { id: true };
184
+ if (view === "summary") return productMediaSummarySelection;
185
+ return productMediaSelection;
186
+ };
78
187
  const parseTags = (tags) => {
79
188
  if (!tags) throw new import_errors.CliError("Missing --tags", 2);
80
189
  const parts = tags.split(",").map((t) => t.trim()).filter(Boolean);
@@ -85,7 +194,39 @@ const normalizeMediaContentType = (value) => {
85
194
  if (!value) return "IMAGE";
86
195
  const v = value.toUpperCase();
87
196
  if (v === "IMAGE" || v === "VIDEO" || v === "MODEL_3D" || v === "EXTERNAL_VIDEO") return v;
88
- throw new import_errors.CliError("--media-type must be IMAGE|VIDEO|MODEL_3D|EXTERNAL_VIDEO", 2);
197
+ throw new import_errors.CliError("--media-content-type/--media-type must be IMAGE|VIDEO|MODEL_3D|EXTERNAL_VIDEO", 2);
198
+ };
199
+ const parsePositiveIntFlag = ({
200
+ value,
201
+ flag
202
+ }) => {
203
+ if (value === void 0) return void 0;
204
+ if (typeof value !== "string") throw new import_errors.CliError(`Invalid ${flag} value`, 2);
205
+ const trimmed = value.trim();
206
+ if (!trimmed) throw new import_errors.CliError(`Invalid ${flag} value`, 2);
207
+ const n = Number(trimmed);
208
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
209
+ throw new import_errors.CliError(`${flag} must be a positive integer`, 2);
210
+ }
211
+ return n;
212
+ };
213
+ const getTopProductMediaIds = async ({
214
+ ctx,
215
+ productId,
216
+ first = 250
217
+ }) => {
218
+ const result = await (0, import_router.runQuery)(ctx, {
219
+ product: {
220
+ __args: { id: productId },
221
+ media: {
222
+ __args: { first, reverse: true, sortKey: "POSITION" },
223
+ nodes: { id: true }
224
+ }
225
+ }
226
+ });
227
+ if (result === void 0) return [];
228
+ const nodes = result.product?.media?.nodes ?? [];
229
+ return nodes.map((n) => typeof n?.id === "string" ? n.id : void 0).filter((id) => typeof id === "string" && id.trim() !== "");
89
230
  };
90
231
  const normalizeMediaId = (value) => {
91
232
  const raw = value.trim();
@@ -108,11 +249,143 @@ const stagedResourceToMediaType = (resource) => {
108
249
  if (resource === "MODEL_3D") return "MODEL_3D";
109
250
  throw new import_errors.CliError("Only IMAGE|VIDEO|MODEL_3D can be uploaded as product media", 2);
110
251
  };
252
+ const parseVariantOptionValues = (value) => {
253
+ if (value === void 0 || value === null) return [];
254
+ const raw = Array.isArray(value) ? value : [value];
255
+ const optionValues = [];
256
+ for (const entry of raw) {
257
+ if (typeof entry !== "string") throw new import_errors.CliError("--variant-option must be a string", 2);
258
+ const trimmed = entry.trim();
259
+ if (!trimmed) continue;
260
+ const eq = trimmed.indexOf("=");
261
+ if (eq <= 0 || eq === trimmed.length - 1) {
262
+ throw new import_errors.CliError(`--variant-option must be in the form OptionName=Value. Got: ${entry}`, 2);
263
+ }
264
+ const optionName = trimmed.slice(0, eq).trim();
265
+ const name = trimmed.slice(eq + 1).trim();
266
+ if (!optionName || !name) {
267
+ throw new import_errors.CliError(`--variant-option must be in the form OptionName=Value. Got: ${entry}`, 2);
268
+ }
269
+ optionValues.push({ optionName, name });
270
+ }
271
+ return optionValues;
272
+ };
273
+ const parseRepeatableStrings = (value, flag, { allowEmpty = false } = {}) => {
274
+ if (value === void 0 || value === null) {
275
+ if (allowEmpty) return [];
276
+ throw new import_errors.CliError(`Missing ${flag}`, 2);
277
+ }
278
+ const raw = Array.isArray(value) ? value : [value];
279
+ const out = [];
280
+ for (const entry of raw) {
281
+ if (typeof entry !== "string") throw new import_errors.CliError(`${flag} must be a string (repeatable)`, 2);
282
+ const trimmed = entry.trim();
283
+ if (!trimmed) throw new import_errors.CliError(`${flag} cannot be empty`, 2);
284
+ out.push(trimmed);
285
+ }
286
+ if (out.length === 0) {
287
+ if (allowEmpty) return [];
288
+ throw new import_errors.CliError(`Missing ${flag}`, 2);
289
+ }
290
+ return out;
291
+ };
292
+ const parseOptionSpec = (value, flag) => {
293
+ const trimmed = value.trim();
294
+ const eq = trimmed.indexOf("=");
295
+ if (eq <= 0 || eq === trimmed.length - 1) {
296
+ throw new import_errors.CliError(`${flag} must be in the form Name=Value1,Value2,... Got: ${value}`, 2);
297
+ }
298
+ const name = trimmed.slice(0, eq).trim();
299
+ const valuesRaw = trimmed.slice(eq + 1).trim();
300
+ if (!name || !valuesRaw) {
301
+ throw new import_errors.CliError(`${flag} must be in the form Name=Value1,Value2,... Got: ${value}`, 2);
302
+ }
303
+ const valueNames = valuesRaw.split(",").map((v) => v.trim()).filter(Boolean);
304
+ if (valueNames.length === 0) {
305
+ throw new import_errors.CliError(`${flag} must include at least one value. Got: ${value}`, 2);
306
+ }
307
+ return {
308
+ name,
309
+ values: valueNames.map((v) => ({ name: v }))
310
+ };
311
+ };
312
+ const parseFromTo = (value, flag) => {
313
+ const trimmed = value.trim();
314
+ const eq = trimmed.indexOf("=");
315
+ if (eq <= 0 || eq === trimmed.length - 1) {
316
+ throw new import_errors.CliError(`${flag} must be in the form From=To. Got: ${value}`, 2);
317
+ }
318
+ const from = trimmed.slice(0, eq).trim();
319
+ const to = trimmed.slice(eq + 1).trim();
320
+ if (!from || !to) {
321
+ throw new import_errors.CliError(`${flag} must be in the form From=To. Got: ${value}`, 2);
322
+ }
323
+ return { from, to };
324
+ };
325
+ const looksLikeGidOrNumericId = (value) => /^gid:\/\//i.test(value) || /^\d+$/.test(value);
326
+ const normalizeProductOptionCreateVariantStrategy = (value) => {
327
+ if (value === void 0 || value === null || value === "") return void 0;
328
+ if (typeof value !== "string") throw new import_errors.CliError("--variant-strategy must be a string", 2);
329
+ const v = value.trim().toUpperCase();
330
+ if (v === "LEAVE_AS_IS" || v === "CREATE") return v;
331
+ throw new import_errors.CliError("--variant-strategy must be LEAVE_AS_IS|CREATE", 2);
332
+ };
333
+ const normalizeProductOptionUpdateVariantStrategy = (value) => {
334
+ if (value === void 0 || value === null || value === "") return void 0;
335
+ if (typeof value !== "string") throw new import_errors.CliError("--variant-strategy must be a string", 2);
336
+ const v = value.trim().toUpperCase();
337
+ if (v === "LEAVE_AS_IS" || v === "MANAGE") return v;
338
+ throw new import_errors.CliError("--variant-strategy must be LEAVE_AS_IS|MANAGE", 2);
339
+ };
340
+ const normalizeProductOptionDeleteStrategy = (value) => {
341
+ if (value === void 0 || value === null || value === "") return void 0;
342
+ if (typeof value !== "string") throw new import_errors.CliError("--strategy must be a string", 2);
343
+ const v = value.trim().toUpperCase();
344
+ if (v === "DEFAULT" || v === "NON_DESTRUCTIVE" || v === "POSITION") return v;
345
+ throw new import_errors.CliError("--strategy must be DEFAULT|NON_DESTRUCTIVE|POSITION", 2);
346
+ };
347
+ const productOptionResolveSelection = {
348
+ id: true,
349
+ name: true,
350
+ position: true,
351
+ values: true,
352
+ optionValues: { id: true, name: true }
353
+ };
354
+ const fetchProductOptionsForResolution = async ({ ctx, productId }) => {
355
+ const result = await (0, import_router.runQuery)(ctx, {
356
+ product: { __args: { id: productId }, options: productOptionResolveSelection }
357
+ });
358
+ if (result === void 0) return [];
359
+ return result.product?.options ?? [];
360
+ };
361
+ const resolveSingleOptionByName = (options, name) => {
362
+ const matches = options.filter((o) => typeof o?.name === "string" && o.name === name);
363
+ if (matches.length === 0) return void 0;
364
+ if (matches.length > 1) {
365
+ throw new import_errors.CliError(`Multiple product options named "${name}" exist. Use --option-id instead.`, 2);
366
+ }
367
+ return matches[0];
368
+ };
369
+ const parseInventoryPolicy = (value) => {
370
+ if (value === void 0 || value === null || value === "") return void 0;
371
+ if (typeof value !== "string") throw new import_errors.CliError("--inventory-policy must be a string", 2);
372
+ const normalized = value.trim().toUpperCase();
373
+ if (normalized === "DENY" || normalized === "CONTINUE") return normalized;
374
+ throw new import_errors.CliError(`--inventory-policy must be DENY|CONTINUE. Got: ${value}`, 2);
375
+ };
111
376
  const runProducts = async ({
112
377
  ctx,
113
378
  verb,
114
379
  argv
115
380
  }) => {
381
+ const groupHelpVerbs = /* @__PURE__ */ new Set(["variants", "options", "media"]);
382
+ if (groupHelpVerbs.has(verb)) {
383
+ const groupHelp = (0, import_render.renderVerbGroupHelp)("products", verb);
384
+ if (groupHelp) console.log(groupHelp);
385
+ if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) return;
386
+ throw new import_errors.CliError(`
387
+ Missing <verb> for "products ${verb}"`, 2);
388
+ }
116
389
  if (argv.includes("--help") || argv.includes("-h")) {
117
390
  console.log(
118
391
  [
@@ -125,14 +398,14 @@ const runProducts = async ({
125
398
  " tags|types|vendors",
126
399
  " change-status|set",
127
400
  " join-selling-plan-groups|leave-selling-plan-groups",
128
- " option-update|options-create|options-delete|options-reorder",
401
+ " options list|options create|options update|options delete|options reorder",
402
+ " variants list|variants create|variants update|variants delete|variants reorder",
129
403
  " combined-listing-update",
130
404
  " add-tags|remove-tags|set-price",
131
405
  " publish|unpublish|publish-all",
132
406
  " bundle-create|bundle-update",
133
407
  " metafields upsert",
134
408
  " media add|media upload|media list|media remove|media reorder|media update",
135
- " create-media|update-media|delete-media|reorder-media",
136
409
  "",
137
410
  "Common output flags:",
138
411
  " --view summary|ids|full|raw",
@@ -142,7 +415,266 @@ const runProducts = async ({
142
415
  );
143
416
  return;
144
417
  }
145
- if (verb === "reorder-media") verb = "media reorder";
418
+ if (verb === "variants list") {
419
+ const args = (0, import_router.parseStandardArgs)({
420
+ argv,
421
+ extraOptions: { "product-id": { type: "string" } }
422
+ });
423
+ if (args.query) {
424
+ throw new import_errors.CliError(
425
+ "--query is not supported for `shop products variants list` (product-scoped variants). Use `shop product-variants list --query ...` for global variant search.",
426
+ 2
427
+ );
428
+ }
429
+ const productId = requireProductIdForSubverb(args);
430
+ const first = (0, import_shared.parseFirst)(args.first);
431
+ const after = args.after;
432
+ const reverse = args.reverse;
433
+ const sortKey = args.sort;
434
+ const nodeSelection = (0, import_select.resolveSelection)({
435
+ typeName: "ProductVariant",
436
+ view: ctx.view,
437
+ baseSelection: getProductVariantSelection(ctx.view),
438
+ select: args.select,
439
+ selection: args.selection,
440
+ include: args.include,
441
+ ensureId: ctx.quiet,
442
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
443
+ });
444
+ const result = await (0, import_router.runQuery)(ctx, {
445
+ product: {
446
+ __args: { id: productId },
447
+ variants: {
448
+ __args: { first, after, reverse, sortKey },
449
+ pageInfo: { hasNextPage: true, endCursor: true },
450
+ nodes: nodeSelection
451
+ }
452
+ }
453
+ });
454
+ if (result === void 0) return;
455
+ const connection = result.product?.variants;
456
+ if (!connection) throw new import_errors.CliError("Product not found", 2);
457
+ (0, import_output.printConnection)({
458
+ connection,
459
+ format: ctx.format,
460
+ quiet: ctx.quiet,
461
+ nextPageArgs: {
462
+ base: "shop products variants list",
463
+ first,
464
+ sort: typeof sortKey === "string" ? sortKey : void 0,
465
+ reverse: reverse === true,
466
+ extraFlags: [{ flag: "--product-id", value: productId }]
467
+ }
468
+ });
469
+ return;
470
+ }
471
+ if (verb === "variants create") {
472
+ const args = (0, import_router.parseStandardArgs)({
473
+ argv,
474
+ extraOptions: {
475
+ "product-id": { type: "string" },
476
+ "variant-option": { type: "string", multiple: true },
477
+ sku: { type: "string" },
478
+ barcode: { type: "string" },
479
+ price: { type: "string" },
480
+ "compare-at-price": { type: "string" },
481
+ "inventory-policy": { type: "string" },
482
+ strategy: { type: "string" }
483
+ }
484
+ });
485
+ const productId = requireProductIdForSubverb(args);
486
+ const optionValues = parseVariantOptionValues(args["variant-option"]);
487
+ if (optionValues.length === 0) {
488
+ throw new import_errors.CliError("Missing --variant-option OptionName=Value (repeatable)", 2);
489
+ }
490
+ const sku = args.sku;
491
+ const barcode = args.barcode;
492
+ const price = args.price;
493
+ const compareAtPrice = args["compare-at-price"];
494
+ const inventoryPolicy = parseInventoryPolicy(args["inventory-policy"]);
495
+ const strategy = args.strategy;
496
+ if (strategy && !["DEFAULT", "PRESERVE_STANDALONE_VARIANT", "REMOVE_STANDALONE_VARIANT"].includes(
497
+ strategy.trim().toUpperCase()
498
+ )) {
499
+ throw new import_errors.CliError(
500
+ `--strategy must be DEFAULT|PRESERVE_STANDALONE_VARIANT|REMOVE_STANDALONE_VARIANT. Got: ${strategy}`,
501
+ 2
502
+ );
503
+ }
504
+ const normalizedStrategy = strategy ? strategy.trim().toUpperCase() : void 0;
505
+ const variantSelection = (0, import_select.resolveSelection)({
506
+ typeName: "ProductVariant",
507
+ view: ctx.view,
508
+ baseSelection: getProductVariantSelection(ctx.view),
509
+ select: args.select,
510
+ selection: args.selection,
511
+ include: args.include,
512
+ ensureId: ctx.quiet,
513
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
514
+ });
515
+ const variantInput = {
516
+ optionValues,
517
+ ...barcode ? { barcode } : {},
518
+ ...price ? { price } : {},
519
+ ...compareAtPrice ? { compareAtPrice } : {},
520
+ ...inventoryPolicy ? { inventoryPolicy } : {},
521
+ ...sku ? { inventoryItem: { sku } } : {}
522
+ };
523
+ const result = await (0, import_router.runMutation)(ctx, {
524
+ productVariantsBulkCreate: {
525
+ __args: {
526
+ productId,
527
+ variants: [variantInput],
528
+ ...normalizedStrategy ? { strategy: normalizedStrategy } : {}
529
+ },
530
+ userErrors: { field: true, message: true },
531
+ productVariants: variantSelection
532
+ }
533
+ });
534
+ if (result === void 0) return;
535
+ (0, import_userErrors.maybeFailOnUserErrors)({
536
+ payload: result.productVariantsBulkCreate,
537
+ failOnUserErrors: ctx.failOnUserErrors
538
+ });
539
+ const created = result.productVariantsBulkCreate?.productVariants?.[0];
540
+ (0, import_output.printNode)({ node: created, format: ctx.format, quiet: ctx.quiet });
541
+ return;
542
+ }
543
+ if (verb === "variants update") {
544
+ const args = (0, import_router.parseStandardArgs)({
545
+ argv,
546
+ extraOptions: {
547
+ "product-id": { type: "string" },
548
+ "variant-id": { type: "string" },
549
+ "variant-option": { type: "string", multiple: true },
550
+ sku: { type: "string" },
551
+ barcode: { type: "string" },
552
+ price: { type: "string" },
553
+ "compare-at-price": { type: "string" },
554
+ "inventory-policy": { type: "string" },
555
+ "allow-partial-updates": { type: "boolean" }
556
+ }
557
+ });
558
+ if (args.id !== void 0) {
559
+ if (args["product-id"] !== void 0) {
560
+ throw new import_errors.CliError("Unknown flag --id, did you mean --variant-id?", 2);
561
+ }
562
+ ;
563
+ args["product-id"] = args.id;
564
+ delete args.id;
565
+ }
566
+ const productId = requireProductIdForSubverb(args);
567
+ const variantId = (0, import_shared.requireId)(args["variant-id"], "ProductVariant", "--variant-id");
568
+ const optionValues = parseVariantOptionValues(args["variant-option"]);
569
+ const sku = args.sku;
570
+ const barcode = args.barcode;
571
+ const price = args.price;
572
+ const compareAtPrice = args["compare-at-price"];
573
+ const inventoryPolicy = parseInventoryPolicy(args["inventory-policy"]);
574
+ const allowPartialUpdates = args["allow-partial-updates"] === true;
575
+ const variantSelection = (0, import_select.resolveSelection)({
576
+ typeName: "ProductVariant",
577
+ view: ctx.view,
578
+ baseSelection: getProductVariantSelection(ctx.view),
579
+ select: args.select,
580
+ selection: args.selection,
581
+ include: args.include,
582
+ ensureId: ctx.quiet,
583
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
584
+ });
585
+ const variantInput = {
586
+ id: variantId,
587
+ ...optionValues.length ? { optionValues } : {},
588
+ ...barcode ? { barcode } : {},
589
+ ...price ? { price } : {},
590
+ ...compareAtPrice ? { compareAtPrice } : {},
591
+ ...inventoryPolicy ? { inventoryPolicy } : {},
592
+ ...sku ? { inventoryItem: { sku } } : {}
593
+ };
594
+ const result = await (0, import_router.runMutation)(ctx, {
595
+ productVariantsBulkUpdate: {
596
+ __args: {
597
+ productId,
598
+ allowPartialUpdates,
599
+ variants: [variantInput]
600
+ },
601
+ userErrors: { field: true, message: true },
602
+ productVariants: variantSelection
603
+ }
604
+ });
605
+ if (result === void 0) return;
606
+ (0, import_userErrors.maybeFailOnUserErrors)({
607
+ payload: result.productVariantsBulkUpdate,
608
+ failOnUserErrors: ctx.failOnUserErrors
609
+ });
610
+ const updated = result.productVariantsBulkUpdate?.productVariants?.[0];
611
+ (0, import_output.printNode)({ node: updated, format: ctx.format, quiet: ctx.quiet });
612
+ return;
613
+ }
614
+ if (verb === "variants delete") {
615
+ const args = (0, import_router.parseStandardArgs)({
616
+ argv,
617
+ extraOptions: {
618
+ "product-id": { type: "string" },
619
+ "variant-id": { type: "string" }
620
+ }
621
+ });
622
+ const productId = requireProductIdForSubverb(args);
623
+ const variantId = (0, import_shared.requireId)(args["variant-id"], "ProductVariant", "--variant-id");
624
+ const result = await (0, import_router.runMutation)(ctx, {
625
+ productVariantsBulkDelete: {
626
+ __args: { productId, variantsIds: [variantId] },
627
+ product: { id: true },
628
+ userErrors: { field: true, message: true }
629
+ }
630
+ });
631
+ if (result === void 0) return;
632
+ (0, import_userErrors.maybeFailOnUserErrors)({
633
+ payload: result.productVariantsBulkDelete,
634
+ failOnUserErrors: ctx.failOnUserErrors
635
+ });
636
+ const verify = await (0, import_router.runQuery)(ctx, {
637
+ nodes: {
638
+ __args: { ids: [variantId] },
639
+ id: true
640
+ }
641
+ });
642
+ if (verify === void 0) return;
643
+ const stillThere = verify.nodes?.[0] !== null && verify.nodes?.[0] !== void 0;
644
+ const deletedVariantId = stillThere ? void 0 : variantId;
645
+ if (ctx.quiet) return (0, import_output.printIds)([deletedVariantId]);
646
+ (0, import_output.printJson)({ productId, deletedVariantId }, ctx.format !== "raw");
647
+ return;
648
+ }
649
+ if (verb === "variants reorder") {
650
+ const args = (0, import_router.parseStandardArgs)({
651
+ argv,
652
+ extraOptions: {
653
+ "product-id": { type: "string" },
654
+ "variant-id": { type: "string" },
655
+ position: { type: "string" }
656
+ }
657
+ });
658
+ const productId = requireProductIdForSubverb(args);
659
+ const variantId = (0, import_shared.requireId)(args["variant-id"], "ProductVariant", "--variant-id");
660
+ const position = (0, import_shared.parseIntFlag)("--position", args.position);
661
+ if (position <= 0) throw new import_errors.CliError("--position must be a positive integer (1-based)", 2);
662
+ const result = await (0, import_router.runMutation)(ctx, {
663
+ productVariantsBulkReorder: {
664
+ __args: { productId, positions: [{ id: variantId, position }] },
665
+ product: { id: true },
666
+ userErrors: { field: true, message: true }
667
+ }
668
+ });
669
+ if (result === void 0) return;
670
+ (0, import_userErrors.maybeFailOnUserErrors)({
671
+ payload: result.productVariantsBulkReorder,
672
+ failOnUserErrors: ctx.failOnUserErrors
673
+ });
674
+ if (ctx.quiet) return (0, import_output.printIds)([variantId]);
675
+ (0, import_output.printJson)({ productId, variantId, position }, ctx.format !== "raw");
676
+ return;
677
+ }
146
678
  if (verb === "by-handle") {
147
679
  const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { handle: { type: "string" } } });
148
680
  const handle = args.handle;
@@ -313,21 +845,29 @@ const runProducts = async ({
313
845
  return;
314
846
  }
315
847
  if (verb === "change-status") {
316
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
317
- const productId = (0, import_shared.requireId)(args.id, "Product");
848
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
849
+ const productId = requireProductIdForRootVerb(args);
318
850
  const status = args.status;
319
851
  if (!status) throw new import_errors.CliError("Missing --status (ACTIVE|DRAFT|ARCHIVED)", 2);
852
+ const selection = (0, import_select.resolveSelection)({
853
+ resource: "products",
854
+ view: ctx.view,
855
+ baseSelection: getProductSelection(ctx.view),
856
+ select: args.select,
857
+ selection: args.selection,
858
+ include: args.include,
859
+ ensureId: ctx.quiet
860
+ });
320
861
  const result = await (0, import_router.runMutation)(ctx, {
321
862
  productChangeStatus: {
322
863
  __args: { productId, status },
323
- product: productSummarySelection,
864
+ product: selection,
324
865
  userErrors: { field: true, message: true }
325
866
  }
326
867
  });
327
868
  if (result === void 0) return;
328
869
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productChangeStatus, failOnUserErrors: ctx.failOnUserErrors });
329
- if (ctx.quiet) return console.log(result.productChangeStatus?.product?.id ?? "");
330
- (0, import_output.printJson)(result.productChangeStatus, ctx.format !== "raw");
870
+ (0, import_output.printNode)({ node: result.productChangeStatus?.product, format: ctx.format, quiet: ctx.quiet });
331
871
  return;
332
872
  }
333
873
  if (verb === "set") {
@@ -381,192 +921,439 @@ const runProducts = async ({
381
921
  return;
382
922
  }
383
923
  if (verb === "join-selling-plan-groups" || verb === "leave-selling-plan-groups") {
384
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "group-ids": { type: "string", multiple: true } } });
385
- const id = (0, import_shared.requireId)(args.id, "Product");
924
+ const args = (0, import_router.parseStandardArgs)({
925
+ argv,
926
+ extraOptions: { "product-id": { type: "string" }, "group-ids": { type: "string", multiple: true } }
927
+ });
928
+ const id = requireProductIdForRootVerb(args);
386
929
  const sellingPlanGroupIds = (0, import_shared.parseIds)(args["group-ids"], "SellingPlanGroup");
930
+ const selection = (0, import_select.resolveSelection)({
931
+ resource: "products",
932
+ view: ctx.view,
933
+ baseSelection: getProductSelection(ctx.view),
934
+ select: args.select,
935
+ selection: args.selection,
936
+ include: args.include,
937
+ ensureId: ctx.quiet
938
+ });
387
939
  const mutation = verb === "join-selling-plan-groups" ? "productJoinSellingPlanGroups" : "productLeaveSellingPlanGroups";
388
940
  const result = await (0, import_router.runMutation)(ctx, {
389
941
  [mutation]: {
390
942
  __args: { id, sellingPlanGroupIds },
391
- product: { id: true, title: true },
943
+ product: selection,
392
944
  userErrors: { field: true, message: true }
393
945
  }
394
946
  });
395
947
  if (result === void 0) return;
396
948
  const payload = result[mutation];
397
949
  (0, import_userErrors.maybeFailOnUserErrors)({ payload, failOnUserErrors: ctx.failOnUserErrors });
398
- if (ctx.quiet) return console.log(payload?.product?.id ?? "");
399
- (0, import_output.printJson)(payload, ctx.format !== "raw");
950
+ (0, import_output.printNode)({ node: payload?.product, format: ctx.format, quiet: ctx.quiet });
400
951
  return;
401
952
  }
402
- if (verb === "create-media" || verb === "update-media") {
403
- const args = (0, import_router.parseStandardArgs)({
404
- argv,
405
- extraOptions: {
406
- "product-id": { type: "string" },
407
- media: { type: "string" }
408
- }
953
+ if (verb === "options list") {
954
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
955
+ const productId = requireProductIdForSubverb(args);
956
+ const selection = (0, import_select.resolveSelection)({
957
+ typeName: "ProductOption",
958
+ view: ctx.view,
959
+ baseSelection: getProductOptionListSelection(ctx.view),
960
+ select: args.select,
961
+ selection: args.selection,
962
+ include: args.include,
963
+ ensureId: ctx.quiet
409
964
  });
410
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
411
- const media = (0, import_shared.parseJsonArg)(args.media, "--media");
412
- if (!Array.isArray(media)) throw new import_errors.CliError("--media must be a JSON array", 2);
413
- const mutation = verb === "create-media" ? "productCreateMedia" : "productUpdateMedia";
414
- const result = await (0, import_router.runMutation)(ctx, {
415
- [mutation]: {
416
- __args: { productId, media },
417
- media: productMediaSelection,
418
- mediaUserErrors: { code: true, field: true, message: true },
419
- product: { id: true, title: true }
965
+ const result = await (0, import_router.runQuery)(ctx, {
966
+ product: {
967
+ __args: { id: productId },
968
+ options: selection
420
969
  }
421
970
  });
422
971
  if (result === void 0) return;
423
- const payload = result[mutation];
424
- (0, import_userErrors.maybeFailOnUserErrors)({ payload, failOnUserErrors: ctx.failOnUserErrors });
425
- if (ctx.quiet) return console.log(payload?.product?.id ?? "");
426
- (0, import_output.printJson)(payload, ctx.format !== "raw");
427
- return;
428
- }
429
- if (verb === "delete-media") {
430
- const args = (0, import_router.parseStandardArgs)({
431
- argv,
432
- extraOptions: {
433
- "product-id": { type: "string" },
434
- "media-ids": { type: "string", multiple: true }
435
- }
436
- });
437
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
438
- const mediaIds = (0, import_shared.parseStringList)(args["media-ids"], "--media-ids");
439
- const result = await (0, import_router.runMutation)(ctx, {
440
- productDeleteMedia: {
441
- __args: { productId, mediaIds },
442
- deletedMediaIds: true,
443
- deletedProductImageIds: true,
444
- mediaUserErrors: { code: true, field: true, message: true },
445
- product: { id: true, title: true }
446
- }
972
+ const options = result.product?.options ?? [];
973
+ (0, import_output.printConnection)({
974
+ connection: { nodes: options, pageInfo: void 0 },
975
+ format: ctx.format,
976
+ quiet: ctx.quiet
447
977
  });
448
- if (result === void 0) return;
449
- (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productDeleteMedia, failOnUserErrors: ctx.failOnUserErrors });
450
- if (ctx.quiet) return console.log(result.productDeleteMedia?.product?.id ?? "");
451
- (0, import_output.printJson)(result.productDeleteMedia, ctx.format !== "raw");
452
978
  return;
453
979
  }
454
- if (verb === "option-update") {
980
+ if (verb === "options create") {
455
981
  const args = (0, import_router.parseStandardArgs)({
456
982
  argv,
457
983
  extraOptions: {
458
984
  "product-id": { type: "string" },
459
- option: { type: "string" },
460
- "option-values-to-add": { type: "string" },
461
- "option-values-to-delete": { type: "string" },
462
- "option-values-to-update": { type: "string" },
985
+ option: { type: "string", multiple: true },
986
+ "options-json": { type: "string" },
463
987
  "variant-strategy": { type: "string" }
464
988
  }
465
989
  });
466
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
467
- const option = (0, import_shared.parseJsonArg)(args.option, "--option");
468
- const optionValuesToAdd = args["option-values-to-add"] ? (0, import_shared.parseJsonArg)(args["option-values-to-add"], "--option-values-to-add") : void 0;
469
- const optionValuesToDelete = args["option-values-to-delete"] ? (0, import_shared.parseJsonArg)(args["option-values-to-delete"], "--option-values-to-delete") : void 0;
470
- const optionValuesToUpdate = args["option-values-to-update"] ? (0, import_shared.parseJsonArg)(args["option-values-to-update"], "--option-values-to-update") : void 0;
471
- const variantStrategy = args["variant-strategy"];
990
+ const productId = requireProductIdForSubverb(args);
991
+ const optionSpecs = parseRepeatableStrings(args.option, "--option", { allowEmpty: true });
992
+ const optionsJsonRaw = args["options-json"];
993
+ if (optionSpecs.length > 0 && optionsJsonRaw) {
994
+ throw new import_errors.CliError("Do not pass both --option and --options-json", 2);
995
+ }
996
+ const options = optionsJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(optionsJsonRaw, "--options-json") : optionSpecs.map((s) => parseOptionSpec(s, "--option"));
997
+ if (!Array.isArray(options) || options.length === 0) {
998
+ throw new import_errors.CliError("Missing options: pass one or more --option entries, or --options-json", 2);
999
+ }
1000
+ const variantStrategy = normalizeProductOptionCreateVariantStrategy(args["variant-strategy"]);
1001
+ const selection = (0, import_select.resolveSelection)({
1002
+ resource: "products",
1003
+ view: ctx.view,
1004
+ baseSelection: getProductSelectionForOptions(ctx.view),
1005
+ select: args.select,
1006
+ selection: args.selection,
1007
+ include: args.include,
1008
+ ensureId: ctx.quiet
1009
+ });
472
1010
  const result = await (0, import_router.runMutation)(ctx, {
473
- productOptionUpdate: {
474
- __args: {
475
- productId,
476
- option,
477
- ...optionValuesToAdd !== void 0 ? { optionValuesToAdd } : {},
478
- ...optionValuesToDelete !== void 0 ? { optionValuesToDelete } : {},
479
- ...optionValuesToUpdate !== void 0 ? { optionValuesToUpdate } : {},
480
- ...variantStrategy ? { variantStrategy } : {}
481
- },
482
- product: productSummarySelection,
1011
+ productOptionsCreate: {
1012
+ __args: { productId, options, ...variantStrategy ? { variantStrategy } : {} },
1013
+ product: selection,
483
1014
  userErrors: { code: true, field: true, message: true }
484
1015
  }
485
1016
  });
486
1017
  if (result === void 0) return;
487
- (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionUpdate, failOnUserErrors: ctx.failOnUserErrors });
488
- if (ctx.quiet) return console.log(result.productOptionUpdate?.product?.id ?? "");
489
- (0, import_output.printJson)(result.productOptionUpdate, ctx.format !== "raw");
1018
+ (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionsCreate, failOnUserErrors: ctx.failOnUserErrors });
1019
+ (0, import_output.printNode)({ node: result.productOptionsCreate?.product, format: ctx.format, quiet: ctx.quiet });
490
1020
  return;
491
1021
  }
492
- if (verb === "options-create") {
1022
+ if (verb === "options update") {
493
1023
  const args = (0, import_router.parseStandardArgs)({
494
1024
  argv,
495
1025
  extraOptions: {
496
1026
  "product-id": { type: "string" },
497
- options: { type: "string" },
1027
+ "option-id": { type: "string" },
1028
+ name: { type: "string" },
1029
+ position: { type: "string" },
1030
+ "add-value": { type: "string", multiple: true },
1031
+ "delete-value": { type: "string", multiple: true },
1032
+ "rename-value": { type: "string", multiple: true },
1033
+ "delete-value-id": { type: "string", multiple: true },
1034
+ "rename-value-id": { type: "string", multiple: true },
1035
+ "option-json": { type: "string" },
1036
+ "add-values-json": { type: "string" },
1037
+ "delete-value-ids-json": { type: "string" },
1038
+ "update-values-json": { type: "string" },
498
1039
  "variant-strategy": { type: "string" }
499
1040
  }
500
1041
  });
501
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
502
- const options = (0, import_shared.parseJsonArg)(args.options, "--options");
503
- if (!Array.isArray(options)) throw new import_errors.CliError("--options must be a JSON array", 2);
504
- const variantStrategy = args["variant-strategy"];
1042
+ const productId = requireProductIdForSubverb(args);
1043
+ const optionIdRaw = args["option-id"];
1044
+ if (!optionIdRaw) throw new import_errors.CliError("Missing --option-id", 2);
1045
+ const optionId = (0, import_gid.coerceGid)(optionIdRaw, "ProductOption");
1046
+ const optionJsonRaw = args["option-json"];
1047
+ const addValuesJsonRaw = args["add-values-json"];
1048
+ const deleteValueIdsJsonRaw = args["delete-value-ids-json"];
1049
+ const updateValuesJsonRaw = args["update-values-json"];
1050
+ const hasJsonMode = Boolean(optionJsonRaw || addValuesJsonRaw || deleteValueIdsJsonRaw || updateValuesJsonRaw);
1051
+ const addValueNames = parseRepeatableStrings(args["add-value"], "--add-value", { allowEmpty: true });
1052
+ const deleteValueNames = parseRepeatableStrings(args["delete-value"], "--delete-value", { allowEmpty: true });
1053
+ const renameValueSpecs = parseRepeatableStrings(args["rename-value"], "--rename-value", { allowEmpty: true });
1054
+ const deleteValueIdSpecs = parseRepeatableStrings(args["delete-value-id"], "--delete-value-id", {
1055
+ allowEmpty: true
1056
+ });
1057
+ const renameValueIdSpecs = parseRepeatableStrings(args["rename-value-id"], "--rename-value-id", {
1058
+ allowEmpty: true
1059
+ });
1060
+ if (hasJsonMode) {
1061
+ if (addValueNames.length || deleteValueNames.length || renameValueSpecs.length || deleteValueIdSpecs.length || renameValueIdSpecs.length || args.name !== void 0 || args.position !== void 0) {
1062
+ throw new import_errors.CliError("Do not mix JSON flags with non-JSON option update flags", 2);
1063
+ }
1064
+ }
1065
+ const position = parsePositiveIntFlag({ value: args.position, flag: "--position" });
1066
+ const hasNonJsonOptionChange = typeof args.name === "string" && args.name.trim() !== "" || position !== void 0;
1067
+ const hasNonJsonValueChange = addValueNames.length > 0 || deleteValueNames.length > 0 || renameValueSpecs.length > 0 || deleteValueIdSpecs.length > 0 || renameValueIdSpecs.length > 0;
1068
+ if (!hasJsonMode && !hasNonJsonOptionChange && !hasNonJsonValueChange) {
1069
+ throw new import_errors.CliError(
1070
+ "No changes specified. Pass --name/--position and/or value change flags (e.g. --add-value, --delete-value, --rename-value).",
1071
+ 2
1072
+ );
1073
+ }
1074
+ const optionUpdate = optionJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(optionJsonRaw, "--option-json") : {
1075
+ id: optionId,
1076
+ ...typeof args.name === "string" && args.name.trim() ? { name: args.name.trim() } : {},
1077
+ ...position !== void 0 ? { position } : {}
1078
+ };
1079
+ if (!optionUpdate || typeof optionUpdate !== "object") {
1080
+ throw new import_errors.CliError("--option-json must be a JSON object", 2);
1081
+ }
1082
+ if (!optionUpdate.id) optionUpdate.id = optionId;
1083
+ const optionValuesToAdd = addValuesJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(addValuesJsonRaw, "--add-values-json") : addValueNames.map((name) => ({ name }));
1084
+ const optionValuesToDeleteJson = deleteValueIdsJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(deleteValueIdsJsonRaw, "--delete-value-ids-json") : void 0;
1085
+ const optionValuesToUpdate = updateValuesJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(updateValuesJsonRaw, "--update-values-json") : void 0;
1086
+ const optionValuesToDeleteFromIds = deleteValueIdSpecs.map((id) => (0, import_gid.coerceGid)(id, "ProductOptionValue"));
1087
+ const needsResolutionByName = deleteValueNames.length > 0 || renameValueSpecs.length > 0;
1088
+ if (needsResolutionByName && ctx.dryRun) {
1089
+ throw new import_errors.CliError(
1090
+ "Name-based value changes are not supported in --dry-run mode. Use --delete-value-id/--rename-value-id or JSON flags.",
1091
+ 2
1092
+ );
1093
+ }
1094
+ let resolvedDeletesFromNames = [];
1095
+ let resolvedUpdatesFromNames = [];
1096
+ if (needsResolutionByName) {
1097
+ const options = await fetchProductOptionsForResolution({ ctx, productId });
1098
+ const option = options.find((o) => typeof o?.id === "string" && o.id === optionId);
1099
+ if (!option) throw new import_errors.CliError(`Option not found on product: ${optionId}`, 2);
1100
+ const optionValues = option.optionValues ?? [];
1101
+ const byName = /* @__PURE__ */ new Map();
1102
+ for (const ov of optionValues) {
1103
+ const n = typeof ov?.name === "string" ? ov.name : void 0;
1104
+ if (!n) continue;
1105
+ const list = byName.get(n) ?? [];
1106
+ list.push(ov);
1107
+ byName.set(n, list);
1108
+ }
1109
+ for (const name of deleteValueNames) {
1110
+ const matches = byName.get(name) ?? [];
1111
+ if (matches.length === 0) throw new import_errors.CliError(`Option value not found: "${name}"`, 2);
1112
+ if (matches.length > 1) {
1113
+ throw new import_errors.CliError(`Multiple option values named "${name}" exist. Use --delete-value-id instead.`, 2);
1114
+ }
1115
+ const id = matches[0]?.id;
1116
+ if (typeof id !== "string" || !id) throw new import_errors.CliError(`Option value "${name}" is missing an ID`, 2);
1117
+ resolvedDeletesFromNames.push(id);
1118
+ }
1119
+ for (const spec of renameValueSpecs) {
1120
+ const { from, to } = parseFromTo(spec, "--rename-value");
1121
+ const matches = byName.get(from) ?? [];
1122
+ if (matches.length === 0) throw new import_errors.CliError(`Option value not found: "${from}"`, 2);
1123
+ if (matches.length > 1) {
1124
+ throw new import_errors.CliError(`Multiple option values named "${from}" exist. Use --rename-value-id instead.`, 2);
1125
+ }
1126
+ const id = matches[0]?.id;
1127
+ if (typeof id !== "string" || !id) throw new import_errors.CliError(`Option value "${from}" is missing an ID`, 2);
1128
+ resolvedUpdatesFromNames.push({ id, name: to });
1129
+ }
1130
+ }
1131
+ const renameUpdatesFromIds = [];
1132
+ for (const spec of renameValueIdSpecs) {
1133
+ const { from, to } = parseFromTo(spec, "--rename-value-id");
1134
+ renameUpdatesFromIds.push({ id: (0, import_gid.coerceGid)(from, "ProductOptionValue"), name: to });
1135
+ }
1136
+ const deletesFromJson = optionValuesToDeleteJson !== void 0 ? (() => {
1137
+ if (!Array.isArray(optionValuesToDeleteJson)) {
1138
+ throw new import_errors.CliError("--delete-value-ids-json must be a JSON array", 2);
1139
+ }
1140
+ return optionValuesToDeleteJson.map((id) => {
1141
+ if (typeof id !== "string" || !id.trim()) throw new import_errors.CliError("delete-value-ids-json must contain strings", 2);
1142
+ return id;
1143
+ });
1144
+ })() : [];
1145
+ const deletes = Array.from(/* @__PURE__ */ new Set([...optionValuesToDeleteFromIds, ...resolvedDeletesFromNames, ...deletesFromJson]));
1146
+ const updates = [
1147
+ ...Array.isArray(optionValuesToUpdate) ? optionValuesToUpdate : optionValuesToUpdate ? [optionValuesToUpdate] : [],
1148
+ ...resolvedUpdatesFromNames,
1149
+ ...renameUpdatesFromIds
1150
+ ];
1151
+ if (optionValuesToAdd !== void 0 && optionValuesToAdd !== null && !Array.isArray(optionValuesToAdd)) {
1152
+ throw new import_errors.CliError("--add-values-json must be a JSON array", 2);
1153
+ }
1154
+ if (optionValuesToUpdate !== void 0 && optionValuesToUpdate !== null && !Array.isArray(optionValuesToUpdate)) {
1155
+ throw new import_errors.CliError("--update-values-json must be a JSON array", 2);
1156
+ }
1157
+ const variantStrategy = normalizeProductOptionUpdateVariantStrategy(args["variant-strategy"]);
1158
+ const selection = (0, import_select.resolveSelection)({
1159
+ resource: "products",
1160
+ view: ctx.view,
1161
+ baseSelection: getProductSelectionForOptions(ctx.view),
1162
+ select: args.select,
1163
+ selection: args.selection,
1164
+ include: args.include,
1165
+ ensureId: ctx.quiet
1166
+ });
505
1167
  const result = await (0, import_router.runMutation)(ctx, {
506
- productOptionsCreate: {
1168
+ productOptionUpdate: {
507
1169
  __args: {
508
1170
  productId,
509
- options,
1171
+ option: optionUpdate,
1172
+ ...Array.isArray(optionValuesToAdd) && optionValuesToAdd.length ? { optionValuesToAdd } : {},
1173
+ ...deletes.length ? { optionValuesToDelete: deletes } : {},
1174
+ ...updates.length ? { optionValuesToUpdate: updates } : {},
510
1175
  ...variantStrategy ? { variantStrategy } : {}
511
1176
  },
512
- product: productSummarySelection,
1177
+ product: selection,
513
1178
  userErrors: { code: true, field: true, message: true }
514
1179
  }
515
1180
  });
516
1181
  if (result === void 0) return;
517
- (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionsCreate, failOnUserErrors: ctx.failOnUserErrors });
518
- if (ctx.quiet) return console.log(result.productOptionsCreate?.product?.id ?? "");
519
- (0, import_output.printJson)(result.productOptionsCreate, ctx.format !== "raw");
1182
+ (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionUpdate, failOnUserErrors: ctx.failOnUserErrors });
1183
+ (0, import_output.printNode)({ node: result.productOptionUpdate?.product, format: ctx.format, quiet: ctx.quiet });
520
1184
  return;
521
1185
  }
522
- if (verb === "options-delete") {
1186
+ if (verb === "options delete") {
523
1187
  const args = (0, import_router.parseStandardArgs)({
524
1188
  argv,
525
1189
  extraOptions: {
526
1190
  "product-id": { type: "string" },
527
- "option-ids": { type: "string", multiple: true },
1191
+ "option-id": { type: "string", multiple: true },
1192
+ "option-name": { type: "string", multiple: true },
528
1193
  strategy: { type: "string" }
529
1194
  }
530
1195
  });
531
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
532
- const options = (0, import_shared.parseStringList)(args["option-ids"], "--option-ids");
533
- const strategy = args.strategy;
1196
+ const productId = requireProductIdForSubverb(args);
1197
+ const optionIdsRaw = (0, import_shared.parseStringList)(args["option-id"], "--option-id", { allowEmpty: true });
1198
+ const optionNames = (0, import_shared.parseStringList)(args["option-name"], "--option-name", { allowEmpty: true });
1199
+ const strategy = normalizeProductOptionDeleteStrategy(args.strategy);
1200
+ if (optionIdsRaw.length === 0 && optionNames.length === 0) {
1201
+ throw new import_errors.CliError("Missing options: pass one or more --option-id and/or --option-name entries", 2);
1202
+ }
1203
+ if (ctx.dryRun && optionNames.length > 0) {
1204
+ throw new import_errors.CliError("Option name resolution is not supported in --dry-run mode. Use --option-id instead.", 2);
1205
+ }
1206
+ const optionIds = optionIdsRaw.map((id) => (0, import_gid.coerceGid)(id, "ProductOption"));
1207
+ if (optionNames.length > 0) {
1208
+ const options = await fetchProductOptionsForResolution({ ctx, productId });
1209
+ for (const name of optionNames) {
1210
+ const opt = resolveSingleOptionByName(options, name);
1211
+ if (!opt) throw new import_errors.CliError(`Option not found on product: "${name}"`, 2);
1212
+ const id = opt?.id;
1213
+ if (typeof id !== "string" || !id) throw new import_errors.CliError(`Option "${name}" is missing an ID`, 2);
1214
+ optionIds.push(id);
1215
+ }
1216
+ }
1217
+ const uniqueOptionIds = Array.from(new Set(optionIds));
534
1218
  const result = await (0, import_router.runMutation)(ctx, {
535
1219
  productOptionsDelete: {
536
- __args: { productId, options, ...strategy ? { strategy } : {} },
1220
+ __args: { productId, options: uniqueOptionIds, ...strategy ? { strategy } : {} },
537
1221
  deletedOptionsIds: true,
538
- product: productSummarySelection,
539
1222
  userErrors: { code: true, field: true, message: true }
540
1223
  }
541
1224
  });
542
1225
  if (result === void 0) return;
543
1226
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionsDelete, failOnUserErrors: ctx.failOnUserErrors });
544
- if (ctx.quiet) return console.log(result.productOptionsDelete?.product?.id ?? "");
545
- (0, import_output.printJson)(result.productOptionsDelete, ctx.format !== "raw");
1227
+ const deleted = result.productOptionsDelete?.deletedOptionsIds ?? [];
1228
+ if (ctx.quiet) {
1229
+ for (const id of deleted) {
1230
+ if (typeof id === "string" && id) console.log(id);
1231
+ }
1232
+ return;
1233
+ }
1234
+ (0, import_output.printNode)({
1235
+ node: { deletedOptionsIds: deleted.filter((id) => typeof id === "string" && id.length > 0) },
1236
+ format: ctx.format,
1237
+ quiet: false
1238
+ });
546
1239
  return;
547
1240
  }
548
- if (verb === "options-reorder") {
1241
+ if (verb === "options reorder") {
549
1242
  const args = (0, import_router.parseStandardArgs)({
550
1243
  argv,
551
1244
  extraOptions: {
552
1245
  "product-id": { type: "string" },
553
- options: { type: "string" }
1246
+ order: { type: "string", multiple: true },
1247
+ "options-json": { type: "string" }
554
1248
  }
555
1249
  });
556
- const productId = (0, import_shared.requireId)(args["product-id"], "Product");
557
- const options = (0, import_shared.parseJsonArg)(args.options, "--options");
558
- if (!Array.isArray(options)) throw new import_errors.CliError("--options must be a JSON array", 2);
1250
+ const productId = requireProductIdForSubverb(args);
1251
+ const orderSpecs = parseRepeatableStrings(args.order, "--order", { allowEmpty: true });
1252
+ const optionsJsonRaw = args["options-json"];
1253
+ if (orderSpecs.length > 0 && optionsJsonRaw) {
1254
+ throw new import_errors.CliError("Do not pass both --order and --options-json", 2);
1255
+ }
1256
+ const selection = (0, import_select.resolveSelection)({
1257
+ resource: "products",
1258
+ view: ctx.view,
1259
+ baseSelection: getProductSelectionForOptions(ctx.view),
1260
+ select: args.select,
1261
+ selection: args.selection,
1262
+ include: args.include,
1263
+ ensureId: ctx.quiet
1264
+ });
1265
+ const optionsInput = optionsJsonRaw !== void 0 ? (0, import_shared.parseJsonArg)(optionsJsonRaw, "--options-json") : void 0;
1266
+ let options = [];
1267
+ if (optionsInput !== void 0) {
1268
+ if (!Array.isArray(optionsInput)) throw new import_errors.CliError("--options-json must be a JSON array", 2);
1269
+ options = optionsInput;
1270
+ } else {
1271
+ if (orderSpecs.length === 0) throw new import_errors.CliError("Missing order: pass one or more --order entries, or --options-json", 2);
1272
+ const parsed = [];
1273
+ const hasAnyValueOrder = orderSpecs.some((s) => s.includes("="));
1274
+ if (hasAnyValueOrder && ctx.dryRun) {
1275
+ throw new import_errors.CliError("Value reordering via --order is not supported in --dry-run mode. Use --options-json.", 2);
1276
+ }
1277
+ let optionsForValidation;
1278
+ if (hasAnyValueOrder) {
1279
+ optionsForValidation = await fetchProductOptionsForResolution({ ctx, productId });
1280
+ }
1281
+ for (const spec of orderSpecs) {
1282
+ const trimmed = spec.trim();
1283
+ const eq = trimmed.indexOf("=");
1284
+ const optionToken = (eq === -1 ? trimmed : trimmed.slice(0, eq)).trim();
1285
+ if (!optionToken) throw new import_errors.CliError("--order cannot be empty", 2);
1286
+ const optionInput = looksLikeGidOrNumericId(optionToken) ? { id: (0, import_gid.coerceGid)(optionToken, "ProductOption") } : { name: optionToken };
1287
+ if (eq !== -1) {
1288
+ const valuesPart = trimmed.slice(eq + 1).trim();
1289
+ if (!valuesPart) throw new import_errors.CliError(`--order "${spec}" must include at least one value after '='`, 2);
1290
+ const valueTokens = valuesPart.split(",").map((v) => v.trim()).filter(Boolean);
1291
+ if (valueTokens.length === 0) throw new import_errors.CliError(`--order "${spec}" must include at least one value after '='`, 2);
1292
+ if (!optionsForValidation) throw new import_errors.CliError("Internal error: missing options for validation", 2);
1293
+ const resolvedOption = "id" in optionInput ? optionsForValidation.find((o) => typeof o?.id === "string" && o.id === optionInput.id) : resolveSingleOptionByName(optionsForValidation, optionInput.name);
1294
+ if (!resolvedOption) {
1295
+ const ref = "id" in optionInput ? optionInput.id : `"${optionInput.name}"`;
1296
+ throw new import_errors.CliError(`Option not found on product: ${ref}`, 2);
1297
+ }
1298
+ const optionValues = resolvedOption.optionValues ?? [];
1299
+ const byName = /* @__PURE__ */ new Map();
1300
+ const existingIds = [];
1301
+ for (const ov of optionValues) {
1302
+ const id = typeof ov?.id === "string" ? ov.id : void 0;
1303
+ const name = typeof ov?.name === "string" ? ov.name : void 0;
1304
+ if (id) existingIds.push(id);
1305
+ if (name) {
1306
+ const list = byName.get(name) ?? [];
1307
+ list.push(ov);
1308
+ byName.set(name, list);
1309
+ }
1310
+ }
1311
+ const providedIds = [];
1312
+ for (const vt of valueTokens) {
1313
+ if (looksLikeGidOrNumericId(vt)) {
1314
+ providedIds.push((0, import_gid.coerceGid)(vt, "ProductOptionValue"));
1315
+ continue;
1316
+ }
1317
+ const matches = byName.get(vt) ?? [];
1318
+ if (matches.length === 0) throw new import_errors.CliError(`Option value not found: "${vt}"`, 2);
1319
+ if (matches.length > 1) throw new import_errors.CliError(`Multiple option values named "${vt}" exist. Use IDs.`, 2);
1320
+ const id = matches[0]?.id;
1321
+ if (typeof id !== "string" || !id) throw new import_errors.CliError(`Option value "${vt}" is missing an ID`, 2);
1322
+ providedIds.push(id);
1323
+ }
1324
+ const existingSet = new Set(existingIds);
1325
+ const providedSet = new Set(providedIds);
1326
+ if (providedSet.size !== providedIds.length) {
1327
+ throw new import_errors.CliError("Duplicate option values provided in --order value list", 2);
1328
+ }
1329
+ const missing = existingIds.filter((id) => !providedSet.has(id));
1330
+ const extra = providedIds.filter((id) => !existingSet.has(id));
1331
+ if (missing.length || extra.length) {
1332
+ throw new import_errors.CliError(
1333
+ `--order "${optionToken}=..." must include the full value order for that option.`,
1334
+ 2
1335
+ );
1336
+ }
1337
+ parsed.push({
1338
+ id: resolvedOption.id,
1339
+ values: providedIds.map((id) => ({ id }))
1340
+ });
1341
+ } else {
1342
+ parsed.push(optionInput);
1343
+ }
1344
+ }
1345
+ options = parsed;
1346
+ }
559
1347
  const result = await (0, import_router.runMutation)(ctx, {
560
1348
  productOptionsReorder: {
561
1349
  __args: { productId, options },
562
- product: productSummarySelection,
1350
+ product: selection,
563
1351
  userErrors: { code: true, field: true, message: true }
564
1352
  }
565
1353
  });
566
1354
  if (result === void 0) return;
567
1355
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productOptionsReorder, failOnUserErrors: ctx.failOnUserErrors });
568
- if (ctx.quiet) return console.log(result.productOptionsReorder?.product?.id ?? "");
569
- (0, import_output.printJson)(result.productOptionsReorder, ctx.format !== "raw");
1356
+ (0, import_output.printNode)({ node: result.productOptionsReorder?.product, format: ctx.format, quiet: ctx.quiet });
570
1357
  return;
571
1358
  }
572
1359
  if (verb === "combined-listing-update") {
@@ -581,12 +1368,21 @@ const runProducts = async ({
581
1368
  "options-and-values": { type: "string" }
582
1369
  }
583
1370
  });
584
- const parentProductId = (0, import_shared.requireId)(args["parent-product-id"], "Product");
1371
+ const parentProductId = (0, import_shared.requireId)(args["parent-product-id"], "Product", "--parent-product-id");
585
1372
  const optionsAndValues = args["options-and-values"] ? (0, import_shared.parseJsonArg)(args["options-and-values"], "--options-and-values") : void 0;
586
1373
  const productsAdded = args["products-added"] ? (0, import_shared.parseJsonArg)(args["products-added"], "--products-added") : void 0;
587
1374
  const productsEdited = args["products-edited"] ? (0, import_shared.parseJsonArg)(args["products-edited"], "--products-edited") : void 0;
588
1375
  const productsRemovedIds = args["products-removed-ids"] ? (0, import_shared.parseIds)(args["products-removed-ids"], "Product") : void 0;
589
1376
  const title = args.title;
1377
+ const selection = (0, import_select.resolveSelection)({
1378
+ resource: "products",
1379
+ view: ctx.view,
1380
+ baseSelection: getProductSelection(ctx.view),
1381
+ select: args.select,
1382
+ selection: args.selection,
1383
+ include: args.include,
1384
+ ensureId: ctx.quiet
1385
+ });
590
1386
  const result = await (0, import_router.runMutation)(ctx, {
591
1387
  combinedListingUpdate: {
592
1388
  __args: {
@@ -597,14 +1393,13 @@ const runProducts = async ({
597
1393
  ...productsEdited !== void 0 ? { productsEdited } : {},
598
1394
  ...productsRemovedIds !== void 0 ? { productsRemovedIds } : {}
599
1395
  },
600
- product: productSummarySelection,
1396
+ product: selection,
601
1397
  userErrors: { code: true, field: true, message: true }
602
1398
  }
603
1399
  });
604
1400
  if (result === void 0) return;
605
1401
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.combinedListingUpdate, failOnUserErrors: ctx.failOnUserErrors });
606
- if (ctx.quiet) return console.log(result.combinedListingUpdate?.product?.id ?? "");
607
- (0, import_output.printJson)(result.combinedListingUpdate, ctx.format !== "raw");
1402
+ (0, import_output.printNode)({ node: result.combinedListingUpdate?.product, format: ctx.format, quiet: ctx.quiet });
608
1403
  return;
609
1404
  }
610
1405
  if (verb === "bundle-create" || verb === "bundle-update") {
@@ -615,6 +1410,15 @@ const runProducts = async ({
615
1410
  setJsonArgs: args["set-json"]
616
1411
  });
617
1412
  if (!built.used) throw new import_errors.CliError("Missing --input or --set/--set-json", 2);
1413
+ const productSelection = (0, import_select.resolveSelection)({
1414
+ resource: "products",
1415
+ view: ctx.view,
1416
+ baseSelection: getProductSelection(ctx.view),
1417
+ select: args.select,
1418
+ selection: args.selection,
1419
+ include: args.include,
1420
+ ensureId: ctx.quiet
1421
+ });
618
1422
  const mutation = verb === "bundle-create" ? "productBundleCreate" : "productBundleUpdate";
619
1423
  const result = await (0, import_router.runMutation)(ctx, {
620
1424
  [mutation]: {
@@ -622,8 +1426,7 @@ const runProducts = async ({
622
1426
  productBundleOperation: {
623
1427
  id: true,
624
1428
  status: true,
625
- product: { id: true, title: true },
626
- userErrors: { field: true, message: true }
1429
+ product: productSelection
627
1430
  },
628
1431
  userErrors: { field: true, message: true }
629
1432
  }
@@ -631,8 +1434,28 @@ const runProducts = async ({
631
1434
  if (result === void 0) return;
632
1435
  const payload = result[mutation];
633
1436
  (0, import_userErrors.maybeFailOnUserErrors)({ payload, failOnUserErrors: ctx.failOnUserErrors });
634
- if (ctx.quiet) return console.log(payload?.productBundleOperation?.id ?? "");
635
- (0, import_output.printJson)(payload, ctx.format !== "raw");
1437
+ const operation = payload?.productBundleOperation ? {
1438
+ id: payload.productBundleOperation.id,
1439
+ status: payload.productBundleOperation.status
1440
+ } : void 0;
1441
+ const product = payload?.productBundleOperation?.product;
1442
+ if (product && typeof product === "object") {
1443
+ (0, import_output.printNode)({
1444
+ node: { ...product, ...operation ? { operation } : {} },
1445
+ format: ctx.format,
1446
+ quiet: ctx.quiet
1447
+ });
1448
+ return;
1449
+ }
1450
+ if (ctx.quiet) return;
1451
+ (0, import_output.printJson)(
1452
+ {
1453
+ product: product ?? null,
1454
+ ...operation ? { operation } : {},
1455
+ userErrors: payload?.userErrors ?? []
1456
+ },
1457
+ ctx.format !== "raw"
1458
+ );
636
1459
  return;
637
1460
  }
638
1461
  if (verb === "count") {
@@ -671,12 +1494,14 @@ const runProducts = async ({
671
1494
  const args = (0, import_router.parseStandardArgs)({
672
1495
  argv,
673
1496
  extraOptions: {
1497
+ "product-id": { type: "string" },
674
1498
  "publication-id": { type: "string", multiple: true },
675
1499
  publication: { type: "string", multiple: true },
676
1500
  at: { type: "string" },
677
1501
  now: { type: "boolean" }
678
1502
  }
679
1503
  });
1504
+ const id = requireProductIdForRootVerb(args);
680
1505
  const publicationIds = args["publication-id"] ?? [];
681
1506
  const publicationNames = args.publication ?? [];
682
1507
  const resolvedPublicationIds = await (0, import_publishablePublish.resolvePublicationIds)({
@@ -688,7 +1513,7 @@ const runProducts = async ({
688
1513
  const publishDate = (0, import_publishablePublish.parsePublishDate)({ at: args.at, now: args.now });
689
1514
  const payload2 = await (0, import_publishablePublish.publishProduct)({
690
1515
  ctx,
691
- id: args.id,
1516
+ id,
692
1517
  publicationIds: resolvedPublicationIds,
693
1518
  publishDate
694
1519
  });
@@ -699,7 +1524,7 @@ const runProducts = async ({
699
1524
  }
700
1525
  const payload = await (0, import_publishablePublish.unpublishProduct)({
701
1526
  ctx,
702
- id: args.id,
1527
+ id,
703
1528
  publicationIds: resolvedPublicationIds
704
1529
  });
705
1530
  if (payload === void 0) return;
@@ -711,6 +1536,7 @@ const runProducts = async ({
711
1536
  const args = (0, import_router.parseStandardArgs)({
712
1537
  argv,
713
1538
  extraOptions: {
1539
+ "product-id": { type: "string" },
714
1540
  at: { type: "string" },
715
1541
  now: { type: "boolean" }
716
1542
  }
@@ -719,13 +1545,14 @@ const runProducts = async ({
719
1545
  await (0, import_resolvePublicationId.listPublications)(ctx);
720
1546
  return;
721
1547
  }
1548
+ const id = requireProductIdForRootVerb(args);
722
1549
  const publishDate = (0, import_publishablePublish.parsePublishDate)({ at: args.at, now: args.now });
723
1550
  const publications = await (0, import_resolvePublicationId.listPublications)(ctx);
724
1551
  const publicationIds = publications.map((p) => p.id).filter(Boolean);
725
1552
  if (publicationIds.length === 0) throw new import_errors.CliError("No publications found to publish to", 2);
726
1553
  const payload = await (0, import_publishablePublish.publishProduct)({
727
1554
  ctx,
728
- id: args.id,
1555
+ id,
729
1556
  publicationIds,
730
1557
  publishDate
731
1558
  });
@@ -735,21 +1562,22 @@ const runProducts = async ({
735
1562
  return;
736
1563
  }
737
1564
  if (verb === "metafields upsert") {
738
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
1565
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1566
+ const productId = requireProductIdForSubverb(args);
739
1567
  const built = (0, import_input.buildInput)({
740
1568
  inputArg: args.input,
741
1569
  setArgs: args.set,
742
1570
  setJsonArgs: args["set-json"]
743
1571
  });
744
1572
  if (!built.used) throw new import_errors.CliError("Missing --input or --set/--set-json", 2);
745
- const result = await (0, import_metafieldsUpsert.metafieldsUpsert)({ ctx, id: args.id, input: built.input });
1573
+ const result = await (0, import_metafieldsUpsert.metafieldsUpsert)({ ctx, id: productId, input: built.input });
746
1574
  if (result === void 0) return;
747
1575
  (0, import_output.printJson)(result);
748
1576
  return;
749
1577
  }
750
1578
  if (verb === "get") {
751
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
752
- const id = (0, import_shared.requireId)(args.id, "Product");
1579
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1580
+ const id = requireProductIdForRootVerb(args);
753
1581
  const selectValues = Array.isArray(args.select) ? args.select : args.select ? [args.select] : [];
754
1582
  const selectionOverride = typeof args.selection === "string" && args.selection.length > 0;
755
1583
  const select = !selectionOverride && ctx.view !== "raw" && ctx.view !== "ids" ? Array.from(/* @__PURE__ */ new Set([...selectValues, "resourcePublicationsV2.nodes.publication.name"])) : args.select;
@@ -829,39 +1657,57 @@ const runProducts = async ({
829
1657
  setJsonArgs: args["set-json"]
830
1658
  });
831
1659
  if (!built.used) throw new import_errors.CliError("Missing --input or --set/--set-json", 2);
1660
+ const selection = (0, import_select.resolveSelection)({
1661
+ resource: "products",
1662
+ view: ctx.view,
1663
+ baseSelection: getProductSelection(ctx.view),
1664
+ select: args.select,
1665
+ selection: args.selection,
1666
+ include: args.include,
1667
+ ensureId: ctx.quiet,
1668
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1669
+ });
832
1670
  const result = await (0, import_router.runMutation)(ctx, {
833
1671
  productCreate: {
834
1672
  __args: { input: built.input },
835
- product: productSummarySelection,
1673
+ product: selection,
836
1674
  userErrors: { field: true, message: true }
837
1675
  }
838
1676
  });
839
1677
  if (result === void 0) return;
840
1678
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productCreate, failOnUserErrors: ctx.failOnUserErrors });
841
- if (ctx.quiet) return console.log(result.productCreate?.product?.id ?? "");
842
- (0, import_output.printJson)(result.productCreate, ctx.format !== "raw");
1679
+ (0, import_output.printNode)({ node: result.productCreate?.product, format: ctx.format, quiet: ctx.quiet });
843
1680
  return;
844
1681
  }
845
1682
  if (verb === "archive" || verb === "unarchive") {
846
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
847
- const id = (0, import_shared.requireId)(args.id, "Product");
1683
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1684
+ const id = requireProductIdForRootVerb(args);
848
1685
  const status = verb === "archive" ? "ARCHIVED" : args.status ?? "DRAFT";
1686
+ const selection = (0, import_select.resolveSelection)({
1687
+ resource: "products",
1688
+ view: ctx.view,
1689
+ baseSelection: getProductSelection(ctx.view),
1690
+ select: args.select,
1691
+ selection: args.selection,
1692
+ include: args.include,
1693
+ ensureId: ctx.quiet,
1694
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1695
+ });
849
1696
  const result = await (0, import_router.runMutation)(ctx, {
850
1697
  productUpdate: {
851
1698
  __args: { input: { id, status } },
852
- product: productSummarySelection,
1699
+ product: selection,
853
1700
  userErrors: { field: true, message: true }
854
1701
  }
855
1702
  });
856
1703
  if (result === void 0) return;
857
1704
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
858
- if (ctx.quiet) return console.log(result.productUpdate?.product?.id ?? "");
859
- (0, import_output.printJson)(result.productUpdate, ctx.format !== "raw");
1705
+ (0, import_output.printNode)({ node: result.productUpdate?.product, format: ctx.format, quiet: ctx.quiet });
860
1706
  return;
861
1707
  }
862
1708
  if (verb === "update") {
863
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
864
- const id = (0, import_shared.requireId)(args.id, "Product");
1709
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1710
+ const id = requireProductIdForRootVerb(args);
865
1711
  const built = (0, import_input.buildInput)({
866
1712
  inputArg: args.input,
867
1713
  setArgs: args.set,
@@ -869,22 +1715,31 @@ const runProducts = async ({
869
1715
  });
870
1716
  if (!built.used) throw new import_errors.CliError("Missing --input or --set/--set-json", 2);
871
1717
  const input = { ...built.input, id };
1718
+ const selection = (0, import_select.resolveSelection)({
1719
+ resource: "products",
1720
+ view: ctx.view,
1721
+ baseSelection: getProductSelection(ctx.view),
1722
+ select: args.select,
1723
+ selection: args.selection,
1724
+ include: args.include,
1725
+ ensureId: ctx.quiet,
1726
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1727
+ });
872
1728
  const result = await (0, import_router.runMutation)(ctx, {
873
1729
  productUpdate: {
874
1730
  __args: { input },
875
- product: productSummarySelection,
1731
+ product: selection,
876
1732
  userErrors: { field: true, message: true }
877
1733
  }
878
1734
  });
879
1735
  if (result === void 0) return;
880
1736
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
881
- if (ctx.quiet) return console.log(result.productUpdate?.product?.id ?? "");
882
- (0, import_output.printJson)(result.productUpdate, ctx.format !== "raw");
1737
+ (0, import_output.printNode)({ node: result.productUpdate?.product, format: ctx.format, quiet: ctx.quiet });
883
1738
  return;
884
1739
  }
885
1740
  if (verb === "delete") {
886
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
887
- const id = (0, import_shared.requireId)(args.id, "Product");
1741
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1742
+ const id = requireProductIdForRootVerb(args);
888
1743
  if (!args.yes) throw new import_errors.CliError("Refusing to delete without --yes", 2);
889
1744
  const result = await (0, import_router.runMutation)(ctx, {
890
1745
  productDelete: {
@@ -900,8 +1755,8 @@ const runProducts = async ({
900
1755
  return;
901
1756
  }
902
1757
  if (verb === "duplicate") {
903
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
904
- const id = (0, import_shared.requireId)(args.id, "Product");
1758
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1759
+ const id = requireProductIdForRootVerb(args);
905
1760
  const built = (0, import_input.buildInput)({
906
1761
  inputArg: void 0,
907
1762
  setArgs: args.set,
@@ -920,35 +1775,53 @@ const runProducts = async ({
920
1775
  newTitle,
921
1776
  ...built.used ? built.input : {}
922
1777
  };
1778
+ const selection = (0, import_select.resolveSelection)({
1779
+ resource: "products",
1780
+ view: ctx.view,
1781
+ baseSelection: getProductSelection(ctx.view),
1782
+ select: args.select,
1783
+ selection: args.selection,
1784
+ include: args.include,
1785
+ ensureId: ctx.quiet,
1786
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1787
+ });
923
1788
  const result = await (0, import_router.runMutation)(ctx, {
924
1789
  productDuplicate: {
925
1790
  __args: mutationArgs,
926
- newProduct: productSummarySelection,
1791
+ newProduct: selection,
927
1792
  userErrors: { field: true, message: true }
928
1793
  }
929
1794
  });
930
1795
  if (result === void 0) return;
931
1796
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productDuplicate, failOnUserErrors: ctx.failOnUserErrors });
932
- if (ctx.quiet) return console.log(result.productDuplicate?.newProduct?.id ?? "");
933
- (0, import_output.printJson)(result.productDuplicate, ctx.format !== "raw");
1797
+ (0, import_output.printNode)({ node: result.productDuplicate?.newProduct, format: ctx.format, quiet: ctx.quiet });
934
1798
  return;
935
1799
  }
936
1800
  if (verb === "set-status") {
937
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
938
- const id = (0, import_shared.requireId)(args.id, "Product");
1801
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1802
+ const id = requireProductIdForRootVerb(args);
939
1803
  const status = args.status;
940
1804
  if (!status) throw new import_errors.CliError("Missing --status (ACTIVE|DRAFT|ARCHIVED)", 2);
1805
+ const selection = (0, import_select.resolveSelection)({
1806
+ resource: "products",
1807
+ view: ctx.view,
1808
+ baseSelection: getProductSelection(ctx.view),
1809
+ select: args.select,
1810
+ selection: args.selection,
1811
+ include: args.include,
1812
+ ensureId: ctx.quiet,
1813
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1814
+ });
941
1815
  const result = await (0, import_router.runMutation)(ctx, {
942
1816
  productUpdate: {
943
1817
  __args: { input: { id, status } },
944
- product: productSummarySelection,
1818
+ product: selection,
945
1819
  userErrors: { field: true, message: true }
946
1820
  }
947
1821
  });
948
1822
  if (result === void 0) return;
949
1823
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
950
- if (ctx.quiet) return console.log(result.productUpdate?.product?.id ?? "");
951
- (0, import_output.printJson)(result.productUpdate, ctx.format !== "raw");
1824
+ (0, import_output.printNode)({ node: result.productUpdate?.product, format: ctx.format, quiet: ctx.quiet });
952
1825
  return;
953
1826
  }
954
1827
  if (verb === "set-price") {
@@ -961,7 +1834,7 @@ const runProducts = async ({
961
1834
  "product-id": { type: "string" }
962
1835
  }
963
1836
  });
964
- const variantId = (0, import_shared.requireId)(args["variant-id"], "ProductVariant");
1837
+ const variantId = (0, import_shared.requireId)(args["variant-id"], "ProductVariant", "--variant-id");
965
1838
  const price = args.price;
966
1839
  if (!price) throw new import_errors.CliError("Missing --price", 2);
967
1840
  const compareAtPrice = args["compare-at-price"];
@@ -984,6 +1857,16 @@ const runProducts = async ({
984
1857
  if (!pid) throw new import_errors.CliError("Could not resolve productId from --variant-id", 2);
985
1858
  productId = pid;
986
1859
  }
1860
+ const variantSelection = (0, import_select.resolveSelection)({
1861
+ typeName: "ProductVariant",
1862
+ view: ctx.view,
1863
+ baseSelection: getProductVariantSelection(ctx.view),
1864
+ select: args.select,
1865
+ selection: args.selection,
1866
+ include: args.include,
1867
+ ensureId: ctx.quiet,
1868
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1869
+ });
987
1870
  const result = await (0, import_router.runMutation)(ctx, {
988
1871
  productVariantsBulkUpdate: {
989
1872
  __args: {
@@ -996,7 +1879,8 @@ const runProducts = async ({
996
1879
  }
997
1880
  ]
998
1881
  },
999
- userErrors: { field: true, message: true }
1882
+ userErrors: { field: true, message: true },
1883
+ productVariants: variantSelection
1000
1884
  }
1001
1885
  });
1002
1886
  if (result === void 0) return;
@@ -1004,19 +1888,29 @@ const runProducts = async ({
1004
1888
  payload: result.productVariantsBulkUpdate,
1005
1889
  failOnUserErrors: ctx.failOnUserErrors
1006
1890
  });
1007
- if (ctx.quiet) return console.log(variantId);
1008
- (0, import_output.printJson)(result.productVariantsBulkUpdate, ctx.format !== "raw");
1891
+ const variant = result.productVariantsBulkUpdate?.productVariants?.[0];
1892
+ (0, import_output.printNode)({ node: variant, format: ctx.format, quiet: ctx.quiet });
1009
1893
  return;
1010
1894
  }
1011
1895
  if (verb === "add-tags" || verb === "remove-tags") {
1012
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
1013
- const id = (0, import_shared.requireId)(args.id, "Product");
1896
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
1897
+ const id = requireProductIdForRootVerb(args);
1014
1898
  const tags = parseTags(args.tags);
1015
1899
  const mutationField = verb === "add-tags" ? "tagsAdd" : "tagsRemove";
1900
+ const nodeSelection = (0, import_select.resolveSelection)({
1901
+ typeName: "Product",
1902
+ view: ctx.view,
1903
+ baseSelection: getProductSelectionForTags(ctx.view),
1904
+ select: args.select,
1905
+ selection: args.selection,
1906
+ include: args.include,
1907
+ ensureId: ctx.quiet,
1908
+ defaultConnectionFirst: ctx.view === "all" ? 50 : 10
1909
+ });
1016
1910
  const request = {
1017
1911
  [mutationField]: {
1018
1912
  __args: { id, tags },
1019
- node: { id: true },
1913
+ node: { __typename: true, on_Product: nodeSelection },
1020
1914
  userErrors: { field: true, message: true }
1021
1915
  }
1022
1916
  };
@@ -1024,91 +1918,240 @@ const runProducts = async ({
1024
1918
  if (result === void 0) return;
1025
1919
  const payload = result[mutationField];
1026
1920
  (0, import_userErrors.maybeFailOnUserErrors)({ payload, failOnUserErrors: ctx.failOnUserErrors });
1027
- if (ctx.quiet) return console.log(payload?.node?.id ?? "");
1028
- (0, import_output.printJson)(payload, ctx.format !== "raw");
1921
+ (0, import_output.printNode)({ node: payload?.node, format: ctx.format, quiet: ctx.quiet });
1029
1922
  return;
1030
1923
  }
1031
1924
  if (verb === "media add") {
1032
1925
  const args = (0, import_router.parseStandardArgs)({
1033
1926
  argv,
1034
1927
  extraOptions: {
1928
+ "product-id": { type: "string" },
1035
1929
  url: { type: "string", multiple: true },
1036
1930
  alt: { type: "string" },
1037
- "media-type": { type: "string" }
1931
+ "media-type": { type: "string" },
1932
+ "media-content-type": { type: "string" },
1933
+ wait: { type: "boolean" },
1934
+ "poll-interval-ms": { type: "string" },
1935
+ "timeout-ms": { type: "string" }
1038
1936
  }
1039
1937
  });
1040
- const id = (0, import_shared.requireId)(args.id, "Product");
1938
+ const productId = requireProductIdForSubverb(args);
1041
1939
  const urls = args.url ?? [];
1042
1940
  if (urls.length === 0) throw new import_errors.CliError("Missing --url (repeatable)", 2);
1043
- const mediaContentType = normalizeMediaContentType(args["media-type"]);
1941
+ const mediaContentTypeRaw = args["media-content-type"];
1942
+ const mediaTypeRaw = args["media-type"];
1943
+ if (mediaContentTypeRaw && mediaTypeRaw) {
1944
+ const a = mediaContentTypeRaw.trim().toUpperCase();
1945
+ const b = mediaTypeRaw.trim().toUpperCase();
1946
+ if (a && b && a !== b) {
1947
+ throw new import_errors.CliError("Do not pass both --media-content-type and --media-type with different values", 2);
1948
+ }
1949
+ }
1950
+ const mediaContentType = normalizeMediaContentType(mediaContentTypeRaw ?? mediaTypeRaw);
1044
1951
  const alt = args.alt;
1952
+ const wait = args.wait === true;
1953
+ const pollIntervalMs = parsePositiveIntFlag({ value: args["poll-interval-ms"], flag: "--poll-interval-ms" }) ?? 1e3;
1954
+ const timeoutMs = parsePositiveIntFlag({ value: args["timeout-ms"], flag: "--timeout-ms" }) ?? 10 * 60 * 1e3;
1955
+ const shouldWait = wait && !ctx.dryRun;
1956
+ const beforeIds = shouldWait ? await getTopProductMediaIds({ ctx, productId }) : [];
1045
1957
  const media = urls.map((url) => ({
1046
1958
  originalSource: url,
1047
1959
  mediaContentType,
1048
1960
  ...alt ? { alt } : {}
1049
1961
  }));
1962
+ const viewForSelection = ctx.view === "all" ? "full" : ctx.view;
1963
+ const nodeSelection = (0, import_select.resolveSelection)({
1964
+ typeName: "Media",
1965
+ view: viewForSelection,
1966
+ baseSelection: getProductMediaSelection(viewForSelection),
1967
+ select: args.select,
1968
+ selection: args.selection,
1969
+ include: args.include,
1970
+ ensureId: ctx.quiet,
1971
+ defaultConnectionFirst: 10
1972
+ });
1050
1973
  const result = await (0, import_router.runMutation)(ctx, {
1051
1974
  productUpdate: {
1052
- __args: { product: { id }, media },
1053
- product: productSummarySelection,
1975
+ __args: { product: { id: productId }, media },
1976
+ product: {
1977
+ id: true,
1978
+ media: {
1979
+ __args: { last: media.length, sortKey: "POSITION" },
1980
+ nodes: nodeSelection
1981
+ }
1982
+ },
1054
1983
  userErrors: { field: true, message: true }
1055
1984
  }
1056
1985
  });
1057
1986
  if (result === void 0) return;
1058
1987
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
1059
- if (ctx.quiet) return console.log(result.productUpdate?.product?.id ?? "");
1060
- (0, import_output.printJson)(result.productUpdate);
1988
+ if (!shouldWait) {
1989
+ const connection = result.productUpdate?.product?.media ?? { nodes: [], pageInfo: void 0 };
1990
+ (0, import_output.printConnection)({ connection, format: ctx.format, quiet: ctx.quiet });
1991
+ return;
1992
+ }
1993
+ const afterIds = await getTopProductMediaIds({ ctx, productId });
1994
+ const before = new Set(beforeIds);
1995
+ const createdIds = afterIds.filter((mid) => !before.has(mid)).slice(0, urls.length);
1996
+ if (createdIds.length !== urls.length) {
1997
+ throw new import_errors.CliError(
1998
+ `Unable to determine created media IDs for waiting (expected ${urls.length}, got ${createdIds.length}).`,
1999
+ 2
2000
+ );
2001
+ }
2002
+ const final = await (0, import_waitForReady.waitForFilesReadyOrFailed)({ ctx, ids: createdIds, pollIntervalMs, timeoutMs });
2003
+ if (ctx.quiet) {
2004
+ for (const mid of createdIds) console.log(mid);
2005
+ } else {
2006
+ (0, import_output.printConnection)({
2007
+ connection: { nodes: final.nodes, pageInfo: void 0 },
2008
+ format: ctx.format,
2009
+ quiet: false
2010
+ });
2011
+ }
2012
+ if (final.failedIds.length > 0) {
2013
+ throw new import_errors.CliError(`One or more media files failed processing: ${final.failedIds.join(", ")}`, 2);
2014
+ }
1061
2015
  return;
1062
2016
  }
1063
2017
  if (verb === "media upload") {
1064
2018
  const args = (0, import_router.parseStandardArgs)({
1065
2019
  argv,
1066
2020
  extraOptions: {
2021
+ "product-id": { type: "string" },
1067
2022
  file: { type: "string", multiple: true },
2023
+ filename: { type: "string" },
1068
2024
  alt: { type: "string" },
1069
2025
  "content-type": { type: "string" },
1070
- "media-type": { type: "string" }
2026
+ "mime-type": { type: "string" },
2027
+ "media-type": { type: "string" },
2028
+ "media-content-type": { type: "string" },
2029
+ wait: { type: "boolean" },
2030
+ "poll-interval-ms": { type: "string" },
2031
+ "timeout-ms": { type: "string" }
1071
2032
  }
1072
2033
  });
1073
- const id = (0, import_shared.requireId)(args.id, "Product");
2034
+ const id = requireProductIdForSubverb(args);
1074
2035
  const filePaths = args.file ?? [];
1075
2036
  if (filePaths.length === 0) throw new import_errors.CliError("Missing --file (repeatable)", 2);
1076
- const forcedMediaType = args["media-type"];
1077
- const forced = forcedMediaType ? normalizeMediaContentType(forcedMediaType) : void 0;
1078
- const resourceOverride = forced ? mediaTypeToStagedResource(forced) : void 0;
1079
- const localFiles = (0, import_stagedUploads.buildLocalFilesForStagedUpload)({
1080
- filePaths,
1081
- contentType: args["content-type"],
1082
- resource: resourceOverride
1083
- });
1084
- const targets = await (0, import_stagedUploads.stagedUploadLocalFiles)(ctx, localFiles);
1085
- if (targets === void 0) return;
1086
- const alt = args.alt;
1087
- const media = targets.map((t, i) => {
1088
- const local = localFiles[i];
1089
- if (!t.resourceUrl) throw new import_errors.CliError(`Missing staged target resourceUrl for ${local.filename}`, 2);
1090
- return {
1091
- originalSource: t.resourceUrl,
1092
- mediaContentType: forced ?? stagedResourceToMediaType(local.resource),
1093
- ...alt ? { alt } : {}
1094
- };
1095
- });
1096
- const result = await (0, import_router.runMutation)(ctx, {
1097
- productUpdate: {
1098
- __args: { product: { id }, media },
1099
- product: productSummarySelection,
1100
- userErrors: { field: true, message: true }
2037
+ const usesStdin = filePaths.includes("-");
2038
+ if (usesStdin && filePaths.length !== 1) {
2039
+ throw new import_errors.CliError("When using --file -, provide exactly one --file", 2);
2040
+ }
2041
+ let cleanupStdin;
2042
+ try {
2043
+ let effectiveFilePaths = filePaths;
2044
+ if (usesStdin) {
2045
+ const stdinFile = await (0, import_stdinFile.writeStdinToTempFile)({ filename: args.filename ?? "" });
2046
+ cleanupStdin = stdinFile.cleanup;
2047
+ effectiveFilePaths = [stdinFile.filePath];
1101
2048
  }
1102
- });
1103
- if (result === void 0) return;
1104
- (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
1105
- if (ctx.quiet) return console.log(result.productUpdate?.product?.id ?? "");
1106
- (0, import_output.printJson)(result.productUpdate);
1107
- return;
2049
+ const mediaContentTypeRaw = args["media-content-type"];
2050
+ const mediaTypeRaw = args["media-type"];
2051
+ if (mediaContentTypeRaw && mediaTypeRaw) {
2052
+ const a = mediaContentTypeRaw.trim().toUpperCase();
2053
+ const b = mediaTypeRaw.trim().toUpperCase();
2054
+ if (a && b && a !== b) {
2055
+ throw new import_errors.CliError("Do not pass both --media-content-type and --media-type with different values", 2);
2056
+ }
2057
+ }
2058
+ const forcedMediaType = mediaContentTypeRaw ?? mediaTypeRaw;
2059
+ const forced = forcedMediaType ? normalizeMediaContentType(forcedMediaType) : void 0;
2060
+ const resourceOverride = forced ? mediaTypeToStagedResource(forced) : void 0;
2061
+ const mimeTypeRaw = args["mime-type"];
2062
+ const contentTypeRaw = args["content-type"];
2063
+ if (mimeTypeRaw && contentTypeRaw) {
2064
+ const a = mimeTypeRaw.trim();
2065
+ const b = contentTypeRaw.trim();
2066
+ if (a && b && a !== b) {
2067
+ throw new import_errors.CliError("Do not pass both --mime-type and --content-type with different values", 2);
2068
+ }
2069
+ }
2070
+ const mimeType = mimeTypeRaw ?? contentTypeRaw;
2071
+ const wait = args.wait === true;
2072
+ const pollIntervalMs = parsePositiveIntFlag({ value: args["poll-interval-ms"], flag: "--poll-interval-ms" }) ?? 1e3;
2073
+ const timeoutMs = parsePositiveIntFlag({ value: args["timeout-ms"], flag: "--timeout-ms" }) ?? 10 * 60 * 1e3;
2074
+ const shouldWait = wait && !ctx.dryRun;
2075
+ const beforeIds = shouldWait ? await getTopProductMediaIds({ ctx, productId: id }) : [];
2076
+ const localFiles = await (0, import_stagedUploads.buildLocalFilesForStagedUpload)({
2077
+ filePaths: effectiveFilePaths,
2078
+ mimeType,
2079
+ resource: resourceOverride
2080
+ });
2081
+ const targets = await (0, import_stagedUploads.stagedUploadLocalFiles)(ctx, localFiles);
2082
+ if (targets === void 0) return;
2083
+ const alt = args.alt;
2084
+ const media = targets.map((t, i) => {
2085
+ const local = localFiles[i];
2086
+ if (!t.resourceUrl) throw new import_errors.CliError(`Missing staged target resourceUrl for ${local.filename}`, 2);
2087
+ return {
2088
+ originalSource: t.resourceUrl,
2089
+ mediaContentType: forced ?? stagedResourceToMediaType(local.resource),
2090
+ ...alt ? { alt } : {}
2091
+ };
2092
+ });
2093
+ const viewForSelection = ctx.view === "all" ? "full" : ctx.view;
2094
+ const nodeSelection = (0, import_select.resolveSelection)({
2095
+ typeName: "Media",
2096
+ view: viewForSelection,
2097
+ baseSelection: getProductMediaSelection(viewForSelection),
2098
+ select: args.select,
2099
+ selection: args.selection,
2100
+ include: args.include,
2101
+ ensureId: ctx.quiet,
2102
+ defaultConnectionFirst: 10
2103
+ });
2104
+ const result = await (0, import_router.runMutation)(ctx, {
2105
+ productUpdate: {
2106
+ __args: { product: { id }, media },
2107
+ product: {
2108
+ id: true,
2109
+ media: {
2110
+ __args: { last: media.length, sortKey: "POSITION" },
2111
+ nodes: nodeSelection
2112
+ }
2113
+ },
2114
+ userErrors: { field: true, message: true }
2115
+ }
2116
+ });
2117
+ if (result === void 0) return;
2118
+ (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.productUpdate, failOnUserErrors: ctx.failOnUserErrors });
2119
+ const connection = result.productUpdate?.product?.media ?? { nodes: [], pageInfo: void 0 };
2120
+ if (!shouldWait) {
2121
+ (0, import_output.printConnection)({ connection, format: ctx.format, quiet: ctx.quiet });
2122
+ return;
2123
+ }
2124
+ const afterIds = await getTopProductMediaIds({ ctx, productId: id });
2125
+ const before = new Set(beforeIds);
2126
+ const expectedCount = localFiles.length;
2127
+ const createdIds = afterIds.filter((mid) => !before.has(mid)).slice(0, expectedCount);
2128
+ if (createdIds.length !== expectedCount) {
2129
+ throw new import_errors.CliError(
2130
+ `Unable to determine created media IDs for waiting (expected ${expectedCount}, got ${createdIds.length}).`,
2131
+ 2
2132
+ );
2133
+ }
2134
+ const final = await (0, import_waitForReady.waitForFilesReadyOrFailed)({ ctx, ids: createdIds, pollIntervalMs, timeoutMs });
2135
+ if (ctx.quiet) {
2136
+ for (const mid of createdIds) console.log(mid);
2137
+ } else {
2138
+ (0, import_output.printConnection)({
2139
+ connection: { nodes: final.nodes, pageInfo: void 0 },
2140
+ format: ctx.format,
2141
+ quiet: false
2142
+ });
2143
+ }
2144
+ if (final.failedIds.length > 0) {
2145
+ throw new import_errors.CliError(`One or more media files failed processing: ${final.failedIds.join(", ")}`, 2);
2146
+ }
2147
+ return;
2148
+ } finally {
2149
+ if (cleanupStdin) await cleanupStdin();
2150
+ }
1108
2151
  }
1109
2152
  if (verb === "media list") {
1110
- const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: {} });
1111
- const id = (0, import_shared.requireId)(args.id, "Product");
2153
+ const args = (0, import_router.parseStandardArgs)({ argv, extraOptions: { "product-id": { type: "string" } } });
2154
+ const id = requireProductIdForSubverb(args);
1112
2155
  const first = (0, import_shared.parseFirst)(args.first);
1113
2156
  const after = args.after;
1114
2157
  const result = await (0, import_router.runQuery)(ctx, {
@@ -1130,7 +2173,7 @@ const runProducts = async ({
1130
2173
  nextPageArgs: {
1131
2174
  base: "shop products media list",
1132
2175
  first,
1133
- extraFlags: [{ flag: "--id", value: id }]
2176
+ extraFlags: [{ flag: "--product-id", value: id }]
1134
2177
  }
1135
2178
  });
1136
2179
  return;
@@ -1139,10 +2182,11 @@ const runProducts = async ({
1139
2182
  const args = (0, import_router.parseStandardArgs)({
1140
2183
  argv,
1141
2184
  extraOptions: {
2185
+ "product-id": { type: "string" },
1142
2186
  "media-id": { type: "string", multiple: true }
1143
2187
  }
1144
2188
  });
1145
- const productId = (0, import_shared.requireId)(args.id, "Product");
2189
+ const productId = requireProductIdForSubverb(args);
1146
2190
  const mediaIds = args["media-id"] ?? [];
1147
2191
  if (mediaIds.length === 0) throw new import_errors.CliError("Missing --media-id (repeatable)", 2);
1148
2192
  const files = mediaIds.map((id) => ({
@@ -1158,8 +2202,15 @@ const runProducts = async ({
1158
2202
  });
1159
2203
  if (result === void 0) return;
1160
2204
  (0, import_userErrors.maybeFailOnUserErrors)({ payload: result.fileUpdate, failOnUserErrors: ctx.failOnUserErrors });
1161
- if (ctx.quiet) return console.log(productId);
1162
- (0, import_output.printJson)(result.fileUpdate, ctx.format !== "raw");
2205
+ const removedMediaIds = (result.fileUpdate?.files ?? []).map((f) => typeof f?.id === "string" ? f.id : void 0).filter((id) => typeof id === "string" && id.trim() !== "");
2206
+ if (ctx.quiet) return (0, import_output.printIds)(removedMediaIds);
2207
+ (0, import_output.printJson)(
2208
+ {
2209
+ productId,
2210
+ removedMediaIds
2211
+ },
2212
+ ctx.format !== "raw"
2213
+ );
1163
2214
  return;
1164
2215
  }
1165
2216
  if (verb === "media update") {
@@ -1192,11 +2243,12 @@ const runProducts = async ({
1192
2243
  const args = (0, import_router.parseStandardArgs)({
1193
2244
  argv,
1194
2245
  extraOptions: {
2246
+ "product-id": { type: "string" },
1195
2247
  moves: { type: "string" },
1196
2248
  move: { type: "string", multiple: true }
1197
2249
  }
1198
2250
  });
1199
- const id = (0, import_shared.requireId)(args.id, "Product");
2251
+ const id = requireProductIdForSubverb(args);
1200
2252
  let moves = [];
1201
2253
  if (args.moves) {
1202
2254
  try {