create-whop-kit 0.5.1 → 0.6.1

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,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/exec.ts
4
+ import { execSync } from "child_process";
5
+ function exec(cmd, cwd, timeoutMs = 12e4) {
6
+ try {
7
+ const stdout = execSync(cmd, {
8
+ cwd,
9
+ stdio: "pipe",
10
+ encoding: "utf-8",
11
+ timeout: timeoutMs
12
+ }).trim();
13
+ return { stdout, stderr: "", success: true };
14
+ } catch (err) {
15
+ const e = err;
16
+ return {
17
+ stdout: e.stdout?.toString?.().trim() ?? "",
18
+ stderr: e.stderr?.toString?.().trim() ?? "",
19
+ success: false
20
+ };
21
+ }
22
+ }
23
+ function execInteractive(cmd, cwd) {
24
+ try {
25
+ execSync(cmd, { cwd, stdio: "inherit", timeout: 3e5 });
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ function execWithStdin(cmd, input, cwd) {
32
+ try {
33
+ const stdout = execSync(cmd, {
34
+ cwd,
35
+ input,
36
+ stdio: ["pipe", "pipe", "pipe"],
37
+ encoding: "utf-8",
38
+ timeout: 12e4
39
+ }).trim();
40
+ return { stdout, stderr: "", success: true };
41
+ } catch (err) {
42
+ const e = err;
43
+ return { stdout: "", stderr: e.stderr?.toString?.().trim() ?? "", success: false };
44
+ }
45
+ }
46
+ function hasCommand(cmd) {
47
+ return exec(`which ${cmd}`).success;
48
+ }
49
+ function detectPackageManager() {
50
+ if (hasCommand("pnpm")) return "pnpm";
51
+ if (hasCommand("yarn")) return "yarn";
52
+ if (hasCommand("bun")) return "bun";
53
+ return "npm";
54
+ }
55
+
56
+ export {
57
+ exec,
58
+ execInteractive,
59
+ execWithStdin,
60
+ hasCommand,
61
+ detectPackageManager
62
+ };
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ exec
4
+ } from "./chunk-42L7PRMT.js";
2
5
 
3
6
  // src/templates.ts
4
7
  var FRAMEWORKS = {
@@ -69,39 +72,6 @@ var APP_TYPES = {
69
72
  }
70
73
  };
71
74
 
72
- // src/utils/exec.ts
73
- import { execSync } from "child_process";
74
- function exec(cmd, cwd) {
75
- try {
76
- const stdout = execSync(cmd, {
77
- cwd,
78
- stdio: "pipe",
79
- encoding: "utf-8",
80
- timeout: 12e4
81
- }).trim();
82
- return { stdout, success: true };
83
- } catch {
84
- return { stdout: "", success: false };
85
- }
86
- }
87
- function execInteractive(cmd, cwd) {
88
- try {
89
- execSync(cmd, { cwd, stdio: "inherit", timeout: 3e5 });
90
- return true;
91
- } catch {
92
- return false;
93
- }
94
- }
95
- function hasCommand(cmd) {
96
- return exec(`which ${cmd}`).success;
97
- }
98
- function detectPackageManager() {
99
- if (hasCommand("pnpm")) return "pnpm";
100
- if (hasCommand("yarn")) return "yarn";
101
- if (hasCommand("bun")) return "bun";
102
- return "npm";
103
- }
104
-
105
75
  // src/scaffolding/manifest.ts
106
76
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
107
77
  import { join } from "path";
@@ -357,10 +327,6 @@ export {
357
327
  TEMPLATES,
358
328
  getTemplate,
359
329
  APP_TYPES,
360
- exec,
361
- execInteractive,
362
- hasCommand,
363
- detectPackageManager,
364
330
  createManifest,
365
331
  readManifest,
366
332
  addFeatureToManifest,
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ exec,
4
+ execInteractive,
5
+ execWithStdin,
6
+ hasCommand
7
+ } from "./chunk-42L7PRMT.js";
8
+
9
+ // src/deploy/index.ts
10
+ import * as p2 from "@clack/prompts";
11
+ import pc2 from "picocolors";
12
+
13
+ // src/deploy/vercel.ts
14
+ import * as p from "@clack/prompts";
15
+ import pc from "picocolors";
16
+ function isVercelInstalled() {
17
+ return hasCommand("vercel");
18
+ }
19
+ async function installVercel() {
20
+ const s = p.spinner();
21
+ s.start("Installing Vercel CLI...");
22
+ const result = exec("npm install -g vercel");
23
+ if (result.success) {
24
+ s.stop("Vercel CLI installed");
25
+ return true;
26
+ }
27
+ s.stop("Failed to install Vercel CLI");
28
+ p.log.error(`Install manually: ${pc.bold("npm install -g vercel")}`);
29
+ return false;
30
+ }
31
+ function isVercelAuthenticated() {
32
+ const result = exec("vercel whoami");
33
+ return result.success;
34
+ }
35
+ async function vercelLogin() {
36
+ p.log.step("Vercel: authenticating (opening browser)...");
37
+ console.log("");
38
+ const ok = execInteractive("vercel login");
39
+ console.log("");
40
+ return ok;
41
+ }
42
+ async function vercelDeploy(projectDir) {
43
+ p.log.step("Vercel: deploying to production (this may take a few minutes)...");
44
+ console.log("");
45
+ const ok = execInteractive("vercel deploy --prod --yes", projectDir);
46
+ console.log("");
47
+ if (!ok) {
48
+ p.log.error("Vercel deployment failed. Check the output above for details.");
49
+ return null;
50
+ }
51
+ const inspect = exec("vercel inspect --json", projectDir);
52
+ if (inspect.success) {
53
+ try {
54
+ const data = JSON.parse(inspect.stdout);
55
+ if (data.url) {
56
+ const url = data.url.startsWith("https://") ? data.url : `https://${data.url}`;
57
+ p.log.success(`Deployed to ${pc.cyan(url)}`);
58
+ return url;
59
+ }
60
+ } catch {
61
+ }
62
+ }
63
+ const ls = exec("vercel ls --json 2>/dev/null | head -1", projectDir);
64
+ if (ls.success) {
65
+ const urlMatch = ls.stdout.match(/https:\/\/[^\s"]+\.vercel\.app/);
66
+ if (urlMatch) {
67
+ p.log.success(`Deployed to ${pc.cyan(urlMatch[0])}`);
68
+ return urlMatch[0];
69
+ }
70
+ }
71
+ p.log.warning("Could not determine deployment URL automatically.");
72
+ const manual = await p.text({
73
+ message: "Paste your Vercel deployment URL",
74
+ placeholder: "https://your-app.vercel.app",
75
+ validate: (v) => {
76
+ if (!v?.startsWith("https://")) return "Must be a https:// URL";
77
+ }
78
+ });
79
+ if (p.isCancel(manual)) return null;
80
+ return manual;
81
+ }
82
+ function vercelEnvSet(key, value, environment = "production", projectDir) {
83
+ const result = execWithStdin(
84
+ `vercel env add ${key} ${environment} --force`,
85
+ value,
86
+ projectDir
87
+ );
88
+ return result.success;
89
+ }
90
+ function vercelEnvSetBatch(vars, projectDir) {
91
+ const success = [];
92
+ const failed = [];
93
+ for (const [key, value] of Object.entries(vars)) {
94
+ if (!value) continue;
95
+ const ok = vercelEnvSet(key, value, "production", projectDir) && vercelEnvSet(key, value, "preview", projectDir) && vercelEnvSet(key, value, "development", projectDir);
96
+ if (ok) success.push(key);
97
+ else failed.push(key);
98
+ }
99
+ return { success, failed };
100
+ }
101
+
102
+ // src/deploy/whop-api.ts
103
+ var WHOP_API = "https://api.whop.com/api/v1";
104
+ function headers(apiKey) {
105
+ return {
106
+ Authorization: `Bearer ${apiKey}`,
107
+ "Content-Type": "application/json"
108
+ };
109
+ }
110
+ async function validateApiKey(apiKey) {
111
+ try {
112
+ const res = await fetch(`${WHOP_API}/apps`, {
113
+ headers: headers(apiKey)
114
+ });
115
+ return res.ok;
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ async function createWhopApp(apiKey, name, redirectUris) {
121
+ try {
122
+ const res = await fetch(`${WHOP_API}/apps`, {
123
+ method: "POST",
124
+ headers: headers(apiKey),
125
+ body: JSON.stringify({
126
+ name,
127
+ redirect_uris: redirectUris
128
+ })
129
+ });
130
+ if (!res.ok) {
131
+ const err = await res.text().catch(() => "");
132
+ console.error(`[Whop API] Create app failed (${res.status}): ${err}`);
133
+ return null;
134
+ }
135
+ const data = await res.json();
136
+ return {
137
+ id: data.id,
138
+ client_secret: data.client_secret
139
+ };
140
+ } catch (err) {
141
+ console.error("[Whop API] Create app error:", err);
142
+ return null;
143
+ }
144
+ }
145
+ async function createWhopWebhook(apiKey, url, events) {
146
+ try {
147
+ const res = await fetch(`${WHOP_API}/webhooks`, {
148
+ method: "POST",
149
+ headers: headers(apiKey),
150
+ body: JSON.stringify({
151
+ url,
152
+ events
153
+ })
154
+ });
155
+ if (!res.ok) {
156
+ const err = await res.text().catch(() => "");
157
+ console.error(`[Whop API] Create webhook failed (${res.status}): ${err}`);
158
+ return null;
159
+ }
160
+ const data = await res.json();
161
+ return {
162
+ id: data.id,
163
+ secret: data.secret || data.signing_secret || data.webhook_secret || ""
164
+ };
165
+ } catch (err) {
166
+ console.error("[Whop API] Create webhook error:", err);
167
+ return null;
168
+ }
169
+ }
170
+
171
+ // src/deploy/index.ts
172
+ var WEBHOOK_EVENTS = [
173
+ "membership.activated",
174
+ "membership.deactivated",
175
+ "membership.cancel_at_period_end_changed",
176
+ "payment.succeeded",
177
+ "payment.failed",
178
+ "refund.created"
179
+ ];
180
+ function openUrl(url) {
181
+ const platform = process.platform;
182
+ if (platform === "darwin") exec(`open "${url}"`);
183
+ else if (platform === "win32") exec(`start "" "${url}"`);
184
+ else exec(`xdg-open "${url}"`);
185
+ }
186
+ async function runDeployPipeline(options) {
187
+ const { projectDir, projectName, databaseUrl, framework } = options;
188
+ if (!isVercelInstalled()) {
189
+ const install = await p2.confirm({
190
+ message: "Vercel CLI not found. Install it now?",
191
+ initialValue: true
192
+ });
193
+ if (p2.isCancel(install) || !install) return null;
194
+ const ok = await installVercel();
195
+ if (!ok) return null;
196
+ }
197
+ if (!isVercelAuthenticated()) {
198
+ const ok = await vercelLogin();
199
+ if (!ok) {
200
+ p2.log.error("Vercel authentication failed. Deploy later with: " + pc2.bold("whop-kit deploy"));
201
+ return null;
202
+ }
203
+ }
204
+ p2.log.success("Vercel authenticated");
205
+ if (databaseUrl) {
206
+ const s2 = p2.spinner();
207
+ s2.start("Setting DATABASE_URL on Vercel...");
208
+ vercelEnvSet("DATABASE_URL", databaseUrl, "production", projectDir);
209
+ vercelEnvSet("DATABASE_URL", databaseUrl, "preview", projectDir);
210
+ vercelEnvSet("DATABASE_URL", databaseUrl, "development", projectDir);
211
+ s2.stop("DATABASE_URL configured");
212
+ }
213
+ const productionUrl = await vercelDeploy(projectDir);
214
+ if (!productionUrl) {
215
+ p2.log.error("Vercel deployment failed. Try deploying manually:");
216
+ p2.log.info(pc2.bold(` cd ${projectName} && vercel deploy --prod`));
217
+ return null;
218
+ }
219
+ p2.log.success(`Live at ${pc2.cyan(productionUrl)}`);
220
+ const connectWhop = await p2.confirm({
221
+ message: "Connect to Whop? (creates OAuth app + webhooks automatically)",
222
+ initialValue: true
223
+ });
224
+ if (p2.isCancel(connectWhop) || !connectWhop) {
225
+ return { productionUrl };
226
+ }
227
+ p2.log.info("");
228
+ p2.note(
229
+ [
230
+ `${pc2.bold("1.")} Go to the Whop Developer Dashboard`,
231
+ ` ${pc2.cyan("https://whop.com/dashboard/developer")}`,
232
+ "",
233
+ `${pc2.bold("2.")} Click ${pc2.bold('"Create"')} under "Company API Keys"`,
234
+ "",
235
+ `${pc2.bold("3.")} Name it anything (e.g. "${projectName}")`,
236
+ "",
237
+ `${pc2.bold("4.")} Select these permissions:`,
238
+ ` ${pc2.green("\u2022")} developer:create_app`,
239
+ ` ${pc2.green("\u2022")} developer:manage_api_key`,
240
+ ` ${pc2.green("\u2022")} developer:manage_webhook`,
241
+ "",
242
+ `${pc2.bold("5.")} Create the key and paste it below`
243
+ ].join("\n"),
244
+ "Create a Company API Key"
245
+ );
246
+ openUrl("https://whop.com/dashboard/developer");
247
+ let apiKey = options.whopCompanyKey ?? "";
248
+ if (!apiKey) {
249
+ const result = await p2.text({
250
+ message: "Paste your Company API key",
251
+ placeholder: "paste the key here...",
252
+ validate: (v) => !v ? "API key is required" : void 0
253
+ });
254
+ if (p2.isCancel(result)) return { productionUrl };
255
+ apiKey = result;
256
+ }
257
+ const s = p2.spinner();
258
+ s.start("Validating API key...");
259
+ const keyValid = await validateApiKey(apiKey);
260
+ if (!keyValid) {
261
+ s.stop("Invalid API key");
262
+ p2.log.error("The key was rejected. Check that it has the required permissions:");
263
+ p2.log.info(" developer:create_app, developer:manage_api_key, developer:manage_webhook");
264
+ p2.log.info(` Dashboard: ${pc2.cyan("https://whop.com/dashboard/developer")}`);
265
+ return { productionUrl };
266
+ }
267
+ s.stop("API key valid");
268
+ const callbackPath = framework === "astro" ? "/api/auth/callback" : "/api/auth/callback";
269
+ const redirectUris = [
270
+ `http://localhost:3000${callbackPath}`,
271
+ `${productionUrl}${callbackPath}`
272
+ ];
273
+ s.start("Creating Whop OAuth app...");
274
+ const app = await createWhopApp(apiKey, projectName, redirectUris);
275
+ if (!app) {
276
+ s.stop("Failed to create Whop app");
277
+ p2.log.error("Create it manually in the Whop dashboard.");
278
+ return { productionUrl };
279
+ }
280
+ s.stop(`Whop app created: ${pc2.bold(app.id)}`);
281
+ const webhookUrl = `${productionUrl}/api/webhooks/whop`;
282
+ s.start("Creating webhook endpoint...");
283
+ const webhook = await createWhopWebhook(apiKey, webhookUrl, WEBHOOK_EVENTS);
284
+ if (!webhook) {
285
+ s.stop("Failed to create webhook");
286
+ p2.log.warning("Create it manually in the Whop dashboard.");
287
+ } else {
288
+ s.stop("Webhook endpoint created");
289
+ }
290
+ const envVars = {};
291
+ if (framework === "nextjs") {
292
+ envVars["NEXT_PUBLIC_WHOP_APP_ID"] = app.id;
293
+ } else {
294
+ envVars["WHOP_APP_ID"] = app.id;
295
+ }
296
+ envVars["WHOP_API_KEY"] = app.client_secret;
297
+ if (webhook?.secret) {
298
+ envVars["WHOP_WEBHOOK_SECRET"] = webhook.secret;
299
+ }
300
+ s.start("Pushing credentials to Vercel...");
301
+ const { success, failed } = vercelEnvSetBatch(envVars, projectDir);
302
+ if (failed.length > 0) {
303
+ s.stop(`Pushed ${success.length} vars, ${failed.length} failed`);
304
+ p2.log.warning(`Failed to push: ${failed.join(", ")}. Add them manually in Vercel dashboard.`);
305
+ } else {
306
+ s.stop(`${success.length} environment variables pushed`);
307
+ }
308
+ p2.log.step("Vercel: redeploying with full configuration...");
309
+ console.log("");
310
+ const redeployOk = execInteractive("vercel deploy --prod --yes", projectDir);
311
+ console.log("");
312
+ if (redeployOk) {
313
+ p2.log.success("Redeployed with full configuration");
314
+ } else {
315
+ p2.log.warning("Redeploy failed \u2014 env vars will apply on next deploy/push");
316
+ }
317
+ return {
318
+ productionUrl,
319
+ whopAppId: app.id,
320
+ whopApiKey: app.client_secret,
321
+ webhookSecret: webhook?.secret
322
+ };
323
+ }
324
+
325
+ export {
326
+ runDeployPipeline
327
+ };
@@ -3,14 +3,16 @@ import {
3
3
  APP_TYPES,
4
4
  FRAMEWORKS,
5
5
  createManifest,
6
- detectPackageManager,
7
- exec,
8
- execInteractive,
9
6
  getTemplate,
10
- hasCommand,
11
7
  installProviderSkills,
12
8
  writeProjectContext
13
- } from "./chunk-BR467LBM.js";
9
+ } from "./chunk-HOQ5QQ2M.js";
10
+ import {
11
+ detectPackageManager,
12
+ exec,
13
+ execInteractive,
14
+ hasCommand
15
+ } from "./chunk-42L7PRMT.js";
14
16
 
15
17
  // src/cli-create.ts
16
18
  import { runMain } from "citty";
@@ -45,70 +47,50 @@ var neonProvider = {
45
47
  },
46
48
  async provision(projectName) {
47
49
  const cli = hasCommand("neonctl") ? "neonctl" : "neon";
50
+ p.log.step("Neon: checking authentication...");
48
51
  const whoami = exec(`${cli} me --output json`);
49
52
  if (!whoami.success) {
50
- p.log.info("You need to authenticate with Neon. This will open your browser.");
53
+ p.log.info("Opening browser for Neon authentication...");
51
54
  console.log("");
52
55
  const authOk = execInteractive(`${cli} auth`);
53
56
  if (!authOk) {
54
- p.log.error("Neon authentication failed. Try running manually:");
55
- p.log.info(pc.bold(` ${cli} auth`));
57
+ p.log.error("Authentication failed. Run manually: " + pc.bold(`${cli} auth`));
56
58
  return null;
57
59
  }
58
60
  console.log("");
59
61
  }
60
- p.log.info(`Creating Neon project "${projectName}"...`);
62
+ p.log.step(`Neon: creating project "${projectName}"...`);
61
63
  console.log("");
62
64
  const createOk = execInteractive(
63
65
  `${cli} projects create --name "${projectName}" --set-context`
64
66
  );
67
+ console.log("");
65
68
  if (!createOk) {
66
- p.log.error("Failed to create Neon project. Try manually at https://console.neon.tech");
69
+ p.log.error("Failed to create project. Try: https://console.neon.tech");
67
70
  return null;
68
71
  }
69
- console.log("");
70
- p.log.success("Neon project created");
72
+ p.log.step("Neon: getting connection string...");
71
73
  let connString = "";
72
- const connResult = exec(`${cli} connection-string --prisma --output json`);
73
- if (connResult.success) {
74
- try {
75
- const parsed = JSON.parse(connResult.stdout);
76
- connString = parsed.connection_string || parsed.connectionString || connResult.stdout;
77
- } catch {
78
- connString = connResult.stdout.trim();
79
- }
80
- }
81
- if (!connString) {
82
- const fallback = exec(`${cli} connection-string --prisma`);
83
- if (fallback.success && fallback.stdout.startsWith("postgres")) {
84
- connString = fallback.stdout.trim();
74
+ for (const flags of ["--prisma", ""]) {
75
+ if (connString) break;
76
+ const result = exec(`${cli} connection-string ${flags}`.trim(), void 0, 3e4);
77
+ if (result.success && result.stdout.startsWith("postgres")) {
78
+ connString = result.stdout.trim();
85
79
  }
86
80
  }
87
81
  if (!connString) {
88
- const raw = exec(`${cli} connection-string`);
89
- if (raw.success && raw.stdout.startsWith("postgres")) {
90
- connString = raw.stdout.trim();
91
- }
92
- }
93
- if (!connString) {
94
- p.log.warning("Could not extract connection string automatically.");
95
- console.log("");
96
- execInteractive(`${cli} connection-string`);
97
- console.log("");
82
+ p.log.warning("Could not retrieve connection string automatically.");
83
+ p.log.info("The connection URI was shown in the table above.");
98
84
  const manual = await p.text({
99
- message: "Paste the connection string shown above",
85
+ message: "Paste the connection string from the output above",
100
86
  placeholder: "postgresql://...",
101
87
  validate: (v) => {
102
- if (!v?.startsWith("postgres")) return "Must be a PostgreSQL connection string";
88
+ if (!v?.startsWith("postgres")) return "Must start with postgresql://";
103
89
  }
104
90
  });
105
91
  if (p.isCancel(manual)) return null;
106
92
  connString = manual;
107
93
  }
108
- if (!connString) {
109
- p.log.error("Could not get connection string. Get it from: https://console.neon.tech");
110
- return null;
111
- }
112
94
  return {
113
95
  connectionString: connString,
114
96
  provider: "neon"
@@ -456,6 +438,15 @@ var init_default = defineCommand({
456
438
  description: "Show what would be created without doing it",
457
439
  default: false
458
440
  },
441
+ "skip-deploy": {
442
+ type: "boolean",
443
+ description: "Skip Vercel deployment",
444
+ default: false
445
+ },
446
+ "whop-company-key": {
447
+ type: "string",
448
+ description: "Whop Company API key for automatic app creation"
449
+ },
459
450
  verbose: {
460
451
  type: "boolean",
461
452
  description: "Show detailed output",
@@ -672,40 +663,70 @@ var init_default = defineCommand({
672
663
  s.stop("Dependencies installed");
673
664
  }
674
665
  initGit(projectDir);
675
- const configured = [];
676
- const missing = [];
677
- if (dbUrl) configured.push("Database");
678
- else missing.push("Database URL");
679
- if (appId) configured.push("Whop App ID");
680
- else missing.push("Whop App ID");
681
- if (apiKey) configured.push("Whop API Key");
682
- else missing.push("Whop API Key");
683
- if (webhookSecret) configured.push("Webhook Secret");
684
- else missing.push("Webhook Secret");
666
+ let deployResult = null;
667
+ if (!args["skip-deploy"] && !args["dry-run"]) {
668
+ const shouldDeploy = isNonInteractive ? false : await (async () => {
669
+ const result = await p5.confirm({
670
+ message: "Deploy to Vercel and connect to Whop?",
671
+ initialValue: true
672
+ });
673
+ return !isCancelled(result) && result;
674
+ })();
675
+ if (shouldDeploy) {
676
+ const { runDeployPipeline } = await import("./deploy-RKVUZYCU.js");
677
+ deployResult = await runDeployPipeline({
678
+ projectDir,
679
+ projectName,
680
+ databaseUrl: dbUrl || void 0,
681
+ framework,
682
+ whopCompanyKey: args["whop-company-key"]
683
+ });
684
+ }
685
+ }
685
686
  let summary = "";
686
- if (configured.length > 0) {
687
- summary += `${pc5.green("\u2713")} ${configured.join(", ")}
687
+ if (deployResult?.productionUrl) {
688
+ summary += `${pc5.green("\u2713")} Deployed to ${pc5.cyan(deployResult.productionUrl)}
688
689
  `;
689
- }
690
- if (dbNote) {
691
- summary += `${pc5.yellow("!")} ${dbNote}
690
+ if (deployResult.whopAppId) summary += `${pc5.green("\u2713")} Whop app: ${deployResult.whopAppId}
692
691
  `;
693
- }
694
- summary += `
692
+ if (deployResult.webhookSecret) summary += `${pc5.green("\u2713")} Webhooks configured
695
693
  `;
696
- summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
694
+ if (dbUrl) summary += `${pc5.green("\u2713")} Database connected
697
695
  `;
698
- if (dbUrl) {
699
- summary += ` ${pc5.bold(`${pm} run db:push`)} ${pc5.dim("# push schema to database")}
696
+ summary += `
700
697
  `;
701
- }
702
- summary += ` ${pc5.bold(`${pm} run dev`)} ${pc5.dim("# start dev server")}
698
+ summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
699
+ `;
700
+ summary += ` ${pc5.bold(`${pm} run dev`)} ${pc5.dim("# local development")}
701
+ `;
702
+ summary += `
703
703
  `;
704
- summary += `
704
+ summary += ` ${pc5.dim(`Production: ${deployResult.productionUrl}`)}`;
705
+ } else {
706
+ if (dbUrl) summary += `${pc5.green("\u2713")} Database configured
707
+ `;
708
+ if (dbNote) summary += `${pc5.yellow("!")} ${dbNote}
709
+ `;
710
+ summary += `
705
711
  `;
706
- summary += ` ${pc5.dim("Open http://localhost:3000 \u2014 the setup wizard will")}
712
+ summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
713
+ `;
714
+ if (dbUrl) {
715
+ summary += ` ${pc5.bold(`${pm} run db:push`)} ${pc5.dim("# push schema to database")}
716
+ `;
717
+ }
718
+ summary += ` ${pc5.bold(`${pm} run dev`)} ${pc5.dim("# start dev server")}
707
719
  `;
708
- summary += ` ${pc5.dim("walk you through connecting your Whop app.")}`;
720
+ summary += `
721
+ `;
722
+ summary += ` ${pc5.dim("Open http://localhost:3000 \u2014 the setup wizard will")}
723
+ `;
724
+ summary += ` ${pc5.dim("walk you through connecting your Whop app.")}
725
+ `;
726
+ summary += `
727
+ `;
728
+ summary += ` ${pc5.dim(`Or run ${pc5.bold("whop-kit deploy")} to deploy + auto-configure.`)}`;
729
+ }
709
730
  p5.note(summary, "Your app is ready");
710
731
  p5.outro(`${pc5.green("Happy building!")} ${pc5.dim("\u2014 whop-kit")}`);
711
732
  }
package/dist/cli-kit.js CHANGED
@@ -4,14 +4,19 @@ import {
4
4
  FRAMEWORKS,
5
5
  TEMPLATES,
6
6
  addFeatureToManifest,
7
- detectPackageManager,
8
- exec,
9
7
  readManifest,
10
8
  writeFeatureSkill
11
- } from "./chunk-BR467LBM.js";
9
+ } from "./chunk-HOQ5QQ2M.js";
10
+ import {
11
+ runDeployPipeline
12
+ } from "./chunk-NRG6LIOM.js";
13
+ import {
14
+ detectPackageManager,
15
+ exec
16
+ } from "./chunk-42L7PRMT.js";
12
17
 
13
18
  // src/cli-kit.ts
14
- import { defineCommand as defineCommand7, runMain } from "citty";
19
+ import { defineCommand as defineCommand8, runMain } from "citty";
15
20
 
16
21
  // src/commands/add.ts
17
22
  import * as p4 from "@clack/prompts";
@@ -452,10 +457,59 @@ var catalog_default = defineCommand4({
452
457
  }
453
458
  });
454
459
 
455
- // src/commands/open.ts
460
+ // src/commands/deploy.ts
456
461
  import * as p8 from "@clack/prompts";
457
462
  import pc6 from "picocolors";
458
463
  import { defineCommand as defineCommand5 } from "citty";
464
+ import { basename } from "path";
465
+ var deploy_default = defineCommand5({
466
+ meta: {
467
+ name: "deploy",
468
+ description: "Deploy your project to Vercel and connect to Whop"
469
+ },
470
+ args: {
471
+ "whop-company-key": {
472
+ type: "string",
473
+ description: "Whop Company API key (skips interactive prompt)"
474
+ },
475
+ "skip-whop": {
476
+ type: "boolean",
477
+ description: "Deploy without Whop configuration",
478
+ default: false
479
+ }
480
+ },
481
+ async run({ args }) {
482
+ console.log("");
483
+ p8.intro(`${pc6.bgCyan(pc6.black(" whop-kit deploy "))}`);
484
+ const manifest = readManifest(".");
485
+ if (!manifest) {
486
+ p8.log.error("No .whop/config.json found. Are you in a whop-kit project?");
487
+ process.exit(1);
488
+ }
489
+ const projectName = basename(process.cwd());
490
+ const result = await runDeployPipeline({
491
+ projectDir: ".",
492
+ projectName,
493
+ framework: manifest.framework,
494
+ whopCompanyKey: args["whop-company-key"]
495
+ });
496
+ if (result) {
497
+ let summary = `${pc6.green("\u2713")} Live at ${pc6.cyan(result.productionUrl)}
498
+ `;
499
+ if (result.whopAppId) summary += `${pc6.green("\u2713")} Whop app: ${result.whopAppId}
500
+ `;
501
+ if (result.webhookSecret) summary += `${pc6.green("\u2713")} Webhook configured
502
+ `;
503
+ p8.note(summary, "Deployment complete");
504
+ }
505
+ p8.outro("Done");
506
+ }
507
+ });
508
+
509
+ // src/commands/open.ts
510
+ import * as p9 from "@clack/prompts";
511
+ import pc7 from "picocolors";
512
+ import { defineCommand as defineCommand6 } from "citty";
459
513
  var DASHBOARDS = {
460
514
  whop: { name: "Whop Developer Dashboard", url: "https://whop.com/dashboard/developer" },
461
515
  neon: { name: "Neon Console", url: "https://console.neon.tech" },
@@ -468,7 +522,7 @@ function openUrl(url) {
468
522
  else if (platform === "win32") exec(`start "${url}"`);
469
523
  else exec(`xdg-open "${url}"`);
470
524
  }
471
- var open_default = defineCommand5({
525
+ var open_default = defineCommand6({
472
526
  meta: {
473
527
  name: "open",
474
528
  description: "Open a provider dashboard in your browser"
@@ -483,7 +537,7 @@ var open_default = defineCommand5({
483
537
  async run({ args }) {
484
538
  let target = args.target;
485
539
  if (!target) {
486
- const result = await p8.select({
540
+ const result = await p9.select({
487
541
  message: "Which dashboard?",
488
542
  options: Object.entries(DASHBOARDS).map(([value, d]) => ({
489
543
  value,
@@ -491,43 +545,43 @@ var open_default = defineCommand5({
491
545
  hint: d.url
492
546
  }))
493
547
  });
494
- if (p8.isCancel(result)) {
495
- p8.cancel("Cancelled.");
548
+ if (p9.isCancel(result)) {
549
+ p9.cancel("Cancelled.");
496
550
  process.exit(0);
497
551
  }
498
552
  target = result;
499
553
  }
500
554
  const dashboard = DASHBOARDS[target];
501
555
  if (!dashboard) {
502
- p8.log.error(`Unknown dashboard "${target}". Options: ${Object.keys(DASHBOARDS).join(", ")}`);
556
+ p9.log.error(`Unknown dashboard "${target}". Options: ${Object.keys(DASHBOARDS).join(", ")}`);
503
557
  process.exit(1);
504
558
  }
505
559
  openUrl(dashboard.url);
506
560
  console.log(`
507
- Opening ${pc6.bold(dashboard.name)} \u2192 ${pc6.cyan(dashboard.url)}
561
+ Opening ${pc7.bold(dashboard.name)} \u2192 ${pc7.cyan(dashboard.url)}
508
562
  `);
509
563
  }
510
564
  });
511
565
 
512
566
  // src/commands/upgrade.ts
513
- import * as p9 from "@clack/prompts";
514
- import pc7 from "picocolors";
515
- import { defineCommand as defineCommand6 } from "citty";
516
- var upgrade_default = defineCommand6({
567
+ import * as p10 from "@clack/prompts";
568
+ import pc8 from "picocolors";
569
+ import { defineCommand as defineCommand7 } from "citty";
570
+ var upgrade_default = defineCommand7({
517
571
  meta: {
518
572
  name: "upgrade",
519
573
  description: "Update whop-kit to the latest version in your project"
520
574
  },
521
575
  async run() {
522
576
  console.log("");
523
- p9.intro(`${pc7.bgCyan(pc7.black(" whop-kit upgrade "))}`);
577
+ p10.intro(`${pc8.bgCyan(pc8.black(" whop-kit upgrade "))}`);
524
578
  const manifest = readManifest(".");
525
579
  if (!manifest) {
526
- p9.log.error("No .whop/config.json found. Are you in a whop-kit project?");
580
+ p10.log.error("No .whop/config.json found. Are you in a whop-kit project?");
527
581
  process.exit(1);
528
582
  }
529
583
  const pm = detectPackageManager();
530
- const s = p9.spinner();
584
+ const s = p10.spinner();
531
585
  s.start("Checking for updates...");
532
586
  const latest = exec("npm view whop-kit version");
533
587
  s.stop(latest.success ? `Latest: whop-kit@${latest.stdout}` : "Could not check latest version");
@@ -535,20 +589,20 @@ var upgrade_default = defineCommand6({
535
589
  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";
536
590
  const result = exec(cmd);
537
591
  if (result.success) {
538
- s.stop(pc7.green("whop-kit upgraded"));
592
+ s.stop(pc8.green("whop-kit upgraded"));
539
593
  } else {
540
- s.stop(pc7.red("Upgrade failed"));
541
- p9.log.error("Try running manually: " + pc7.bold(cmd));
594
+ s.stop(pc8.red("Upgrade failed"));
595
+ p10.log.error("Try running manually: " + pc8.bold(cmd));
542
596
  }
543
- p9.outro("Done");
597
+ p10.outro("Done");
544
598
  }
545
599
  });
546
600
 
547
601
  // src/cli-kit.ts
548
- var main = defineCommand7({
602
+ var main = defineCommand8({
549
603
  meta: {
550
604
  name: "whop-kit",
551
- version: "0.5.0",
605
+ version: "0.6.0",
552
606
  description: "Manage your Whop project"
553
607
  },
554
608
  subCommands: {
@@ -556,6 +610,7 @@ var main = defineCommand7({
556
610
  status: status_default,
557
611
  env: env_default,
558
612
  catalog: catalog_default,
613
+ deploy: deploy_default,
559
614
  open: open_default,
560
615
  upgrade: upgrade_default
561
616
  }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runDeployPipeline
4
+ } from "./chunk-NRG6LIOM.js";
5
+ import "./chunk-42L7PRMT.js";
6
+ export {
7
+ runDeployPipeline
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-whop-kit",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Scaffold and manage Whop-powered apps with whop-kit",
5
5
  "type": "module",
6
6
  "license": "MIT",