@superbuilds/create-supersaas-app 2.0.0 → 2.0.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.
Files changed (3) hide show
  1. package/README.md +61 -86
  2. package/dist/index.js +150 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,138 +2,113 @@
2
2
 
3
3
  > Scaffold production-ready SaaS apps in seconds
4
4
 
5
- The fastest way to get your indie SaaS off the ground. Built for serious builders who ship.
5
+ The fastest way to get your indie SaaS off the ground. One command, full source code, ready to ship.
6
6
 
7
7
  ## Quick Start
8
8
 
9
9
  ```bash
10
- npx create-supersaas-app my-saas --key YOUR_LICENSE_KEY
10
+ npx create-supersaas-app my-saas
11
11
  cd my-saas
12
- npm run dev
12
+ pnpm dev
13
+ # Open http://localhost:3000/setup to configure
13
14
  ```
14
15
 
15
16
  ## What You Get
16
17
 
17
- - **Next.js 15** + React 19 + TypeScript
18
- - **Authentication** (Better Auth) - email, OAuth, magic links
19
- - **Payments** (Stripe) - subscriptions, one-time, webhooks
20
- - **Database** (Drizzle ORM) - type-safe, migrations included
21
- - **Email** (Resend) - transactional + newsletter
22
- - **Teams** - multi-tenant with invites
23
- - **Admin Dashboard** - user management, analytics
24
- - **Beautiful UI** - Tailwind CSS v4, dark mode, responsive
25
- - **API Routes** - rate limiting, validation, error handling
18
+ - **Next.js 15** App Router + React 19 + TypeScript (strict mode)
19
+ - **Authentication** — Better Auth: email/password, magic links, 2FA, Google/GitHub/Twitter OAuth
20
+ - **4 Payment Providers** — Stripe, Polar, LemonSqueezy, DodoPayments (switch with one env var)
21
+ - **5 Email Providers** — Resend, Postmark, SendGrid, Mailgun, Nodemailer/SMTP
22
+ - **Database** — Drizzle ORM on PostgreSQL (Neon, Supabase, or local Docker)
23
+ - **Teams** — multi-tenant orgs with invites, owner/admin/member roles
24
+ - **Admin Dashboard** — user management, analytics, subscriptions, leads, feature flags
25
+ - **13 Themes** — complete UI vibes, switch with one line in globals.css
26
+ - **Blog + Changelog** — MDX with syntax highlighting, RSS, reading time
27
+ - **Rate Limiting** — Upstash Redis middleware
28
+ - **i18n** — locale detection, server + client translations
29
+ - **Setup Wizard** — browser-based `/setup` generates your .env in 2 minutes
30
+ - **AI-Ready** — ships with CLAUDE.md + AGENTS.md for Claude Code, Cursor, Copilot, Windsurf
26
31
 
27
32
  ## Usage
28
33
 
29
- ### Interactive Mode (Recommended)
34
+ ### Interactive Mode
30
35
 
31
36
  ```bash
32
37
  npx create-supersaas-app
33
38
  ```
34
39
 
35
40
  You'll be prompted for:
36
- - App name
37
- - Database provider (Neon, Supabase, PlanetScale, SQLite)
38
- - Optional features (blog, AI routes, admin, waitlist)
41
+ 1. License key (from your purchase email)
42
+ 2. App name
43
+ 3. Database provider (Neon, Supabase, or local Docker Postgres)
44
+ 4. Optional features (blog, admin dashboard)
45
+ 5. Install dependencies (yes/no)
39
46
 
40
47
  ### Non-Interactive Mode
41
48
 
42
- Perfect for CI/CD or automation:
49
+ For CI/CD, automation, or AI agents:
43
50
 
44
51
  ```bash
45
52
  npx create-supersaas-app my-saas \
46
53
  --key YOUR_LICENSE_KEY \
47
54
  --db neon \
48
- --features blog,ai,admin \
55
+ --features blog,admin \
56
+ --install \
49
57
  --yes
50
58
  ```
51
59
 
52
- ## Options
60
+ ### Dev Mode
53
61
 
54
- | Flag | Description | Example |
55
- |------|-------------|---------|
56
- | `--key` | License key (required) | `--key lic_abc123` |
57
- | `--db` | Database provider | `--db neon` |
58
- | `--features` | Comma-separated features | `--features blog,ai` |
59
- | `--yes` | Skip prompts, use defaults | `--yes` |
62
+ For local development without a license:
60
63
 
61
- ## Features
62
-
63
- ### Core (All Tiers)
64
-
65
- - Authentication (email + OAuth)
66
- - Database with migrations
67
- - Payment processing
68
- - Email delivery
69
- - Team management
70
- - API routes
71
-
72
- ### Pro Tier
73
-
74
- - AI routes (OpenAI/Anthropic)
75
- - Admin dashboard
76
- - Advanced themes
77
- - Priority support
78
-
79
- ### Bundle Tier
64
+ ```bash
65
+ npx create-supersaas-app my-saas --key dev
66
+ ```
80
67
 
81
- - Everything in Pro
82
- - Lifetime updates
83
- - Team licenses
84
- - Early access to new features
68
+ ## CLI Options
85
69
 
86
- ## Database Options
70
+ | Flag | Description | Values |
71
+ |------|-------------|--------|
72
+ | `--key <key>` | License key | Your key from purchase email |
73
+ | `--db <provider>` | Database provider | `neon`, `supabase`, `local` |
74
+ | `--features <list>` | Optional features | `blog`, `admin` (comma-separated) |
75
+ | `--install` | Install dependencies after scaffolding | — |
76
+ | `--no-git` | Skip git repository initialization | — |
77
+ | `--yes` / `--defaults` | Skip prompts, use defaults | — |
87
78
 
88
- - **Neon** (Postgres) - Recommended, generous free tier
89
- - **Supabase** (Postgres) - Auth + DB + Storage
90
- - **PlanetScale** (MySQL) - Serverless MySQL
91
- - **SQLite** - Local development
79
+ ## Deactivate License
92
80
 
93
- ## After Installation
81
+ Free up a machine activation slot:
94
82
 
95
83
  ```bash
96
- cd your-app
97
-
98
- # 1. Set up environment variables
99
- cp .env.example .env.local
100
- # Edit .env.local with your keys
101
-
102
- # 2. Run migrations
103
- npm run db:push
104
-
105
- # 3. Start dev server
106
- npm run dev
84
+ npx create-supersaas-app deactivate
107
85
  ```
108
86
 
109
- Visit `http://localhost:3000`
110
-
111
- ## CLI Commands (built-in)
112
-
113
- Once your app is created, use the built-in CLI from your project directory:
87
+ ## After Scaffolding
114
88
 
115
89
  ```bash
116
- npx supersaas setup stripe
117
- npx supersaas add blog
118
- npx supersaas deploy
90
+ cd my-saas
91
+ pnpm dev # deps already installed if you said yes during setup
92
+ # Open http://localhost:3000/setup — the setup wizard walks you through everything
119
93
  ```
120
94
 
121
- ## License
122
-
123
- You need a valid supersaas-kit license to use this. Get yours at [supersaas-kit.superbuilds.fun](https://supersaas-kit.superbuilds.fun)
95
+ The setup wizard configures:
96
+ - Database connection
97
+ - Auth secrets
98
+ - Payment provider
99
+ - Email provider
100
+ - App identity
124
101
 
125
- ## Support
102
+ After setup, manage runtime config at `/admin/settings`.
126
103
 
127
- - **Docs:** https://supersaas-kit.superbuilds.fun/docs
128
- - **Issues:** https://github.com/bixblues/supersaas-kit/issues
129
- - **Email:** hi@superbuilds.fun
104
+ ## License
130
105
 
131
- ## What's Next?
106
+ Requires a valid SuperSaaS Kit license. One purchase ($129), unlimited projects, 3 machine activations.
132
107
 
133
- 1. Read the docs: https://supersaas-kit.superbuilds.fun/docs
134
- 2. Join Discord: https://supersaas-kit.superbuilds.fun/discord
135
- 3. Follow updates: https://x.com/superbuilds
108
+ Get yours at [supersaas-kit.superbuilds.fun](https://supersaas-kit.superbuilds.fun)
136
109
 
137
- ---
110
+ ## Support
138
111
 
139
- Built with ❤️ by indie hackers, for indie hackers.
112
+ - **Docs:** [supersaas-kit.superbuilds.fun/docs](https://supersaas-kit.superbuilds.fun/docs)
113
+ - **Discord:** [discord.gg/bM3w3RZYYH](https://discord.gg/bM3w3RZYYH)
114
+ - **Email:** hi@superbuilds.fun
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ async function downloadTemplate(licenseKey, targetDir) {
47
47
  const msg = errorBody.error || `HTTP ${res.status}`;
48
48
  p3.log.error(chalk.red(msg));
49
49
  if (res.status === 403) {
50
- p3.log.info(chalk.dim("Your license may not include this tier. Check https://supersaas-kit.superbuilds.fun"));
50
+ p3.log.info(chalk.dim("Your license may be invalid. Check https://supersaas-kit.superbuilds.fun"));
51
51
  }
52
52
  if (res.status === 429) {
53
53
  p3.log.info(chalk.dim("Too many downloads. Wait an hour and try again."));
@@ -61,9 +61,22 @@ async function downloadTemplate(licenseKey, targetDir) {
61
61
  await fs.writeFile(archivePath, Buffer.from(arrayBuffer));
62
62
  spinner.text = "Extracting template...";
63
63
  await fs.ensureDir(targetDir);
64
- execSync(`tar -xzf "${archivePath}" -C "${targetDir}"`, {
65
- stdio: "pipe"
66
- });
64
+ try {
65
+ execSync(`tar -xzf "${archivePath}" -C "${targetDir}"`, {
66
+ stdio: "pipe"
67
+ });
68
+ } catch (tarErr) {
69
+ spinner.fail(chalk.red("Extraction failed"));
70
+ const isWindows = process.platform === "win32";
71
+ if (isWindows) {
72
+ p3.log.error(chalk.red("tar is not available on this system."));
73
+ p3.log.info(chalk.dim("Install tar via Windows Build Tools, or use WSL."));
74
+ } else {
75
+ p3.log.error(chalk.red(`tar extraction failed: ${tarErr.message}`));
76
+ }
77
+ await fs.remove(tmpDir);
78
+ return false;
79
+ }
67
80
  await fs.remove(tmpDir);
68
81
  spinner.succeed(chalk.green("Template downloaded and extracted"));
69
82
  return true;
@@ -86,6 +99,9 @@ async function createApp(config) {
86
99
  }
87
100
  const downloaded = await downloadTemplate(config.licenseKey, targetDir);
88
101
  if (!downloaded) {
102
+ if (await fs.pathExists(targetDir)) {
103
+ await fs.remove(targetDir);
104
+ }
89
105
  process.exit(1);
90
106
  }
91
107
  const excludePaths = /* @__PURE__ */ new Set();
@@ -112,11 +128,49 @@ async function createApp(config) {
112
128
  const envExample = path.join(targetDir, ".env.example");
113
129
  if (await fs.pathExists(envExample)) {
114
130
  await fs.copy(envExample, path.join(targetDir, ".env"));
131
+ p3.log.info(chalk.dim("Created .env from template \u2014 configure via /setup after first run"));
115
132
  }
116
133
  await cleanupFeatureImports(targetDir, config);
117
134
  const activeFeatures = ["auth", "payments", "email", "seo", ...config.features];
118
135
  p3.log.info(chalk.dim(`Database: ${config.database}`));
119
136
  p3.log.info(chalk.dim(`Features: ${activeFeatures.join(", ")}`));
137
+ if (config.install) {
138
+ const pm = detectPackageManager();
139
+ const spinner = ora(`Installing dependencies with ${pm}...`).start();
140
+ try {
141
+ execSync(`${pm} install`, { cwd: targetDir, stdio: "pipe" });
142
+ spinner.succeed(chalk.green("Dependencies installed"));
143
+ } catch {
144
+ spinner.fail(chalk.yellow("Install failed \u2014 run it manually after setup"));
145
+ }
146
+ }
147
+ if (!config.skipGit && hasGit()) {
148
+ try {
149
+ execSync("git init", { cwd: targetDir, stdio: "pipe" });
150
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
151
+ execSync('git commit -m "Initial commit from create-supersaas-app"', {
152
+ cwd: targetDir,
153
+ stdio: "pipe"
154
+ });
155
+ p3.log.info(chalk.dim("Initialized git repository"));
156
+ } catch {
157
+ }
158
+ }
159
+ }
160
+ function detectPackageManager() {
161
+ const userAgent = process.env.npm_config_user_agent || "";
162
+ if (userAgent.startsWith("pnpm")) return "pnpm";
163
+ if (userAgent.startsWith("yarn")) return "yarn";
164
+ if (userAgent.startsWith("bun")) return "bun";
165
+ return "pnpm";
166
+ }
167
+ function hasGit() {
168
+ try {
169
+ execSync("git --version", { stdio: "pipe" });
170
+ return true;
171
+ } catch {
172
+ return false;
173
+ }
120
174
  }
121
175
  async function configureDatabase(targetDir, config) {
122
176
  const dbConfig = DATABASE_CONFIGS[config.database];
@@ -131,14 +185,6 @@ DATABASE_URL=`);
131
185
  await fs.writeFile(envPath, content);
132
186
  }
133
187
  }
134
- if (config.database === "local") {
135
- const envPath2 = path.join(targetDir, ".env.example");
136
- if (await fs.pathExists(envPath2)) {
137
- let content = await fs.readFile(envPath2, "utf-8");
138
- if (content.includes("DATABASE_URL=postgres://postgres:postgres@localhost:5432/app")) ;
139
- await fs.writeFile(envPath2, content);
140
- }
141
- }
142
188
  }
143
189
  async function updateManifest(targetDir, config) {
144
190
  const manifestPath = path.join(targetDir, ".supersaas", "manifest.json");
@@ -146,7 +192,6 @@ async function updateManifest(targetDir, config) {
146
192
  const manifest = await fs.readJson(manifestPath);
147
193
  manifest.database = config.database;
148
194
  manifest.features = config.features;
149
- manifest.tier = config.tier;
150
195
  manifest.createdAt = (/* @__PURE__ */ new Date()).toISOString();
151
196
  if (manifest.providers) {
152
197
  if (manifest.providers.db) manifest.providers.db = "postgres";
@@ -184,7 +229,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
184
229
  const sidebarPath = path.join(targetDir, "src/components/layout/sidebar.tsx");
185
230
  if (await fs.pathExists(sidebarPath)) {
186
231
  let content = await fs.readFile(sidebarPath, "utf-8");
187
- content = content.replace(/\s*\{\s*label:\s*["']Admin["'].*?\},?\n?/gs, "");
232
+ content = content.replace(/\s*\{\s*name:\s*["']Admin["'].*?\},?\n?/gs, "");
188
233
  await fs.writeFile(sidebarPath, content);
189
234
  }
190
235
  }
@@ -237,14 +282,11 @@ async function getAllFiles(dir) {
237
282
  var SUPERSAAS_DIR = path.join(os2.homedir(), ".supersaas");
238
283
  var LICENSE_PATH = path.join(SUPERSAAS_DIR, "license.json");
239
284
  var DODO_BASE_URL = process.env.SUPERSAAS_DODO_URL || "https://test.dodopayments.com";
240
- function inferTierFromActivation(_data) {
241
- return "bundle";
242
- }
243
285
  async function loadLocalLicense() {
244
286
  try {
245
287
  if (await fs.pathExists(LICENSE_PATH)) {
246
288
  const data = await fs.readJson(LICENSE_PATH);
247
- if (data.key && data.tier && data.instance_id) {
289
+ if (data.key && data.instance_id) {
248
290
  return data;
249
291
  }
250
292
  }
@@ -291,10 +333,8 @@ async function activateOnline(key) {
291
333
  return null;
292
334
  }
293
335
  const result = await res.json();
294
- const tier = inferTierFromActivation(result);
295
336
  const license = {
296
337
  key,
297
- tier,
298
338
  instance_id: result.id || result.license_key_instance_id || "",
299
339
  activated_at: (/* @__PURE__ */ new Date()).toISOString(),
300
340
  valid: true
@@ -315,14 +355,14 @@ async function activate(keyArg, nonInteractive) {
315
355
  const check = await validateKey(existing.key).catch(() => ({ valid: true }));
316
356
  if (check.valid) {
317
357
  p3.log.success(
318
- chalk.dim(
319
- `Licensed (${existing.tier}) - ${existing.key.slice(0, 12)}...`
320
- )
358
+ chalk.dim(`Licensed \u2014 ${existing.key.slice(0, 12)}...`)
321
359
  );
322
360
  return existing;
323
361
  }
324
362
  await fs.remove(LICENSE_PATH);
325
- p3.log.warning(chalk.yellow("License expired or revoked. Please re-activate."));
363
+ if (!nonInteractive) {
364
+ p3.log.warning(chalk.yellow("License expired or revoked. Please re-activate."));
365
+ }
326
366
  }
327
367
  if (!keyArg && nonInteractive) {
328
368
  return null;
@@ -339,18 +379,51 @@ async function activate(keyArg, nonInteractive) {
339
379
  p3.log.step("Activating license...");
340
380
  const license = await activateOnline(key);
341
381
  if (license) {
342
- p3.log.success(chalk.green(`Activated! Tier: ${license.tier}`));
382
+ p3.log.success(chalk.green("License activated!"));
343
383
  return license;
344
384
  }
345
385
  return null;
346
386
  }
387
+ async function deactivate() {
388
+ const existing = await loadLocalLicense();
389
+ if (!existing) {
390
+ p3.log.warning(chalk.yellow("No active license found on this machine."));
391
+ return false;
392
+ }
393
+ try {
394
+ const res = await fetch(`${DODO_BASE_URL}/licenses/deactivate`, {
395
+ method: "POST",
396
+ headers: { "Content-Type": "application/json" },
397
+ body: JSON.stringify({
398
+ license_key: existing.key,
399
+ license_key_instance_id: existing.instance_id
400
+ })
401
+ });
402
+ if (res.ok) {
403
+ await fs.remove(LICENSE_PATH);
404
+ p3.log.success(chalk.green("License deactivated. Machine slot freed."));
405
+ return true;
406
+ }
407
+ const body = await res.json().catch(() => ({}));
408
+ p3.log.error(chalk.red(`Deactivation failed: ${body.message || "Unknown error"}`));
409
+ return false;
410
+ } catch {
411
+ p3.log.error(chalk.red("Could not reach license server."));
412
+ return false;
413
+ }
414
+ }
347
415
  var require2 = createRequire(import.meta.url);
348
416
  var { version } = require2("../package.json");
349
417
  var VALID_DBS = ["neon", "supabase", "local"];
350
418
  var VALID_FEATURES = ["blog", "admin"];
351
- program.name("create-supersaas-app").description("Scaffold production-ready SaaS apps in seconds").version(version).argument("[name]", "Name of your app").option("--key <license-key>", "License key for activation").option("--defaults", "Skip prompts, use default settings (for agents)").option("--yes", "Alias for --defaults (non-interactive mode)").option("--db <provider>", "Database provider (neon|supabase|local)").option("--features <list>", "Comma-separated features (blog,admin)").action(async (name, options) => {
419
+ program.name("create-supersaas-app").description("Scaffold production-ready SaaS apps in seconds").version(version).argument("[name]", "Name of your app").option("--key <license-key>", "License key for activation").option("--defaults", "Skip prompts, use default settings (for agents)").option("--yes", "Alias for --defaults (non-interactive mode)").option("--db <provider>", "Database provider (neon|supabase|local)").option("--features <list>", "Comma-separated features (blog,admin)").option("--install", "Install dependencies after scaffolding").option("--no-git", "Skip git repository initialization").action(async (name, options) => {
352
420
  const nonInteractive = options.defaults || options.yes || !process.stdin.isTTY;
353
- if (!nonInteractive) p3.intro(chalk.bold("supersaas-kit"));
421
+ if (!nonInteractive) {
422
+ p3.intro(
423
+ `${chalk.bold("supersaas-kit")} ${chalk.dim(`v${version}`)}
424
+ ` + chalk.dim(" Production-ready SaaS in one command")
425
+ );
426
+ }
354
427
  let licenseResult = await activate(options.key, nonInteractive);
355
428
  if (!licenseResult) {
356
429
  if (!nonInteractive) p3.cancel("License activation failed.");
@@ -418,8 +491,8 @@ program.name("create-supersaas-app").description("Scaffold production-ready SaaS
418
491
  features = await p3.multiselect({
419
492
  message: "Include optional modules?",
420
493
  options: [
421
- { value: "blog", label: "Blog (MDX)", hint: "SEO boost" },
422
- { value: "admin", label: "Admin Dashboard", hint: "Pro" }
494
+ { value: "blog", label: "Blog (MDX)", hint: "SEO-ready with RSS feed" },
495
+ { value: "admin", label: "Admin Dashboard", hint: "user management, analytics" }
423
496
  ],
424
497
  required: false
425
498
  });
@@ -428,19 +501,62 @@ program.name("create-supersaas-app").description("Scaffold production-ready SaaS
428
501
  process.exit(0);
429
502
  }
430
503
  }
504
+ let shouldInstall = options.install || false;
505
+ if (!nonInteractive && !options.install) {
506
+ shouldInstall = await p3.confirm({
507
+ message: "Install dependencies?",
508
+ initialValue: true
509
+ });
510
+ if (p3.isCancel(shouldInstall)) {
511
+ p3.cancel("Cancelled.");
512
+ process.exit(0);
513
+ }
514
+ }
431
515
  await createApp({
432
516
  name: appName,
433
517
  database: dbChoice,
434
518
  features: features || [],
435
- tier: licenseResult.tier,
436
- licenseKey: licenseResult.key
519
+ licenseKey: licenseResult.key,
520
+ install: shouldInstall,
521
+ skipGit: options.git === false
437
522
  });
438
523
  if (!nonInteractive) {
439
- p3.outro(
440
- chalk.green("Done!") + ` Run ${chalk.cyan(`cd ${appName} && npm install && npm run dev`)} to start.`
524
+ const steps = [
525
+ chalk.green("Your app is ready!"),
526
+ "",
527
+ ` ${chalk.cyan("cd")} ${appName}`
528
+ ];
529
+ if (!shouldInstall) {
530
+ steps.push(` ${chalk.cyan("pnpm install")}`);
531
+ }
532
+ steps.push(
533
+ ` ${chalk.cyan("pnpm dev")}`,
534
+ "",
535
+ chalk.dim(" Then visit /setup to configure your services."),
536
+ chalk.dim(" Docs: https://supersaas-kit.superbuilds.fun/docs")
441
537
  );
538
+ p3.outro(steps.join("\n"));
442
539
  } else {
443
- console.log(`\u2714 Created ${appName}. Run: cd ${appName} && npm install && npm run dev`);
540
+ const installCmd = shouldInstall ? "" : "pnpm install && ";
541
+ console.log(`\u2714 Created ${appName}. Run: cd ${appName} && ${installCmd}pnpm dev`);
542
+ }
543
+ });
544
+ program.command("deactivate").description("Deactivate your license on this machine (frees a machine slot)").action(async () => {
545
+ p3.intro(chalk.bold("supersaas-kit \u2014 deactivate"));
546
+ const confirmed = await p3.confirm({
547
+ message: "This will free one of your 3 machine activation slots. Continue?",
548
+ initialValue: false
549
+ });
550
+ if (p3.isCancel(confirmed) || !confirmed) {
551
+ p3.outro(chalk.dim("Deactivation cancelled."));
552
+ return;
553
+ }
554
+ const success = await deactivate();
555
+ if (success) {
556
+ p3.outro(chalk.green("License deactivated. You can activate on another machine now."));
557
+ } else {
558
+ p3.outro(chalk.red("Deactivation failed. Run again or contact hi@superbuilds.fun"));
559
+ process.exit(1);
444
560
  }
445
561
  });
446
562
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superbuilds/create-supersaas-app",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Scaffold production-ready SaaS apps in seconds — SuperSaaS Kit CLI",
5
5
  "author": "superbuilds",
6
6
  "license": "MIT",