create-shopify-firebase-app 1.2.0 → 1.3.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 (3) hide show
  1. package/README.md +9 -13
  2. package/lib/index.js +120 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -86,19 +86,15 @@ The interactive CLI guides you through everything — creating your Shopify app,
86
86
  === Shopify Setup ===
87
87
 
88
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
91
-
92
- ℹ Opening Shopify Partner Dashboard...
93
-
94
- Follow these steps:
95
-
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...
89
+ ❯ Create a new app via Shopify CLI
90
+ Link an existing app via Shopify CLI
91
+ Enter credentials manually (Client ID + Secret)
92
+
93
+ ℹ Logging into Shopify CLI...
94
+ Logged into Shopify
95
+ ℹ Creating a new Shopify app...
96
+ (Shopify CLI handles app creation interactively)
97
+ Client ID: abc123...
102
98
  ? Paste your Client Secret: ********
103
99
  ? What API access does your app need?
104
100
  ❯ Read products read_products
package/lib/index.js CHANGED
@@ -128,6 +128,47 @@ function listFirebaseProjects() {
128
128
  return [];
129
129
  }
130
130
 
131
+ // ─── Run an interactive command (user sees prompts) ─────────────────────
132
+ function execInteractive(cmd, cwd) {
133
+ return new Promise((resolve, reject) => {
134
+ const child = spawn(cmd, {
135
+ cwd,
136
+ shell: true,
137
+ stdio: "inherit",
138
+ });
139
+ child.on("close", (code) => {
140
+ if (code === 0) resolve();
141
+ else reject(new Error(`Command exited with code ${code}`));
142
+ });
143
+ });
144
+ }
145
+
146
+ // ─── Check if logged into Shopify CLI ───────────────────────────────────
147
+ function isShopifyLoggedIn() {
148
+ try {
149
+ // `shopify app config link --help` doesn't need auth,
150
+ // but `shopify app info` does — use a quick check
151
+ const out = execSync("shopify auth login --help", {
152
+ encoding: "utf8",
153
+ stdio: ["ignore", "pipe", "ignore"],
154
+ });
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ // ─── Parse TOML file for client_id ──────────────────────────────────────
162
+ function parseTomlClientId(tomlPath) {
163
+ try {
164
+ const content = fs.readFileSync(tomlPath, "utf8");
165
+ const match = content.match(/client_id\s*=\s*"([^"]+)"/);
166
+ return match ? match[1] : null;
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+
131
172
  // ─── Create Firebase project ─────────────────────────────────────────────
132
173
  async function createFirebaseProject(projectId, displayName) {
133
174
  try {
@@ -205,48 +246,97 @@ async function getConfig(args) {
205
246
  // ═══════════════════════════════════════════════════════════════════
206
247
  section("Shopify Setup");
207
248
 
249
+ const hasShopifyCli = hasCommand("shopify");
250
+
251
+ // Build choices based on what's available
252
+ const shopifyChoices = [];
253
+ if (hasShopifyCli) {
254
+ shopifyChoices.push(
255
+ { title: "Create a new app via Shopify CLI", value: "cli-create" },
256
+ { title: "Link an existing app via Shopify CLI", value: "cli-link" },
257
+ );
258
+ }
259
+ shopifyChoices.push(
260
+ { title: `Enter credentials manually ${c.dim}(Client ID + Secret)${c.reset}`, value: "manual" },
261
+ );
262
+
208
263
  const { shopifySetup } = await prompts({
209
264
  type: "select",
210
265
  name: "shopifySetup",
211
266
  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
- ],
267
+ choices: shopifyChoices,
216
268
  }, { onCancel });
217
269
 
218
270
  let apiKey, apiSecret;
219
271
 
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");
272
+ if (shopifySetup === "cli-create" || shopifySetup === "cli-link") {
273
+ // ── Use Shopify CLI to create/link the app ────────────────────
274
+ // We need a temp directory with a shopify.app.toml for the CLI to work
275
+ const tmpDir = path.resolve(process.cwd(), `__shopify_setup_${Date.now()}`);
276
+ fs.mkdirSync(tmpDir, { recursive: true });
232
277
 
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
- {
278
+ try {
279
+ // Ensure logged in
280
+ console.log();
281
+ info("Logging into Shopify CLI...");
282
+ info("A browser window will open sign in to your Partner account.");
283
+ console.log();
284
+ await execInteractive("shopify auth login", tmpDir);
285
+ ok("Logged into Shopify");
286
+
287
+ // Run config link — it handles both create and link interactively
288
+ console.log();
289
+ if (shopifySetup === "cli-create") {
290
+ info("Creating a new Shopify app...");
291
+ info(`Select ${c.bold}"Create a new app"${c.reset} when prompted.`);
292
+ } else {
293
+ info("Linking an existing Shopify app...");
294
+ info("Select your app from the list when prompted.");
295
+ }
296
+ console.log();
297
+ await execInteractive("shopify app config link", tmpDir);
298
+
299
+ // Parse the generated TOML to get client_id
300
+ const tomlFiles = fs.readdirSync(tmpDir).filter((f) => f.endsWith(".toml"));
301
+ let parsedKey = null;
302
+ for (const f of tomlFiles) {
303
+ parsedKey = parseTomlClientId(path.join(tmpDir, f));
304
+ if (parsedKey) break;
305
+ }
306
+
307
+ if (parsedKey) {
308
+ apiKey = parsedKey;
309
+ ok(`Client ID: ${c.cyan}${apiKey}${c.reset}`);
310
+ } else {
311
+ warn("Could not read Client ID from TOML");
312
+ const res = await prompts({
313
+ type: "text",
314
+ name: "apiKey",
315
+ message: "Paste your Client ID (API Key)",
316
+ validate: (v) => (v.trim() ? true : "Required"),
317
+ }, { onCancel });
318
+ apiKey = res.apiKey;
319
+ }
320
+
321
+ // API Secret is never in the TOML — always need to ask
322
+ console.log();
323
+ info("The API Secret is not stored in config files for security.");
324
+ info(`Find it at: ${c.cyan}https://partners.shopify.com${c.reset} → Apps → your app → Client credentials`);
325
+ console.log();
326
+ const { secret } = await prompts({
241
327
  type: "password",
242
- name: "apiSecret",
328
+ name: "secret",
243
329
  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;
330
+ validate: (v) => (v.trim() ? true : "Required — find it in Partner Dashboard → Apps → Client credentials"),
331
+ }, { onCancel });
332
+ apiSecret = secret;
333
+
334
+ } finally {
335
+ // Clean up temp directory
336
+ fs.rmSync(tmpDir, { recursive: true, force: true });
337
+ }
249
338
  } else {
339
+ // ── Manual entry ──────────────────────────────────────────────
250
340
  const creds = await prompts([
251
341
  {
252
342
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-shopify-firebase-app",
3
- "version": "1.2.0",
3
+ "version": "1.3.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",