create-whop-kit 0.1.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.
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addFeatureToManifest,
4
+ detectPackageManager,
5
+ exec,
6
+ readManifest
7
+ } from "./chunk-M4AXERQP.js";
8
+
9
+ // src/cli-kit.ts
10
+ import { defineCommand as defineCommand5, runMain } from "citty";
11
+
12
+ // src/commands/add.ts
13
+ import * as p4 from "@clack/prompts";
14
+ import pc2 from "picocolors";
15
+ import { defineCommand } from "citty";
16
+
17
+ // src/features/email.ts
18
+ import * as p from "@clack/prompts";
19
+
20
+ // src/features/helpers.ts
21
+ import { readFileSync, writeFileSync, existsSync } from "fs";
22
+ import { join } from "path";
23
+ function appendEnvVar(projectDir, key, value) {
24
+ const envPath = join(projectDir, ".env.local");
25
+ if (!existsSync(envPath)) {
26
+ writeFileSync(envPath, `${key}="${value}"
27
+ `);
28
+ return;
29
+ }
30
+ let content = readFileSync(envPath, "utf-8");
31
+ const pattern = new RegExp(`^(#\\s*)?${key}=.*$`, "m");
32
+ if (pattern.test(content)) {
33
+ content = content.replace(pattern, `${key}="${value}"`);
34
+ } else {
35
+ content = content.trimEnd() + `
36
+ ${key}="${value}"
37
+ `;
38
+ }
39
+ writeFileSync(envPath, content);
40
+ }
41
+
42
+ // src/features/email.ts
43
+ var emailFeature = {
44
+ name: "Email",
45
+ description: "Transactional email via Resend or SendGrid",
46
+ configKey: "email",
47
+ async run(projectDir) {
48
+ const provider = await p.select({
49
+ message: "Email provider",
50
+ options: [
51
+ { value: "resend", label: "Resend", hint: "Modern email API" },
52
+ { value: "sendgrid", label: "SendGrid", hint: "Established platform" }
53
+ ]
54
+ });
55
+ if (p.isCancel(provider)) {
56
+ p.cancel("Cancelled.");
57
+ process.exit(0);
58
+ }
59
+ const apiKey = await p.text({
60
+ message: `${provider === "resend" ? "Resend" : "SendGrid"} API key`,
61
+ placeholder: provider === "resend" ? "re_xxxxxxxxx" : "SG.xxxxxxxxx",
62
+ validate: (v) => !v ? "API key is required" : void 0
63
+ });
64
+ if (p.isCancel(apiKey)) {
65
+ p.cancel("Cancelled.");
66
+ process.exit(0);
67
+ }
68
+ const fromAddress = await p.text({
69
+ message: "From email address",
70
+ placeholder: "noreply@yourdomain.com"
71
+ });
72
+ if (p.isCancel(fromAddress)) {
73
+ p.cancel("Cancelled.");
74
+ process.exit(0);
75
+ }
76
+ appendEnvVar(projectDir, "EMAIL_PROVIDER", provider);
77
+ appendEnvVar(projectDir, "EMAIL_API_KEY", apiKey);
78
+ if (fromAddress) {
79
+ appendEnvVar(projectDir, "EMAIL_FROM_ADDRESS", fromAddress);
80
+ }
81
+ }
82
+ };
83
+
84
+ // src/features/analytics.ts
85
+ import * as p2 from "@clack/prompts";
86
+ var analyticsFeature = {
87
+ name: "Analytics",
88
+ description: "Product analytics via PostHog, Google Analytics, or Plausible",
89
+ configKey: "analytics",
90
+ async run(projectDir) {
91
+ const provider = await p2.select({
92
+ message: "Analytics provider",
93
+ options: [
94
+ { value: "posthog", label: "PostHog", hint: "Open-source product analytics" },
95
+ { value: "google", label: "Google Analytics", hint: "GA4" },
96
+ { value: "plausible", label: "Plausible", hint: "Privacy-friendly analytics" }
97
+ ]
98
+ });
99
+ if (p2.isCancel(provider)) {
100
+ p2.cancel("Cancelled.");
101
+ process.exit(0);
102
+ }
103
+ const placeholders = {
104
+ posthog: "phc_xxxxxxxxx",
105
+ google: "G-XXXXXXXXXX",
106
+ plausible: "yourdomain.com"
107
+ };
108
+ const id = await p2.text({
109
+ message: `${provider === "google" ? "Measurement" : provider === "posthog" ? "Project API" : "Site"} ID`,
110
+ placeholder: placeholders[provider] ?? "",
111
+ validate: (v) => !v ? "ID is required" : void 0
112
+ });
113
+ if (p2.isCancel(id)) {
114
+ p2.cancel("Cancelled.");
115
+ process.exit(0);
116
+ }
117
+ appendEnvVar(projectDir, "ANALYTICS_PROVIDER", provider);
118
+ appendEnvVar(projectDir, "ANALYTICS_ID", id);
119
+ }
120
+ };
121
+
122
+ // src/features/webhook-event.ts
123
+ import * as p3 from "@clack/prompts";
124
+ import pc from "picocolors";
125
+ var webhookEventFeature = {
126
+ name: "Webhook Event",
127
+ description: "Add a new webhook event handler",
128
+ configKey: "webhook-event",
129
+ async run() {
130
+ const eventName = await p3.text({
131
+ message: "Event name",
132
+ placeholder: "payment_succeeded",
133
+ validate: (v) => !v ? "Event name is required" : void 0
134
+ });
135
+ if (p3.isCancel(eventName)) {
136
+ p3.cancel("Cancelled.");
137
+ process.exit(0);
138
+ }
139
+ const code = `
140
+ ${eventName}: async (data) => {
141
+ const userId = data.user_id as string | undefined;
142
+ if (!userId) return;
143
+ // TODO: Handle ${eventName}
144
+ console.log(\`[Webhook] ${eventName} for \${userId}\`);
145
+ },`;
146
+ p3.note(
147
+ `Add this to the ${pc.bold("on")} object in your webhook route:
148
+
149
+ ${pc.cyan(code)}`,
150
+ "Add to your webhook handler"
151
+ );
152
+ p3.log.info(
153
+ `File: ${pc.dim("app/api/webhooks/whop/route.ts")} (Next.js) or ${pc.dim("src/pages/api/webhooks/whop.ts")} (Astro)`
154
+ );
155
+ }
156
+ };
157
+
158
+ // src/commands/add.ts
159
+ var FEATURES = {
160
+ email: emailFeature,
161
+ analytics: analyticsFeature,
162
+ "webhook-event": webhookEventFeature
163
+ };
164
+ var add_default = defineCommand({
165
+ meta: {
166
+ name: "add",
167
+ description: "Add a feature to your Whop project"
168
+ },
169
+ args: {
170
+ feature: {
171
+ type: "positional",
172
+ description: `Feature to add: ${Object.keys(FEATURES).join(", ")}`,
173
+ required: false
174
+ }
175
+ },
176
+ async run({ args }) {
177
+ console.log("");
178
+ p4.intro(`${pc2.bgCyan(pc2.black(" whop-kit add "))} Add a feature`);
179
+ const manifest = readManifest(".");
180
+ if (!manifest) {
181
+ p4.log.error(
182
+ "No .whop/config.json found. Run this command from a project created with create-whop-kit."
183
+ );
184
+ process.exit(1);
185
+ }
186
+ let featureKey = args.feature;
187
+ if (!featureKey) {
188
+ const result = await p4.select({
189
+ message: "What would you like to add?",
190
+ options: Object.entries(FEATURES).map(([value, f]) => {
191
+ const installed = manifest.features.includes(f.configKey);
192
+ return {
193
+ value,
194
+ label: installed ? `${f.name} ${pc2.green("\u2713 configured")}` : f.name,
195
+ hint: f.description
196
+ };
197
+ })
198
+ });
199
+ if (p4.isCancel(result)) {
200
+ p4.cancel("Cancelled.");
201
+ process.exit(0);
202
+ }
203
+ featureKey = result;
204
+ }
205
+ const feature = FEATURES[featureKey];
206
+ if (!feature) {
207
+ p4.log.error(
208
+ `Unknown feature "${featureKey}". Available: ${Object.keys(FEATURES).join(", ")}`
209
+ );
210
+ process.exit(1);
211
+ }
212
+ if (manifest.features.includes(feature.configKey)) {
213
+ const proceed = await p4.confirm({
214
+ message: `${feature.name} is already configured. Reconfigure?`,
215
+ initialValue: false
216
+ });
217
+ if (p4.isCancel(proceed) || !proceed) {
218
+ p4.cancel("Cancelled.");
219
+ process.exit(0);
220
+ }
221
+ }
222
+ await feature.run(".");
223
+ addFeatureToManifest(".", feature.configKey);
224
+ p4.outro(`${pc2.green("\u2713")} ${feature.name} configured`);
225
+ }
226
+ });
227
+
228
+ // src/commands/status.ts
229
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
230
+ import { join as join2 } from "path";
231
+ import * as p5 from "@clack/prompts";
232
+ import pc3 from "picocolors";
233
+ import { defineCommand as defineCommand2 } from "citty";
234
+ var ENV_CHECKS = [
235
+ { key: "DATABASE_URL", label: "Database", required: true },
236
+ { key: "NEXT_PUBLIC_WHOP_APP_ID", label: "Whop App ID", required: true },
237
+ { key: "WHOP_API_KEY", label: "Whop API Key", required: true },
238
+ { key: "WHOP_WEBHOOK_SECRET", label: "Webhook Secret", required: true },
239
+ { key: "EMAIL_PROVIDER", label: "Email Provider", required: false },
240
+ { key: "EMAIL_API_KEY", label: "Email API Key", required: false },
241
+ { key: "ANALYTICS_PROVIDER", label: "Analytics Provider", required: false },
242
+ { key: "ANALYTICS_ID", label: "Analytics ID", required: false }
243
+ ];
244
+ function readEnvFile(projectDir) {
245
+ const envPath = join2(projectDir, ".env.local");
246
+ if (!existsSync2(envPath)) return {};
247
+ const content = readFileSync2(envPath, "utf-8");
248
+ const vars = {};
249
+ for (const line of content.split("\n")) {
250
+ const trimmed = line.trim();
251
+ if (!trimmed || trimmed.startsWith("#")) continue;
252
+ const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=["']?(.*)["']?$/);
253
+ if (match) {
254
+ vars[match[1]] = match[2].replace(/["']$/, "");
255
+ }
256
+ }
257
+ return vars;
258
+ }
259
+ var status_default = defineCommand2({
260
+ meta: {
261
+ name: "status",
262
+ description: "Show your project's configuration status"
263
+ },
264
+ async run() {
265
+ console.log("");
266
+ p5.intro(`${pc3.bgCyan(pc3.black(" whop-kit status "))} Project health`);
267
+ const manifest = readManifest(".");
268
+ if (!manifest) {
269
+ p5.log.error(
270
+ "No .whop/config.json found. Are you in a project created with create-whop-kit?"
271
+ );
272
+ process.exit(1);
273
+ }
274
+ const envVars = readEnvFile(".");
275
+ if (!envVars["NEXT_PUBLIC_WHOP_APP_ID"] && envVars["WHOP_APP_ID"]) {
276
+ envVars["NEXT_PUBLIC_WHOP_APP_ID"] = envVars["WHOP_APP_ID"];
277
+ }
278
+ console.log(` ${pc3.bold("Framework:")} ${manifest.framework}`);
279
+ console.log(` ${pc3.bold("App type:")} ${manifest.appType}`);
280
+ console.log(` ${pc3.bold("Database:")} ${manifest.database}`);
281
+ console.log(` ${pc3.bold("Created:")} ${new Date(manifest.createdAt).toLocaleDateString()}`);
282
+ console.log("");
283
+ console.log(` ${pc3.bold("Configuration:")}`);
284
+ let allRequired = true;
285
+ for (const check of ENV_CHECKS) {
286
+ const value = envVars[check.key];
287
+ const isSet = !!value;
288
+ if (check.required && !isSet) allRequired = false;
289
+ const icon = isSet ? pc3.green("\u2713") : check.required ? pc3.red("\u2717") : pc3.yellow("\u25CB");
290
+ const maskedValue = isSet ? pc3.dim(value.substring(0, 8) + "...") : check.required ? pc3.red("not set") : pc3.dim("not set (optional)");
291
+ console.log(` ${icon} ${check.label.padEnd(20)} ${maskedValue}`);
292
+ }
293
+ console.log("");
294
+ if (manifest.features.length > 0) {
295
+ console.log(` ${pc3.bold("Features:")} ${manifest.features.join(", ")}`);
296
+ }
297
+ if (allRequired) {
298
+ p5.outro(pc3.green("All required configuration is set. Ready to run!"));
299
+ } else {
300
+ p5.outro(
301
+ `${pc3.yellow("Some required config is missing.")} Run ${pc3.bold("whop-kit add")} or edit ${pc3.dim(".env.local")}`
302
+ );
303
+ }
304
+ }
305
+ });
306
+
307
+ // src/commands/open.ts
308
+ import * as p6 from "@clack/prompts";
309
+ import pc4 from "picocolors";
310
+ import { defineCommand as defineCommand3 } from "citty";
311
+ var DASHBOARDS = {
312
+ whop: { name: "Whop Developer Dashboard", url: "https://whop.com/dashboard/developer" },
313
+ neon: { name: "Neon Console", url: "https://console.neon.tech" },
314
+ supabase: { name: "Supabase Dashboard", url: "https://supabase.com/dashboard" },
315
+ vercel: { name: "Vercel Dashboard", url: "https://vercel.com/dashboard" }
316
+ };
317
+ function openUrl(url) {
318
+ const platform = process.platform;
319
+ if (platform === "darwin") exec(`open "${url}"`);
320
+ else if (platform === "win32") exec(`start "${url}"`);
321
+ else exec(`xdg-open "${url}"`);
322
+ }
323
+ var open_default = defineCommand3({
324
+ meta: {
325
+ name: "open",
326
+ description: "Open a provider dashboard in your browser"
327
+ },
328
+ args: {
329
+ target: {
330
+ type: "positional",
331
+ description: `Dashboard to open: ${Object.keys(DASHBOARDS).join(", ")}`,
332
+ required: false
333
+ }
334
+ },
335
+ async run({ args }) {
336
+ let target = args.target;
337
+ if (!target) {
338
+ const result = await p6.select({
339
+ message: "Which dashboard?",
340
+ options: Object.entries(DASHBOARDS).map(([value, d]) => ({
341
+ value,
342
+ label: d.name,
343
+ hint: d.url
344
+ }))
345
+ });
346
+ if (p6.isCancel(result)) {
347
+ p6.cancel("Cancelled.");
348
+ process.exit(0);
349
+ }
350
+ target = result;
351
+ }
352
+ const dashboard = DASHBOARDS[target];
353
+ if (!dashboard) {
354
+ p6.log.error(`Unknown dashboard "${target}". Options: ${Object.keys(DASHBOARDS).join(", ")}`);
355
+ process.exit(1);
356
+ }
357
+ openUrl(dashboard.url);
358
+ console.log(`
359
+ Opening ${pc4.bold(dashboard.name)} \u2192 ${pc4.cyan(dashboard.url)}
360
+ `);
361
+ }
362
+ });
363
+
364
+ // src/commands/upgrade.ts
365
+ import * as p7 from "@clack/prompts";
366
+ import pc5 from "picocolors";
367
+ import { defineCommand as defineCommand4 } from "citty";
368
+ var upgrade_default = defineCommand4({
369
+ meta: {
370
+ name: "upgrade",
371
+ description: "Update whop-kit to the latest version in your project"
372
+ },
373
+ async run() {
374
+ console.log("");
375
+ p7.intro(`${pc5.bgCyan(pc5.black(" whop-kit upgrade "))}`);
376
+ const manifest = readManifest(".");
377
+ if (!manifest) {
378
+ p7.log.error("No .whop/config.json found. Are you in a whop-kit project?");
379
+ process.exit(1);
380
+ }
381
+ const pm = detectPackageManager();
382
+ const s = p7.spinner();
383
+ s.start("Checking for updates...");
384
+ const latest = exec("npm view whop-kit version");
385
+ s.stop(latest.success ? `Latest: whop-kit@${latest.stdout}` : "Could not check latest version");
386
+ s.start(`Upgrading whop-kit with ${pm}...`);
387
+ const cmd = pm === "npm" ? "npm install whop-kit@latest" : pm === "yarn" ? "yarn add whop-kit@latest" : pm === "bun" ? "bun add whop-kit@latest" : "pnpm add whop-kit@latest";
388
+ const result = exec(cmd);
389
+ if (result.success) {
390
+ s.stop(pc5.green("whop-kit upgraded"));
391
+ } else {
392
+ s.stop(pc5.red("Upgrade failed"));
393
+ p7.log.error("Try running manually: " + pc5.bold(cmd));
394
+ }
395
+ p7.outro("Done");
396
+ }
397
+ });
398
+
399
+ // src/cli-kit.ts
400
+ var main = defineCommand5({
401
+ meta: {
402
+ name: "whop-kit",
403
+ version: "0.2.0",
404
+ description: "Manage your Whop project"
405
+ },
406
+ subCommands: {
407
+ add: add_default,
408
+ status: status_default,
409
+ open: open_default,
410
+ upgrade: upgrade_default
411
+ }
412
+ });
413
+ runMain(main);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-whop-kit",
3
- "version": "0.1.0",
4
- "description": "Scaffold a new Whop-powered app with whop-kit",
3
+ "version": "0.3.0",
4
+ "description": "Scaffold and manage Whop-powered apps with whop-kit",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Colin McDermott",
@@ -10,22 +10,22 @@
10
10
  "url": "https://github.com/colinmcdermott/create-whop-kit"
11
11
  },
12
12
  "bin": {
13
- "create-whop-kit": "./dist/index.js"
13
+ "create-whop-kit": "./dist/cli-create.js",
14
+ "whop-kit": "./dist/cli-kit.js"
14
15
  },
15
16
  "files": [
16
17
  "dist",
17
- "templates",
18
- "README.md",
19
- "LICENSE"
18
+ "README.md"
20
19
  ],
21
20
  "scripts": {
22
21
  "build": "tsup",
23
22
  "dev": "tsup --watch",
24
- "start": "node dist/index.js",
25
23
  "prepublishOnly": "npm run build"
26
24
  },
27
25
  "dependencies": {
28
- "@clack/prompts": "^0.10.0"
26
+ "@clack/prompts": "^0.10.0",
27
+ "citty": "^0.2.2",
28
+ "picocolors": "^1.1.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "tsup": "^8.4.0",
package/dist/index.js DELETED
@@ -1,209 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import * as p from "@clack/prompts";
5
- import { execSync } from "child_process";
6
- import { existsSync, readFileSync, writeFileSync } from "fs";
7
- import { resolve, join, basename } from "path";
8
- import { fileURLToPath } from "url";
9
- var __dirname = fileURLToPath(new URL(".", import.meta.url));
10
- var TEMPLATES_DIR = resolve(__dirname, "..", "templates");
11
- var TEMPLATES = {
12
- nextjs: {
13
- name: "Next.js",
14
- description: "Full-stack React with App Router, SSR, and API routes",
15
- repo: "colinmcdermott/whop-saas-starter-v2",
16
- available: true
17
- },
18
- astro: {
19
- name: "Astro",
20
- description: "Content-focused with islands architecture",
21
- repo: "",
22
- available: false
23
- },
24
- tanstack: {
25
- name: "TanStack Start",
26
- description: "Full-stack React with TanStack Router",
27
- repo: "",
28
- available: false
29
- },
30
- vite: {
31
- name: "Vite + React",
32
- description: "Lightweight SPA with Vite bundler",
33
- repo: "",
34
- available: false
35
- }
36
- };
37
- var APP_TYPES = {
38
- saas: {
39
- name: "SaaS",
40
- description: "Subscription tiers, dashboard, billing portal",
41
- available: true
42
- },
43
- course: {
44
- name: "Course",
45
- description: "Lessons, progress tracking, drip content",
46
- available: false
47
- },
48
- community: {
49
- name: "Community",
50
- description: "Member feeds, gated content, roles",
51
- available: false
52
- },
53
- blank: {
54
- name: "Blank",
55
- description: "Just auth + payments, you build the rest",
56
- available: false
57
- }
58
- };
59
- var DB_OPTIONS = {
60
- neon: {
61
- name: "Neon",
62
- description: "Serverless Postgres (recommended)",
63
- envVarHint: "postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require"
64
- },
65
- supabase: {
66
- name: "Supabase",
67
- description: "Open-source Firebase alternative",
68
- envVarHint: "postgresql://postgres.xxx:pass@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
69
- },
70
- local: {
71
- name: "Local PostgreSQL",
72
- description: "Your own Postgres instance",
73
- envVarHint: "postgresql://postgres:postgres@localhost:5432/myapp"
74
- },
75
- later: {
76
- name: "Configure later",
77
- description: "Skip database setup for now",
78
- envVarHint: ""
79
- }
80
- };
81
- function run(cmd, cwd) {
82
- try {
83
- return execSync(cmd, { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
84
- } catch {
85
- return "";
86
- }
87
- }
88
- function hasCommand(cmd) {
89
- return run(`which ${cmd}`) !== "";
90
- }
91
- async function main() {
92
- const args = process.argv.slice(2);
93
- const projectName = args[0];
94
- console.log("");
95
- p.intro("Create Whop Kit App");
96
- const name = projectName ?? await p.text({
97
- message: "Project name",
98
- placeholder: "my-whop-app",
99
- validate: (v) => {
100
- if (!v) return "Project name is required";
101
- if (existsSync(resolve(v))) return `Directory "${v}" already exists`;
102
- }
103
- });
104
- if (p.isCancel(name)) {
105
- p.cancel("Cancelled.");
106
- process.exit(0);
107
- }
108
- const appType = await p.select({
109
- message: "What are you building?",
110
- options: Object.entries(APP_TYPES).map(([value, { name: name2, description, available }]) => ({
111
- value,
112
- label: available ? name2 : `${name2} (coming soon)`,
113
- hint: description,
114
- disabled: !available
115
- }))
116
- });
117
- if (p.isCancel(appType)) {
118
- p.cancel("Cancelled.");
119
- process.exit(0);
120
- }
121
- const framework = await p.select({
122
- message: "Which framework?",
123
- options: Object.entries(TEMPLATES).map(([value, { name: name2, description, available }]) => ({
124
- value,
125
- label: available ? name2 : `${name2} (coming soon)`,
126
- hint: description,
127
- disabled: !available
128
- }))
129
- });
130
- if (p.isCancel(framework)) {
131
- p.cancel("Cancelled.");
132
- process.exit(0);
133
- }
134
- const database = await p.select({
135
- message: "Which database?",
136
- options: Object.entries(DB_OPTIONS).map(([value, { name: name2, description }]) => ({
137
- value,
138
- label: name2,
139
- hint: description
140
- }))
141
- });
142
- if (p.isCancel(database)) {
143
- p.cancel("Cancelled.");
144
- process.exit(0);
145
- }
146
- let databaseUrl = "";
147
- if (database !== "later") {
148
- const dbUrl = await p.text({
149
- message: "Database URL",
150
- placeholder: DB_OPTIONS[database].envVarHint,
151
- validate: (v) => {
152
- if (!v) return "Database URL is required (or go back and choose 'Configure later')";
153
- }
154
- });
155
- if (p.isCancel(dbUrl)) {
156
- p.cancel("Cancelled.");
157
- process.exit(0);
158
- }
159
- databaseUrl = dbUrl;
160
- }
161
- const template = TEMPLATES[framework];
162
- const projectDir = resolve(name);
163
- const s = p.spinner();
164
- s.start("Cloning template...");
165
- const cloneResult = run(
166
- `git clone --depth 1 https://github.com/${template.repo}.git "${projectDir}" 2>&1`
167
- );
168
- if (!existsSync(projectDir)) {
169
- s.stop("Failed to clone template");
170
- p.log.error(cloneResult || "Git clone failed. Make sure git is installed.");
171
- process.exit(1);
172
- }
173
- run(`rm -rf "${join(projectDir, ".git")}"`);
174
- const pkgPath = join(projectDir, "package.json");
175
- if (existsSync(pkgPath)) {
176
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
177
- pkg.name = basename(name);
178
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
179
- }
180
- s.stop("Template cloned");
181
- if (databaseUrl) {
182
- s.start("Configuring environment...");
183
- const envContent = `DATABASE_URL="${databaseUrl}"
184
- `;
185
- writeFileSync(join(projectDir, ".env.local"), envContent);
186
- s.stop("Environment configured");
187
- }
188
- const packageManager = hasCommand("pnpm") ? "pnpm" : hasCommand("yarn") ? "yarn" : "npm";
189
- s.start(`Installing dependencies with ${packageManager}...`);
190
- run(`${packageManager} install`, projectDir);
191
- s.stop("Dependencies installed");
192
- run("git init", projectDir);
193
- run("git add -A", projectDir);
194
- run('git commit -m "initial: scaffolded with create-whop-kit"', projectDir);
195
- const relativePath = name;
196
- p.note(
197
- [
198
- `cd ${relativePath}`,
199
- databaseUrl ? `${packageManager} run db:push` : `# Add DATABASE_URL to .env.local first`,
200
- `${packageManager} run dev`
201
- ].join("\n"),
202
- "Next steps"
203
- );
204
- p.outro("Happy building!");
205
- }
206
- main().catch((err) => {
207
- console.error(err);
208
- process.exit(1);
209
- });