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.
Files changed (3) hide show
  1. package/README.md +42 -0
  2. package/dist/index.js +209 -0
  3. 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
+ }