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 CHANGED
@@ -1,41 +1,104 @@
1
1
  # create-whop-kit
2
2
 
3
- Scaffold a new [Whop](https://whop.com)-powered app with [whop-kit](https://www.npmjs.com/package/whop-kit).
3
+ Scaffold and manage [Whop](https://whop.com)-powered apps with [whop-kit](https://www.npmjs.com/package/whop-kit).
4
4
 
5
- ## Usage
5
+ ## Create a new project
6
6
 
7
7
  ```bash
8
8
  npx create-whop-kit my-app
9
9
  ```
10
10
 
11
- You'll be prompted to choose:
11
+ Interactive prompts guide you through:
12
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
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 the template, installs dependencies, configures your `.env.local`, and initializes a git repo.
18
+ The CLI clones a template, provisions your database, writes `.env.local`, installs dependencies, and initializes git.
18
19
 
19
- ## Options
20
+ ### Non-interactive mode
20
21
 
21
22
  ```bash
22
- # Provide the project name as an argument
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
- # Or run interactively
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
- | 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 |
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,
@@ -3,8 +3,9 @@ import {
3
3
  createManifest,
4
4
  detectPackageManager,
5
5
  exec,
6
+ execInteractive,
6
7
  hasCommand
7
- } from "./chunk-M4AXERQP.js";
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 TEMPLATES = {
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
- p.log.info(`Running ${pc.bold(`${cli} auth`)} \u2014 this will open your browser.`);
118
- const authResult = exec(`${cli} auth`);
119
- if (!authResult.success) {
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
- const s = p.spinner();
126
- s.start(`Creating Neon project "${projectName}"...`);
127
- const createResult = exec(
128
- `${cli} projects create --name "${projectName}" --set-context --output json`
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 (!createResult.success) {
131
- s.stop("Failed to create Neon project");
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
- s.stop("Neon project created");
136
- s.start("Getting connection string...");
137
- const connResult = exec(`${cli} connection-string --prisma`);
138
- if (!connResult.success) {
139
- s.stop("Could not retrieve connection string");
140
- p.log.error("Get it from: https://console.neon.tech");
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: connResult.stdout,
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(TEMPLATES).map(([value, t]) => ({
581
+ options: Object.entries(FRAMEWORKS).map(([value, f]) => ({
550
582
  value,
551
- label: t.available ? t.name : `${t.name} ${pc5.dim("(coming soon)")}`,
552
- hint: t.description,
553
- disabled: !t.available
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 = TEMPLATES[framework];
594
+ const template = getTemplate(appType, framework);
563
595
  if (!template || !template.available) {
564
- p5.log.error(`Framework "${framework}" is not available. Options: ${Object.keys(TEMPLATES).filter((k) => TEMPLATES[k].available).join(", ")}`);
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:")} ${DB_OPTIONS[database]?.name ?? 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("Configure via the setup wizard or .env.local")}
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
@@ -4,7 +4,7 @@ import {
4
4
  detectPackageManager,
5
5
  exec,
6
6
  readManifest
7
- } from "./chunk-M4AXERQP.js";
7
+ } from "./chunk-KO52YVBE.js";
8
8
 
9
9
  // src/cli-kit.ts
10
10
  import { defineCommand as defineCommand5, runMain } from "citty";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-whop-kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Scaffold and manage Whop-powered apps with whop-kit",
5
5
  "type": "module",
6
6
  "license": "MIT",