create-whop-kit 0.7.0 → 0.8.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.
|
@@ -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 p3 from "@clack/prompts";
|
|
11
|
+
import pc3 from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/deploy/vercel.ts
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
@@ -147,6 +147,67 @@ function vercelEnvSetBatch(vars, projectDir) {
|
|
|
147
147
|
return { success, failed };
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// src/deploy/github.ts
|
|
151
|
+
import * as p2 from "@clack/prompts";
|
|
152
|
+
import pc2 from "picocolors";
|
|
153
|
+
function isGhInstalled() {
|
|
154
|
+
return hasCommand("gh");
|
|
155
|
+
}
|
|
156
|
+
function isGhAuthenticated() {
|
|
157
|
+
const result = exec("gh auth status");
|
|
158
|
+
return result.success;
|
|
159
|
+
}
|
|
160
|
+
async function installGh() {
|
|
161
|
+
const s = p2.spinner();
|
|
162
|
+
s.start("Installing GitHub CLI...");
|
|
163
|
+
const result = exec("npm install -g gh", void 0, 6e4);
|
|
164
|
+
if (result.success && hasCommand("gh")) {
|
|
165
|
+
s.stop("GitHub CLI installed");
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
s.stop("Could not auto-install GitHub CLI");
|
|
169
|
+
p2.log.info("Install manually:");
|
|
170
|
+
p2.log.info(pc2.bold(" https://cli.github.com"));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
async function ghLogin() {
|
|
174
|
+
p2.log.info("You'll be redirected to GitHub to sign in.");
|
|
175
|
+
console.log("");
|
|
176
|
+
const ok = execInteractive("gh auth login --web");
|
|
177
|
+
console.log("");
|
|
178
|
+
return ok;
|
|
179
|
+
}
|
|
180
|
+
async function createGitHubRepo(projectDir, projectName) {
|
|
181
|
+
const s = p2.spinner();
|
|
182
|
+
s.start("Creating private GitHub repository...");
|
|
183
|
+
const result = exec(
|
|
184
|
+
`gh repo create ${projectName} --private --source=. --push`,
|
|
185
|
+
projectDir,
|
|
186
|
+
6e4
|
|
187
|
+
);
|
|
188
|
+
if (!result.success) {
|
|
189
|
+
s.stop("Could not create repo");
|
|
190
|
+
const stderr = result.stderr || result.stdout;
|
|
191
|
+
if (stderr.includes("already exists")) {
|
|
192
|
+
p2.log.warning(`Repository "${projectName}" already exists on GitHub.`);
|
|
193
|
+
exec(`git remote add origin https://github.com/$(gh api user --jq .login)/${projectName}.git`, projectDir);
|
|
194
|
+
const pushResult = exec("git push -u origin main", projectDir, 3e4);
|
|
195
|
+
if (pushResult.success) {
|
|
196
|
+
const remote = exec("gh repo view --json url --jq .url", projectDir);
|
|
197
|
+
return remote.success ? remote.stdout.trim() : null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const repoUrl = exec("gh repo view --json url --jq .url", projectDir);
|
|
203
|
+
if (repoUrl.success) {
|
|
204
|
+
s.stop(`GitHub repo created: ${pc2.cyan(repoUrl.stdout.trim())}`);
|
|
205
|
+
return repoUrl.stdout.trim();
|
|
206
|
+
}
|
|
207
|
+
s.stop("GitHub repo created");
|
|
208
|
+
return `https://github.com/${projectName}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
150
211
|
// src/deploy/whop-api.ts
|
|
151
212
|
var WHOP_API = "https://api.whop.com/api/v1";
|
|
152
213
|
function headers(apiKey) {
|
|
@@ -233,84 +294,125 @@ function openUrl(url) {
|
|
|
233
294
|
}
|
|
234
295
|
async function runDeployPipeline(options) {
|
|
235
296
|
const { projectDir, projectName, databaseUrl, framework } = options;
|
|
236
|
-
|
|
237
|
-
if (!
|
|
297
|
+
p3.log.info(pc3.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"));
|
|
298
|
+
if (!isGhInstalled()) {
|
|
299
|
+
const install = await p3.confirm({
|
|
300
|
+
message: "GitHub CLI (gh) not found. Install it?",
|
|
301
|
+
initialValue: true
|
|
302
|
+
});
|
|
303
|
+
if (p3.isCancel(install) || !install) {
|
|
304
|
+
p3.log.warning("Skipping GitHub. Deploy will upload directly (no auto-deploy on push).");
|
|
305
|
+
} else {
|
|
306
|
+
await installGh();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
let githubRepoUrl = null;
|
|
310
|
+
if (isGhInstalled()) {
|
|
311
|
+
if (!isGhAuthenticated()) {
|
|
312
|
+
const loginOk = await ghLogin();
|
|
313
|
+
if (!loginOk) {
|
|
314
|
+
p3.log.warning("GitHub auth failed. Skipping GitHub repo creation.");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (isGhAuthenticated()) {
|
|
318
|
+
githubRepoUrl = await createGitHubRepo(projectDir, projectName);
|
|
319
|
+
if (githubRepoUrl) {
|
|
320
|
+
p3.log.success(`Code pushed to ${pc3.cyan(githubRepoUrl)}`);
|
|
321
|
+
} else {
|
|
322
|
+
p3.log.warning("Could not create GitHub repo. Continuing without it.");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
p3.log.info(pc3.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"));
|
|
327
|
+
const vercelOk = await installOrUpdateVercel();
|
|
328
|
+
if (!vercelOk) return null;
|
|
238
329
|
if (!isVercelAuthenticated()) {
|
|
239
330
|
const loginOk = await vercelLogin();
|
|
240
331
|
if (!loginOk) {
|
|
241
|
-
|
|
332
|
+
p3.log.error("Vercel auth failed. Run " + pc3.bold("whop-kit deploy") + " later.");
|
|
242
333
|
return null;
|
|
243
334
|
}
|
|
244
335
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
336
|
+
const vercelUser = getVercelUser();
|
|
337
|
+
p3.log.success(`Signed in${vercelUser ? ` as ${pc3.bold(vercelUser)}` : ""}`);
|
|
247
338
|
const linkOk = await vercelLink(projectDir);
|
|
248
339
|
if (!linkOk) {
|
|
249
|
-
|
|
340
|
+
p3.log.warning("Could not link project.");
|
|
341
|
+
}
|
|
342
|
+
if (githubRepoUrl) {
|
|
343
|
+
const s2 = p3.spinner();
|
|
344
|
+
s2.start("Connecting GitHub repo to Vercel (enables auto-deploy on push)...");
|
|
345
|
+
const connectResult = exec(
|
|
346
|
+
`vercel git connect ${githubRepoUrl}`,
|
|
347
|
+
projectDir,
|
|
348
|
+
3e4
|
|
349
|
+
);
|
|
350
|
+
if (connectResult.success) {
|
|
351
|
+
s2.stop("GitHub connected \u2014 future pushes will auto-deploy");
|
|
352
|
+
} else {
|
|
353
|
+
s2.stop("Could not auto-connect GitHub (connect manually in Vercel dashboard)");
|
|
354
|
+
}
|
|
250
355
|
}
|
|
251
356
|
if (databaseUrl) {
|
|
252
|
-
const s2 =
|
|
253
|
-
s2.start("
|
|
357
|
+
const s2 = p3.spinner();
|
|
358
|
+
s2.start("Setting DATABASE_URL on Vercel...");
|
|
254
359
|
vercelEnvSet("DATABASE_URL", databaseUrl, "production", projectDir);
|
|
255
360
|
vercelEnvSet("DATABASE_URL", databaseUrl, "preview", projectDir);
|
|
256
361
|
vercelEnvSet("DATABASE_URL", databaseUrl, "development", projectDir);
|
|
257
|
-
s2.stop("DATABASE_URL
|
|
362
|
+
s2.stop("DATABASE_URL configured");
|
|
258
363
|
}
|
|
259
364
|
const productionUrl = await vercelDeploy(projectDir);
|
|
260
365
|
if (!productionUrl) {
|
|
261
|
-
|
|
262
|
-
p2.log.info(pc2.bold(` cd ${projectName} && vercel deploy --prod`));
|
|
366
|
+
p3.log.error("Deployment failed. Try: " + pc3.bold(`cd ${projectName} && vercel deploy --prod`));
|
|
263
367
|
return null;
|
|
264
368
|
}
|
|
265
|
-
|
|
369
|
+
p3.log.info(pc3.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"));
|
|
370
|
+
const connectWhop = await p3.confirm({
|
|
266
371
|
message: "Connect to Whop? (creates OAuth app + webhooks automatically)",
|
|
267
372
|
initialValue: true
|
|
268
373
|
});
|
|
269
|
-
if (
|
|
374
|
+
if (p3.isCancel(connectWhop) || !connectWhop) {
|
|
270
375
|
return { productionUrl };
|
|
271
376
|
}
|
|
272
|
-
|
|
273
|
-
p2.note(
|
|
377
|
+
p3.note(
|
|
274
378
|
[
|
|
275
|
-
`${
|
|
276
|
-
` ${
|
|
379
|
+
`${pc3.bold("1.")} Go to the Whop Developer Dashboard`,
|
|
380
|
+
` ${pc3.cyan("https://whop.com/dashboard/developer")}`,
|
|
277
381
|
"",
|
|
278
|
-
`${
|
|
382
|
+
`${pc3.bold("2.")} Click ${pc3.bold('"Create"')} under "Company API Keys"`,
|
|
279
383
|
"",
|
|
280
|
-
`${
|
|
384
|
+
`${pc3.bold("3.")} Name it anything (e.g. "${projectName}")`,
|
|
281
385
|
"",
|
|
282
|
-
`${
|
|
283
|
-
` ${
|
|
284
|
-
` ${
|
|
285
|
-
` ${
|
|
386
|
+
`${pc3.bold("4.")} Select these permissions:`,
|
|
387
|
+
` ${pc3.green("\u2022")} developer:create_app`,
|
|
388
|
+
` ${pc3.green("\u2022")} developer:manage_api_key`,
|
|
389
|
+
` ${pc3.green("\u2022")} developer:manage_webhook`,
|
|
286
390
|
"",
|
|
287
|
-
`${
|
|
391
|
+
`${pc3.bold("5.")} Create the key and paste it below`
|
|
288
392
|
].join("\n"),
|
|
289
393
|
"Create a Company API Key"
|
|
290
394
|
);
|
|
291
395
|
openUrl("https://whop.com/dashboard/developer");
|
|
292
396
|
let apiKey = options.whopCompanyKey ?? "";
|
|
293
397
|
if (!apiKey) {
|
|
294
|
-
const result = await
|
|
398
|
+
const result = await p3.text({
|
|
295
399
|
message: "Paste your Company API key",
|
|
296
400
|
placeholder: "paste the key here...",
|
|
297
401
|
validate: (v) => !v ? "API key is required" : void 0
|
|
298
402
|
});
|
|
299
|
-
if (
|
|
403
|
+
if (p3.isCancel(result)) return { productionUrl };
|
|
300
404
|
apiKey = result;
|
|
301
405
|
}
|
|
302
|
-
const s =
|
|
406
|
+
const s = p3.spinner();
|
|
303
407
|
s.start("Validating API key...");
|
|
304
408
|
const keyValid = await validateApiKey(apiKey);
|
|
305
409
|
if (!keyValid) {
|
|
306
410
|
s.stop("Invalid API key");
|
|
307
|
-
|
|
308
|
-
p2.log.info(" developer:create_app, developer:manage_api_key, developer:manage_webhook");
|
|
309
|
-
p2.log.info(` Dashboard: ${pc2.cyan("https://whop.com/dashboard/developer")}`);
|
|
411
|
+
p3.log.error("Check permissions: developer:create_app, developer:manage_api_key, developer:manage_webhook");
|
|
310
412
|
return { productionUrl };
|
|
311
413
|
}
|
|
312
414
|
s.stop("API key valid");
|
|
313
|
-
const callbackPath =
|
|
415
|
+
const callbackPath = "/api/auth/callback";
|
|
314
416
|
const redirectUris = [
|
|
315
417
|
`http://localhost:3000${callbackPath}`,
|
|
316
418
|
`${productionUrl}${callbackPath}`
|
|
@@ -318,19 +420,19 @@ async function runDeployPipeline(options) {
|
|
|
318
420
|
s.start("Creating Whop OAuth app...");
|
|
319
421
|
const app = await createWhopApp(apiKey, projectName, redirectUris);
|
|
320
422
|
if (!app) {
|
|
321
|
-
s.stop("Failed to create
|
|
322
|
-
|
|
423
|
+
s.stop("Failed to create app");
|
|
424
|
+
p3.log.error("Create manually at: " + pc3.cyan("https://whop.com/dashboard/developer"));
|
|
323
425
|
return { productionUrl };
|
|
324
426
|
}
|
|
325
|
-
s.stop(`
|
|
427
|
+
s.stop(`OAuth app created: ${pc3.bold(app.id)}`);
|
|
326
428
|
const webhookUrl = `${productionUrl}/api/webhooks/whop`;
|
|
327
429
|
s.start("Creating webhook endpoint...");
|
|
328
430
|
const webhook = await createWhopWebhook(apiKey, webhookUrl, WEBHOOK_EVENTS);
|
|
329
431
|
if (!webhook) {
|
|
330
432
|
s.stop("Failed to create webhook");
|
|
331
|
-
|
|
433
|
+
p3.log.warning("Create manually in the Whop dashboard.");
|
|
332
434
|
} else {
|
|
333
|
-
s.stop("Webhook
|
|
435
|
+
s.stop("Webhook created");
|
|
334
436
|
}
|
|
335
437
|
const envVars = {};
|
|
336
438
|
if (framework === "nextjs") {
|
|
@@ -342,22 +444,19 @@ async function runDeployPipeline(options) {
|
|
|
342
444
|
if (webhook?.secret) {
|
|
343
445
|
envVars["WHOP_WEBHOOK_SECRET"] = webhook.secret;
|
|
344
446
|
}
|
|
345
|
-
s.start("Pushing credentials to Vercel...");
|
|
447
|
+
s.start("Pushing Whop credentials to Vercel...");
|
|
346
448
|
const { success, failed } = vercelEnvSetBatch(envVars, projectDir);
|
|
347
449
|
if (failed.length > 0) {
|
|
348
|
-
s.stop(
|
|
349
|
-
p2.log.warning(`Failed to push: ${failed.join(", ")}. Add them manually in Vercel dashboard.`);
|
|
450
|
+
s.stop(`${success.length} pushed, ${failed.length} failed`);
|
|
350
451
|
} else {
|
|
351
|
-
s.stop(
|
|
452
|
+
s.stop("Credentials pushed to Vercel");
|
|
352
453
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (redeployOk) {
|
|
358
|
-
p2.log.success("Redeployed with full configuration");
|
|
454
|
+
s.start("Redeploying with full configuration...");
|
|
455
|
+
const redeployResult = exec("vercel deploy --prod --yes", projectDir, 3e5);
|
|
456
|
+
if (redeployResult.success) {
|
|
457
|
+
s.stop("Redeployed successfully");
|
|
359
458
|
} else {
|
|
360
|
-
|
|
459
|
+
s.stop("Redeploy pending \u2014 will apply on next git push");
|
|
361
460
|
}
|
|
362
461
|
return {
|
|
363
462
|
productionUrl,
|
package/dist/cli-create.js
CHANGED
|
@@ -675,7 +675,7 @@ var init_default = defineCommand({
|
|
|
675
675
|
})();
|
|
676
676
|
if (shouldDeploy) {
|
|
677
677
|
deployAttempted = true;
|
|
678
|
-
const { runDeployPipeline } = await import("./deploy-
|
|
678
|
+
const { runDeployPipeline } = await import("./deploy-4GLNQM3I.js");
|
|
679
679
|
deployResult = await runDeployPipeline({
|
|
680
680
|
projectDir,
|
|
681
681
|
projectName,
|
package/dist/cli-kit.js
CHANGED