create-whop-kit 0.3.0 → 0.4.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.
- package/README.md +83 -20
- package/dist/{chunk-M4AXERQP.js → chunk-KO52YVBE.js} +9 -0
- package/dist/cli-create.js +99 -63
- package/dist/cli-kit.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,104 @@
|
|
|
1
1
|
# create-whop-kit
|
|
2
2
|
|
|
3
|
-
Scaffold
|
|
3
|
+
Scaffold and manage [Whop](https://whop.com)-powered apps with [whop-kit](https://www.npmjs.com/package/whop-kit).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Create a new project
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx create-whop-kit my-app
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Interactive prompts guide you through:
|
|
12
12
|
|
|
13
|
-
1. **What you
|
|
14
|
-
2. **
|
|
15
|
-
3. **
|
|
13
|
+
1. **What are you building?** — SaaS (full dashboard + billing) or Blank (just auth + webhooks)
|
|
14
|
+
2. **Which framework?** — Next.js or Astro
|
|
15
|
+
3. **Which database?** — Neon (auto-provisioned), Prisma Postgres (instant), Supabase, manual URL, or skip
|
|
16
|
+
4. **Whop credentials** — App ID, API key, webhook secret (optional, can use setup wizard later)
|
|
16
17
|
|
|
17
|
-
The CLI clones
|
|
18
|
+
The CLI clones a template, provisions your database, writes `.env.local`, installs dependencies, and initializes git.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
### Non-interactive mode
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
#
|
|
23
|
-
npx create-whop-kit my-app
|
|
23
|
+
# Skip all prompts
|
|
24
|
+
npx create-whop-kit my-app --framework nextjs --type saas --db neon --yes
|
|
25
|
+
|
|
26
|
+
# With credentials
|
|
27
|
+
npx create-whop-kit my-app --framework nextjs --db later --app-id "app_xxx" --api-key "apik_xxx"
|
|
24
28
|
|
|
25
|
-
#
|
|
26
|
-
npx create-whop-kit
|
|
29
|
+
# Preview without creating files
|
|
30
|
+
npx create-whop-kit my-app --framework nextjs --db later --dry-run
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
### All flags
|
|
34
|
+
|
|
35
|
+
| Flag | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `--framework` | `nextjs` or `astro` |
|
|
38
|
+
| `--type` | `saas` or `blank` (default: `saas`) |
|
|
39
|
+
| `--db` | `neon`, `prisma-postgres`, `supabase`, `manual`, `later` |
|
|
40
|
+
| `--db-url` | PostgreSQL connection URL (skips DB provisioning) |
|
|
41
|
+
| `--app-id` | Whop App ID |
|
|
42
|
+
| `--api-key` | Whop API Key |
|
|
43
|
+
| `--webhook-secret` | Whop webhook secret |
|
|
44
|
+
| `-y, --yes` | Skip optional prompts |
|
|
45
|
+
| `--dry-run` | Show what would be created |
|
|
46
|
+
| `--verbose` | Detailed output |
|
|
47
|
+
|
|
48
|
+
## Manage your project
|
|
49
|
+
|
|
50
|
+
After creating a project, use `whop-kit` to add features and check status:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Check project health
|
|
54
|
+
npx whop-kit status
|
|
55
|
+
|
|
56
|
+
# Add email (Resend or SendGrid)
|
|
57
|
+
npx whop-kit add email
|
|
58
|
+
|
|
59
|
+
# Add analytics (PostHog, Google Analytics, or Plausible)
|
|
60
|
+
npx whop-kit add analytics
|
|
61
|
+
|
|
62
|
+
# Add a webhook event handler
|
|
63
|
+
npx whop-kit add webhook-event
|
|
64
|
+
|
|
65
|
+
# Open provider dashboards
|
|
66
|
+
npx whop-kit open whop
|
|
67
|
+
npx whop-kit open neon
|
|
68
|
+
npx whop-kit open vercel
|
|
69
|
+
|
|
70
|
+
# Update whop-kit to latest
|
|
71
|
+
npx whop-kit upgrade
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Database provisioning
|
|
75
|
+
|
|
76
|
+
The CLI can provision databases automatically — no need to leave the terminal:
|
|
77
|
+
|
|
78
|
+
| Provider | How it works |
|
|
79
|
+
|----------|-------------|
|
|
80
|
+
| **Neon** | Installs `neonctl` → authenticates (browser) → creates project → gets connection string |
|
|
81
|
+
| **Prisma Postgres** | Runs `npx create-db` → instant database, no account needed |
|
|
82
|
+
| **Supabase** | Installs CLI → authenticates → creates project → guides you to get connection string |
|
|
83
|
+
|
|
29
84
|
## Templates
|
|
30
85
|
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
| SaaS | Next.js | Available |
|
|
34
|
-
| SaaS | Astro |
|
|
35
|
-
|
|
|
36
|
-
| Course | — | Coming soon |
|
|
37
|
-
| Community | — | Coming soon |
|
|
38
|
-
|
|
86
|
+
| App Type | Framework | Template | Status |
|
|
87
|
+
|----------|-----------|----------|--------|
|
|
88
|
+
| SaaS | Next.js | Full dashboard, pricing, billing, docs | Available |
|
|
89
|
+
| SaaS | Astro | Auth, payments, webhooks | Available |
|
|
90
|
+
| Blank | Next.js | Just auth + webhooks — build anything | Available |
|
|
91
|
+
| Course | — | — | Coming soon |
|
|
92
|
+
| Community | — | — | Coming soon |
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
1. **Template** — clones a starter repo from GitHub
|
|
97
|
+
2. **Database** — optionally provisions via provider CLI
|
|
98
|
+
3. **Environment** — writes `.env.local` from the template's `.env.example`
|
|
99
|
+
4. **Manifest** — creates `.whop/config.json` tracking your project state
|
|
100
|
+
5. **Dependencies** — installs with your preferred package manager
|
|
101
|
+
6. **Git** — initializes a fresh repo
|
|
39
102
|
|
|
40
103
|
## License
|
|
41
104
|
|
|
@@ -15,6 +15,14 @@ function exec(cmd, cwd) {
|
|
|
15
15
|
return { stdout: "", success: false };
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function execInteractive(cmd, cwd) {
|
|
19
|
+
try {
|
|
20
|
+
execSync(cmd, { cwd, stdio: "inherit", timeout: 3e5 });
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
18
26
|
function hasCommand(cmd) {
|
|
19
27
|
return exec(`which ${cmd}`).success;
|
|
20
28
|
}
|
|
@@ -65,6 +73,7 @@ function addFeatureToManifest(projectDir, feature) {
|
|
|
65
73
|
|
|
66
74
|
export {
|
|
67
75
|
exec,
|
|
76
|
+
execInteractive,
|
|
68
77
|
hasCommand,
|
|
69
78
|
detectPackageManager,
|
|
70
79
|
createManifest,
|
package/dist/cli-create.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
createManifest,
|
|
4
4
|
detectPackageManager,
|
|
5
5
|
exec,
|
|
6
|
+
execInteractive,
|
|
6
7
|
hasCommand
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KO52YVBE.js";
|
|
8
9
|
|
|
9
10
|
// src/cli-create.ts
|
|
10
11
|
import { runMain } from "citty";
|
|
@@ -17,38 +18,62 @@ import pc5 from "picocolors";
|
|
|
17
18
|
import { defineCommand } from "citty";
|
|
18
19
|
|
|
19
20
|
// src/templates.ts
|
|
20
|
-
var
|
|
21
|
+
var FRAMEWORKS = {
|
|
21
22
|
nextjs: {
|
|
22
23
|
name: "Next.js",
|
|
23
24
|
description: "Full-stack React with App Router, SSR, and API routes",
|
|
24
|
-
repo: "colinmcdermott/whop-saas-starter-v2",
|
|
25
25
|
available: true
|
|
26
26
|
},
|
|
27
27
|
astro: {
|
|
28
28
|
name: "Astro",
|
|
29
29
|
description: "Content-focused with islands architecture",
|
|
30
|
-
repo: "colinmcdermott/whop-astro-starter",
|
|
31
30
|
available: true
|
|
32
31
|
},
|
|
33
32
|
tanstack: {
|
|
34
33
|
name: "TanStack Start",
|
|
35
34
|
description: "Full-stack React with TanStack Router",
|
|
36
|
-
repo: "",
|
|
37
35
|
available: false
|
|
38
36
|
},
|
|
39
37
|
vite: {
|
|
40
38
|
name: "Vite + React",
|
|
41
39
|
description: "Lightweight SPA with Vite bundler",
|
|
42
|
-
repo: "",
|
|
43
40
|
available: false
|
|
44
41
|
}
|
|
45
42
|
};
|
|
43
|
+
var TEMPLATES = {
|
|
44
|
+
"saas:nextjs": {
|
|
45
|
+
name: "Next.js SaaS",
|
|
46
|
+
description: "Full SaaS with dashboard, pricing, billing, and docs",
|
|
47
|
+
repo: "colinmcdermott/whop-saas-starter-v2",
|
|
48
|
+
available: true
|
|
49
|
+
},
|
|
50
|
+
"saas:astro": {
|
|
51
|
+
name: "Astro SaaS",
|
|
52
|
+
description: "SaaS with auth, payments, and webhooks",
|
|
53
|
+
repo: "colinmcdermott/whop-astro-starter",
|
|
54
|
+
available: true
|
|
55
|
+
},
|
|
56
|
+
"blank:nextjs": {
|
|
57
|
+
name: "Next.js Blank",
|
|
58
|
+
description: "Just auth + webhooks \u2014 build anything",
|
|
59
|
+
repo: "colinmcdermott/whop-blank-starter",
|
|
60
|
+
available: true
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function getTemplate(appType, framework) {
|
|
64
|
+
return TEMPLATES[`${appType}:${framework}`] ?? null;
|
|
65
|
+
}
|
|
46
66
|
var APP_TYPES = {
|
|
47
67
|
saas: {
|
|
48
68
|
name: "SaaS",
|
|
49
69
|
description: "Subscription tiers, dashboard, billing portal",
|
|
50
70
|
available: true
|
|
51
71
|
},
|
|
72
|
+
blank: {
|
|
73
|
+
name: "Blank",
|
|
74
|
+
description: "Just auth + payments, you build the rest",
|
|
75
|
+
available: true
|
|
76
|
+
},
|
|
52
77
|
course: {
|
|
53
78
|
name: "Course",
|
|
54
79
|
description: "Lessons, progress tracking, drip content",
|
|
@@ -58,33 +83,6 @@ var APP_TYPES = {
|
|
|
58
83
|
name: "Community",
|
|
59
84
|
description: "Member feeds, gated content, roles",
|
|
60
85
|
available: false
|
|
61
|
-
},
|
|
62
|
-
blank: {
|
|
63
|
-
name: "Blank",
|
|
64
|
-
description: "Just auth + payments, you build the rest",
|
|
65
|
-
available: false
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
var DB_OPTIONS = {
|
|
69
|
-
neon: {
|
|
70
|
-
name: "Neon",
|
|
71
|
-
description: "Serverless Postgres (recommended)",
|
|
72
|
-
envVarHint: "postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require"
|
|
73
|
-
},
|
|
74
|
-
supabase: {
|
|
75
|
-
name: "Supabase",
|
|
76
|
-
description: "Open-source Firebase alternative",
|
|
77
|
-
envVarHint: "postgresql://postgres.xxx:pass@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
|
|
78
|
-
},
|
|
79
|
-
local: {
|
|
80
|
-
name: "Local PostgreSQL",
|
|
81
|
-
description: "Your own Postgres instance",
|
|
82
|
-
envVarHint: "postgresql://postgres:postgres@localhost:5432/myapp"
|
|
83
|
-
},
|
|
84
|
-
later: {
|
|
85
|
-
name: "Configure later",
|
|
86
|
-
description: "Skip database setup for now",
|
|
87
|
-
envVarHint: ""
|
|
88
86
|
}
|
|
89
87
|
};
|
|
90
88
|
|
|
@@ -111,38 +109,72 @@ var neonProvider = {
|
|
|
111
109
|
},
|
|
112
110
|
async provision(projectName) {
|
|
113
111
|
const cli = hasCommand("neonctl") ? "neonctl" : "neon";
|
|
114
|
-
const whoami = exec(`${cli} me`);
|
|
112
|
+
const whoami = exec(`${cli} me --output json`);
|
|
115
113
|
if (!whoami.success) {
|
|
116
|
-
p.log.info("You need to authenticate with Neon.");
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
114
|
+
p.log.info("You need to authenticate with Neon. This will open your browser.");
|
|
115
|
+
console.log("");
|
|
116
|
+
const authOk = execInteractive(`${cli} auth`);
|
|
117
|
+
if (!authOk) {
|
|
120
118
|
p.log.error("Neon authentication failed. Try running manually:");
|
|
121
119
|
p.log.info(pc.bold(` ${cli} auth`));
|
|
122
120
|
return null;
|
|
123
121
|
}
|
|
122
|
+
console.log("");
|
|
124
123
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
`${cli} projects create --name "${projectName}" --set-context
|
|
124
|
+
p.log.info(`Creating Neon project "${projectName}"...`);
|
|
125
|
+
console.log("");
|
|
126
|
+
const createOk = execInteractive(
|
|
127
|
+
`${cli} projects create --name "${projectName}" --set-context`
|
|
129
128
|
);
|
|
130
|
-
if (!
|
|
131
|
-
|
|
132
|
-
p.log.error("Try creating manually at https://console.neon.tech");
|
|
129
|
+
if (!createOk) {
|
|
130
|
+
p.log.error("Failed to create Neon project. Try manually at https://console.neon.tech");
|
|
133
131
|
return null;
|
|
134
132
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
console.log("");
|
|
134
|
+
p.log.success("Neon project created");
|
|
135
|
+
let connString = "";
|
|
136
|
+
const connResult = exec(`${cli} connection-string --prisma --output json`);
|
|
137
|
+
if (connResult.success) {
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(connResult.stdout);
|
|
140
|
+
connString = parsed.connection_string || parsed.connectionString || connResult.stdout;
|
|
141
|
+
} catch {
|
|
142
|
+
connString = connResult.stdout.trim();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!connString) {
|
|
146
|
+
const fallback = exec(`${cli} connection-string --prisma`);
|
|
147
|
+
if (fallback.success && fallback.stdout.startsWith("postgres")) {
|
|
148
|
+
connString = fallback.stdout.trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!connString) {
|
|
152
|
+
const raw = exec(`${cli} connection-string`);
|
|
153
|
+
if (raw.success && raw.stdout.startsWith("postgres")) {
|
|
154
|
+
connString = raw.stdout.trim();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!connString) {
|
|
158
|
+
p.log.warning("Could not extract connection string automatically.");
|
|
159
|
+
console.log("");
|
|
160
|
+
execInteractive(`${cli} connection-string`);
|
|
161
|
+
console.log("");
|
|
162
|
+
const manual = await p.text({
|
|
163
|
+
message: "Paste the connection string shown above",
|
|
164
|
+
placeholder: "postgresql://...",
|
|
165
|
+
validate: (v) => {
|
|
166
|
+
if (!v?.startsWith("postgres")) return "Must be a PostgreSQL connection string";
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
if (p.isCancel(manual)) return null;
|
|
170
|
+
connString = manual;
|
|
171
|
+
}
|
|
172
|
+
if (!connString) {
|
|
173
|
+
p.log.error("Could not get connection string. Get it from: https://console.neon.tech");
|
|
141
174
|
return null;
|
|
142
175
|
}
|
|
143
|
-
s.stop("Connection string retrieved");
|
|
144
176
|
return {
|
|
145
|
-
connectionString:
|
|
177
|
+
connectionString: connString,
|
|
146
178
|
provider: "neon"
|
|
147
179
|
};
|
|
148
180
|
}
|
|
@@ -546,11 +578,11 @@ var init_default = defineCommand({
|
|
|
546
578
|
if (!framework) {
|
|
547
579
|
const result = await p5.select({
|
|
548
580
|
message: "Which framework?",
|
|
549
|
-
options: Object.entries(
|
|
581
|
+
options: Object.entries(FRAMEWORKS).map(([value, f]) => ({
|
|
550
582
|
value,
|
|
551
|
-
label:
|
|
552
|
-
hint:
|
|
553
|
-
disabled: !
|
|
583
|
+
label: f.available ? f.name : `${f.name} ${pc5.dim("(coming soon)")}`,
|
|
584
|
+
hint: f.description,
|
|
585
|
+
disabled: !f.available
|
|
554
586
|
}))
|
|
555
587
|
});
|
|
556
588
|
if (isCancelled(result)) {
|
|
@@ -559,9 +591,9 @@ var init_default = defineCommand({
|
|
|
559
591
|
}
|
|
560
592
|
framework = result;
|
|
561
593
|
}
|
|
562
|
-
const template =
|
|
594
|
+
const template = getTemplate(appType, framework);
|
|
563
595
|
if (!template || !template.available) {
|
|
564
|
-
p5.log.error(`
|
|
596
|
+
p5.log.error(`No template available for ${appType} + ${framework}. Try a different combination.`);
|
|
565
597
|
process.exit(1);
|
|
566
598
|
}
|
|
567
599
|
let database = args.db;
|
|
@@ -667,7 +699,7 @@ var init_default = defineCommand({
|
|
|
667
699
|
console.log(` ${pc5.bold("Project:")} ${projectName}`);
|
|
668
700
|
console.log(` ${pc5.bold("Framework:")} ${template.name}`);
|
|
669
701
|
console.log(` ${pc5.bold("App type:")} ${APP_TYPES[appType]?.name ?? appType}`);
|
|
670
|
-
console.log(` ${pc5.bold("Database:")} ${
|
|
702
|
+
console.log(` ${pc5.bold("Database:")} ${database}`);
|
|
671
703
|
console.log(` ${pc5.bold("Template:")} github.com/${template.repo}`);
|
|
672
704
|
if (dbUrl) console.log(` ${pc5.bold("DB URL:")} ${dbUrl.substring(0, 30)}...`);
|
|
673
705
|
if (appId) console.log(` ${pc5.bold("Whop App:")} ${appId}`);
|
|
@@ -736,7 +768,7 @@ var init_default = defineCommand({
|
|
|
736
768
|
if (missing.length > 0) {
|
|
737
769
|
summary += `${pc5.yellow("\u25CB")} Missing: ${missing.join(", ")}
|
|
738
770
|
`;
|
|
739
|
-
summary += ` ${pc5.dim("
|
|
771
|
+
summary += ` ${pc5.dim("The setup wizard at http://localhost:3000 will guide you through it")}
|
|
740
772
|
`;
|
|
741
773
|
}
|
|
742
774
|
if (dbNote) {
|
|
@@ -748,10 +780,14 @@ var init_default = defineCommand({
|
|
|
748
780
|
summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
|
|
749
781
|
`;
|
|
750
782
|
if (dbUrl) {
|
|
751
|
-
summary += ` ${pc5.bold(`${pm} run db:push`)}
|
|
783
|
+
summary += ` ${pc5.bold(`${pm} run db:push`)} ${pc5.dim("# push schema to database")}
|
|
752
784
|
`;
|
|
753
785
|
}
|
|
754
|
-
summary += ` ${pc5.bold(`${pm} run dev`)}
|
|
786
|
+
summary += ` ${pc5.bold(`${pm} run dev`)} ${pc5.dim("# start dev server")}
|
|
787
|
+
`;
|
|
788
|
+
summary += `
|
|
789
|
+
`;
|
|
790
|
+
summary += ` ${pc5.dim("Then open http://localhost:3000")}`;
|
|
755
791
|
p5.note(summary, "Your app is ready");
|
|
756
792
|
p5.outro(`${pc5.green("Happy building!")} ${pc5.dim("\u2014 whop-kit")}`);
|
|
757
793
|
}
|
package/dist/cli-kit.js
CHANGED