shopify-starter-kit 1.0.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 (28) hide show
  1. package/.agent/skills/shopify-apps/SKILL.md +47 -0
  2. package/.agent/skills/shopify-automation/SKILL.md +172 -0
  3. package/.agent/skills/shopify-development/README.md +60 -0
  4. package/.agent/skills/shopify-development/SKILL.md +368 -0
  5. package/.agent/skills/shopify-development/references/app-development.md +578 -0
  6. package/.agent/skills/shopify-development/references/extensions.md +555 -0
  7. package/.agent/skills/shopify-development/references/themes.md +498 -0
  8. package/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
  9. package/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
  10. package/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
  11. package/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  12. package/bin/cli.js +3 -0
  13. package/package.json +32 -0
  14. package/src/index.js +116 -0
  15. package/templates/.agent/skills/shopify-apps/SKILL.md +47 -0
  16. package/templates/.agent/skills/shopify-automation/SKILL.md +172 -0
  17. package/templates/.agent/skills/shopify-development/README.md +60 -0
  18. package/templates/.agent/skills/shopify-development/SKILL.md +368 -0
  19. package/templates/.agent/skills/shopify-development/references/app-development.md +578 -0
  20. package/templates/.agent/skills/shopify-development/references/extensions.md +555 -0
  21. package/templates/.agent/skills/shopify-development/references/themes.md +498 -0
  22. package/templates/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
  23. package/templates/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
  24. package/templates/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
  25. package/templates/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  26. package/templates/.devcontainer/devcontainer.json +27 -0
  27. package/templates/tests/playwright.config.ts +26 -0
  28. package/templates/tests/vitest.config.ts +9 -0
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shopify Project Initialization Script
4
+
5
+ Interactive script to scaffold Shopify apps, extensions, or themes.
6
+ Supports environment variable loading from multiple locations.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import json
12
+ import subprocess
13
+ from pathlib import Path
14
+ from typing import Dict, Optional, List
15
+ from dataclasses import dataclass
16
+
17
+
18
+ @dataclass
19
+ class EnvConfig:
20
+ """Environment configuration container."""
21
+ shopify_api_key: Optional[str] = None
22
+ shopify_api_secret: Optional[str] = None
23
+ shop_domain: Optional[str] = None
24
+ scopes: Optional[str] = None
25
+
26
+
27
+ class EnvLoader:
28
+ """Load environment variables from multiple sources in priority order."""
29
+
30
+ @staticmethod
31
+ def load_env_file(filepath: Path) -> Dict[str, str]:
32
+ """
33
+ Load environment variables from .env file.
34
+
35
+ Args:
36
+ filepath: Path to .env file
37
+
38
+ Returns:
39
+ Dictionary of environment variables
40
+ """
41
+ env_vars = {}
42
+ if not filepath.exists():
43
+ return env_vars
44
+
45
+ try:
46
+ with open(filepath, 'r') as f:
47
+ for line in f:
48
+ line = line.strip()
49
+ if line and not line.startswith('#') and '=' in line:
50
+ key, value = line.split('=', 1)
51
+ env_vars[key.strip()] = value.strip().strip('"').strip("'")
52
+ except Exception as e:
53
+ print(f"Warning: Failed to load {filepath}: {e}")
54
+
55
+ return env_vars
56
+
57
+ @staticmethod
58
+ def get_env_paths(skill_dir: Path) -> List[Path]:
59
+ """
60
+ Get list of .env file paths in priority order.
61
+
62
+ Works with any AI tool directory structure:
63
+ - .agent/skills/ (universal)
64
+ - .claude/skills/ (Claude Code)
65
+ - .gemini/skills/ (Gemini CLI)
66
+ - .cursor/skills/ (Cursor)
67
+
68
+ Priority: process.env > skill/.env > skills/.env > agent_dir/.env
69
+
70
+ Args:
71
+ skill_dir: Path to skill directory
72
+
73
+ Returns:
74
+ List of .env file paths
75
+ """
76
+ paths = []
77
+
78
+ # skill/.env
79
+ skill_env = skill_dir / '.env'
80
+ if skill_env.exists():
81
+ paths.append(skill_env)
82
+
83
+ # skills/.env
84
+ skills_env = skill_dir.parent / '.env'
85
+ if skills_env.exists():
86
+ paths.append(skills_env)
87
+
88
+ # agent_dir/.env (e.g., .agent, .claude, .gemini, .cursor)
89
+ agent_env = skill_dir.parent.parent / '.env'
90
+ if agent_env.exists():
91
+ paths.append(agent_env)
92
+
93
+ return paths
94
+
95
+ @staticmethod
96
+ def load_config(skill_dir: Path) -> EnvConfig:
97
+ """
98
+ Load configuration from environment variables.
99
+
100
+ Works with any AI tool directory structure.
101
+ Priority: process.env > skill/.env > skills/.env > agent_dir/.env
102
+
103
+ Args:
104
+ skill_dir: Path to skill directory
105
+
106
+ Returns:
107
+ EnvConfig object
108
+ """
109
+ config = EnvConfig()
110
+
111
+ # Load from .env files (reverse priority order)
112
+ for env_path in reversed(EnvLoader.get_env_paths(skill_dir)):
113
+ env_vars = EnvLoader.load_env_file(env_path)
114
+ if 'SHOPIFY_API_KEY' in env_vars:
115
+ config.shopify_api_key = env_vars['SHOPIFY_API_KEY']
116
+ if 'SHOPIFY_API_SECRET' in env_vars:
117
+ config.shopify_api_secret = env_vars['SHOPIFY_API_SECRET']
118
+ if 'SHOP_DOMAIN' in env_vars:
119
+ config.shop_domain = env_vars['SHOP_DOMAIN']
120
+ if 'SCOPES' in env_vars:
121
+ config.scopes = env_vars['SCOPES']
122
+
123
+ # Override with process environment (highest priority)
124
+ if 'SHOPIFY_API_KEY' in os.environ:
125
+ config.shopify_api_key = os.environ['SHOPIFY_API_KEY']
126
+ if 'SHOPIFY_API_SECRET' in os.environ:
127
+ config.shopify_api_secret = os.environ['SHOPIFY_API_SECRET']
128
+ if 'SHOP_DOMAIN' in os.environ:
129
+ config.shop_domain = os.environ['SHOP_DOMAIN']
130
+ if 'SCOPES' in os.environ:
131
+ config.scopes = os.environ['SCOPES']
132
+
133
+ return config
134
+
135
+
136
+ class ShopifyInitializer:
137
+ """Initialize Shopify projects."""
138
+
139
+ def __init__(self, config: EnvConfig):
140
+ """
141
+ Initialize ShopifyInitializer.
142
+
143
+ Args:
144
+ config: Environment configuration
145
+ """
146
+ self.config = config
147
+
148
+ def prompt(self, message: str, default: Optional[str] = None) -> str:
149
+ """
150
+ Prompt user for input.
151
+
152
+ Args:
153
+ message: Prompt message
154
+ default: Default value
155
+
156
+ Returns:
157
+ User input or default
158
+ """
159
+ if default:
160
+ message = f"{message} [{default}]"
161
+ user_input = input(f"{message}: ").strip()
162
+ return user_input if user_input else (default or '')
163
+
164
+ def select_option(self, message: str, options: List[str]) -> str:
165
+ """
166
+ Prompt user to select from options.
167
+
168
+ Args:
169
+ message: Prompt message
170
+ options: List of options
171
+
172
+ Returns:
173
+ Selected option
174
+ """
175
+ print(f"\n{message}")
176
+ for i, option in enumerate(options, 1):
177
+ print(f"{i}. {option}")
178
+
179
+ while True:
180
+ try:
181
+ choice = int(input("Select option: ").strip())
182
+ if 1 <= choice <= len(options):
183
+ return options[choice - 1]
184
+ print(f"Please select 1-{len(options)}")
185
+ except (ValueError, KeyboardInterrupt):
186
+ print("Invalid input")
187
+
188
+ def check_cli_installed(self) -> bool:
189
+ """
190
+ Check if Shopify CLI is installed.
191
+
192
+ Returns:
193
+ True if installed, False otherwise
194
+ """
195
+ try:
196
+ result = subprocess.run(
197
+ ['shopify', 'version'],
198
+ capture_output=True,
199
+ text=True,
200
+ timeout=5
201
+ )
202
+ return result.returncode == 0
203
+ except (subprocess.SubprocessError, FileNotFoundError):
204
+ return False
205
+
206
+ def create_app_config(self, project_dir: Path, app_name: str, scopes: str) -> None:
207
+ """
208
+ Create shopify.app.toml configuration file.
209
+
210
+ Args:
211
+ project_dir: Project directory
212
+ app_name: Application name
213
+ scopes: Access scopes
214
+ """
215
+ config_content = f"""# Shopify App Configuration
216
+ name = "{app_name}"
217
+ client_id = "{self.config.shopify_api_key or 'YOUR_API_KEY'}"
218
+ application_url = "https://your-app.com"
219
+ embedded = true
220
+
221
+ [build]
222
+ automatically_update_urls_on_dev = true
223
+ dev_store_url = "{self.config.shop_domain or 'your-store.myshopify.com'}"
224
+
225
+ [access_scopes]
226
+ scopes = "{scopes}"
227
+
228
+ [webhooks]
229
+ api_version = "2026-01"
230
+
231
+ [[webhooks.subscriptions]]
232
+ topics = ["app/uninstalled"]
233
+ uri = "/webhooks/app/uninstalled"
234
+
235
+ [webhooks.privacy_compliance]
236
+ customer_data_request_url = "/webhooks/gdpr/data-request"
237
+ customer_deletion_url = "/webhooks/gdpr/customer-deletion"
238
+ shop_deletion_url = "/webhooks/gdpr/shop-deletion"
239
+ """
240
+ config_path = project_dir / 'shopify.app.toml'
241
+ config_path.write_text(config_content)
242
+ print(f"✓ Created {config_path}")
243
+
244
+ def create_extension_config(self, project_dir: Path, extension_name: str, extension_type: str) -> None:
245
+ """
246
+ Create shopify.extension.toml configuration file.
247
+
248
+ Args:
249
+ project_dir: Project directory
250
+ extension_name: Extension name
251
+ extension_type: Extension type
252
+ """
253
+ target_map = {
254
+ 'checkout': 'purchase.checkout.block.render',
255
+ 'admin_action': 'admin.product-details.action.render',
256
+ 'admin_block': 'admin.product-details.block.render',
257
+ 'pos': 'pos.home.tile.render',
258
+ 'function': 'function',
259
+ 'customer_account': 'customer-account.order-status.block.render',
260
+ 'theme_app': 'theme-app-extension'
261
+ }
262
+
263
+ config_content = f"""name = "{extension_name}"
264
+ type = "ui_extension"
265
+ handle = "{extension_name.lower().replace(' ', '-')}"
266
+
267
+ [extension_points]
268
+ api_version = "2026-01"
269
+
270
+ [[extension_points.targets]]
271
+ target = "{target_map.get(extension_type, 'purchase.checkout.block.render')}"
272
+
273
+ [capabilities]
274
+ network_access = true
275
+ api_access = true
276
+ """
277
+ config_path = project_dir / 'shopify.extension.toml'
278
+ config_path.write_text(config_content)
279
+ print(f"✓ Created {config_path}")
280
+
281
+ def create_readme(self, project_dir: Path, project_type: str, project_name: str) -> None:
282
+ """
283
+ Create README.md file.
284
+
285
+ Args:
286
+ project_dir: Project directory
287
+ project_type: Project type (app/extension/theme)
288
+ project_name: Project name
289
+ """
290
+ content = f"""# {project_name}
291
+
292
+ Shopify {project_type.capitalize()} project.
293
+
294
+ ## Setup
295
+
296
+ ```bash
297
+ # Install dependencies
298
+ npm install
299
+
300
+ # Start development
301
+ shopify {project_type} dev
302
+ ```
303
+
304
+ ## Deployment
305
+
306
+ ```bash
307
+ # Deploy to Shopify
308
+ shopify {project_type} deploy
309
+ ```
310
+
311
+ ## Resources
312
+
313
+ - [Shopify Documentation](https://shopify.dev/docs)
314
+ - [Shopify CLI](https://shopify.dev/docs/api/shopify-cli)
315
+ """
316
+ readme_path = project_dir / 'README.md'
317
+ readme_path.write_text(content)
318
+ print(f"✓ Created {readme_path}")
319
+
320
+ def init_app(self) -> None:
321
+ """Initialize Shopify app project."""
322
+ print("\n=== Shopify App Initialization ===\n")
323
+
324
+ app_name = self.prompt("App name", "my-shopify-app")
325
+ scopes = self.prompt("Access scopes", self.config.scopes or "read_products,write_products")
326
+
327
+ project_dir = Path.cwd() / app_name
328
+ project_dir.mkdir(exist_ok=True)
329
+
330
+ print(f"\nCreating app in {project_dir}...")
331
+
332
+ self.create_app_config(project_dir, app_name, scopes)
333
+ self.create_readme(project_dir, "app", app_name)
334
+
335
+ # Create basic package.json
336
+ package_json = {
337
+ "name": app_name.lower().replace(' ', '-'),
338
+ "version": "1.0.0",
339
+ "scripts": {
340
+ "dev": "shopify app dev",
341
+ "deploy": "shopify app deploy"
342
+ }
343
+ }
344
+ (project_dir / 'package.json').write_text(json.dumps(package_json, indent=2))
345
+ print(f"✓ Created package.json")
346
+
347
+ print(f"\n✓ App '{app_name}' initialized successfully!")
348
+ print(f"\nNext steps:")
349
+ print(f" cd {app_name}")
350
+ print(f" npm install")
351
+ print(f" shopify app dev")
352
+
353
+ def init_extension(self) -> None:
354
+ """Initialize Shopify extension project."""
355
+ print("\n=== Shopify Extension Initialization ===\n")
356
+
357
+ extension_types = [
358
+ 'checkout',
359
+ 'admin_action',
360
+ 'admin_block',
361
+ 'pos',
362
+ 'function',
363
+ 'customer_account',
364
+ 'theme_app'
365
+ ]
366
+ extension_type = self.select_option("Select extension type", extension_types)
367
+
368
+ extension_name = self.prompt("Extension name", "my-extension")
369
+
370
+ project_dir = Path.cwd() / extension_name
371
+ project_dir.mkdir(exist_ok=True)
372
+
373
+ print(f"\nCreating extension in {project_dir}...")
374
+
375
+ self.create_extension_config(project_dir, extension_name, extension_type)
376
+ self.create_readme(project_dir, "extension", extension_name)
377
+
378
+ print(f"\n✓ Extension '{extension_name}' initialized successfully!")
379
+ print(f"\nNext steps:")
380
+ print(f" cd {extension_name}")
381
+ print(f" shopify app dev")
382
+
383
+ def init_theme(self) -> None:
384
+ """Initialize Shopify theme project."""
385
+ print("\n=== Shopify Theme Initialization ===\n")
386
+
387
+ theme_name = self.prompt("Theme name", "my-theme")
388
+
389
+ print(f"\nInitializing theme '{theme_name}'...")
390
+ print("\nRecommended: Use 'shopify theme init' for full theme scaffolding")
391
+ print(f"\nRun: shopify theme init {theme_name}")
392
+
393
+ def run(self) -> None:
394
+ """Run interactive initialization."""
395
+ print("=" * 60)
396
+ print("Shopify Project Initializer")
397
+ print("=" * 60)
398
+
399
+ # Check CLI
400
+ if not self.check_cli_installed():
401
+ print("\n⚠ Shopify CLI not found!")
402
+ print("Install: npm install -g @shopify/cli@latest")
403
+ sys.exit(1)
404
+
405
+ # Select project type
406
+ project_types = ['app', 'extension', 'theme']
407
+ project_type = self.select_option("Select project type", project_types)
408
+
409
+ # Initialize based on type
410
+ if project_type == 'app':
411
+ self.init_app()
412
+ elif project_type == 'extension':
413
+ self.init_extension()
414
+ elif project_type == 'theme':
415
+ self.init_theme()
416
+
417
+
418
+ def main() -> None:
419
+ """Main entry point."""
420
+ try:
421
+ # Get skill directory
422
+ script_dir = Path(__file__).parent
423
+ skill_dir = script_dir.parent
424
+
425
+ # Load configuration
426
+ config = EnvLoader.load_config(skill_dir)
427
+
428
+ # Initialize project
429
+ initializer = ShopifyInitializer(config)
430
+ initializer.run()
431
+
432
+ except KeyboardInterrupt:
433
+ print("\n\nAborted.")
434
+ sys.exit(0)
435
+ except Exception as e:
436
+ print(f"\n✗ Error: {e}", file=sys.stderr)
437
+ sys.exit(1)
438
+
439
+
440
+ if __name__ == '__main__':
441
+ main()