create-whop-kit 0.2.0 → 0.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 (2) hide show
  1. package/dist/cli-create.js +348 -67
  2. package/package.json +1 -1
@@ -11,9 +11,9 @@ import { runMain } from "citty";
11
11
 
12
12
  // src/commands/init.ts
13
13
  import { resolve, basename as basename2 } from "path";
14
- import { existsSync as existsSync4 } from "fs";
15
- import * as p2 from "@clack/prompts";
16
- import pc2 from "picocolors";
14
+ import { existsSync as existsSync5 } from "fs";
15
+ import * as p5 from "@clack/prompts";
16
+ import pc5 from "picocolors";
17
17
  import { defineCommand } from "citty";
18
18
 
19
19
  // src/templates.ts
@@ -88,22 +88,266 @@ var DB_OPTIONS = {
88
88
  }
89
89
  };
90
90
 
91
- // src/utils/checks.ts
91
+ // src/providers/neon.ts
92
92
  import * as p from "@clack/prompts";
93
93
  import pc from "picocolors";
94
+ var neonProvider = {
95
+ name: "Neon",
96
+ description: "Serverless Postgres \u2014 free tier, scales to zero",
97
+ isInstalled() {
98
+ return hasCommand("neonctl") || hasCommand("neon");
99
+ },
100
+ async install() {
101
+ const s = p.spinner();
102
+ s.start("Installing neonctl...");
103
+ const result = exec("npm install -g neonctl");
104
+ if (result.success) {
105
+ s.stop("neonctl installed");
106
+ return true;
107
+ }
108
+ s.stop("Failed to install neonctl");
109
+ p.log.error(`Install manually: ${pc.bold("npm install -g neonctl")}`);
110
+ return false;
111
+ },
112
+ async provision(projectName) {
113
+ const cli = hasCommand("neonctl") ? "neonctl" : "neon";
114
+ const whoami = exec(`${cli} me`);
115
+ if (!whoami.success) {
116
+ p.log.info("You need to authenticate with Neon.");
117
+ p.log.info(`Running ${pc.bold(`${cli} auth`)} \u2014 this will open your browser.`);
118
+ const authResult = exec(`${cli} auth`);
119
+ if (!authResult.success) {
120
+ p.log.error("Neon authentication failed. Try running manually:");
121
+ p.log.info(pc.bold(` ${cli} auth`));
122
+ return null;
123
+ }
124
+ }
125
+ const s = p.spinner();
126
+ s.start(`Creating Neon project "${projectName}"...`);
127
+ const createResult = exec(
128
+ `${cli} projects create --name "${projectName}" --set-context --output json`
129
+ );
130
+ if (!createResult.success) {
131
+ s.stop("Failed to create Neon project");
132
+ p.log.error("Try creating manually at https://console.neon.tech");
133
+ return null;
134
+ }
135
+ s.stop("Neon project created");
136
+ s.start("Getting connection string...");
137
+ const connResult = exec(`${cli} connection-string --prisma`);
138
+ if (!connResult.success) {
139
+ s.stop("Could not retrieve connection string");
140
+ p.log.error("Get it from: https://console.neon.tech");
141
+ return null;
142
+ }
143
+ s.stop("Connection string retrieved");
144
+ return {
145
+ connectionString: connResult.stdout,
146
+ provider: "neon"
147
+ };
148
+ }
149
+ };
150
+
151
+ // src/providers/supabase.ts
152
+ import * as p2 from "@clack/prompts";
153
+ import pc2 from "picocolors";
154
+ var supabaseProvider = {
155
+ name: "Supabase",
156
+ description: "Open-source Firebase alternative with Postgres",
157
+ isInstalled() {
158
+ return hasCommand("supabase");
159
+ },
160
+ async install() {
161
+ const s = p2.spinner();
162
+ s.start("Checking Supabase CLI...");
163
+ if (hasCommand("brew")) {
164
+ s.message("Installing via Homebrew...");
165
+ const result = exec("brew install supabase/tap/supabase");
166
+ if (result.success) {
167
+ s.stop("Supabase CLI installed");
168
+ return true;
169
+ }
170
+ }
171
+ s.stop("Supabase CLI not found");
172
+ p2.log.info("Install the Supabase CLI:");
173
+ p2.log.info(pc2.bold(" brew install supabase/tap/supabase"));
174
+ p2.log.info(pc2.dim(" or use npx supabase <command>"));
175
+ const useNpx = await p2.confirm({
176
+ message: "Continue with npx supabase? (slower but works without install)",
177
+ initialValue: true
178
+ });
179
+ if (p2.isCancel(useNpx) || !useNpx) return false;
180
+ return true;
181
+ },
182
+ async provision(projectName) {
183
+ const cli = hasCommand("supabase") ? "supabase" : "npx supabase";
184
+ const projectsList = exec(`${cli} projects list`);
185
+ if (!projectsList.success) {
186
+ p2.log.info("You need to authenticate with Supabase.");
187
+ p2.log.info(`Running ${pc2.bold(`${cli} login`)} \u2014 this will open your browser.`);
188
+ const authResult = exec(`${cli} login`);
189
+ if (!authResult.success) {
190
+ p2.log.error("Supabase authentication failed. Try:");
191
+ p2.log.info(pc2.bold(` ${cli} login`));
192
+ return null;
193
+ }
194
+ }
195
+ const orgsResult = exec(`${cli} orgs list`);
196
+ let orgId = "";
197
+ if (orgsResult.success && orgsResult.stdout) {
198
+ const lines = orgsResult.stdout.split("\n").filter((l) => l.trim());
199
+ if (lines.length > 1) {
200
+ const orgInput = await p2.text({
201
+ message: "Supabase Organization ID",
202
+ placeholder: "Find in dashboard: supabase.com/dashboard",
203
+ validate: (v) => !v ? "Required" : void 0
204
+ });
205
+ if (p2.isCancel(orgInput)) return null;
206
+ orgId = orgInput;
207
+ }
208
+ }
209
+ const password = Array.from(crypto.getRandomValues(new Uint8Array(16))).map((b) => b.toString(16).padStart(2, "0")).join("");
210
+ const s = p2.spinner();
211
+ s.start(`Creating Supabase project "${projectName}"...`);
212
+ const createCmd = orgId ? `${cli} projects create "${projectName}" --org-id "${orgId}" --db-password "${password}" --region us-east-1` : `${cli} projects create "${projectName}" --db-password "${password}" --region us-east-1`;
213
+ const createResult = exec(createCmd);
214
+ if (!createResult.success) {
215
+ s.stop("Failed to create Supabase project");
216
+ p2.log.error("Create manually at: https://supabase.com/dashboard");
217
+ return null;
218
+ }
219
+ s.stop("Supabase project created");
220
+ p2.log.info("");
221
+ p2.log.info(pc2.bold("Get your connection string:"));
222
+ p2.log.info(" 1. Go to https://supabase.com/dashboard");
223
+ p2.log.info(" 2. Select your new project");
224
+ p2.log.info(' 3. Click "Connect" \u2192 "Session pooler"');
225
+ p2.log.info(" 4. Copy the connection string");
226
+ p2.log.info("");
227
+ const connString = await p2.text({
228
+ message: "Paste your Supabase connection string",
229
+ placeholder: "postgresql://postgres.[ref]:[password]@...",
230
+ validate: (v) => {
231
+ if (!v) return "Required";
232
+ if (!v.startsWith("postgres")) return "Must be a PostgreSQL connection string";
233
+ }
234
+ });
235
+ if (p2.isCancel(connString)) return null;
236
+ return {
237
+ connectionString: connString,
238
+ provider: "supabase"
239
+ };
240
+ }
241
+ };
242
+
243
+ // src/providers/prisma-postgres.ts
244
+ import * as p3 from "@clack/prompts";
245
+ import pc3 from "picocolors";
246
+ import { readFileSync, existsSync } from "fs";
247
+ var prismaPostgresProvider = {
248
+ name: "Prisma Postgres",
249
+ description: "Instant Postgres \u2014 no auth needed, free tier",
250
+ isInstalled() {
251
+ return true;
252
+ },
253
+ async install() {
254
+ return true;
255
+ },
256
+ async provision(projectName) {
257
+ const region = await p3.select({
258
+ message: "Prisma Postgres region",
259
+ options: [
260
+ { value: "us-east-1", label: "US East (Virginia)", hint: "default" },
261
+ { value: "us-west-1", label: "US West (Oregon)" },
262
+ { value: "eu-central-1", label: "EU Central (Frankfurt)" },
263
+ { value: "eu-west-3", label: "EU West (Paris)" },
264
+ { value: "ap-southeast-1", label: "Asia Pacific (Singapore)" },
265
+ { value: "ap-northeast-1", label: "Asia Pacific (Tokyo)" }
266
+ ]
267
+ });
268
+ if (p3.isCancel(region)) return null;
269
+ const s = p3.spinner();
270
+ s.start("Creating Prisma Postgres database...");
271
+ const result = exec(`npx create-db@latest --region ${region} --json`);
272
+ if (!result.success) {
273
+ const fallback = exec(`npx -y create-db@latest --region ${region}`);
274
+ if (!fallback.success) {
275
+ s.stop("Failed to create database");
276
+ p3.log.error("Try manually: " + pc3.bold("npx create-db@latest"));
277
+ return null;
278
+ }
279
+ const match = fallback.stdout.match(/postgresql:\/\/[^\s"]+/);
280
+ if (match) {
281
+ s.stop("Prisma Postgres database created");
282
+ return {
283
+ connectionString: match[0],
284
+ provider: "prisma-postgres",
285
+ note: "This is a temporary database (24h). Claim it via the link in the output to make it permanent."
286
+ };
287
+ }
288
+ }
289
+ try {
290
+ const data = JSON.parse(result.stdout);
291
+ if (data.connectionString || data.url) {
292
+ s.stop("Prisma Postgres database created");
293
+ return {
294
+ connectionString: data.connectionString || data.url,
295
+ provider: "prisma-postgres",
296
+ note: "This is a temporary database (24h). Claim it to make it permanent."
297
+ };
298
+ }
299
+ } catch {
300
+ const match = result.stdout.match(/postgresql:\/\/[^\s"]+/);
301
+ if (match) {
302
+ s.stop("Prisma Postgres database created");
303
+ return {
304
+ connectionString: match[0],
305
+ provider: "prisma-postgres",
306
+ note: "This is a temporary database (24h). Claim it to make it permanent."
307
+ };
308
+ }
309
+ }
310
+ if (existsSync(".env")) {
311
+ const envContent = readFileSync(".env", "utf-8");
312
+ const match = envContent.match(/DATABASE_URL="?(postgresql:\/\/[^\s"]+)"?/);
313
+ if (match) {
314
+ s.stop("Prisma Postgres database created");
315
+ return {
316
+ connectionString: match[1],
317
+ provider: "prisma-postgres",
318
+ note: "This is a temporary database (24h). Claim it to make it permanent."
319
+ };
320
+ }
321
+ }
322
+ s.stop("Database created but could not extract connection string");
323
+ p3.log.warning("Check the output above for your DATABASE_URL, or visit https://console.prisma.io");
324
+ return null;
325
+ }
326
+ };
327
+
328
+ // src/providers/index.ts
329
+ var DB_PROVIDERS = {
330
+ neon: neonProvider,
331
+ supabase: supabaseProvider,
332
+ "prisma-postgres": prismaPostgresProvider
333
+ };
334
+
335
+ // src/utils/checks.ts
336
+ import * as p4 from "@clack/prompts";
337
+ import pc4 from "picocolors";
94
338
  function checkNodeVersion(minimum = 18) {
95
339
  const major = parseInt(process.versions.node.split(".")[0], 10);
96
340
  if (major < minimum) {
97
- p.log.error(
98
- `Node.js ${pc.bold(`v${minimum}+`)} is required. You have ${pc.bold(`v${process.versions.node}`)}.`
341
+ p4.log.error(
342
+ `Node.js ${pc4.bold(`v${minimum}+`)} is required. You have ${pc4.bold(`v${process.versions.node}`)}.`
99
343
  );
100
344
  process.exit(1);
101
345
  }
102
346
  }
103
347
  function checkGit() {
104
348
  if (!hasCommand("git")) {
105
- p.log.error(
106
- `${pc.bold("git")} is required but not found. Install it from ${pc.cyan("https://git-scm.com")}`
349
+ p4.log.error(
350
+ `${pc4.bold("git")} is required but not found. Install it from ${pc4.cyan("https://git-scm.com")}`
107
351
  );
108
352
  process.exit(1);
109
353
  }
@@ -122,9 +366,9 @@ function validateWhopAppId(id) {
122
366
  }
123
367
 
124
368
  // src/utils/cleanup.ts
125
- import { rmSync, existsSync } from "fs";
369
+ import { rmSync, existsSync as existsSync2 } from "fs";
126
370
  function cleanupDir(dir) {
127
- if (existsSync(dir)) {
371
+ if (existsSync2(dir)) {
128
372
  try {
129
373
  rmSync(dir, { recursive: true, force: true });
130
374
  } catch {
@@ -133,25 +377,25 @@ function cleanupDir(dir) {
133
377
  }
134
378
 
135
379
  // src/scaffolding/clone.ts
136
- import { existsSync as existsSync2, readFileSync, writeFileSync, rmSync as rmSync2 } from "fs";
380
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, rmSync as rmSync2 } from "fs";
137
381
  import { join, basename } from "path";
138
382
  function cloneTemplate(repo, projectDir) {
139
383
  const result = exec(
140
384
  `git clone --depth 1 https://github.com/${repo}.git "${projectDir}"`
141
385
  );
142
- if (!result.success || !existsSync2(projectDir)) {
386
+ if (!result.success || !existsSync3(projectDir)) {
143
387
  return false;
144
388
  }
145
389
  const gitDir = join(projectDir, ".git");
146
- if (existsSync2(gitDir)) {
390
+ if (existsSync3(gitDir)) {
147
391
  rmSync2(gitDir, { recursive: true, force: true });
148
392
  }
149
393
  return true;
150
394
  }
151
395
  function updatePackageName(projectDir, name) {
152
396
  const pkgPath = join(projectDir, "package.json");
153
- if (existsSync2(pkgPath)) {
154
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
397
+ if (existsSync3(pkgPath)) {
398
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
155
399
  pkg.name = basename(name);
156
400
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
157
401
  }
@@ -163,7 +407,7 @@ function initGit(projectDir) {
163
407
  }
164
408
 
165
409
  // src/scaffolding/env-file.ts
166
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
410
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
167
411
  import { join as join2 } from "path";
168
412
  function writeEnvFile(projectDir, values) {
169
413
  const examplePath = join2(projectDir, ".env.example");
@@ -171,8 +415,8 @@ function writeEnvFile(projectDir, values) {
171
415
  const filled = Object.fromEntries(
172
416
  Object.entries(values).filter(([, v]) => v)
173
417
  );
174
- if (existsSync3(examplePath)) {
175
- let content = readFileSync2(examplePath, "utf-8");
418
+ if (existsSync4(examplePath)) {
419
+ let content = readFileSync3(examplePath, "utf-8");
176
420
  for (const [key, value] of Object.entries(filled)) {
177
421
  const pattern = new RegExp(
178
422
  `^(#\\s*)?${escapeRegex(key)}=.*$`,
@@ -196,7 +440,7 @@ function escapeRegex(str) {
196
440
 
197
441
  // src/commands/init.ts
198
442
  function isCancelled(value) {
199
- return p2.isCancel(value);
443
+ return p5.isCancel(value);
200
444
  }
201
445
  var init_default = defineCommand({
202
446
  meta: {
@@ -260,94 +504,127 @@ var init_default = defineCommand({
260
504
  checkNodeVersion(18);
261
505
  checkGit();
262
506
  console.log("");
263
- p2.intro(`${pc2.bgCyan(pc2.black(" create-whop-kit "))} Create a Whop-powered app`);
507
+ p5.intro(`${pc5.bgCyan(pc5.black(" create-whop-kit "))} Create a Whop-powered app`);
264
508
  const isNonInteractive = !!(args.framework && args.db);
265
509
  let projectName = args.name;
266
510
  if (!projectName) {
267
- const result = await p2.text({
511
+ const result = await p5.text({
268
512
  message: "Project name",
269
513
  placeholder: "my-whop-app",
270
514
  validate: (v) => {
271
515
  if (!v) return "Project name is required";
272
- if (existsSync4(resolve(v))) return `Directory "${v}" already exists`;
516
+ if (existsSync5(resolve(v))) return `Directory "${v}" already exists`;
273
517
  }
274
518
  });
275
519
  if (isCancelled(result)) {
276
- p2.cancel("Cancelled.");
520
+ p5.cancel("Cancelled.");
277
521
  process.exit(0);
278
522
  }
279
523
  projectName = result;
280
- } else if (existsSync4(resolve(projectName))) {
281
- p2.log.error(`Directory "${projectName}" already exists`);
524
+ } else if (existsSync5(resolve(projectName))) {
525
+ p5.log.error(`Directory "${projectName}" already exists`);
282
526
  process.exit(1);
283
527
  }
284
528
  let appType = args.type;
285
529
  if (!isNonInteractive && !args.yes) {
286
- const result = await p2.select({
530
+ const result = await p5.select({
287
531
  message: "What are you building?",
288
532
  options: Object.entries(APP_TYPES).map(([value, t]) => ({
289
533
  value,
290
- label: t.available ? t.name : `${t.name} ${pc2.dim("(coming soon)")}`,
534
+ label: t.available ? t.name : `${t.name} ${pc5.dim("(coming soon)")}`,
291
535
  hint: t.description,
292
536
  disabled: !t.available
293
537
  }))
294
538
  });
295
539
  if (isCancelled(result)) {
296
- p2.cancel("Cancelled.");
540
+ p5.cancel("Cancelled.");
297
541
  process.exit(0);
298
542
  }
299
543
  appType = result;
300
544
  }
301
545
  let framework = args.framework;
302
546
  if (!framework) {
303
- const result = await p2.select({
547
+ const result = await p5.select({
304
548
  message: "Which framework?",
305
549
  options: Object.entries(TEMPLATES).map(([value, t]) => ({
306
550
  value,
307
- label: t.available ? t.name : `${t.name} ${pc2.dim("(coming soon)")}`,
551
+ label: t.available ? t.name : `${t.name} ${pc5.dim("(coming soon)")}`,
308
552
  hint: t.description,
309
553
  disabled: !t.available
310
554
  }))
311
555
  });
312
556
  if (isCancelled(result)) {
313
- p2.cancel("Cancelled.");
557
+ p5.cancel("Cancelled.");
314
558
  process.exit(0);
315
559
  }
316
560
  framework = result;
317
561
  }
318
562
  const template = TEMPLATES[framework];
319
563
  if (!template || !template.available) {
320
- p2.log.error(`Framework "${framework}" is not available. Options: ${Object.keys(TEMPLATES).filter((k) => TEMPLATES[k].available).join(", ")}`);
564
+ p5.log.error(`Framework "${framework}" is not available. Options: ${Object.keys(TEMPLATES).filter((k) => TEMPLATES[k].available).join(", ")}`);
321
565
  process.exit(1);
322
566
  }
323
567
  let database = args.db;
324
568
  if (!database) {
325
- const result = await p2.select({
569
+ const result = await p5.select({
326
570
  message: "Which database?",
327
- options: Object.entries(DB_OPTIONS).map(([value, d]) => ({
328
- value,
329
- label: d.name,
330
- hint: d.description
331
- }))
571
+ options: [
572
+ { value: "neon", label: "Neon", hint: "Serverless Postgres \u2014 auto-provisioned (recommended)" },
573
+ { value: "prisma-postgres", label: "Prisma Postgres", hint: "Instant database \u2014 no account needed" },
574
+ { value: "supabase", label: "Supabase", hint: "Open-source Firebase alternative" },
575
+ { value: "manual", label: "I have a connection string", hint: "Paste an existing PostgreSQL URL" },
576
+ { value: "later", label: "Configure later", hint: "Skip database setup for now" }
577
+ ]
332
578
  });
333
579
  if (isCancelled(result)) {
334
- p2.cancel("Cancelled.");
580
+ p5.cancel("Cancelled.");
335
581
  process.exit(0);
336
582
  }
337
583
  database = result;
338
584
  }
339
585
  let dbUrl = args["db-url"] ?? "";
340
- if (database !== "later" && !dbUrl) {
341
- const result = await p2.text({
586
+ let dbNote = "";
587
+ if (database !== "later" && database !== "manual" && !dbUrl) {
588
+ const provider = DB_PROVIDERS[database];
589
+ if (provider) {
590
+ if (!provider.isInstalled()) {
591
+ const install = await p5.confirm({
592
+ message: `${provider.name} CLI not found. Install it now?`,
593
+ initialValue: true
594
+ });
595
+ if (isCancelled(install) || !install) {
596
+ p5.log.warning("Skipping database provisioning. You can configure it later.");
597
+ database = "later";
598
+ } else {
599
+ const installed = await provider.install();
600
+ if (!installed) {
601
+ p5.log.warning("Skipping database provisioning.");
602
+ database = "later";
603
+ }
604
+ }
605
+ }
606
+ if (database !== "later") {
607
+ const result = await provider.provision(projectName);
608
+ if (result) {
609
+ dbUrl = result.connectionString;
610
+ if (result.note) dbNote = result.note;
611
+ p5.log.success(`${pc5.bold(provider.name)} database ready`);
612
+ } else {
613
+ p5.log.warning("Database provisioning skipped. Configure manually later.");
614
+ }
615
+ }
616
+ }
617
+ } else if (database === "manual" && !dbUrl) {
618
+ const result = await p5.text({
342
619
  message: "Database URL",
343
- placeholder: DB_OPTIONS[database]?.envVarHint ?? "postgresql://...",
620
+ placeholder: "postgresql://user:pass@host:5432/dbname",
344
621
  validate: (v) => {
345
622
  if (!v) return "Required (choose 'Configure later' to skip)";
346
623
  return validateDatabaseUrl(v);
347
624
  }
348
625
  });
349
626
  if (isCancelled(result)) {
350
- p2.cancel("Cancelled.");
627
+ p5.cancel("Cancelled.");
351
628
  process.exit(0);
352
629
  }
353
630
  dbUrl = result;
@@ -356,13 +633,13 @@ var init_default = defineCommand({
356
633
  let apiKey = args["api-key"] ?? "";
357
634
  let webhookSecret = args["webhook-secret"] ?? "";
358
635
  if (!isNonInteractive && !args.yes) {
359
- const setupWhop = await p2.confirm({
636
+ const setupWhop = await p5.confirm({
360
637
  message: "Configure Whop credentials now? (you can do this later via the setup wizard)",
361
638
  initialValue: false
362
639
  });
363
640
  if (!isCancelled(setupWhop) && setupWhop) {
364
641
  if (!appId) {
365
- const result = await p2.text({
642
+ const result = await p5.text({
366
643
  message: "Whop App ID",
367
644
  placeholder: "app_xxxxxxxxx",
368
645
  validate: (v) => v ? validateWhopAppId(v) : void 0
@@ -370,14 +647,14 @@ var init_default = defineCommand({
370
647
  if (!isCancelled(result)) appId = result ?? "";
371
648
  }
372
649
  if (!apiKey) {
373
- const result = await p2.text({
650
+ const result = await p5.text({
374
651
  message: "Whop API Key",
375
652
  placeholder: "apik_xxxxxxxxx (optional, press Enter to skip)"
376
653
  });
377
654
  if (!isCancelled(result)) apiKey = result ?? "";
378
655
  }
379
656
  if (!webhookSecret) {
380
- const result = await p2.text({
657
+ const result = await p5.text({
381
658
  message: "Whop Webhook Secret",
382
659
  placeholder: "optional, press Enter to skip"
383
660
  });
@@ -386,25 +663,25 @@ var init_default = defineCommand({
386
663
  }
387
664
  }
388
665
  if (args["dry-run"]) {
389
- p2.log.info(pc2.dim("Dry run \u2014 showing what would be created:\n"));
390
- console.log(` ${pc2.bold("Project:")} ${projectName}`);
391
- console.log(` ${pc2.bold("Framework:")} ${template.name}`);
392
- console.log(` ${pc2.bold("App type:")} ${APP_TYPES[appType]?.name ?? appType}`);
393
- console.log(` ${pc2.bold("Database:")} ${DB_OPTIONS[database]?.name ?? database}`);
394
- console.log(` ${pc2.bold("Template:")} github.com/${template.repo}`);
395
- if (dbUrl) console.log(` ${pc2.bold("DB URL:")} ${dbUrl.substring(0, 30)}...`);
396
- if (appId) console.log(` ${pc2.bold("Whop App:")} ${appId}`);
666
+ p5.log.info(pc5.dim("Dry run \u2014 showing what would be created:\n"));
667
+ console.log(` ${pc5.bold("Project:")} ${projectName}`);
668
+ console.log(` ${pc5.bold("Framework:")} ${template.name}`);
669
+ console.log(` ${pc5.bold("App type:")} ${APP_TYPES[appType]?.name ?? appType}`);
670
+ console.log(` ${pc5.bold("Database:")} ${DB_OPTIONS[database]?.name ?? database}`);
671
+ console.log(` ${pc5.bold("Template:")} github.com/${template.repo}`);
672
+ if (dbUrl) console.log(` ${pc5.bold("DB URL:")} ${dbUrl.substring(0, 30)}...`);
673
+ if (appId) console.log(` ${pc5.bold("Whop App:")} ${appId}`);
397
674
  console.log("");
398
- p2.outro("No files were created.");
675
+ p5.outro("No files were created.");
399
676
  return;
400
677
  }
401
678
  const projectDir = resolve(projectName);
402
- const s = p2.spinner();
679
+ const s = p5.spinner();
403
680
  s.start(`Cloning ${template.name} template...`);
404
681
  const cloned = cloneTemplate(template.repo, projectDir);
405
682
  if (!cloned) {
406
683
  s.stop("Failed to clone template");
407
- p2.log.error(`Could not clone github.com/${template.repo}. Check your internet connection.`);
684
+ p5.log.error(`Could not clone github.com/${template.repo}. Check your internet connection.`);
408
685
  cleanupDir(projectDir);
409
686
  process.exit(1);
410
687
  }
@@ -436,7 +713,7 @@ var init_default = defineCommand({
436
713
  const installResult = exec(`${pm} install`, projectDir);
437
714
  if (!installResult.success) {
438
715
  s.stop(`${pm} install failed`);
439
- p2.log.warning("Dependency installation failed. Run it manually after setup.");
716
+ p5.log.warning("Dependency installation failed. Run it manually after setup.");
440
717
  } else {
441
718
  s.stop("Dependencies installed");
442
719
  }
@@ -453,26 +730,30 @@ var init_default = defineCommand({
453
730
  else missing.push("Webhook Secret");
454
731
  let summary = "";
455
732
  if (configured.length > 0) {
456
- summary += `${pc2.green("\u2713")} ${configured.join(", ")}
733
+ summary += `${pc5.green("\u2713")} ${configured.join(", ")}
457
734
  `;
458
735
  }
459
736
  if (missing.length > 0) {
460
- summary += `${pc2.yellow("\u25CB")} Missing: ${missing.join(", ")}
737
+ summary += `${pc5.yellow("\u25CB")} Missing: ${missing.join(", ")}
738
+ `;
739
+ summary += ` ${pc5.dim("Configure via the setup wizard or .env.local")}
461
740
  `;
462
- summary += ` ${pc2.dim("Configure via the setup wizard or .env.local")}
741
+ }
742
+ if (dbNote) {
743
+ summary += `${pc5.yellow("!")} ${dbNote}
463
744
  `;
464
745
  }
465
746
  summary += `
466
747
  `;
467
- summary += ` ${pc2.bold("cd")} ${basename2(projectName)}
748
+ summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
468
749
  `;
469
750
  if (dbUrl) {
470
- summary += ` ${pc2.bold(`${pm} run db:push`)}
751
+ summary += ` ${pc5.bold(`${pm} run db:push`)}
471
752
  `;
472
753
  }
473
- summary += ` ${pc2.bold(`${pm} run dev`)}`;
474
- p2.note(summary, "Your app is ready");
475
- p2.outro(`${pc2.green("Happy building!")} ${pc2.dim("\u2014 whop-kit")}`);
754
+ summary += ` ${pc5.bold(`${pm} run dev`)}`;
755
+ p5.note(summary, "Your app is ready");
756
+ p5.outro(`${pc5.green("Happy building!")} ${pc5.dim("\u2014 whop-kit")}`);
476
757
  }
477
758
  });
478
759
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-whop-kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold and manage Whop-powered apps with whop-kit",
5
5
  "type": "module",
6
6
  "license": "MIT",