shopify-theme-devtools 2.2.1 → 2.2.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.
package/README.md CHANGED
@@ -96,6 +96,28 @@ The Console panel includes an interactive expression evaluator for testing Liqui
96
96
  - **Cart History** — View previous cart states with timestamps and restore any snapshot
97
97
  - **Attribute Editor** — Modify cart attributes and notes
98
98
  - **Diff View** — See what changed between cart states
99
+ - **Scenario Builder** — Create and save cart scenarios for quick testing
100
+ - **Cart Tests** — Write validation rules to automatically test cart items
101
+
102
+ ### Cart Tests
103
+
104
+ The Cart Tests feature lets you create custom validation rules to ensure cart items meet your business requirements. Tests can be run manually or automatically whenever the cart changes.
105
+
106
+ **Rule Types:**
107
+
108
+ | Type | Description | Example |
109
+ |------|-------------|---------|
110
+ | **Property Dependency** | If item has property X, must have properties Y, Z | GWP items require `_gwp_price` and `_gwp_source` |
111
+ | **Field Value** | If field equals X, another field must meet condition | Gift Cards must have quantity = 1 |
112
+ | **Cart Composition** | If item X exists, item Y must also exist | Electronics require warranty item |
113
+ | **Quantity** | Min/max/multiple constraints per-item or cart-total | Max 10 per item, cart total max 50 |
114
+
115
+ **Features:**
116
+
117
+ - **Pre-built Templates** — 16 ready-to-use templates for common scenarios
118
+ - **Auto-run** — Toggle to run all tests whenever cart changes
119
+ - **Import/Export** — Save and share test configurations as JSON
120
+ - **Inline Results** — Pass/fail badges show test status at a glance
99
121
 
100
122
  ### Liquid Error Detection
101
123
 
@@ -163,6 +185,16 @@ npx shopify-theme-devtools init --inject
163
185
  | `--inject` | Automatically add render tag to `layout/theme.liquid` |
164
186
  | `--force` | Overwrite existing files without prompting |
165
187
 
188
+ The CLI automatically detects and injects your `metafields.json` schema from `.shopify/metafields.json` (or theme root), enabling devtools to show all defined metafields.
189
+
190
+ #### Syncing Metafields
191
+
192
+ When your metafields change over time, run the sync command to update the schema without overwriting other customizations:
193
+
194
+ ```bash
195
+ npx shopify-theme-devtools sync
196
+ ```
197
+
166
198
  ```bash
167
199
  # Examples
168
200
  npx shopify-theme-devtools init # Uses CDN (recommended)
@@ -202,7 +234,7 @@ The devtools panel automatically appears on **unpublished/development themes onl
202
234
  |-------|-------------|
203
235
  | **Objects** | Inspect all Liquid objects with search and tree navigation |
204
236
  | **Metafields** | Browse metafields by resource (product, collection, shop, etc.) |
205
- | **Cart** | Live cart state, history snapshots, and manipulation tools |
237
+ | **Cart** | Live cart state, history, scenarios, tests, and manipulation tools |
206
238
  | **Locale** | Markets, currencies, languages with locale switching |
207
239
  | **Analytics** | Detected tracking codes and analytics configuration |
208
240
  | **SEO** | Meta tags, Open Graph, Twitter Cards, JSON-LD structured data |
@@ -285,6 +317,8 @@ src/
285
317
  │ │ ├── theme-devtools.js # Main component with tab management
286
318
  │ │ ├── object-inspector.js # Tree view inspector
287
319
  │ │ └── panels/ # Panel components
320
+ │ ├── lib/
321
+ │ │ └── cart-test-templates.js # Pre-built cart test templates
288
322
  │ ├── services/
289
323
  │ │ ├── cart.js # Cart API with history
290
324
  │ │ ├── product.js # Product API (variants/images)
package/bin/cli.js CHANGED
@@ -47,6 +47,7 @@ ${COLORS.bold}Usage:${COLORS.reset}
47
47
 
48
48
  ${COLORS.bold}Commands:${COLORS.reset}
49
49
  init Initialize devtools in your Shopify theme
50
+ sync Sync metafields schema from .shopify/metafields.json
50
51
 
51
52
  ${COLORS.bold}Options for init:${COLORS.reset}
52
53
  --local Copy JS/CSS to assets folder instead of using CDN
@@ -58,6 +59,7 @@ ${COLORS.bold}Examples:${COLORS.reset}
58
59
  npx shopify-theme-devtools init
59
60
  npx shopify-theme-devtools init --local
60
61
  npx shopify-theme-devtools init --local --inject
62
+ npx shopify-theme-devtools sync
61
63
  `);
62
64
  }
63
65
 
@@ -77,6 +79,57 @@ function copyFile(src, dest, force = false) {
77
79
  return true;
78
80
  }
79
81
 
82
+ function findMetafieldsSchema(themeRoot) {
83
+ // Look for metafields.json in common locations
84
+ const possiblePaths = [
85
+ join(themeRoot, '.shopify', 'metafields.json'),
86
+ join(themeRoot, 'metafields.json'),
87
+ join(themeRoot, 'config', 'metafields.json'),
88
+ ];
89
+
90
+ for (const schemaPath of possiblePaths) {
91
+ if (existsSync(schemaPath)) {
92
+ try {
93
+ const content = readFileSync(schemaPath, 'utf-8');
94
+ // Validate it's valid JSON
95
+ JSON.parse(content);
96
+ return { path: schemaPath, content };
97
+ } catch {
98
+ // Invalid JSON, skip
99
+ }
100
+ }
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ function injectMetafieldsSchema(liquidContent, schemaContent) {
107
+ // Replace the default empty schema with the actual schema
108
+ const defaultSchema = `{%- capture devtools_metafields_schema -%}
109
+ {
110
+ "article": [],
111
+ "blog": [],
112
+ "collection": [],
113
+ "company": [],
114
+ "company_location": [],
115
+ "location": [],
116
+ "market": [],
117
+ "order": [],
118
+ "page": [],
119
+ "product": [],
120
+ "variant": [],
121
+ "shop": [],
122
+ "customer": []
123
+ }
124
+ {%- endcapture -%}`;
125
+
126
+ const newSchema = `{%- capture devtools_metafields_schema -%}
127
+ ${schemaContent.trim()}
128
+ {%- endcapture -%}`;
129
+
130
+ return liquidContent.replace(defaultSchema, newSchema);
131
+ }
132
+
80
133
  function patchLiquidForLocal(content) {
81
134
  // Change CDN to asset_url
82
135
  let patched = content.replace(
@@ -198,6 +251,13 @@ async function init(args) {
198
251
  '{%- assign devtools_local = false -%}'
199
252
  );
200
253
 
254
+ // Auto-detect and inject metafields schema
255
+ const metafieldsSchema = findMetafieldsSchema(themeRoot);
256
+ if (metafieldsSchema) {
257
+ liquidContent = injectMetafieldsSchema(liquidContent, metafieldsSchema.content);
258
+ success(`Injected metafields schema from ${metafieldsSchema.path.replace(themeRoot + '/', '')}`);
259
+ }
260
+
201
261
  if (useLocal) {
202
262
  liquidContent = patchLiquidForLocal(liquidContent);
203
263
  }
@@ -255,6 +315,67 @@ async function init(args) {
255
315
  console.log();
256
316
  }
257
317
 
318
+ async function sync() {
319
+ const cwd = process.cwd();
320
+
321
+ console.log();
322
+ log('Shopify Theme Devtools', COLORS.bold);
323
+ console.log();
324
+
325
+ // Detect theme root
326
+ const themeRoot = detectThemeRoot(cwd);
327
+
328
+ if (!themeRoot) {
329
+ error('Could not detect Shopify theme directory');
330
+ info('Make sure you run this command from your theme root');
331
+ info('(should contain layout/, snippets/, templates/ folders)');
332
+ process.exit(1);
333
+ }
334
+
335
+ const snippetsDir = join(themeRoot, 'snippets');
336
+ const liquidDest = join(snippetsDir, 'theme-devtools-bridge.liquid');
337
+
338
+ // Check if snippet exists
339
+ if (!existsSync(liquidDest)) {
340
+ error('Devtools snippet not found');
341
+ info('Run "npx shopify-theme-devtools init" first');
342
+ process.exit(1);
343
+ }
344
+
345
+ // Find metafields schema
346
+ const metafieldsSchema = findMetafieldsSchema(themeRoot);
347
+
348
+ if (!metafieldsSchema) {
349
+ error('No metafields.json found');
350
+ info('Looked in: .shopify/metafields.json, metafields.json, config/metafields.json');
351
+ process.exit(1);
352
+ }
353
+
354
+ // Read current snippet
355
+ let liquidContent = readFileSync(liquidDest, 'utf-8');
356
+
357
+ // Replace the metafields schema using a regex that matches any JSON content
358
+ const schemaRegex = /\{%- capture devtools_metafields_schema -%\}[\s\S]*?\{%- endcapture -%\}/;
359
+
360
+ if (!schemaRegex.test(liquidContent)) {
361
+ error('Could not find metafields schema section in snippet');
362
+ info('The snippet may be corrupted. Run "npx shopify-theme-devtools init --force" to regenerate');
363
+ process.exit(1);
364
+ }
365
+
366
+ const newSchema = `{%- capture devtools_metafields_schema -%}
367
+ ${metafieldsSchema.content.trim()}
368
+ {%- endcapture -%}`;
369
+
370
+ liquidContent = liquidContent.replace(schemaRegex, newSchema);
371
+
372
+ // Write updated snippet
373
+ writeFileSync(liquidDest, liquidContent);
374
+ success(`Synced metafields schema from ${metafieldsSchema.path.replace(themeRoot + '/', '')}`);
375
+
376
+ console.log();
377
+ }
378
+
258
379
  // Parse arguments
259
380
  const args = process.argv.slice(2);
260
381
  const command = args[0];
@@ -266,6 +387,8 @@ if (!command || command === '--help' || command === '-h') {
266
387
 
267
388
  if (command === 'init') {
268
389
  init(args);
390
+ } else if (command === 'sync') {
391
+ sync();
269
392
  } else {
270
393
  error(`Unknown command: ${command}`);
271
394
  printHelp();