shopify-agentic-dev-workflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.cursor/rules/shopify-agentic-dev.mdc +290 -0
  2. package/AGENTS.md +285 -0
  3. package/CHANGELOG.md +14 -0
  4. package/CLAUDE.md +285 -0
  5. package/GEMINI.md +285 -0
  6. package/HANDOFF.md +351 -0
  7. package/LICENSE +21 -0
  8. package/README.md +370 -0
  9. package/bin/shopify-agent.js +2786 -0
  10. package/docs/00-prerequisites.md +88 -0
  11. package/docs/01-onboarding.md +111 -0
  12. package/docs/02-theme-sdlc.md +106 -0
  13. package/docs/03-app-sdlc.md +66 -0
  14. package/docs/04-admin-api-ops.md +58 -0
  15. package/docs/05-codex-prompts.md +37 -0
  16. package/docs/06-security-guardrails.md +47 -0
  17. package/docs/07-github-rollout.md +30 -0
  18. package/docs/08-product-design.md +168 -0
  19. package/docs/09-shopify-cli-4-capabilities.md +48 -0
  20. package/docs/10-field-learnings.md +66 -0
  21. package/package.json +82 -0
  22. package/scripts/bootstrap.sh +35 -0
  23. package/scripts/validate-graphql.js +303 -0
  24. package/templates/.env.example +20 -0
  25. package/templates/codex-config.toml +3 -0
  26. package/templates/github-actions/deploy-theme.yml +32 -0
  27. package/templates/graphql/content/pages-list.graphql +12 -0
  28. package/templates/graphql/content/redirects-list.graphql +9 -0
  29. package/templates/graphql/customers/new-customers.graphql +15 -0
  30. package/templates/graphql/customers/top-spenders.graphql +16 -0
  31. package/templates/graphql/discounts/active-discounts.graphql +27 -0
  32. package/templates/graphql/discounts-list.graphql +40 -0
  33. package/templates/graphql/inventory-audit.graphql +38 -0
  34. package/templates/graphql/metafields/product-metafields.graphql +14 -0
  35. package/templates/graphql/orders/list-open.graphql +30 -0
  36. package/templates/graphql/orders/revenue-summary.graphql +15 -0
  37. package/templates/graphql/products/list.graphql +16 -0
  38. package/templates/graphql/products/low-inventory.graphql +18 -0
  39. package/templates/graphql/products-seo-audit.graphql +14 -0
  40. package/templates/graphql/shop-query.graphql +9 -0
  41. package/templates/graphql/store/full-info.graphql +23 -0
  42. package/templates/graphql/store/webhooks.graphql +16 -0
  43. package/templates/prompts/admin-operation.md +12 -0
  44. package/templates/prompts/theme-task.md +19 -0
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * validate-graphql.js
4
+ *
5
+ * Runs a minimal read-only probe for every query shape used by shopify-agent
6
+ * against the configured live store. Catches schema drift (renamed/removed
7
+ * fields) before they reach users.
8
+ *
9
+ * Usage:
10
+ * node scripts/validate-graphql.js # test all queries
11
+ * node scripts/validate-graphql.js products # test only matching labels
12
+ *
13
+ * Requirements:
14
+ * - shopify-agent init completed in this directory
15
+ * - shopify-agent store:auth completed (Admin API token stored)
16
+ * - Shopify CLI 4.x (npm install -g @shopify/cli@latest)
17
+ */
18
+
19
+ "use strict";
20
+
21
+ const { spawnSync } = require("child_process");
22
+ const fs = require("fs");
23
+ const path = require("path");
24
+ const os = require("os");
25
+
26
+ // ─── Config loading (mirrors bin/shopify-agent.js) ───────────────────────────
27
+
28
+ const root = process.cwd();
29
+ const configDir = path.join(root, ".shopify-agent");
30
+ const profilesDir = path.join(configDir, "profiles");
31
+ const configPath = path.join(configDir, "config.json");
32
+
33
+ function loadDotEnv() {
34
+ const envFile = path.join(root, ".env");
35
+ if (!fs.existsSync(envFile)) return {};
36
+ const out = {};
37
+ fs.readFileSync(envFile, "utf8").split(/\r?\n/).forEach((line) => {
38
+ const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
39
+ if (m) out[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
40
+ });
41
+ return out;
42
+ }
43
+
44
+ function loadActiveProfile() {
45
+ const activePath = path.join(configDir, "active-profile");
46
+ let name = "default";
47
+ if (fs.existsSync(activePath)) name = fs.readFileSync(activePath, "utf8").trim();
48
+ else if (fs.existsSync(configPath)) {
49
+ try { name = JSON.parse(fs.readFileSync(configPath, "utf8")).profile || "default"; } catch {}
50
+ }
51
+ const profileFile = path.join(profilesDir, `${name}.json`);
52
+ if (fs.existsSync(profileFile)) {
53
+ try { return JSON.parse(fs.readFileSync(profileFile, "utf8")); } catch {}
54
+ }
55
+ return {};
56
+ }
57
+
58
+ function loadConfig() {
59
+ const env = { ...loadDotEnv(), ...process.env };
60
+ const profile = loadActiveProfile();
61
+ return {
62
+ store: env.SHOPIFY_STORE || profile.store || "",
63
+ apiVersion: env.SHOPIFY_API_VERSION || profile.apiVersion || "2026-04",
64
+ };
65
+ }
66
+
67
+ // ─── GraphQL runner ───────────────────────────────────────────────────────────
68
+
69
+ function stripAnsi(str) {
70
+ return (str || "").replace(/\x1B\[[0-9;]*[mGKHF]/g, "");
71
+ }
72
+
73
+ function runQuery(store, queryStr) {
74
+ const tmp = path.join(os.tmpdir(), `shopify-validate-${Date.now()}.graphql`);
75
+ fs.writeFileSync(tmp, `query { ${queryStr} }`);
76
+ try {
77
+ const result = spawnSync(
78
+ "shopify",
79
+ ["store", "execute", "--store", store, "--query-file", tmp],
80
+ { encoding: "utf8", env: { ...process.env, FORCE_COLOR: "0" } }
81
+ );
82
+ const raw = stripAnsi(result.stdout || "");
83
+ const err = stripAnsi(result.stderr || "");
84
+
85
+ if (!result.ok && (err.includes("No stored app authentication") || raw.includes("No stored app authentication"))) {
86
+ return { ok: false, error: "NOT_AUTHED" };
87
+ }
88
+
89
+ const jsonStart = raw.indexOf("{");
90
+ if (jsonStart === -1) return { ok: false, error: err || raw || "No JSON in response" };
91
+
92
+ const parsed = JSON.parse(raw.slice(jsonStart));
93
+ if (parsed.errors && parsed.errors.length) {
94
+ return { ok: false, error: parsed.errors.map((e) => `${e.message} (${JSON.stringify(e.extensions || {})})`).join("; ") };
95
+ }
96
+ return { ok: true };
97
+ } catch (e) {
98
+ return { ok: false, error: e.message };
99
+ } finally {
100
+ try { fs.unlinkSync(tmp); } catch {}
101
+ }
102
+ }
103
+
104
+ // ─── Query catalogue ──────────────────────────────────────────────────────────
105
+ // Each entry: { label, query }
106
+ // query = the inner body (without the outer `query { }` wrapper)
107
+ // All are read-only — no mutations tested here.
108
+
109
+ const PROBES = [
110
+ // ── Setup / store ──────────────────────────────────────────────────────────
111
+ {
112
+ label: "shop info",
113
+ query: `shop { name currencyCode primaryDomain { url } }`,
114
+ },
115
+ {
116
+ label: "store:locations",
117
+ query: `locations(first: 1) { edges { node { id name isActive fulfillsOnlineOrders } } }`,
118
+ },
119
+
120
+ // ── Products ───────────────────────────────────────────────────────────────
121
+ {
122
+ label: "products:list",
123
+ query: `products(first: 1) { edges { node { id title status totalInventory vendor productType } } }`,
124
+ },
125
+ {
126
+ label: "products:variants",
127
+ query: `products(first: 1) { edges { node { variants(first: 1) { edges { node {
128
+ id title price sku inventoryQuantity
129
+ inventoryItem { id }
130
+ } } } } } }`,
131
+ },
132
+ {
133
+ label: "collections:list",
134
+ query: `collections(first: 1) { edges { node { id title handle productsCount { count } } } }`,
135
+ },
136
+
137
+ // ── Inventory ──────────────────────────────────────────────────────────────
138
+ {
139
+ label: "inventory:list (locations)",
140
+ query: `locations(first: 5) { edges { node { id name isActive } } }`,
141
+ },
142
+
143
+ // ── Orders ─────────────────────────────────────────────────────────────────
144
+ {
145
+ label: "orders:list",
146
+ query: `orders(first: 1, query: "status:any", sortKey: CREATED_AT, reverse: true) {
147
+ edges { node {
148
+ id name createdAt
149
+ displayFinancialStatus displayFulfillmentStatus
150
+ totalPriceSet { presentmentMoney { amount currencyCode } }
151
+ customer { displayName email }
152
+ } }
153
+ }`,
154
+ },
155
+ {
156
+ label: "orders:get (detail fields)",
157
+ query: `orders(first: 1, query: "status:any") { edges { node {
158
+ id name
159
+ lineItems(first: 3) { edges { node {
160
+ title quantity originalUnitPriceSet { presentmentMoney { amount currencyCode } }
161
+ } } }
162
+ shippingAddress { name address1 city country }
163
+ fulfillments(first: 1) { trackingInfo { number url } }
164
+ } } }`,
165
+ },
166
+
167
+ // ── Customers ─────────────────────────────────────────────────────────────
168
+ {
169
+ label: "customers:list",
170
+ query: `customers(first: 1) { edges { node {
171
+ id displayName email phone numberOfOrders
172
+ totalSpentV2 { amount currencyCode }
173
+ defaultAddress { city country }
174
+ } } }`,
175
+ },
176
+
177
+ // ── Discounts ─────────────────────────────────────────────────────────────
178
+ {
179
+ label: "discounts:list (usageCount on DiscountCodeBasic)",
180
+ query: `codeDiscountNodes(first: 1) { edges { node {
181
+ id
182
+ codeDiscount {
183
+ ... on DiscountCodeBasic {
184
+ title status usageCount appliesOncePerCustomer usageLimit startsAt endsAt
185
+ codes(first: 1) { edges { node { code } } }
186
+ customerGets { value {
187
+ ... on DiscountPercentage { percentage }
188
+ ... on DiscountAmount { amount { amount currencyCode } }
189
+ } }
190
+ }
191
+ }
192
+ } } }`,
193
+ },
194
+
195
+ // ── Gift cards ────────────────────────────────────────────────────────────
196
+ {
197
+ label: "gift-cards:list",
198
+ query: `giftCards(first: 1) { edges { node {
199
+ id lastCharacters maskedCode enabled expiresOn
200
+ initialValue { amount currencyCode }
201
+ balance { amount currencyCode }
202
+ } } }`,
203
+ },
204
+
205
+ // ── Content ───────────────────────────────────────────────────────────────
206
+ {
207
+ label: "pages:list",
208
+ query: `pages(first: 1) { edges { node { id title handle isPublished updatedAt } } }`,
209
+ },
210
+ {
211
+ label: "blogs:list",
212
+ query: `blogs(first: 1) { edges { node { id title handle } } }`,
213
+ },
214
+ {
215
+ label: "redirects:list",
216
+ query: `urlRedirects(first: 1) { edges { node { id path target } } }`,
217
+ },
218
+
219
+ // ── Metafields ────────────────────────────────────────────────────────────
220
+ {
221
+ label: "metafields (shop level)",
222
+ query: `shop { metafields(first: 1) { edges { node { id namespace key value type } } } }`,
223
+ },
224
+ {
225
+ label: "metafields (product level)",
226
+ query: `products(first: 1) { edges { node { metafields(first: 1) { edges { node { id namespace key value type } } } } } }`,
227
+ },
228
+
229
+ // ── Dashboard composite ───────────────────────────────────────────────────
230
+ {
231
+ label: "dashboard composite query",
232
+ query: `
233
+ shop { name currencyCode }
234
+ orders(first: 1, query: "status:open", sortKey: CREATED_AT, reverse: true) {
235
+ edges { node {
236
+ name createdAt displayFinancialStatus displayFulfillmentStatus
237
+ totalPriceSet { presentmentMoney { amount currencyCode } }
238
+ customer { displayName }
239
+ } }
240
+ }
241
+ products(first: 1, query: "status:active") {
242
+ edges { node { id title totalInventory } }
243
+ }
244
+ codeDiscountNodes(first: 1) { edges { node { codeDiscount {
245
+ ... on DiscountCodeBasic { title status usageCount codes(first: 1) { edges { node { code } } } }
246
+ } } } }
247
+ `,
248
+ },
249
+ ];
250
+
251
+ // ─── Main ─────────────────────────────────────────────────────────────────────
252
+
253
+ const filter = process.argv[2] ? process.argv[2].toLowerCase() : null;
254
+ const probes = filter ? PROBES.filter((p) => p.label.toLowerCase().includes(filter)) : PROBES;
255
+
256
+ const cfg = loadConfig();
257
+
258
+ if (!cfg.store) {
259
+ console.error("No store configured. Run: shopify-agent init");
260
+ process.exit(1);
261
+ }
262
+
263
+ console.log(`\nShopify GraphQL Schema Validator`);
264
+ console.log(`Store: ${cfg.store}`);
265
+ console.log(`API: ${cfg.apiVersion}`);
266
+ console.log(`Probes: ${probes.length}${filter ? ` (filtered: "${filter}")` : ""}`);
267
+ console.log("─".repeat(60));
268
+
269
+ let passed = 0;
270
+ let failed = 0;
271
+ const failures = [];
272
+
273
+ for (const probe of probes) {
274
+ process.stdout.write(` ${probe.label.padEnd(48)}`);
275
+ const result = runQuery(cfg.store, probe.query);
276
+ if (result.ok) {
277
+ process.stdout.write("✓ PASS\n");
278
+ passed++;
279
+ } else if (result.error === "NOT_AUTHED") {
280
+ process.stdout.write("⚠ SKIP (run: shopify-agent store:auth)\n");
281
+ failed++;
282
+ failures.push({ label: probe.label, error: "Admin API token not found — run shopify-agent store:auth" });
283
+ break; // all will fail, no point continuing
284
+ } else {
285
+ process.stdout.write("✗ FAIL\n");
286
+ failed++;
287
+ failures.push({ label: probe.label, error: result.error });
288
+ }
289
+ }
290
+
291
+ console.log("─".repeat(60));
292
+ console.log(`Result: ${passed} passed, ${failed} failed out of ${probes.length} probes\n`);
293
+
294
+ if (failures.length) {
295
+ console.log("Failures:\n");
296
+ failures.forEach((f) => {
297
+ console.log(` ✗ ${f.label}`);
298
+ console.log(` ${f.error}\n`);
299
+ });
300
+ process.exit(1);
301
+ } else {
302
+ console.log("All queries validated against the live schema.\n");
303
+ }
@@ -0,0 +1,20 @@
1
+ # Store domain, for example quickstart-abc123.myshopify.com
2
+ SHOPIFY_STORE=
3
+
4
+ # Theme Access password or a custom app token with read_themes/write_themes.
5
+ SHOPIFY_CLI_THEME_TOKEN=
6
+
7
+ # Optional default staging theme id or theme name.
8
+ SHOPIFY_THEME_ID=
9
+
10
+ # Optional app config name used by `shopify app ... --config`.
11
+ SHOPIFY_APP_CONFIG=
12
+
13
+ # Optional app client id used by Shopify CLI app commands.
14
+ SHOPIFY_APP_CLIENT_ID=
15
+
16
+ # Scopes used by Shopify CLI 4.x `shopify store auth`.
17
+ SHOPIFY_STORE_SCOPES=read_products,write_products,read_orders,write_orders,read_inventory,write_inventory,read_customers,write_customers,read_discounts,write_discounts,read_price_rules,write_price_rules,read_content,write_content,read_online_store_navigation,write_online_store_navigation,read_gift_cards,write_gift_cards,read_locations,read_metaobjects,write_metaobjects,read_metaobject_definitions
18
+
19
+ # Optional API version for Admin GraphQL.
20
+ SHOPIFY_API_VERSION=2026-04
@@ -0,0 +1,3 @@
1
+ [mcp_servers.shopify-dev-mcp]
2
+ command = "npx"
3
+ args = ["-y", "@shopify/dev-mcp@latest"]
@@ -0,0 +1,32 @@
1
+ name: Theme deploy
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ deploy:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+
19
+ - name: Install Shopify CLI
20
+ run: npm install -g @shopify/cli@latest
21
+
22
+ - name: Theme Check
23
+ run: shopify theme check
24
+
25
+ - name: Push staging theme
26
+ run: |
27
+ shopify theme push \
28
+ --json \
29
+ --store "${{ secrets.SHOPIFY_FLAG_STORE }}" \
30
+ --theme "${{ secrets.SHOPIFY_STAGING_THEME_ID }}" \
31
+ --password "${{ secrets.SHOPIFY_CLI_THEME_TOKEN }}"
32
+
@@ -0,0 +1,12 @@
1
+ query PagesList {
2
+ pages(first: 50, sortKey: TITLE) {
3
+ edges {
4
+ node {
5
+ id title handle isPublished
6
+ bodySummary
7
+ createdAt updatedAt
8
+ seo { title description }
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ query RedirectsList {
2
+ urlRedirects(first: 100) {
3
+ edges {
4
+ node {
5
+ id path target
6
+ }
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,15 @@
1
+ # Customers created in the last 30 days
2
+ query NewCustomers {
3
+ customers(first: 50, sortKey: CREATED_AT, reverse: true, query: "created_at:>2026-04-23") {
4
+ edges {
5
+ node {
6
+ id displayName numberOfOrders
7
+ defaultEmailAddress { emailAddress }
8
+ defaultPhoneNumber { phoneNumber }
9
+ amountSpent { amount currencyCode }
10
+ defaultAddress { city country }
11
+ createdAt
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,16 @@
1
+ # Customer spend overview
2
+ query CustomerSpendOverview {
3
+ customers(first: 30) {
4
+ edges {
5
+ node {
6
+ id displayName numberOfOrders
7
+ defaultEmailAddress { emailAddress }
8
+ defaultPhoneNumber { phoneNumber }
9
+ amountSpent { amount currencyCode }
10
+ tags
11
+ defaultAddress { city country }
12
+ createdAt
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,27 @@
1
+ query ActiveDiscounts {
2
+ codeDiscountNodes(first: 30, query: "status:active") {
3
+ edges {
4
+ node {
5
+ id
6
+ codeDiscount {
7
+ ... on DiscountCodeBasic {
8
+ title status appliesOncePerCustomer usageLimit startsAt endsAt
9
+ codes(first: 5) { edges { node { code asyncUsageCount } } }
10
+ customerGets {
11
+ value {
12
+ ... on DiscountPercentage { percentage }
13
+ ... on DiscountAmount { amount { amount currencyCode } }
14
+ }
15
+ items {
16
+ ... on AllDiscountItems { allItems }
17
+ }
18
+ }
19
+ customerSelection {
20
+ ... on DiscountCustomers { customers { id displayName } }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,40 @@
1
+ query DiscountsList {
2
+ codeDiscountNodes(first: 20) {
3
+ edges {
4
+ node {
5
+ id
6
+ codeDiscount {
7
+ ... on DiscountCodeBasic {
8
+ title
9
+ status
10
+ codes(first: 5) {
11
+ edges {
12
+ node {
13
+ code
14
+ asyncUsageCount
15
+ }
16
+ }
17
+ }
18
+ startsAt
19
+ endsAt
20
+ usageLimit
21
+ appliesOncePerCustomer
22
+ customerGets {
23
+ value {
24
+ ... on DiscountPercentage {
25
+ percentage
26
+ }
27
+ ... on DiscountAmount {
28
+ amount {
29
+ amount
30
+ currencyCode
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,38 @@
1
+ query InventoryAudit {
2
+ products(first: 50) {
3
+ edges {
4
+ node {
5
+ id
6
+ title
7
+ status
8
+ variants(first: 20) {
9
+ edges {
10
+ node {
11
+ id
12
+ title
13
+ sku
14
+ inventoryItem {
15
+ id
16
+ tracked
17
+ }
18
+ inventoryQuantity
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ locations(first: 10) {
26
+ edges {
27
+ node {
28
+ id
29
+ name
30
+ isActive
31
+ address {
32
+ city
33
+ countryCode
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,14 @@
1
+ # Replace PRODUCT_GID with the actual product global ID, e.g. gid://shopify/Product/123
2
+ query ProductMetafields {
3
+ product(id: "PRODUCT_GID") {
4
+ id title
5
+ metafields(first: 20) {
6
+ edges {
7
+ node {
8
+ id namespace key value type
9
+ createdAt updatedAt
10
+ }
11
+ }
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,30 @@
1
+ query OpenOrders {
2
+ orders(first: 50, query: "status:open", sortKey: CREATED_AT, reverse: true) {
3
+ edges {
4
+ node {
5
+ id name createdAt displayFinancialStatus displayFulfillmentStatus
6
+ totalPriceSet { presentmentMoney { amount currencyCode } }
7
+ customer {
8
+ displayName
9
+ defaultEmailAddress { emailAddress }
10
+ defaultPhoneNumber { phoneNumber }
11
+ }
12
+ shippingAddress { address1 city province country zip }
13
+ lineItems(first: 5) {
14
+ edges {
15
+ node {
16
+ title quantity
17
+ originalUnitPriceSet { presentmentMoney { amount currencyCode } }
18
+ variant { sku }
19
+ }
20
+ }
21
+ }
22
+ fulfillments {
23
+ status
24
+ trackingInfo { number url company }
25
+ }
26
+ note tags
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ # Revenue and order summary — use with admin:query for a quick snapshot
2
+ query RevenueSummary {
3
+ orders(first: 250, query: "created_at:>2026-01-01 financial_status:paid") {
4
+ edges {
5
+ node {
6
+ id name createdAt
7
+ totalPriceSet { presentmentMoney { amount currencyCode } }
8
+ subtotalPriceSet { presentmentMoney { amount currencyCode } }
9
+ totalShippingPriceSet { presentmentMoney { amount currencyCode } }
10
+ displayFulfillmentStatus
11
+ customer { displayName }
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,16 @@
1
+ query ProductsList {
2
+ products(first: 50, query: "status:active") {
3
+ edges {
4
+ node {
5
+ id title status productType vendor totalInventory
6
+ priceRangeV2 {
7
+ minVariantPrice { amount currencyCode }
8
+ maxVariantPrice { amount currencyCode }
9
+ }
10
+ images(first: 1) { edges { node { url } } }
11
+ variants(first: 1) { edges { node { sku } } }
12
+ createdAt updatedAt
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ # Lists active products where total inventory is below 5 — useful for restocking alerts
2
+ query LowInventoryProducts {
3
+ products(first: 100, query: "status:active") {
4
+ edges {
5
+ node {
6
+ id title totalInventory vendor
7
+ variants(first: 10) {
8
+ edges {
9
+ node {
10
+ title sku inventoryQuantity
11
+ inventoryItem { id tracked }
12
+ }
13
+ }
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ query ProductsSeoAudit {
2
+ products(first: 50) {
3
+ nodes {
4
+ id
5
+ title
6
+ handle
7
+ status
8
+ seo {
9
+ title
10
+ description
11
+ }
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ query ShopInfo {
2
+ shop {
3
+ name
4
+ myshopifyDomain
5
+ primaryDomain {
6
+ url
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,23 @@
1
+ query StoreFullInfo {
2
+ shop {
3
+ id name myshopifyDomain
4
+ primaryDomain { url sslEnabled }
5
+ plan { displayName }
6
+ currencyCode weightUnit ianaTimezone
7
+ email
8
+ shopAddress { address1 address2 city province country zip phone }
9
+ enabledPresentmentCurrencies
10
+ checkoutApiSupported
11
+ features { storefront }
12
+ contactEmail
13
+ createdAt
14
+ }
15
+ locations(first: 10) {
16
+ edges {
17
+ node {
18
+ id name isActive fulfillsOnlineOrders
19
+ address { address1 city country }
20
+ }
21
+ }
22
+ }
23
+ }