create-shopify-firebase-app 1.1.2 → 1.2.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/README.md +73 -31
- package/lib/index.js +283 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,25 @@ npx create-shopify-firebase-app my-app
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
## Table of Contents
|
|
22
|
+
|
|
23
|
+
- [What is this?](#what-is-this)
|
|
24
|
+
- [Quick Start](#quick-start)
|
|
25
|
+
- [Why Firebase?](#why-firebase)
|
|
26
|
+
- [What's Inside](#whats-inside)
|
|
27
|
+
- [Architecture](#architecture)
|
|
28
|
+
- [CLI Usage](#cli-usage)
|
|
29
|
+
- [Development](#development)
|
|
30
|
+
- [Extending Your App](#extending-your-app)
|
|
31
|
+
- [How Many Stores Can You Run for Free?](#how-many-stores-can-you-run-for-free)
|
|
32
|
+
- [GDPR Compliance](#gdpr-compliance)
|
|
33
|
+
- [Troubleshooting](#troubleshooting)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [Related](#related)
|
|
36
|
+
- [License](#license)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
21
40
|
## What is this?
|
|
22
41
|
|
|
23
42
|
The **Firebase alternative** to `shopify app init`. Instead of Remix + Prisma + Vercel, you get:
|
|
@@ -44,53 +63,74 @@ One `npx` command scaffolds everything, installs dependencies, wires up Firebase
|
|
|
44
63
|
| Firebase CLI | `npm i -g firebase-tools` | Yes, installed automatically if missing |
|
|
45
64
|
| Shopify CLI | `npm i -g @shopify/cli` | Yes, installed automatically if missing |
|
|
46
65
|
|
|
47
|
-
### 1.
|
|
48
|
-
|
|
49
|
-
Go to [partners.shopify.com](https://partners.shopify.com/) → **Apps** → **Create app** → **Create app manually**.
|
|
50
|
-
Copy the **Client ID** (API Key) and **Client Secret** (API Secret).
|
|
51
|
-
|
|
52
|
-
### 2. Create your Firebase project
|
|
53
|
-
|
|
54
|
-
Go to [console.firebase.google.com](https://console.firebase.google.com/) → **Add project**.
|
|
55
|
-
Enable **Cloud Firestore** (production mode). Note the **Project ID**.
|
|
56
|
-
|
|
57
|
-
### 3. Run the scaffold
|
|
66
|
+
### 1. Run the scaffold
|
|
58
67
|
|
|
59
68
|
```bash
|
|
60
69
|
npx create-shopify-firebase-app my-app
|
|
61
70
|
```
|
|
62
71
|
|
|
63
|
-
The interactive CLI
|
|
72
|
+
The interactive CLI guides you through everything — creating your Shopify app, setting up Firebase, and wiring it all together:
|
|
64
73
|
|
|
65
74
|
```
|
|
66
|
-
|
|
75
|
+
🛍️ + 🔥 create-shopify-firebase-app
|
|
76
|
+
Serverless Shopify apps — free until you scale
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
✔ App name: My App
|
|
70
|
-
✔ Shopify API Key: abc123...
|
|
71
|
-
✔ Shopify API Secret: ********
|
|
72
|
-
✔ API Scopes: read_products
|
|
73
|
-
✔ Firebase Project ID: my-app-12345
|
|
78
|
+
=== App Configuration ===
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
? Project directory name: my-app
|
|
81
|
+
? App name (shown in Shopify admin): My App
|
|
82
|
+
? What kind of app are you building?
|
|
83
|
+
❯ Public app — list on the Shopify App Store
|
|
84
|
+
Custom app — built for a single store
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
✔ Dependencies installed
|
|
86
|
+
=== Shopify Setup ===
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
? How would you like to connect your Shopify app?
|
|
89
|
+
❯ Create a new app — we'll guide you through it
|
|
90
|
+
I already have an app — enter my credentials
|
|
83
91
|
|
|
84
|
-
|
|
85
|
-
✔ Firebase project linked: my-app-12345
|
|
92
|
+
ℹ Opening Shopify Partner Dashboard...
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
✔ Shopify CLI detected
|
|
94
|
+
Follow these steps:
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
1. Sign in to your Partner account
|
|
97
|
+
2. Go to Apps → Create app → Create app manually
|
|
98
|
+
3. Enter app name: My App
|
|
99
|
+
4. Copy the Client ID and Client Secret
|
|
100
|
+
|
|
101
|
+
? Paste your Client ID (API Key): abc123...
|
|
102
|
+
? Paste your Client Secret: ********
|
|
103
|
+
? What API access does your app need?
|
|
104
|
+
❯ Read products read_products
|
|
105
|
+
Read + write products read_products,write_products
|
|
106
|
+
Orders + products read_products,write_products,read_orders,write_orders
|
|
107
|
+
Custom scopes — enter manually
|
|
108
|
+
|
|
109
|
+
=== Firebase Setup ===
|
|
110
|
+
|
|
111
|
+
ℹ Fetching your Firebase projects...
|
|
112
|
+
? Select a Firebase project
|
|
113
|
+
❯ [create a new project]
|
|
114
|
+
My Project (my-project-123)
|
|
115
|
+
Another Project (another-456)
|
|
116
|
+
[enter project ID manually]
|
|
117
|
+
|
|
118
|
+
=== Setting Up ===
|
|
119
|
+
|
|
120
|
+
ℹ Scaffolding project...
|
|
121
|
+
✔ Created 27 files in my-app/
|
|
122
|
+
ℹ Installing dependencies...
|
|
123
|
+
✔ Dependencies installed
|
|
124
|
+
ℹ Building TypeScript...
|
|
125
|
+
✔ TypeScript compiled successfully
|
|
126
|
+
ℹ Setting up Firebase...
|
|
127
|
+
✔ Firebase project linked + Firestore provisioned
|
|
128
|
+
ℹ Checking Shopify CLI...
|
|
129
|
+
✔ Shopify CLI detected
|
|
130
|
+
ℹ Initializing git...
|
|
91
131
|
✔ Git repository initialized with first commit
|
|
92
132
|
|
|
93
|
-
|
|
133
|
+
✔ All done! Your Shopify + Firebase app is ready.
|
|
94
134
|
|
|
95
135
|
Next steps:
|
|
96
136
|
|
|
@@ -98,6 +138,8 @@ The interactive CLI asks for your credentials and does the rest:
|
|
|
98
138
|
firebase deploy
|
|
99
139
|
```
|
|
100
140
|
|
|
141
|
+
No need to visit Firebase Console or Shopify Partner Dashboard beforehand — the CLI guides you through everything.
|
|
142
|
+
|
|
101
143
|
### 4. Deploy & Install
|
|
102
144
|
|
|
103
145
|
```bash
|
package/lib/index.js
CHANGED
|
@@ -37,10 +37,11 @@ const c = {
|
|
|
37
37
|
const ok = (msg) => console.log(` ${c.green}✔${c.reset} ${msg}`);
|
|
38
38
|
const warn = (msg) => console.log(` ${c.yellow}⚠${c.reset} ${msg}`);
|
|
39
39
|
const info = (msg) => console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
|
|
40
|
-
const
|
|
41
|
-
console.log(
|
|
42
|
-
|
|
43
|
-
);
|
|
40
|
+
const section = (title) => {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(` ${c.cyan}===${c.reset} ${c.bold}${title}${c.reset} ${c.cyan}===${c.reset}`);
|
|
43
|
+
console.log();
|
|
44
|
+
};
|
|
44
45
|
|
|
45
46
|
// ─── Check if a CLI tool is available ────────────────────────────────────
|
|
46
47
|
function hasCommand(cmd) {
|
|
@@ -95,6 +96,48 @@ function parseArgs(argv) {
|
|
|
95
96
|
return { projectName, ...args };
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
// ─── Open URL in browser (cross-platform) ───────────────────────────────
|
|
100
|
+
function openBrowser(url) {
|
|
101
|
+
const platform = process.platform;
|
|
102
|
+
try {
|
|
103
|
+
if (platform === "win32") execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
104
|
+
else if (platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
|
|
105
|
+
else execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
106
|
+
} catch {
|
|
107
|
+
info(`Open this URL in your browser: ${url}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── List Firebase projects ──────────────────────────────────────────────
|
|
112
|
+
function listFirebaseProjects() {
|
|
113
|
+
try {
|
|
114
|
+
const output = execSync("firebase projects:list --json", {
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
117
|
+
});
|
|
118
|
+
const data = JSON.parse(output);
|
|
119
|
+
if (data.status === "success" && Array.isArray(data.result)) {
|
|
120
|
+
return data.result
|
|
121
|
+
.filter((p) => p.projectId)
|
|
122
|
+
.map((p) => ({
|
|
123
|
+
projectId: p.projectId,
|
|
124
|
+
displayName: p.displayName || p.projectId,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Create Firebase project ─────────────────────────────────────────────
|
|
132
|
+
async function createFirebaseProject(projectId, displayName) {
|
|
133
|
+
try {
|
|
134
|
+
await exec(`firebase projects:create "${projectId}" --display-name "${displayName}"`);
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
// ─── Interactive prompts ─────────────────────────────────────────────────
|
|
99
142
|
async function getConfig(args) {
|
|
100
143
|
// Check if running non-interactively
|
|
@@ -113,16 +156,22 @@ async function getConfig(args) {
|
|
|
113
156
|
};
|
|
114
157
|
}
|
|
115
158
|
|
|
159
|
+
const onCancel = () => {
|
|
160
|
+
console.log("\n Cancelled.\n");
|
|
161
|
+
process.exit(0);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// ── Banner ────────────────────────────────────────────────────────
|
|
116
165
|
console.log();
|
|
117
|
-
console.log(
|
|
118
|
-
|
|
119
|
-
);
|
|
120
|
-
console.log();
|
|
166
|
+
console.log(` ${c.green}${c.bold}🛍️ + 🔥${c.reset} ${c.bold}create-shopify-firebase-app${c.reset}`);
|
|
167
|
+
console.log(` ${c.dim}Serverless Shopify apps — free until you scale${c.reset}`);
|
|
121
168
|
|
|
122
|
-
|
|
169
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
170
|
+
section("App Configuration");
|
|
123
171
|
|
|
124
|
-
|
|
125
|
-
|
|
172
|
+
let projectName = args.projectName;
|
|
173
|
+
if (!projectName) {
|
|
174
|
+
const res = await prompts({
|
|
126
175
|
type: "text",
|
|
127
176
|
name: "projectName",
|
|
128
177
|
message: "Project directory name",
|
|
@@ -132,58 +181,225 @@ async function getConfig(args) {
|
|
|
132
181
|
if (/[^a-zA-Z0-9._-]/.test(v)) return "Use only letters, numbers, dots, hyphens, underscores";
|
|
133
182
|
return true;
|
|
134
183
|
},
|
|
135
|
-
});
|
|
184
|
+
}, { onCancel });
|
|
185
|
+
projectName = res.projectName;
|
|
136
186
|
}
|
|
137
187
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
188
|
+
const { appName } = await prompts({
|
|
189
|
+
type: "text",
|
|
190
|
+
name: "appName",
|
|
191
|
+
message: "App name (shown in Shopify admin)",
|
|
192
|
+
initial: projectName || "My Shopify App",
|
|
193
|
+
}, { onCancel });
|
|
194
|
+
|
|
195
|
+
const { appType } = await prompts({
|
|
196
|
+
type: "select",
|
|
197
|
+
name: "appType",
|
|
198
|
+
message: "What kind of app are you building?",
|
|
199
|
+
choices: [
|
|
200
|
+
{ title: "Public app — list on the Shopify App Store", value: "public" },
|
|
201
|
+
{ title: "Custom app — built for a single store", value: "custom" },
|
|
202
|
+
],
|
|
203
|
+
}, { onCancel });
|
|
204
|
+
|
|
205
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
206
|
+
section("Shopify Setup");
|
|
207
|
+
|
|
208
|
+
const { shopifySetup } = await prompts({
|
|
209
|
+
type: "select",
|
|
210
|
+
name: "shopifySetup",
|
|
211
|
+
message: "How would you like to connect your Shopify app?",
|
|
212
|
+
choices: [
|
|
213
|
+
{ title: "Create a new app — we'll guide you through it", value: "create" },
|
|
214
|
+
{ title: "I already have an app — enter my credentials", value: "existing" },
|
|
215
|
+
],
|
|
216
|
+
}, { onCancel });
|
|
217
|
+
|
|
218
|
+
let apiKey, apiSecret;
|
|
219
|
+
|
|
220
|
+
if (shopifySetup === "create") {
|
|
221
|
+
console.log();
|
|
222
|
+
info("Opening Shopify Partner Dashboard...");
|
|
223
|
+
console.log();
|
|
224
|
+
console.log(` ${c.dim}Follow these steps:${c.reset}`);
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(` ${c.cyan}1.${c.reset} Sign in to your Partner account`);
|
|
227
|
+
console.log(` ${c.cyan}2.${c.reset} Go to ${c.bold}Apps${c.reset} → ${c.bold}Create app${c.reset} → ${c.bold}Create app manually${c.reset}`);
|
|
228
|
+
console.log(` ${c.cyan}3.${c.reset} Enter app name: ${c.cyan}${appName}${c.reset}`);
|
|
229
|
+
console.log(` ${c.cyan}4.${c.reset} Copy the ${c.bold}Client ID${c.reset} and ${c.bold}Client Secret${c.reset}`);
|
|
230
|
+
console.log();
|
|
231
|
+
openBrowser("https://partners.shopify.com");
|
|
232
|
+
|
|
233
|
+
const creds = await prompts([
|
|
234
|
+
{
|
|
235
|
+
type: "text",
|
|
236
|
+
name: "apiKey",
|
|
237
|
+
message: `Paste your Client ID (API Key)`,
|
|
238
|
+
validate: (v) => (v.trim() ? true : "Required — copy from the app you just created"),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
type: "password",
|
|
242
|
+
name: "apiSecret",
|
|
243
|
+
message: "Paste your Client Secret (API Secret)",
|
|
244
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
245
|
+
},
|
|
246
|
+
], { onCancel });
|
|
247
|
+
apiKey = creds.apiKey;
|
|
248
|
+
apiSecret = creds.apiSecret;
|
|
249
|
+
} else {
|
|
250
|
+
const creds = await prompts([
|
|
251
|
+
{
|
|
252
|
+
type: "text",
|
|
253
|
+
name: "apiKey",
|
|
254
|
+
message: `Shopify API Key ${c.dim}(Client ID)${c.reset}`,
|
|
255
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: "password",
|
|
259
|
+
name: "apiSecret",
|
|
260
|
+
message: "Shopify API Secret",
|
|
261
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
262
|
+
},
|
|
263
|
+
], { onCancel });
|
|
264
|
+
apiKey = creds.apiKey;
|
|
265
|
+
apiSecret = creds.apiSecret;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Scope presets (like Shopify CLI template selection)
|
|
269
|
+
const { scopeChoice } = await prompts({
|
|
270
|
+
type: "select",
|
|
271
|
+
name: "scopeChoice",
|
|
272
|
+
message: "What API access does your app need?",
|
|
273
|
+
choices: [
|
|
274
|
+
{ title: `Read products ${c.dim}read_products${c.reset}`, value: "read_products" },
|
|
275
|
+
{ title: `Read + write products ${c.dim}read_products,write_products${c.reset}`, value: "read_products,write_products" },
|
|
276
|
+
{ title: `Orders + products ${c.dim}read_products,write_products,read_orders,write_orders${c.reset}`, value: "read_products,write_products,read_orders,write_orders" },
|
|
277
|
+
{ title: "Custom scopes — enter manually", value: "__custom__" },
|
|
278
|
+
],
|
|
279
|
+
}, { onCancel });
|
|
280
|
+
|
|
281
|
+
let scopes;
|
|
282
|
+
if (scopeChoice === "__custom__") {
|
|
283
|
+
const res = await prompts({
|
|
158
284
|
type: "text",
|
|
159
285
|
name: "scopes",
|
|
160
|
-
message: "
|
|
286
|
+
message: "Enter scopes (comma-separated)",
|
|
161
287
|
initial: "read_products",
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const onCancel = () => {
|
|
172
|
-
console.log("\n Cancelled.\n");
|
|
173
|
-
process.exit(0);
|
|
174
|
-
};
|
|
288
|
+
validate: (v) => (v.trim() ? true : "At least one scope is required"),
|
|
289
|
+
}, { onCancel });
|
|
290
|
+
scopes = res.scopes;
|
|
291
|
+
} else {
|
|
292
|
+
scopes = scopeChoice;
|
|
293
|
+
}
|
|
175
294
|
|
|
176
|
-
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
296
|
+
section("Firebase Setup");
|
|
297
|
+
|
|
298
|
+
let projectId;
|
|
299
|
+
const hasFirebase = hasCommand("firebase");
|
|
300
|
+
|
|
301
|
+
if (hasFirebase) {
|
|
302
|
+
info("Fetching your Firebase projects...");
|
|
303
|
+
|
|
304
|
+
const choices = [
|
|
305
|
+
{ title: `${c.cyan}[create a new project]${c.reset}`, value: "__create__" },
|
|
306
|
+
];
|
|
307
|
+
const projects = listFirebaseProjects();
|
|
308
|
+
if (projects.length > 0) {
|
|
309
|
+
for (const p of projects) {
|
|
310
|
+
choices.push({
|
|
311
|
+
title: `${p.displayName} ${c.dim}(${p.projectId})${c.reset}`,
|
|
312
|
+
value: p.projectId,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
choices.push({ title: `${c.dim}[enter project ID manually]${c.reset}`, value: "__manual__" });
|
|
317
|
+
|
|
318
|
+
const { firebaseChoice } = await prompts({
|
|
319
|
+
type: "select",
|
|
320
|
+
name: "firebaseChoice",
|
|
321
|
+
message: "Select a Firebase project",
|
|
322
|
+
choices,
|
|
323
|
+
}, { onCancel });
|
|
324
|
+
|
|
325
|
+
if (firebaseChoice === "__create__") {
|
|
326
|
+
const { newProjectId } = await prompts({
|
|
327
|
+
type: "text",
|
|
328
|
+
name: "newProjectId",
|
|
329
|
+
message: "New project ID",
|
|
330
|
+
initial: projectName,
|
|
331
|
+
validate: (v) => {
|
|
332
|
+
if (!v.trim()) return "Required";
|
|
333
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(v)) return "Only lowercase letters, numbers, and hyphens";
|
|
334
|
+
if (v.length < 6 || v.length > 30) return "Must be 6-30 characters";
|
|
335
|
+
return true;
|
|
336
|
+
},
|
|
337
|
+
}, { onCancel });
|
|
338
|
+
|
|
339
|
+
info(`Creating Firebase project: ${c.cyan}${newProjectId}${c.reset}...`);
|
|
340
|
+
const created = await createFirebaseProject(newProjectId, appName);
|
|
341
|
+
if (created) {
|
|
342
|
+
ok(`Project created: ${c.cyan}${newProjectId}${c.reset}`);
|
|
343
|
+
projectId = newProjectId;
|
|
344
|
+
} else {
|
|
345
|
+
warn("Could not create project automatically");
|
|
346
|
+
info("Create one at https://console.firebase.google.com");
|
|
347
|
+
const { manualId } = await prompts({
|
|
348
|
+
type: "text",
|
|
349
|
+
name: "manualId",
|
|
350
|
+
message: "Firebase Project ID",
|
|
351
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
352
|
+
}, { onCancel });
|
|
353
|
+
projectId = manualId;
|
|
354
|
+
}
|
|
355
|
+
} else if (firebaseChoice === "__manual__") {
|
|
356
|
+
const { manualId } = await prompts({
|
|
357
|
+
type: "text",
|
|
358
|
+
name: "manualId",
|
|
359
|
+
message: "Firebase Project ID",
|
|
360
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
361
|
+
}, { onCancel });
|
|
362
|
+
projectId = manualId;
|
|
363
|
+
} else {
|
|
364
|
+
projectId = firebaseChoice;
|
|
365
|
+
ok(`Using project: ${c.cyan}${projectId}${c.reset}`);
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
// No Firebase CLI — manual setup
|
|
369
|
+
const { firebaseSetup } = await prompts({
|
|
370
|
+
type: "select",
|
|
371
|
+
name: "firebaseSetup",
|
|
372
|
+
message: "How would you like to set up Firebase?",
|
|
373
|
+
choices: [
|
|
374
|
+
{ title: "Create a new project — opens Firebase Console", value: "create" },
|
|
375
|
+
{ title: "Enter project ID manually", value: "manual" },
|
|
376
|
+
],
|
|
377
|
+
}, { onCancel });
|
|
378
|
+
|
|
379
|
+
if (firebaseSetup === "create") {
|
|
380
|
+
console.log();
|
|
381
|
+
info("Opening Firebase Console...");
|
|
382
|
+
info("Create a new project and note the Project ID");
|
|
383
|
+
console.log();
|
|
384
|
+
openBrowser("https://console.firebase.google.com");
|
|
385
|
+
}
|
|
177
386
|
|
|
178
|
-
|
|
179
|
-
|
|
387
|
+
const { manualId } = await prompts({
|
|
388
|
+
type: "text",
|
|
389
|
+
name: "manualId",
|
|
390
|
+
message: "Firebase Project ID",
|
|
391
|
+
validate: (v) => (v.trim() ? true : "Required"),
|
|
392
|
+
}, { onCancel });
|
|
393
|
+
projectId = manualId;
|
|
394
|
+
}
|
|
180
395
|
|
|
181
396
|
return {
|
|
182
397
|
projectName,
|
|
183
|
-
appName:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
398
|
+
appName: appName || projectName,
|
|
399
|
+
appType,
|
|
400
|
+
apiKey,
|
|
401
|
+
apiSecret,
|
|
402
|
+
scopes: scopes || "read_products",
|
|
187
403
|
projectId,
|
|
188
404
|
appUrl: `https://${projectId}.web.app`,
|
|
189
405
|
};
|
|
@@ -326,15 +542,14 @@ export async function run(argv) {
|
|
|
326
542
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
327
543
|
}
|
|
328
544
|
|
|
329
|
-
|
|
545
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
546
|
+
section("Setting Up");
|
|
330
547
|
|
|
331
|
-
|
|
332
|
-
step(1, totalSteps, "Scaffolding project...");
|
|
548
|
+
info("Scaffolding project...");
|
|
333
549
|
const fileCount = scaffold(outputDir, config);
|
|
334
550
|
ok(`Created ${fileCount} files in ${c.cyan}${config.projectName}/${c.reset}`);
|
|
335
551
|
|
|
336
|
-
|
|
337
|
-
step(2, totalSteps, "Installing dependencies...");
|
|
552
|
+
info("Installing dependencies...");
|
|
338
553
|
const functionsDir = path.join(outputDir, "functions");
|
|
339
554
|
try {
|
|
340
555
|
await exec("npm install", functionsDir);
|
|
@@ -343,8 +558,7 @@ export async function run(argv) {
|
|
|
343
558
|
warn(`npm install failed — run manually: cd ${config.projectName}/functions && npm install`);
|
|
344
559
|
}
|
|
345
560
|
|
|
346
|
-
|
|
347
|
-
step(3, totalSteps, "Building TypeScript...");
|
|
561
|
+
info("Building TypeScript...");
|
|
348
562
|
try {
|
|
349
563
|
await exec("npm run build", functionsDir);
|
|
350
564
|
ok("TypeScript compiled successfully");
|
|
@@ -352,8 +566,7 @@ export async function run(argv) {
|
|
|
352
566
|
warn("Build failed — run manually: cd functions && npm run build");
|
|
353
567
|
}
|
|
354
568
|
|
|
355
|
-
|
|
356
|
-
step(4, totalSteps, "Setting up Firebase...");
|
|
569
|
+
info("Setting up Firebase...");
|
|
357
570
|
if (!hasCommand("firebase")) {
|
|
358
571
|
info("Firebase CLI not found — installing globally...");
|
|
359
572
|
try {
|
|
@@ -375,10 +588,9 @@ export async function run(argv) {
|
|
|
375
588
|
});
|
|
376
589
|
}
|
|
377
590
|
|
|
378
|
-
|
|
379
|
-
step(5, totalSteps, "Checking Shopify CLI...");
|
|
591
|
+
info("Checking Shopify CLI...");
|
|
380
592
|
if (hasCommand("shopify")) {
|
|
381
|
-
ok("Shopify CLI detected
|
|
593
|
+
ok("Shopify CLI detected");
|
|
382
594
|
} else {
|
|
383
595
|
info("Shopify CLI not found — installing globally...");
|
|
384
596
|
try {
|
|
@@ -387,12 +599,10 @@ export async function run(argv) {
|
|
|
387
599
|
} catch (e) {
|
|
388
600
|
warn("Could not install Shopify CLI automatically");
|
|
389
601
|
info("Install manually: npm i -g @shopify/cli");
|
|
390
|
-
info("Optional — you can also develop with Firebase emulators");
|
|
391
602
|
}
|
|
392
603
|
}
|
|
393
604
|
|
|
394
|
-
|
|
395
|
-
step(6, totalSteps, "Initializing git...");
|
|
605
|
+
info("Initializing git...");
|
|
396
606
|
if (hasCommand("git")) {
|
|
397
607
|
try {
|
|
398
608
|
await exec("git init", outputDir);
|
|
@@ -403,43 +613,35 @@ export async function run(argv) {
|
|
|
403
613
|
warn("Git init failed — initialize manually if needed");
|
|
404
614
|
}
|
|
405
615
|
} else {
|
|
406
|
-
warn("Git not found — skipping
|
|
616
|
+
warn("Git not found — skipping");
|
|
407
617
|
}
|
|
408
618
|
|
|
409
|
-
// ── Done! ─────────────────────────────────────────────────────────
|
|
410
619
|
printSuccess(config);
|
|
411
620
|
}
|
|
412
621
|
|
|
413
622
|
// ─── Success output ──────────────────────────────────────────────────────
|
|
414
623
|
function printSuccess(config) {
|
|
415
624
|
console.log();
|
|
416
|
-
console.log(
|
|
417
|
-
` ${c.bgGreen}${c.white}${c.bold} SUCCESS ${c.reset} Your Shopify + Firebase app is ready!`,
|
|
418
|
-
);
|
|
625
|
+
console.log(` ${c.green}${c.bold}✔ All done!${c.reset} Your Shopify + Firebase app is ready.`);
|
|
419
626
|
console.log();
|
|
420
627
|
console.log(` ${c.bold}Next steps:${c.reset}`);
|
|
421
628
|
console.log();
|
|
422
629
|
console.log(` ${c.cyan}cd ${config.projectName}${c.reset}`);
|
|
423
630
|
console.log(` ${c.cyan}firebase deploy${c.reset}`);
|
|
424
631
|
console.log();
|
|
425
|
-
console.log(` ${c.bold}
|
|
632
|
+
console.log(` ${c.bold}Install on your dev store:${c.reset}`);
|
|
426
633
|
console.log();
|
|
427
|
-
console.log(
|
|
428
|
-
` ${c.cyan}${config.appUrl}/auth?shop=YOUR-STORE.myshopify.com${c.reset}`,
|
|
429
|
-
);
|
|
634
|
+
console.log(` ${c.cyan}${config.appUrl}/auth?shop=YOUR-STORE.myshopify.com${c.reset}`);
|
|
430
635
|
console.log();
|
|
431
|
-
console.log(` ${c.bold}Or
|
|
636
|
+
console.log(` ${c.bold}Or develop locally:${c.reset}`);
|
|
432
637
|
console.log();
|
|
433
638
|
console.log(` ${c.cyan}shopify app dev${c.reset}`);
|
|
434
639
|
console.log();
|
|
435
640
|
console.log(` ${c.dim}─────────────────────────────────────────${c.reset}`);
|
|
436
|
-
console.log();
|
|
437
641
|
console.log(` ${c.dim}App URL: ${config.appUrl}${c.reset}`);
|
|
438
642
|
console.log(` ${c.dim}Firebase: ${config.projectId}${c.reset}`);
|
|
439
643
|
console.log(` ${c.dim}Scopes: ${config.scopes}${c.reset}`);
|
|
440
644
|
console.log();
|
|
441
|
-
console.log(` ${c.dim}Docs: https://github.com/mksd0398/create-shopify-firebase-app${c.reset}`);
|
|
442
|
-
console.log();
|
|
443
645
|
}
|
|
444
646
|
|
|
445
647
|
// ─── Help output ─────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-shopify-firebase-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Create Shopify apps powered by Firebase — serverless, lightweight, zero-framework. The official alternative to Remix for Shopify + Firebase developers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shopify",
|