shop-cli 0.1.3 → 0.1.4

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.
@@ -216,6 +216,10 @@ const renderTopLevelHelp = (command = (0, import_command.resolveCliCommand)()) =
216
216
  " --selection <graphql> (selection override; can be @file.gql)",
217
217
  " --quiet (IDs only when possible)",
218
218
  "",
219
+ "IDs:",
220
+ " By default, many flags accept numeric IDs and will coerce them to gid://shopify/... IDs.",
221
+ " --strict-ids (or env SHOP_CLI_STRICT_IDS=1; require full gid://shopify/... IDs)",
222
+ "",
219
223
  "Debug:",
220
224
  " --dry-run (print GraphQL op + variables, do not execute)",
221
225
  " --no-fail-on-user-errors (do not exit non-zero on userErrors)",
@@ -234,10 +238,10 @@ const renderTopLevelHelp = (command = (0, import_command.resolveCliCommand)()) =
234
238
  ` ${command} products list --first 5 --format table`,
235
239
  ` ${command} products get --id gid://shopify/Product/123 --format markdown`,
236
240
  ` ${command} products create --set title="Hat" --set status="ACTIVE"`,
237
- ` ${command} products add-tags --id 123 --tags "summer,featured"`,
241
+ ` ${command} products add-tags --id gid://shopify/Product/123 --tags "summer,featured"`,
238
242
  ` ${command} publications resolve --publication "Online Store"`,
239
- ` ${command} products publish --id 123 --publication "Online Store" --now`,
240
- ` ${command} products metafields upsert --id 123 --set namespace=custom --set key=foo --set type=single_line_text_field --set value=bar`
243
+ ` ${command} products publish --id gid://shopify/Product/123 --publication "Online Store" --now`,
244
+ ` ${command} products metafields upsert --id gid://shopify/Product/123 --set namespace=custom --set key=foo --set type=single_line_text_field --set value=bar`
241
245
  ].join("\n");
242
246
  };
243
247
  const renderResourceHelp = (resource, command = (0, import_command.resolveCliCommand)()) => {
@@ -55,11 +55,11 @@ const parseIntFlag = (flag, value) => {
55
55
  const requireId = (id, type, flag = "--id") => {
56
56
  const normalized = typeof id === "string" ? id : typeof id === "number" && Number.isFinite(id) ? String(id) : void 0;
57
57
  if (!normalized) throw new import_errors.CliError(`Missing ${flag}`, 2);
58
- return (0, import_gid.coerceGid)(normalized, type);
58
+ return (0, import_gid.coerceGid)(normalized, type, flag);
59
59
  };
60
60
  const requireGidFlag = (value, flag, type) => {
61
61
  if (typeof value !== "string" || !value) throw new import_errors.CliError(`Missing ${flag}`, 2);
62
- return (0, import_gid.coerceGid)(value, type);
62
+ return (0, import_gid.coerceGid)(value, type, flag);
63
63
  };
64
64
  const requireStringFlag = (value, flag) => {
65
65
  if (typeof value !== "string" || !value) throw new import_errors.CliError(`Missing ${flag}`, 2);
@@ -67,7 +67,7 @@ const requireStringFlag = (value, flag) => {
67
67
  };
68
68
  const requireLocationId = (value, flag = "--location-id") => {
69
69
  if (typeof value !== "string" || !value) throw new import_errors.CliError(`Missing ${flag}`, 2);
70
- return (0, import_gid.coerceGid)(value, "Location");
70
+ return (0, import_gid.coerceGid)(value, "Location", flag);
71
71
  };
72
72
  const parseDateTime = (value, flag) => {
73
73
  if (typeof value !== "string" || !value) throw new import_errors.CliError(`Missing ${flag}`, 2);
@@ -87,7 +87,7 @@ const parseIds = (value, type) => {
87
87
  parts.push(...v.split(",").map((s) => s.trim()).filter(Boolean));
88
88
  }
89
89
  if (parts.length === 0) throw new import_errors.CliError("Missing --ids", 2);
90
- return parts.map((id) => (0, import_gid.coerceGid)(id, type));
90
+ return parts.map((id) => (0, import_gid.coerceGid)(id, type, "--ids"));
91
91
  };
92
92
  const readUtf8 = (path) => (0, import_node_fs.readFileSync)(path, "utf8");
93
93
  const parseJson = (value, label) => {
@@ -22,6 +22,7 @@ __export(files_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(files_exports);
24
24
  var import_errors = require("../errors");
25
+ var import_gid = require("../gid");
25
26
  var import_router = require("../router");
26
27
  var import_userErrors = require("../userErrors");
27
28
  var import_stagedUploads = require("../workflows/files/stagedUploads");
@@ -123,8 +124,16 @@ const runFiles = async ({
123
124
  if (!raw) throw new import_errors.CliError("Missing --id", 2);
124
125
  const trimmed = raw.trim();
125
126
  if (!trimmed) throw new import_errors.CliError("Missing --id", 2);
126
- const query = trimmed.startsWith("gid://") ? `ids:${trimmed}` : /^\d+$/.test(trimmed) ? `id:${trimmed}` : (() => {
127
- throw new import_errors.CliError("--id must be a numeric ID or full gid://shopify/... GID", 2);
127
+ const query = trimmed.startsWith("gid://") ? `ids:${trimmed}` : /^\d+$/.test(trimmed) ? (() => {
128
+ if ((0, import_gid.isStrictIdsMode)()) {
129
+ throw new import_errors.CliError(
130
+ "--id must be a full Shopify GID (gid://shopify/...). Strict IDs mode is enabled (SHOP_CLI_STRICT_IDS or --strict-ids).",
131
+ 2
132
+ );
133
+ }
134
+ return `id:${trimmed}`;
135
+ })() : (() => {
136
+ throw new import_errors.CliError("--id must be a full Shopify GID (gid://shopify/...)", 2);
128
137
  })();
129
138
  const result = await (0, import_router.runQuery)(ctx, {
130
139
  files: { __args: { first: 1, query }, nodes: fileSelection }
@@ -67,9 +67,11 @@ const getMobilePlatformApplicationSelection = (view) => {
67
67
  const extractUnionId = (node) => node?.on_AndroidApplication?.id ?? node?.on_AppleApplication?.id;
68
68
  const normalizeMobileAppId = (raw, platform) => {
69
69
  if (typeof raw !== "string" || !raw) throw new import_errors.CliError("Missing --id", 2);
70
- if (raw.startsWith("gid://")) return raw;
71
- const p = (platform ?? "android").trim().toLowerCase();
70
+ const p = platform?.trim().toLowerCase();
72
71
  const type = p === "apple" || p === "ios" ? "AppleApplication" : "AndroidApplication";
72
+ if (raw.startsWith("gid://")) {
73
+ return p ? (0, import_gid.coerceGid)(raw, type, "--id") : raw;
74
+ }
73
75
  return (0, import_gid.coerceGid)(raw, type);
74
76
  };
75
77
  const runMobilePlatformApplications = async ({
@@ -228,10 +228,11 @@ const getTopProductMediaIds = async ({
228
228
  const nodes = result.product?.media?.nodes ?? [];
229
229
  return nodes.map((n) => typeof n?.id === "string" ? n.id : void 0).filter((id) => typeof id === "string" && id.trim() !== "");
230
230
  };
231
- const normalizeMediaId = (value) => {
231
+ const allowedMediaGidTypes = ["MediaImage", "Video", "ExternalVideo", "Model3d"];
232
+ const normalizeMediaId = (value, label = "Media ID") => {
232
233
  const raw = value.trim();
233
- if (!raw) throw new import_errors.CliError("Media ID cannot be empty", 2);
234
- if (raw.startsWith("gid://")) return raw;
234
+ if (!raw) throw new import_errors.CliError(`${label} cannot be empty`, 2);
235
+ if (raw.startsWith("gid://")) return (0, import_gid.assertShopifyGidTypeIn)(raw, allowedMediaGidTypes, label);
235
236
  throw new import_errors.CliError(
236
237
  `Numeric media ID "${raw}" is ambiguous. Use the full GID from "shop products media list" (e.g. gid://shopify/MediaImage/${raw})`,
237
238
  2
@@ -2190,7 +2191,7 @@ Missing <verb> for "products ${verb}"`, 2);
2190
2191
  const mediaIds = args["media-id"] ?? [];
2191
2192
  if (mediaIds.length === 0) throw new import_errors.CliError("Missing --media-id (repeatable)", 2);
2192
2193
  const files = mediaIds.map((id) => ({
2193
- id: normalizeMediaId(id),
2194
+ id: normalizeMediaId(id, "--media-id"),
2194
2195
  referencesToRemove: [productId]
2195
2196
  }));
2196
2197
  const result = await (0, import_router.runMutation)(ctx, {
@@ -2225,7 +2226,7 @@ Missing <verb> for "products ${verb}"`, 2);
2225
2226
  if (!mediaIdRaw) throw new import_errors.CliError("Missing --media-id", 2);
2226
2227
  const alt = args.alt;
2227
2228
  if (alt === void 0) throw new import_errors.CliError("Missing --alt", 2);
2228
- const files = [{ id: normalizeMediaId(mediaIdRaw), alt }];
2229
+ const files = [{ id: normalizeMediaId(mediaIdRaw, "--media-id"), alt }];
2229
2230
  const result = await (0, import_router.runMutation)(ctx, {
2230
2231
  fileUpdate: {
2231
2232
  __args: { files },
@@ -2268,7 +2269,7 @@ Missing <verb> for "products ${verb}"`, 2);
2268
2269
  const pos = Number(item.slice(idx + 1).trim());
2269
2270
  if (!mediaId) throw new import_errors.CliError("--move mediaId cannot be empty", 2);
2270
2271
  if (!Number.isFinite(pos) || pos < 0) throw new import_errors.CliError("--move newPosition must be a non-negative number", 2);
2271
- parsedMoves.push({ id: normalizeMediaId(mediaId), newPosition: String(Math.floor(pos)) });
2272
+ parsedMoves.push({ id: normalizeMediaId(mediaId, "--move mediaId"), newPosition: String(Math.floor(pos)) });
2272
2273
  }
2273
2274
  moves = parsedMoves;
2274
2275
  }
@@ -2285,7 +2286,7 @@ Missing <verb> for "products ${verb}"`, 2);
2285
2286
  if (!Number.isFinite(pos) || pos < 0) {
2286
2287
  throw new import_errors.CliError(`moves[${i}].newPosition must be a non-negative number`, 2);
2287
2288
  }
2288
- return { id: normalizeMediaId(id2), newPosition: String(Math.floor(pos)) };
2289
+ return { id: normalizeMediaId(id2, `moves[${i}].id`), newPosition: String(Math.floor(pos)) };
2289
2290
  });
2290
2291
  const result = await (0, import_router.runMutation)(ctx, {
2291
2292
  productReorderMedia: {
@@ -40,7 +40,7 @@ const runPublishables = async ({
40
40
  " publish-to-current-channel|unpublish-to-current-channel",
41
41
  "",
42
42
  "Notes:",
43
- " --id must be a full gid://shopify/... ID (numeric IDs cannot be coerced for Publishable)."
43
+ " --id must be a full Shopify GID (gid://shopify/...)."
44
44
  ].join("\n")
45
45
  );
46
46
  return;
package/dist/cli.js CHANGED
@@ -36,6 +36,7 @@ var import_command = require("./cli/command");
36
36
  var import_parse_command = require("./cli/parse-command");
37
37
  var import_output = require("./cli/output");
38
38
  var import_suggest = require("./cli/suggest");
39
+ var import_gid = require("./cli/gid");
39
40
  import_dotenv.default.config({ path: (0, import_path.resolve)(process.cwd(), ".env"), quiet: true });
40
41
  const helpFlags = /* @__PURE__ */ new Set(["--help", "-h", "--help-full", "--help-all"]);
41
42
  const versionFlags = /* @__PURE__ */ new Set(["--version", "-v"]);
@@ -133,6 +134,8 @@ Missing <verb> for "${resource}"`, 2);
133
134
  }
134
135
  const parsed = (0, import_globalFlags.parseGlobalFlags)(rewrittenRest);
135
136
  const dryRun = parsed.dryRun ?? false;
137
+ const strictIds = parsed.strictIds ?? (0, import_gid.parseEnvBoolean)(process.env.SHOP_CLI_STRICT_IDS);
138
+ (0, import_gid.setStrictIdsMode)(strictIds);
136
139
  const isOfflineCommand = verb === "fields" || isTypesCommand;
137
140
  const shopDomain = parsed.shopDomain;
138
141
  const graphqlEndpoint = parsed.graphqlEndpoint;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shop-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "An agent-friendly command-line interface for managing Shopify stores",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",