create-whop-kit 1.0.7 → 1.0.8
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.
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
} from "./chunk-42L7PRMT.js";
|
|
8
8
|
|
|
9
9
|
// src/deploy/index.ts
|
|
10
|
-
import * as
|
|
11
|
-
import
|
|
10
|
+
import * as p4 from "@clack/prompts";
|
|
11
|
+
import pc4 from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/deploy/vercel.ts
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
@@ -194,6 +194,8 @@ function getGitHubOrgs() {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// src/deploy/whop-api.ts
|
|
197
|
+
import * as p3 from "@clack/prompts";
|
|
198
|
+
import pc3 from "picocolors";
|
|
197
199
|
var WHOP_API = "https://api.whop.com/api/v1";
|
|
198
200
|
function headers(apiKey) {
|
|
199
201
|
return {
|
|
@@ -211,13 +213,40 @@ async function validateApiKey(apiKey) {
|
|
|
211
213
|
return false;
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
|
-
async function
|
|
216
|
+
async function getCompanyId(apiKey) {
|
|
217
|
+
try {
|
|
218
|
+
const res = await fetch(`${WHOP_API}/companies`, {
|
|
219
|
+
headers: headers(apiKey)
|
|
220
|
+
});
|
|
221
|
+
if (res.ok) {
|
|
222
|
+
const data = await res.json();
|
|
223
|
+
const companies = data.data || data;
|
|
224
|
+
if (Array.isArray(companies) && companies.length > 0) {
|
|
225
|
+
return companies[0].id;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
p3.log.info(pc3.dim("Could not detect your company ID automatically."));
|
|
231
|
+
const result = await p3.text({
|
|
232
|
+
message: "Your Whop Company ID",
|
|
233
|
+
placeholder: "biz_xxxxxxxxx (find it in your dashboard URL)",
|
|
234
|
+
validate: (v) => {
|
|
235
|
+
if (!v) return "Required";
|
|
236
|
+
if (!v.startsWith("biz_")) return 'Must start with "biz_"';
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
if (p3.isCancel(result)) return null;
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
async function createWhopApp(apiKey, name, redirectUris, companyId) {
|
|
215
243
|
try {
|
|
216
244
|
const res = await fetch(`${WHOP_API}/apps`, {
|
|
217
245
|
method: "POST",
|
|
218
246
|
headers: headers(apiKey),
|
|
219
247
|
body: JSON.stringify({
|
|
220
248
|
name,
|
|
249
|
+
company_id: companyId,
|
|
221
250
|
redirect_uris: redirectUris
|
|
222
251
|
})
|
|
223
252
|
});
|
|
@@ -233,12 +262,12 @@ async function createWhopApp(apiKey, name, redirectUris) {
|
|
|
233
262
|
return null;
|
|
234
263
|
}
|
|
235
264
|
}
|
|
236
|
-
async function createWhopWebhook(apiKey, url, events) {
|
|
265
|
+
async function createWhopWebhook(apiKey, url, events, companyId) {
|
|
237
266
|
try {
|
|
238
267
|
const res = await fetch(`${WHOP_API}/webhooks`, {
|
|
239
268
|
method: "POST",
|
|
240
269
|
headers: headers(apiKey),
|
|
241
|
-
body: JSON.stringify({ url, events })
|
|
270
|
+
body: JSON.stringify({ url, events, company_id: companyId })
|
|
242
271
|
});
|
|
243
272
|
if (res.ok) {
|
|
244
273
|
const data = await res.json();
|
|
@@ -273,7 +302,7 @@ function openUrl(url) {
|
|
|
273
302
|
}
|
|
274
303
|
async function runDeployPipeline(options) {
|
|
275
304
|
const { projectDir, projectName, databaseUrl, framework } = options;
|
|
276
|
-
const setupMode = await
|
|
305
|
+
const setupMode = await p4.select({
|
|
277
306
|
message: "How would you like to deploy?",
|
|
278
307
|
options: [
|
|
279
308
|
{
|
|
@@ -293,32 +322,32 @@ async function runDeployPipeline(options) {
|
|
|
293
322
|
}
|
|
294
323
|
]
|
|
295
324
|
});
|
|
296
|
-
if (
|
|
325
|
+
if (p4.isCancel(setupMode)) return null;
|
|
297
326
|
const useGithub = setupMode === "github-vercel" || setupMode === "github-only";
|
|
298
327
|
const useVercel = setupMode === "github-vercel" || setupMode === "vercel-only";
|
|
299
328
|
let githubRepoUrl = null;
|
|
300
329
|
let productionUrl = null;
|
|
301
330
|
if (useGithub) {
|
|
302
|
-
|
|
331
|
+
p4.log.info(pc4.bold("\n\u2500\u2500 GitHub \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
303
332
|
if (!isGhInstalled()) {
|
|
304
|
-
|
|
333
|
+
p4.log.info("The GitHub CLI (gh) is needed to create your repo.");
|
|
305
334
|
const installed = await installGh();
|
|
306
335
|
if (!installed) {
|
|
307
|
-
|
|
336
|
+
p4.log.warning("Skipping GitHub \u2014 install gh manually: https://cli.github.com");
|
|
308
337
|
}
|
|
309
338
|
}
|
|
310
339
|
if (isGhInstalled()) {
|
|
311
340
|
if (!isGhAuthenticated()) {
|
|
312
341
|
const loginOk = await ghLogin();
|
|
313
342
|
if (!loginOk) {
|
|
314
|
-
|
|
343
|
+
p4.log.warning("GitHub auth failed. Skipping repo creation.");
|
|
315
344
|
}
|
|
316
345
|
}
|
|
317
346
|
if (isGhAuthenticated()) {
|
|
318
347
|
const orgs = getGitHubOrgs();
|
|
319
348
|
let repoFullName = projectName;
|
|
320
349
|
if (orgs.length > 0) {
|
|
321
|
-
const orgChoice = await
|
|
350
|
+
const orgChoice = await p4.select({
|
|
322
351
|
message: "Which GitHub account?",
|
|
323
352
|
options: [
|
|
324
353
|
{ value: "", label: "Personal account", hint: "your personal GitHub" },
|
|
@@ -329,7 +358,7 @@ async function runDeployPipeline(options) {
|
|
|
329
358
|
}))
|
|
330
359
|
]
|
|
331
360
|
});
|
|
332
|
-
if (!
|
|
361
|
+
if (!p4.isCancel(orgChoice) && orgChoice) {
|
|
333
362
|
repoFullName = `${orgChoice}/${projectName}`;
|
|
334
363
|
}
|
|
335
364
|
}
|
|
@@ -338,24 +367,24 @@ async function runDeployPipeline(options) {
|
|
|
338
367
|
}
|
|
339
368
|
}
|
|
340
369
|
if (useVercel) {
|
|
341
|
-
|
|
370
|
+
p4.log.info(pc4.bold("\n\u2500\u2500 Vercel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
342
371
|
const vercelOk = await installOrUpdateVercel();
|
|
343
372
|
if (!vercelOk) {
|
|
344
|
-
|
|
373
|
+
p4.log.error("Could not set up Vercel CLI.");
|
|
345
374
|
return githubRepoUrl ? { productionUrl: "", githubUrl: githubRepoUrl } : null;
|
|
346
375
|
}
|
|
347
376
|
if (!isVercelAuthenticated()) {
|
|
348
377
|
const loginOk = await vercelLogin();
|
|
349
378
|
if (!loginOk) {
|
|
350
|
-
|
|
379
|
+
p4.log.error("Vercel auth failed. Run " + pc4.bold("whop-kit deploy") + " later.");
|
|
351
380
|
return githubRepoUrl ? { productionUrl: "", githubUrl: githubRepoUrl } : null;
|
|
352
381
|
}
|
|
353
382
|
}
|
|
354
383
|
const vercelUser = getVercelUser();
|
|
355
|
-
|
|
384
|
+
p4.log.success(`Signed in${vercelUser ? ` as ${pc4.bold(vercelUser)}` : ""}`);
|
|
356
385
|
await vercelLink(projectDir);
|
|
357
386
|
if (githubRepoUrl) {
|
|
358
|
-
const s =
|
|
387
|
+
const s = p4.spinner();
|
|
359
388
|
s.start("Connecting GitHub to Vercel (auto-deploy on push)...");
|
|
360
389
|
const connectResult = exec(`vercel git connect ${githubRepoUrl}`, projectDir, 3e4);
|
|
361
390
|
if (connectResult.success) {
|
|
@@ -365,40 +394,37 @@ async function runDeployPipeline(options) {
|
|
|
365
394
|
}
|
|
366
395
|
}
|
|
367
396
|
if (databaseUrl) {
|
|
368
|
-
let s =
|
|
397
|
+
let s = p4.spinner();
|
|
369
398
|
s.start("Setting DATABASE_URL \u2192 production...");
|
|
370
399
|
vercelEnvSet("DATABASE_URL", databaseUrl, "production", projectDir);
|
|
371
400
|
s.stop("DATABASE_URL \u2192 production \u2713");
|
|
372
|
-
s =
|
|
401
|
+
s = p4.spinner();
|
|
373
402
|
s.start("Setting DATABASE_URL \u2192 preview...");
|
|
374
403
|
vercelEnvSet("DATABASE_URL", databaseUrl, "preview", projectDir);
|
|
375
404
|
s.stop("DATABASE_URL \u2192 preview \u2713");
|
|
376
|
-
s =
|
|
405
|
+
s = p4.spinner();
|
|
377
406
|
s.start("Setting DATABASE_URL \u2192 development...");
|
|
378
407
|
vercelEnvSet("DATABASE_URL", databaseUrl, "development", projectDir);
|
|
379
408
|
s.stop("DATABASE_URL \u2192 development \u2713");
|
|
380
409
|
}
|
|
381
410
|
productionUrl = await vercelDeploy(projectDir);
|
|
382
411
|
if (!productionUrl) {
|
|
383
|
-
|
|
412
|
+
p4.log.error("Deploy failed. Try: " + pc4.bold(`cd ${projectName} && vercel deploy --prod`));
|
|
384
413
|
}
|
|
385
414
|
}
|
|
386
415
|
if (productionUrl) {
|
|
387
|
-
|
|
388
|
-
const connectWhop = await
|
|
416
|
+
p4.log.info(pc4.bold("\n\u2500\u2500 Whop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
417
|
+
const connectWhop = await p4.confirm({
|
|
389
418
|
message: "Connect to Whop? (creates OAuth app + webhooks automatically)",
|
|
390
419
|
initialValue: true
|
|
391
420
|
});
|
|
392
|
-
if (!
|
|
393
|
-
|
|
421
|
+
if (!p4.isCancel(connectWhop) && connectWhop) {
|
|
422
|
+
p4.note(
|
|
394
423
|
[
|
|
395
|
-
|
|
396
|
-
"",
|
|
397
|
-
`${
|
|
398
|
-
`${
|
|
399
|
-
`${pc3.bold("3.")} Give it a name (e.g. "${projectName}")`,
|
|
400
|
-
`${pc3.bold("4.")} Click Create and copy the key`,
|
|
401
|
-
`${pc3.bold("5.")} Paste it below`
|
|
424
|
+
`${pc4.bold("1.")} Go to ${pc4.cyan("https://whop.com/dashboard/developer")}`,
|
|
425
|
+
`${pc4.bold("2.")} Under "Company API Keys", click ${pc4.bold('"Create"')}`,
|
|
426
|
+
`${pc4.bold("3.")} Name it (e.g. "${projectName}"), click Create`,
|
|
427
|
+
`${pc4.bold("4.")} Copy the key and paste it below`
|
|
402
428
|
].join("\n"),
|
|
403
429
|
"Whop Company API Key"
|
|
404
430
|
);
|
|
@@ -407,18 +433,18 @@ async function runDeployPipeline(options) {
|
|
|
407
433
|
let keyValid = false;
|
|
408
434
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
409
435
|
if (!apiKey) {
|
|
410
|
-
const result = await
|
|
436
|
+
const result = await p4.text({
|
|
411
437
|
message: attempt === 0 ? "Paste your Company API key" : "Paste a new Company API key",
|
|
412
|
-
placeholder: "
|
|
413
|
-
validate: (v) => !v ? "
|
|
438
|
+
placeholder: "apik_...",
|
|
439
|
+
validate: (v) => !v ? "Required" : void 0
|
|
414
440
|
});
|
|
415
|
-
if (
|
|
441
|
+
if (p4.isCancel(result)) {
|
|
416
442
|
return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
417
443
|
}
|
|
418
444
|
apiKey = result;
|
|
419
445
|
}
|
|
420
|
-
|
|
421
|
-
s2.start("Validating
|
|
446
|
+
let s2 = p4.spinner();
|
|
447
|
+
s2.start("Validating...");
|
|
422
448
|
keyValid = await validateApiKey(apiKey);
|
|
423
449
|
if (keyValid) {
|
|
424
450
|
s2.stop("API key valid");
|
|
@@ -426,37 +452,49 @@ async function runDeployPipeline(options) {
|
|
|
426
452
|
}
|
|
427
453
|
s2.stop("API key invalid");
|
|
428
454
|
if (attempt < 2) {
|
|
429
|
-
const retry = await
|
|
430
|
-
|
|
431
|
-
initialValue: true
|
|
432
|
-
});
|
|
433
|
-
if (p3.isCancel(retry) || !retry) {
|
|
434
|
-
return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
435
|
-
}
|
|
455
|
+
const retry = await p4.confirm({ message: "Try a different key?", initialValue: true });
|
|
456
|
+
if (p4.isCancel(retry) || !retry) return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
436
457
|
apiKey = "";
|
|
437
|
-
} else {
|
|
438
|
-
p3.log.error("Could not validate. Configure Whop manually via the setup wizard.");
|
|
439
|
-
return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
440
458
|
}
|
|
441
459
|
}
|
|
442
|
-
if (!keyValid) {
|
|
443
|
-
|
|
444
|
-
}
|
|
460
|
+
if (!keyValid) return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
461
|
+
const companyId = await getCompanyId(apiKey);
|
|
462
|
+
if (!companyId) return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
445
463
|
const redirectUris = [
|
|
446
464
|
"http://localhost:3000/api/auth/callback",
|
|
447
465
|
`${productionUrl}/api/auth/callback`
|
|
448
466
|
];
|
|
449
|
-
|
|
467
|
+
let s = p4.spinner();
|
|
450
468
|
s.start("Creating Whop OAuth app...");
|
|
451
|
-
const app = await createWhopApp(apiKey, projectName, redirectUris);
|
|
469
|
+
const app = await createWhopApp(apiKey, projectName, redirectUris, companyId);
|
|
452
470
|
if (!app) {
|
|
453
471
|
s.stop("Failed to create app");
|
|
454
|
-
|
|
472
|
+
p4.log.error("Create manually in the Whop dashboard.");
|
|
455
473
|
return { productionUrl, githubUrl: githubRepoUrl ?? void 0 };
|
|
456
474
|
}
|
|
457
|
-
s.stop(`OAuth app created: ${
|
|
475
|
+
s.stop(`OAuth app created: ${pc4.bold(app.id)}`);
|
|
476
|
+
p4.note(
|
|
477
|
+
[
|
|
478
|
+
`Your app ${pc4.bold(app.id)} was created. Now get its API key:`,
|
|
479
|
+
"",
|
|
480
|
+
`${pc4.bold("1.")} Go to ${pc4.cyan("https://whop.com/dashboard/developer")}`,
|
|
481
|
+
`${pc4.bold("2.")} Click on your new app "${projectName}"`,
|
|
482
|
+
`${pc4.bold("3.")} Find ${pc4.bold("WHOP_API_KEY")} in the Environment Variables section`,
|
|
483
|
+
`${pc4.bold("4.")} Click reveal, copy it, and paste below`
|
|
484
|
+
].join("\n"),
|
|
485
|
+
"App API Key"
|
|
486
|
+
);
|
|
487
|
+
openUrl("https://whop.com/dashboard/developer");
|
|
488
|
+
let appApiKey = "";
|
|
489
|
+
const appKeyResult = await p4.text({
|
|
490
|
+
message: "Paste the App API key (WHOP_API_KEY)",
|
|
491
|
+
placeholder: "starts with apik_...",
|
|
492
|
+
validate: (v) => !v ? "Required \u2014 find it in the app's Environment Variables" : void 0
|
|
493
|
+
});
|
|
494
|
+
if (!p4.isCancel(appKeyResult)) appApiKey = appKeyResult;
|
|
495
|
+
s = p4.spinner();
|
|
458
496
|
s.start("Creating webhook...");
|
|
459
|
-
const webhook = await createWhopWebhook(apiKey, `${productionUrl}/api/webhooks/whop`, WEBHOOK_EVENTS);
|
|
497
|
+
const webhook = await createWhopWebhook(apiKey, `${productionUrl}/api/webhooks/whop`, WEBHOOK_EVENTS, companyId);
|
|
460
498
|
if (!webhook) {
|
|
461
499
|
s.stop("Failed (create manually in Whop dashboard)");
|
|
462
500
|
} else {
|
|
@@ -469,16 +507,18 @@ async function runDeployPipeline(options) {
|
|
|
469
507
|
} else {
|
|
470
508
|
envVars["WHOP_APP_ID"] = app.id;
|
|
471
509
|
}
|
|
472
|
-
envVars["WHOP_API_KEY"] =
|
|
510
|
+
if (appApiKey) envVars["WHOP_API_KEY"] = appApiKey;
|
|
473
511
|
if (webhook?.secret) envVars["WHOP_WEBHOOK_SECRET"] = webhook.secret;
|
|
474
512
|
for (const [key, value] of Object.entries(envVars)) {
|
|
475
|
-
|
|
513
|
+
if (!value) continue;
|
|
514
|
+
const vs = p4.spinner();
|
|
476
515
|
vs.start(`Pushing ${key}...`);
|
|
477
516
|
vercelEnvSet(key, value, "production", projectDir);
|
|
478
517
|
vercelEnvSet(key, value, "preview", projectDir);
|
|
479
518
|
vercelEnvSet(key, value, "development", projectDir);
|
|
480
519
|
vs.stop(`${key} \u2713`);
|
|
481
520
|
}
|
|
521
|
+
s = p4.spinner();
|
|
482
522
|
s.start("Redeploying with full configuration...");
|
|
483
523
|
const redeploy = exec("vercel deploy --prod --yes", projectDir, 3e5);
|
|
484
524
|
s.stop(redeploy.success ? "Redeployed" : "Redeploy pending \u2014 will apply on next git push");
|
|
@@ -487,7 +527,7 @@ async function runDeployPipeline(options) {
|
|
|
487
527
|
productionUrl,
|
|
488
528
|
githubUrl: githubRepoUrl ?? void 0,
|
|
489
529
|
whopAppId: app.id,
|
|
490
|
-
whopApiKey:
|
|
530
|
+
whopApiKey: appApiKey || void 0,
|
|
491
531
|
webhookSecret: webhook?.secret
|
|
492
532
|
};
|
|
493
533
|
}
|
package/dist/cli-create.js
CHANGED
|
@@ -684,7 +684,7 @@ var init_default = defineCommand({
|
|
|
684
684
|
});
|
|
685
685
|
if (!isCancelled(deployChoice) && deployChoice === "deploy") {
|
|
686
686
|
deployAttempted = true;
|
|
687
|
-
const { runDeployPipeline } = await import("./deploy-
|
|
687
|
+
const { runDeployPipeline } = await import("./deploy-HMCWU54H.js");
|
|
688
688
|
deployResult = await runDeployPipeline({
|
|
689
689
|
projectDir,
|
|
690
690
|
projectName,
|
package/dist/cli-kit.js
CHANGED