shopify-theme-devtools 2.1.0 → 2.2.1

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
@@ -6,6 +6,20 @@
6
6
 
7
7
  A powerful in-browser developer tools panel for Shopify theme development. Inspect Liquid objects, evaluate expressions, explore metafields, manipulate cart state, detect Liquid errors, and much more — all without leaving the browser.
8
8
 
9
+ ## Table of Contents
10
+
11
+ - [Features](#features)
12
+ - [Quick Start](#quick-start)
13
+ - [Keyboard Shortcuts](#keyboard-shortcuts)
14
+ - [Panel Reference](#panel-reference)
15
+ - [Configuration](#configuration)
16
+ - [Development](#development)
17
+ - [How It Works](#how-it-works)
18
+ - [Tech Stack](#tech-stack)
19
+ - [Browser Support](#browser-support)
20
+ - [Contributing](#contributing)
21
+ - [License](#license)
22
+
9
23
  ## Features
10
24
 
11
25
  ### Core Panels
@@ -13,7 +27,7 @@ A powerful in-browser developer tools panel for Shopify theme development. Inspe
13
27
  - **Objects Inspector** — Browse `shop`, `product`, `collection`, `customer`, `cart` and more with deep search and collapsible tree view
14
28
  - **Metafields Viewer** — Explore metafields across all resources and namespaces with type labels
15
29
  - **Cart Panel** — Real-time cart state with add/remove/quantity controls, cart history with restore, and attribute editing
16
- - **Console** — Chrome DevTools-style console with Liquid expression evaluator, autocomplete, and log filtering
30
+ - **Console** — Chrome DevTools-style console with Liquid expression evaluator, and autocomplete
17
31
  - **Localization** — Markets, currencies, languages, and country data with quick switcher
18
32
  - **SEO Inspector** — Meta tags, Open Graph, Twitter Cards, and structured data (JSON-LD) validation
19
33
  - **Analytics Viewer** — Detects Google Analytics, Facebook Pixel, and other tracking codes
@@ -29,10 +43,21 @@ Browse and search through all Liquid objects with a collapsible tree view. Click
29
43
 
30
44
  ### Console Panel
31
45
 
32
- Evaluate Liquid expressions in real-time with autocomplete, filter support, and command history.
46
+ A powerful Liquid debugger combining error detection and expression evaluation.
33
47
 
34
48
  ![Console Panel](screenshots/console-panel.gif)
35
49
 
50
+ **Liquid Error Detection:**
51
+ - **Liquid Errors** — Detects `Liquid error:` and `Liquid syntax error:` messages in page content
52
+ - **Drop Object Leaks** — Finds raw Drop objects rendered on page (`ProductDrop`, `#<ProductDrop:0x...>`)
53
+ - **Asset Errors** — Missing snippets, images, and asset files
54
+ - **Schema Errors** — Invalid JSON in section schemas
55
+ - **JSON Filter Errors** — Detects `{"error":"..."}` output from failed `| json` filters
56
+ - **Form Errors** — Missing product/customer objects for forms
57
+ - **Smart Hints** — Context-aware fix suggestions for each error type
58
+
59
+ **Expression Evaluator:**
60
+
36
61
  ### Cart Snapshots
37
62
 
38
63
  Track cart state changes over time and restore any previous snapshot with a single click.
@@ -82,38 +107,80 @@ Automatically scans the page for common Liquid issues:
82
107
  - Schema validation errors
83
108
  - Deprecation warnings
84
109
 
85
- ### Network Monitor
110
+ ### Network Panel
86
111
 
87
- Captures failed HTTP requests with:
88
- - Status codes and response times
89
- - Request URLs with query parameters
90
- - Error categorization (4xx, 5xx, CORS, timeout)
112
+ Captures and inspects Shopify API requests with powerful debugging tools:
91
113
 
92
- ## Quick Start
114
+ - **Request Logging** — Auto-captures cart, product, collection, search, and GraphQL requests
115
+ - **Request Editor** — Edit and resend requests with modified method, URL, headers, and body
116
+ - **Replay** — Re-execute any captured request with one click
117
+ - **Cart State Diff** — See exactly what changed in the cart after mutations (items added/removed/changed)
118
+ - **Copy as cURL/Fetch** — Export requests for debugging or sharing
119
+ - **Persistence** — Last 32 requests are saved to localStorage and restored on reload
120
+ - **GraphQL Prettifier** — Syntax-highlighted, formatted GraphQL queries
121
+ - **Error Diagnosis** — Smart suggestions for common cart and API errors (out of stock, invalid variants, quantity limits)
93
122
 
94
- ### 1. Add the snippet to your theme
123
+ ### Tab Customization
95
124
 
96
- Copy the Liquid bridge to your theme's snippets folder:
125
+ Customize your devtools layout like Chrome DevTools:
126
+
127
+ - **Right-click any tab** to show/hide it
128
+ - **Drag tabs** to reorder them
129
+ - **Preferences tab** cannot be hidden (ensures you can always restore tabs)
130
+ - **"Reset to Defaults"** restores original tab order and visibility
131
+ - Hidden tabs are completely unloaded from DOM (saves memory/CPU)
132
+
133
+ ## Quick Start
134
+
135
+ ### Option A: Using npm (Recommended)
97
136
 
98
137
  ```bash
99
- # Option A: Download directly
100
- curl -o snippets/theme-devtools-bridge.liquid \
101
- https://raw.githubusercontent.com/user/shopify-theme-devtools/main/src/liquid/theme-devtools-bridge.liquid
138
+ # Run directly with npx (no install needed)
139
+ npx shopify-theme-devtools init
102
140
 
103
- # Option B: Install via npm
104
- npm install shopify-theme-devtools
105
- cp node_modules/shopify-theme-devtools/src/liquid/theme-devtools-bridge.liquid snippets/
141
+ # Or install globally for repeated use
142
+ npm install -g shopify-theme-devtools
143
+ shopify-theme-devtools init
106
144
  ```
107
145
 
108
- ### 2. Include in your layout
109
-
110
- Add this line to `layout/theme.liquid` just before `</body>`:
146
+ This copies the Liquid snippet to your `snippets/` folder. Then add this line to `layout/theme.liquid` just before `</body>`:
111
147
 
112
148
  ```liquid
113
149
  {% render 'theme-devtools-bridge' %}
114
150
  ```
115
151
 
116
- ### 3. You're done!
152
+ Or use `--inject` to add it automatically:
153
+
154
+ ```bash
155
+ npx shopify-theme-devtools init --inject
156
+ ```
157
+
158
+ #### CLI Options
159
+
160
+ | Option | Description |
161
+ |--------|-------------|
162
+ | `--local` | Copy JS/CSS to `assets/` folder instead of using CDN |
163
+ | `--inject` | Automatically add render tag to `layout/theme.liquid` |
164
+ | `--force` | Overwrite existing files without prompting |
165
+
166
+ ```bash
167
+ # Examples
168
+ npx shopify-theme-devtools init # Uses CDN (recommended)
169
+ npx shopify-theme-devtools init --local # Self-hosted assets
170
+ npx shopify-theme-devtools init --local --inject # Self-hosted + auto-inject
171
+ ```
172
+
173
+ ### Option B: Manual Download
174
+
175
+ ```bash
176
+ # Download directly
177
+ curl -o snippets/theme-devtools-bridge.liquid \
178
+ https://raw.githubusercontent.com/yakohere/shopify-theme-devtools/main/src/liquid/theme-devtools-bridge.liquid
179
+ ```
180
+
181
+ Then add `{% render 'theme-devtools-bridge' %}` to `layout/theme.liquid` before `</body>`.
182
+
183
+ ### You're done!
117
184
 
118
185
  The devtools panel automatically appears on **unpublished/development themes only**. It's safe to commit — it won't render on your live published theme.
119
186
 
@@ -126,6 +193,8 @@ The devtools panel automatically appears on **unpublished/development themes onl
126
193
  | Evaluate Expression | `Enter` in Console input |
127
194
  | Autocomplete | `Tab` in Console input |
128
195
  | Command History | `↑` / `↓` in Console input |
196
+ | Show/Hide Tabs | Right-click any tab |
197
+ | Reorder Tabs | Drag and drop tabs |
129
198
 
130
199
  ## Panel Reference
131
200
 
@@ -138,6 +207,7 @@ The devtools panel automatically appears on **unpublished/development themes onl
138
207
  | **Analytics** | Detected tracking codes and analytics configuration |
139
208
  | **SEO** | Meta tags, Open Graph, Twitter Cards, JSON-LD structured data |
140
209
  | **Apps** | Installed Shopify apps detected on the page |
210
+ | **Network** | API request monitor with edit/replay, cart diffs, and error diagnosis |
141
211
  | **Console** | Logs, errors, and Liquid expression evaluator |
142
212
  | **Cookies** | Browser cookies with expiration and flags |
143
213
  | **Storage** | localStorage and sessionStorage key-value pairs |
@@ -212,7 +282,7 @@ src/
212
282
  │ └── theme-devtools-bridge.liquid # Liquid snippet for themes
213
283
  ├── scripts/
214
284
  │ ├── components/
215
- │ │ ├── theme-devtools.js # Main component
285
+ │ │ ├── theme-devtools.js # Main component with tab management
216
286
  │ │ ├── object-inspector.js # Tree view inspector
217
287
  │ │ └── panels/ # Panel components
218
288
  │ ├── services/
@@ -220,6 +290,7 @@ src/
220
290
  │ │ ├── product.js # Product API (variants/images)
221
291
  │ │ ├── context.js # Liquid context parser
222
292
  │ │ ├── expression-evaluator.js # Liquid expression engine
293
+ │ │ ├── network-interceptor.js # Fetch/XHR interceptor with persistence
223
294
  │ │ └── settings.js # User preferences
224
295
  │ └── styles/
225
296
  │ └── theme.js # CSS variables and themes
@@ -247,7 +318,6 @@ src/
247
318
  - [Lit](https://lit.dev/) — Lightweight Web Components
248
319
  - [LiquidJS](https://liquidjs.com/) — Liquid template engine
249
320
  - [Vite](https://vitejs.dev/) — Fast build tooling
250
- - Single IIFE bundle (~55KB gzipped)
251
321
 
252
322
  ## Browser Support
253
323
 
package/bin/cli.js ADDED
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from 'fs';
4
+ import { dirname, join, resolve } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const packageRoot = resolve(__dirname, '..');
10
+
11
+ const COLORS = {
12
+ reset: '\x1b[0m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ blue: '\x1b[34m',
16
+ red: '\x1b[31m',
17
+ dim: '\x1b[2m',
18
+ bold: '\x1b[1m'
19
+ };
20
+
21
+ function log(message, color = '') {
22
+ console.log(`${color}${message}${COLORS.reset}`);
23
+ }
24
+
25
+ function success(message) {
26
+ log(`✓ ${message}`, COLORS.green);
27
+ }
28
+
29
+ function warn(message) {
30
+ log(`⚠ ${message}`, COLORS.yellow);
31
+ }
32
+
33
+ function error(message) {
34
+ log(`✗ ${message}`, COLORS.red);
35
+ }
36
+
37
+ function info(message) {
38
+ log(` ${message}`, COLORS.dim);
39
+ }
40
+
41
+ function printHelp() {
42
+ console.log(`
43
+ ${COLORS.bold}Shopify Theme Devtools CLI${COLORS.reset}
44
+
45
+ ${COLORS.bold}Usage:${COLORS.reset}
46
+ npx shopify-theme-devtools <command> [options]
47
+
48
+ ${COLORS.bold}Commands:${COLORS.reset}
49
+ init Initialize devtools in your Shopify theme
50
+
51
+ ${COLORS.bold}Options for init:${COLORS.reset}
52
+ --local Copy JS/CSS to assets folder instead of using CDN
53
+ --inject Add render tag to layout/theme.liquid automatically
54
+ --force Overwrite existing files without prompting
55
+ --help, -h Show this help message
56
+
57
+ ${COLORS.bold}Examples:${COLORS.reset}
58
+ npx shopify-theme-devtools init
59
+ npx shopify-theme-devtools init --local
60
+ npx shopify-theme-devtools init --local --inject
61
+ `);
62
+ }
63
+
64
+ function ensureDir(dir) {
65
+ if (!existsSync(dir)) {
66
+ mkdirSync(dir, { recursive: true });
67
+ }
68
+ }
69
+
70
+ function copyFile(src, dest, force = false) {
71
+ if (existsSync(dest) && !force) {
72
+ warn(`File already exists: ${dest}`);
73
+ info('Use --force to overwrite');
74
+ return false;
75
+ }
76
+ copyFileSync(src, dest);
77
+ return true;
78
+ }
79
+
80
+ function patchLiquidForLocal(content) {
81
+ // Change CDN to asset_url
82
+ let patched = content.replace(
83
+ /{%- assign devtools_cdn_base = '[^']+' -%}/,
84
+ "{%- assign devtools_cdn_base = '' -%}"
85
+ );
86
+
87
+ // Replace the script loading section for local assets
88
+ patched = patched.replace(
89
+ /<script>\s*window\.__THEME_DEVTOOLS_CSS_URL__[^<]+<\/script>\s*<script src="{{ devtools_cdn_base }}\/theme-devtools\.js" defer><\/script>/s,
90
+ `<script>
91
+ window.__THEME_DEVTOOLS_CSS_URL__ = '{{ 'theme-devtools.css' | asset_url }}';
92
+ </script>
93
+ <script src="{{ 'theme-devtools.js' | asset_url }}" defer></script>`
94
+ );
95
+
96
+ return patched;
97
+ }
98
+
99
+ function injectRenderTag(themeLiquidPath) {
100
+ if (!existsSync(themeLiquidPath)) {
101
+ warn(`layout/theme.liquid not found at: ${themeLiquidPath}`);
102
+ return false;
103
+ }
104
+
105
+ let content = readFileSync(themeLiquidPath, 'utf-8');
106
+ const renderTag = "{% render 'theme-devtools-bridge' %}";
107
+
108
+ if (content.includes('theme-devtools-bridge')) {
109
+ info('Render tag already exists in theme.liquid');
110
+ return true;
111
+ }
112
+
113
+ // Insert before </body>
114
+ const bodyCloseRegex = /<\/body>/i;
115
+ if (!bodyCloseRegex.test(content)) {
116
+ warn('Could not find </body> tag in theme.liquid');
117
+ return false;
118
+ }
119
+
120
+ content = content.replace(bodyCloseRegex, ` ${renderTag}\n </body>`);
121
+ writeFileSync(themeLiquidPath, content);
122
+ return true;
123
+ }
124
+
125
+ function detectThemeRoot(cwd) {
126
+ // Check if we're in a Shopify theme directory
127
+ const markers = ['layout', 'snippets', 'templates', 'config'];
128
+ const hasMarkers = markers.filter(dir => existsSync(join(cwd, dir)));
129
+
130
+ if (hasMarkers.length >= 2) {
131
+ return cwd;
132
+ }
133
+
134
+ // Check if there's a theme subdirectory
135
+ const possibleThemeDirs = ['theme', 'shopify', 'src'];
136
+ for (const dir of possibleThemeDirs) {
137
+ const themePath = join(cwd, dir);
138
+ if (existsSync(themePath)) {
139
+ const subMarkers = markers.filter(m => existsSync(join(themePath, m)));
140
+ if (subMarkers.length >= 2) {
141
+ return themePath;
142
+ }
143
+ }
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ async function init(args) {
150
+ const cwd = process.cwd();
151
+ const useLocal = args.includes('--local');
152
+ const shouldInject = args.includes('--inject');
153
+ const force = args.includes('--force');
154
+
155
+ console.log();
156
+ log('Shopify Theme Devtools', COLORS.bold);
157
+ console.log();
158
+
159
+ // Detect theme root
160
+ const themeRoot = detectThemeRoot(cwd);
161
+
162
+ if (!themeRoot) {
163
+ error('Could not detect Shopify theme directory');
164
+ info('Make sure you run this command from your theme root');
165
+ info('(should contain layout/, snippets/, templates/ folders)');
166
+ process.exit(1);
167
+ }
168
+
169
+ if (themeRoot !== cwd) {
170
+ info(`Detected theme root: ${themeRoot}`);
171
+ }
172
+
173
+ const snippetsDir = join(themeRoot, 'snippets');
174
+ const assetsDir = join(themeRoot, 'assets');
175
+ const layoutDir = join(themeRoot, 'layout');
176
+
177
+ // Ensure directories exist
178
+ ensureDir(snippetsDir);
179
+ if (useLocal) {
180
+ ensureDir(assetsDir);
181
+ }
182
+
183
+ // Source files
184
+ const liquidSrc = join(packageRoot, 'src', 'liquid', 'theme-devtools-bridge.liquid');
185
+ const jsSrc = join(packageRoot, 'dist', 'theme-devtools.js');
186
+ const cssSrc = join(packageRoot, 'dist', 'theme-devtools.css');
187
+
188
+ // Destination files
189
+ const liquidDest = join(snippetsDir, 'theme-devtools-bridge.liquid');
190
+ const jsDest = join(assetsDir, 'theme-devtools.js');
191
+ const cssDest = join(assetsDir, 'theme-devtools.css');
192
+
193
+ let liquidContent = readFileSync(liquidSrc, 'utf-8');
194
+
195
+ // Set devtools_local to false for production use
196
+ liquidContent = liquidContent.replace(
197
+ /{%- assign devtools_local = true -%}/,
198
+ '{%- assign devtools_local = false -%}'
199
+ );
200
+
201
+ if (useLocal) {
202
+ liquidContent = patchLiquidForLocal(liquidContent);
203
+ }
204
+
205
+ // Copy liquid snippet
206
+ if (existsSync(liquidDest) && !force) {
207
+ warn(`Snippet already exists: snippets/theme-devtools-bridge.liquid`);
208
+ info('Use --force to overwrite');
209
+ } else {
210
+ writeFileSync(liquidDest, liquidContent);
211
+ success('Created snippets/theme-devtools-bridge.liquid');
212
+ }
213
+
214
+ // Copy assets if --local
215
+ if (useLocal) {
216
+ if (!existsSync(jsSrc)) {
217
+ error('dist/theme-devtools.js not found. Run npm run build first.');
218
+ process.exit(1);
219
+ }
220
+
221
+ if (copyFile(jsSrc, jsDest, force)) {
222
+ success('Copied assets/theme-devtools.js');
223
+ }
224
+
225
+ if (existsSync(cssSrc)) {
226
+ if (copyFile(cssSrc, cssDest, force)) {
227
+ success('Copied assets/theme-devtools.css');
228
+ }
229
+ }
230
+ }
231
+
232
+ // Inject render tag if --inject
233
+ if (shouldInject) {
234
+ const themeLiquid = join(layoutDir, 'theme.liquid');
235
+ if (injectRenderTag(themeLiquid)) {
236
+ success('Added render tag to layout/theme.liquid');
237
+ }
238
+ }
239
+
240
+ console.log();
241
+ log('Setup complete!', COLORS.green + COLORS.bold);
242
+ console.log();
243
+
244
+ if (!shouldInject) {
245
+ log('Next step:', COLORS.bold);
246
+ info("Add this line to layout/theme.liquid before </body>:");
247
+ console.log();
248
+ log(" {% render 'theme-devtools-bridge' %}", COLORS.blue);
249
+ console.log();
250
+ info('Or run with --inject to do this automatically');
251
+ }
252
+
253
+ console.log();
254
+ info('The devtools panel will only appear on unpublished/development themes.');
255
+ console.log();
256
+ }
257
+
258
+ // Parse arguments
259
+ const args = process.argv.slice(2);
260
+ const command = args[0];
261
+
262
+ if (!command || command === '--help' || command === '-h') {
263
+ printHelp();
264
+ process.exit(0);
265
+ }
266
+
267
+ if (command === 'init') {
268
+ init(args);
269
+ } else {
270
+ error(`Unknown command: ${command}`);
271
+ printHelp();
272
+ process.exit(1);
273
+ }