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.
- package/.agent/skills/shopify-apps/SKILL.md +47 -0
- package/.agent/skills/shopify-automation/SKILL.md +172 -0
- package/.agent/skills/shopify-development/README.md +60 -0
- package/.agent/skills/shopify-development/SKILL.md +368 -0
- package/.agent/skills/shopify-development/references/app-development.md +578 -0
- package/.agent/skills/shopify-development/references/extensions.md +555 -0
- package/.agent/skills/shopify-development/references/themes.md +498 -0
- package/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
- package/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
- package/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
- package/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
- package/bin/cli.js +3 -0
- package/package.json +32 -0
- package/src/index.js +116 -0
- package/templates/.agent/skills/shopify-apps/SKILL.md +47 -0
- package/templates/.agent/skills/shopify-automation/SKILL.md +172 -0
- package/templates/.agent/skills/shopify-development/README.md +60 -0
- package/templates/.agent/skills/shopify-development/SKILL.md +368 -0
- package/templates/.agent/skills/shopify-development/references/app-development.md +578 -0
- package/templates/.agent/skills/shopify-development/references/extensions.md +555 -0
- package/templates/.agent/skills/shopify-development/references/themes.md +498 -0
- package/templates/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
- package/templates/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
- package/templates/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
- package/templates/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
- package/templates/.devcontainer/devcontainer.json +27 -0
- package/templates/tests/playwright.config.ts +26 -0
- 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()
|