create-whop-kit 0.1.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.
- package/README.md +42 -0
- package/dist/index.js +209 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# create-whop-kit
|
|
2
|
+
|
|
3
|
+
Scaffold a new [Whop](https://whop.com)-powered app with [whop-kit](https://www.npmjs.com/package/whop-kit).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-whop-kit my-app
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
You'll be prompted to choose:
|
|
12
|
+
|
|
13
|
+
1. **What you're building** — SaaS, Course, Community, or Blank
|
|
14
|
+
2. **Framework** — Next.js (more coming soon)
|
|
15
|
+
3. **Database** — Neon, Supabase, Local PostgreSQL, or configure later
|
|
16
|
+
|
|
17
|
+
The CLI clones the template, installs dependencies, configures your `.env.local`, and initializes a git repo.
|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Provide the project name as an argument
|
|
23
|
+
npx create-whop-kit my-app
|
|
24
|
+
|
|
25
|
+
# Or run interactively
|
|
26
|
+
npx create-whop-kit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Templates
|
|
30
|
+
|
|
31
|
+
| Template | Framework | Status |
|
|
32
|
+
|----------|-----------|--------|
|
|
33
|
+
| SaaS | Next.js | Available |
|
|
34
|
+
| SaaS | Astro | Coming soon |
|
|
35
|
+
| SaaS | TanStack Start | Coming soon |
|
|
36
|
+
| Course | — | Coming soon |
|
|
37
|
+
| Community | — | Coming soon |
|
|
38
|
+
| Blank | — | Coming soon |
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-whop-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a new Whop-powered app with whop-kit",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Colin McDermott",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/colinmcdermott/create-whop-kit"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-whop-kit": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"templates",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@clack/prompts": "^0.10.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsup": "^8.4.0",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
|
+
}
|
|
34
|
+
}
|