@victusvinceere/saas-cli 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/dist/index.d.mts +2 -0
- package/dist/index.mjs +761 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -0
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/utils/logger.ts
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
var logger = {
|
|
9
|
+
info: (message) => console.log(chalk.blue("info"), message),
|
|
10
|
+
success: (message) => console.log(chalk.green("\u2713"), message),
|
|
11
|
+
warn: (message) => console.log(chalk.yellow("\u26A0"), message),
|
|
12
|
+
error: (message) => console.log(chalk.red("\u2717"), message),
|
|
13
|
+
log: (message) => console.log(message)
|
|
14
|
+
};
|
|
15
|
+
function printBanner() {
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.bold.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"));
|
|
18
|
+
console.log(chalk.bold.cyan(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"));
|
|
19
|
+
console.log(chalk.bold.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"));
|
|
20
|
+
console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551"));
|
|
21
|
+
console.log(chalk.bold.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551"));
|
|
22
|
+
console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(chalk.bold(" SaaS Kit CLI - Build your SaaS faster"));
|
|
25
|
+
console.log();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/commands/init.ts
|
|
29
|
+
import fs from "fs-extra";
|
|
30
|
+
import path from "path";
|
|
31
|
+
import chalk2 from "chalk";
|
|
32
|
+
import ora from "ora";
|
|
33
|
+
import inquirer from "inquirer";
|
|
34
|
+
async function init(projectName, options = {}) {
|
|
35
|
+
logger.log(chalk2.bold("\nLet's create your new SaaS project!\n"));
|
|
36
|
+
let name = projectName;
|
|
37
|
+
if (!name) {
|
|
38
|
+
const answers = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: "input",
|
|
41
|
+
name: "projectName",
|
|
42
|
+
message: "What is your project name?",
|
|
43
|
+
default: "my-saas-app",
|
|
44
|
+
validate: (input) => {
|
|
45
|
+
if (/^[a-z0-9-]+$/.test(input)) return true;
|
|
46
|
+
return "Project name can only contain lowercase letters, numbers, and hyphens";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
name = answers.projectName;
|
|
51
|
+
}
|
|
52
|
+
const config = await inquirer.prompt([
|
|
53
|
+
{
|
|
54
|
+
type: "checkbox",
|
|
55
|
+
name: "modules",
|
|
56
|
+
message: "Which modules would you like to include?",
|
|
57
|
+
choices: [
|
|
58
|
+
{ name: "Payments (Lemon Squeezy)", value: "payments", checked: true },
|
|
59
|
+
{ name: "Admin Panel", value: "admin", checked: true },
|
|
60
|
+
{ name: "Blog (MDX)", value: "blog", checked: false },
|
|
61
|
+
{ name: "Landing Pages", value: "landing", checked: true }
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "checkbox",
|
|
66
|
+
name: "providers",
|
|
67
|
+
message: "Which auth providers do you want?",
|
|
68
|
+
choices: [
|
|
69
|
+
{ name: "Google", value: "google", checked: true },
|
|
70
|
+
{ name: "GitHub", value: "github", checked: false },
|
|
71
|
+
{ name: "Email (Magic Links)", value: "email", checked: true }
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: "list",
|
|
76
|
+
name: "database",
|
|
77
|
+
message: "Which database will you use?",
|
|
78
|
+
choices: [
|
|
79
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
80
|
+
{ name: "MySQL", value: "mysql" },
|
|
81
|
+
{ name: "SQLite", value: "sqlite" }
|
|
82
|
+
],
|
|
83
|
+
default: "postgresql"
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
const projectDir = path.resolve(process.cwd(), name);
|
|
87
|
+
if (await fs.pathExists(projectDir)) {
|
|
88
|
+
const { overwrite } = await inquirer.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: "confirm",
|
|
91
|
+
name: "overwrite",
|
|
92
|
+
message: `Directory ${name} already exists. Overwrite?`,
|
|
93
|
+
default: false
|
|
94
|
+
}
|
|
95
|
+
]);
|
|
96
|
+
if (!overwrite) {
|
|
97
|
+
logger.info("Aborted.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await fs.remove(projectDir);
|
|
101
|
+
}
|
|
102
|
+
const spinner = ora("Creating project...").start();
|
|
103
|
+
try {
|
|
104
|
+
await fs.ensureDir(projectDir);
|
|
105
|
+
const packageJson = {
|
|
106
|
+
name,
|
|
107
|
+
version: "0.1.0",
|
|
108
|
+
private: true,
|
|
109
|
+
scripts: {
|
|
110
|
+
dev: "next dev",
|
|
111
|
+
build: "next build",
|
|
112
|
+
start: "next start",
|
|
113
|
+
lint: "next lint",
|
|
114
|
+
"db:push": "prisma db push",
|
|
115
|
+
"db:generate": "prisma generate",
|
|
116
|
+
"db:studio": "prisma studio"
|
|
117
|
+
},
|
|
118
|
+
dependencies: {
|
|
119
|
+
"@saas-kit/core": "^0.1.0",
|
|
120
|
+
...config.modules.includes("payments") && {
|
|
121
|
+
"@saas-kit/payments": "^0.1.0"
|
|
122
|
+
},
|
|
123
|
+
...config.modules.includes("admin") && {
|
|
124
|
+
"@saas-kit/admin": "^0.1.0"
|
|
125
|
+
},
|
|
126
|
+
...config.modules.includes("blog") && { "@saas-kit/blog": "^0.1.0" },
|
|
127
|
+
...config.modules.includes("landing") && {
|
|
128
|
+
"@saas-kit/landing": "^0.1.0"
|
|
129
|
+
},
|
|
130
|
+
next: "^14.0.0",
|
|
131
|
+
react: "^18.0.0",
|
|
132
|
+
"react-dom": "^18.0.0",
|
|
133
|
+
"next-auth": "^5.0.0-beta.0"
|
|
134
|
+
},
|
|
135
|
+
devDependencies: {
|
|
136
|
+
"@types/node": "^20",
|
|
137
|
+
"@types/react": "^18",
|
|
138
|
+
"@types/react-dom": "^18",
|
|
139
|
+
typescript: "^5",
|
|
140
|
+
prisma: "^5.0.0",
|
|
141
|
+
tailwindcss: "^3.4.0",
|
|
142
|
+
postcss: "^8.4.0",
|
|
143
|
+
autoprefixer: "^10.4.0"
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
await fs.writeJSON(path.join(projectDir, "package.json"), packageJson, {
|
|
147
|
+
spaces: 2
|
|
148
|
+
});
|
|
149
|
+
await fs.ensureDir(path.join(projectDir, "src/app"));
|
|
150
|
+
await fs.ensureDir(path.join(projectDir, "src/components"));
|
|
151
|
+
await fs.ensureDir(path.join(projectDir, "src/lib"));
|
|
152
|
+
await fs.ensureDir(path.join(projectDir, "prisma"));
|
|
153
|
+
const tsconfig = {
|
|
154
|
+
compilerOptions: {
|
|
155
|
+
target: "ES2022",
|
|
156
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
157
|
+
allowJs: true,
|
|
158
|
+
skipLibCheck: true,
|
|
159
|
+
strict: true,
|
|
160
|
+
noEmit: true,
|
|
161
|
+
esModuleInterop: true,
|
|
162
|
+
module: "esnext",
|
|
163
|
+
moduleResolution: "bundler",
|
|
164
|
+
resolveJsonModule: true,
|
|
165
|
+
isolatedModules: true,
|
|
166
|
+
jsx: "preserve",
|
|
167
|
+
incremental: true,
|
|
168
|
+
plugins: [{ name: "next" }],
|
|
169
|
+
paths: { "@/*": ["./src/*"] }
|
|
170
|
+
},
|
|
171
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
172
|
+
exclude: ["node_modules"]
|
|
173
|
+
};
|
|
174
|
+
await fs.writeJSON(path.join(projectDir, "tsconfig.json"), tsconfig, {
|
|
175
|
+
spaces: 2
|
|
176
|
+
});
|
|
177
|
+
const envExample = `# Database
|
|
178
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
|
|
179
|
+
|
|
180
|
+
# NextAuth
|
|
181
|
+
AUTH_SECRET="your-secret-here"
|
|
182
|
+
NEXTAUTH_URL="http://localhost:3000"
|
|
183
|
+
|
|
184
|
+
# Google OAuth
|
|
185
|
+
GOOGLE_CLIENT_ID=""
|
|
186
|
+
GOOGLE_CLIENT_SECRET=""
|
|
187
|
+
|
|
188
|
+
# GitHub OAuth (if using)
|
|
189
|
+
GITHUB_CLIENT_ID=""
|
|
190
|
+
GITHUB_CLIENT_SECRET=""
|
|
191
|
+
|
|
192
|
+
# Email (Resend)
|
|
193
|
+
AUTH_RESEND_KEY=""
|
|
194
|
+
EMAIL_FROM="noreply@yourdomain.com"
|
|
195
|
+
|
|
196
|
+
# Lemon Squeezy (if using payments)
|
|
197
|
+
LEMONSQUEEZY_API_KEY=""
|
|
198
|
+
LEMONSQUEEZY_STORE_ID=""
|
|
199
|
+
LEMONSQUEEZY_WEBHOOK_SECRET=""
|
|
200
|
+
|
|
201
|
+
# App
|
|
202
|
+
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
203
|
+
`;
|
|
204
|
+
await fs.writeFile(path.join(projectDir, ".env.example"), envExample);
|
|
205
|
+
const configFile = `import { defineConfig } from "@saas-kit/core";
|
|
206
|
+
|
|
207
|
+
export default defineConfig({
|
|
208
|
+
app: {
|
|
209
|
+
name: "${name}",
|
|
210
|
+
url: process.env.NEXT_PUBLIC_APP_URL,
|
|
211
|
+
description: "Your SaaS application",
|
|
212
|
+
},
|
|
213
|
+
auth: {
|
|
214
|
+
providers: [${config.providers.map((p) => `"${p}"`).join(", ")}],
|
|
215
|
+
pages: {
|
|
216
|
+
signIn: "/login",
|
|
217
|
+
error: "/auth-error",
|
|
218
|
+
verifyRequest: "/verify-request",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
features: {
|
|
222
|
+
payments: ${config.modules.includes("payments")},
|
|
223
|
+
admin: ${config.modules.includes("admin")},
|
|
224
|
+
blog: ${config.modules.includes("blog")},
|
|
225
|
+
},
|
|
226
|
+
theme: {
|
|
227
|
+
defaultTheme: "system",
|
|
228
|
+
enableSystem: true,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
`;
|
|
232
|
+
await fs.writeFile(path.join(projectDir, "saas-kit.config.ts"), configFile);
|
|
233
|
+
const layoutFile = `import { SaasKitProvider } from "@saas-kit/core/providers";
|
|
234
|
+
import "./globals.css";
|
|
235
|
+
|
|
236
|
+
export default function RootLayout({
|
|
237
|
+
children,
|
|
238
|
+
}: {
|
|
239
|
+
children: React.ReactNode;
|
|
240
|
+
}) {
|
|
241
|
+
return (
|
|
242
|
+
<html lang="en" suppressHydrationWarning>
|
|
243
|
+
<body>
|
|
244
|
+
<SaasKitProvider>
|
|
245
|
+
{children}
|
|
246
|
+
</SaasKitProvider>
|
|
247
|
+
</body>
|
|
248
|
+
</html>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
await fs.writeFile(
|
|
253
|
+
path.join(projectDir, "src/app/layout.tsx"),
|
|
254
|
+
layoutFile
|
|
255
|
+
);
|
|
256
|
+
const globalsCss = `@tailwind base;
|
|
257
|
+
@tailwind components;
|
|
258
|
+
@tailwind utilities;
|
|
259
|
+
|
|
260
|
+
:root {
|
|
261
|
+
--background: 0 0% 100%;
|
|
262
|
+
--foreground: 222.2 47.4% 11.2%;
|
|
263
|
+
--primary: 222.2 47.4% 11.2%;
|
|
264
|
+
--primary-foreground: 210 40% 98%;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.dark {
|
|
268
|
+
--background: 224 71% 4%;
|
|
269
|
+
--foreground: 213 31% 91%;
|
|
270
|
+
--primary: 210 40% 98%;
|
|
271
|
+
--primary-foreground: 222.2 47.4% 1.2%;
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
await fs.writeFile(
|
|
275
|
+
path.join(projectDir, "src/app/globals.css"),
|
|
276
|
+
globalsCss
|
|
277
|
+
);
|
|
278
|
+
const pageTsx = `export default function Home() {
|
|
279
|
+
return (
|
|
280
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-24">
|
|
281
|
+
<h1 className="text-4xl font-bold">Welcome to ${name}</h1>
|
|
282
|
+
<p className="mt-4 text-muted-foreground">
|
|
283
|
+
Your SaaS application is ready!
|
|
284
|
+
</p>
|
|
285
|
+
</main>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
289
|
+
await fs.writeFile(path.join(projectDir, "src/app/page.tsx"), pageTsx);
|
|
290
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
291
|
+
module.exports = {
|
|
292
|
+
darkMode: ["class"],
|
|
293
|
+
content: [
|
|
294
|
+
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
|
295
|
+
"./node_modules/@saas-kit/*/dist/**/*.{js,ts,jsx,tsx}",
|
|
296
|
+
],
|
|
297
|
+
theme: {
|
|
298
|
+
extend: {},
|
|
299
|
+
},
|
|
300
|
+
plugins: [],
|
|
301
|
+
};
|
|
302
|
+
`;
|
|
303
|
+
await fs.writeFile(
|
|
304
|
+
path.join(projectDir, "tailwind.config.js"),
|
|
305
|
+
tailwindConfig
|
|
306
|
+
);
|
|
307
|
+
const postcssConfig = `module.exports = {
|
|
308
|
+
plugins: {
|
|
309
|
+
tailwindcss: {},
|
|
310
|
+
autoprefixer: {},
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
`;
|
|
314
|
+
await fs.writeFile(
|
|
315
|
+
path.join(projectDir, "postcss.config.js"),
|
|
316
|
+
postcssConfig
|
|
317
|
+
);
|
|
318
|
+
const nextConfig = `/** @type {import('next').NextConfig} */
|
|
319
|
+
const nextConfig = {
|
|
320
|
+
experimental: {
|
|
321
|
+
serverActions: true,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
module.exports = nextConfig;
|
|
326
|
+
`;
|
|
327
|
+
await fs.writeFile(path.join(projectDir, "next.config.js"), nextConfig);
|
|
328
|
+
const gitignore = `# dependencies
|
|
329
|
+
node_modules
|
|
330
|
+
.pnpm-store
|
|
331
|
+
|
|
332
|
+
# next.js
|
|
333
|
+
.next/
|
|
334
|
+
out/
|
|
335
|
+
|
|
336
|
+
# production
|
|
337
|
+
build
|
|
338
|
+
dist
|
|
339
|
+
|
|
340
|
+
# misc
|
|
341
|
+
.DS_Store
|
|
342
|
+
*.pem
|
|
343
|
+
|
|
344
|
+
# debug
|
|
345
|
+
npm-debug.log*
|
|
346
|
+
yarn-debug.log*
|
|
347
|
+
yarn-error.log*
|
|
348
|
+
|
|
349
|
+
# local env files
|
|
350
|
+
.env
|
|
351
|
+
.env.local
|
|
352
|
+
.env.development.local
|
|
353
|
+
.env.test.local
|
|
354
|
+
.env.production.local
|
|
355
|
+
|
|
356
|
+
# vercel
|
|
357
|
+
.vercel
|
|
358
|
+
|
|
359
|
+
# typescript
|
|
360
|
+
*.tsbuildinfo
|
|
361
|
+
next-env.d.ts
|
|
362
|
+
`;
|
|
363
|
+
await fs.writeFile(path.join(projectDir, ".gitignore"), gitignore);
|
|
364
|
+
spinner.succeed("Project created successfully!");
|
|
365
|
+
logger.log("");
|
|
366
|
+
logger.log(chalk2.bold("Next steps:"));
|
|
367
|
+
logger.log("");
|
|
368
|
+
logger.log(chalk2.cyan(` cd ${name}`));
|
|
369
|
+
logger.log(chalk2.cyan(" npm install"));
|
|
370
|
+
logger.log(chalk2.cyan(" cp .env.example .env.local"));
|
|
371
|
+
logger.log(chalk2.cyan(" # Edit .env.local with your credentials"));
|
|
372
|
+
logger.log(chalk2.cyan(" npm run dev"));
|
|
373
|
+
logger.log("");
|
|
374
|
+
logger.success("Happy building!");
|
|
375
|
+
} catch (error) {
|
|
376
|
+
spinner.fail("Failed to create project");
|
|
377
|
+
logger.error(String(error));
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/commands/add.ts
|
|
383
|
+
import fs2 from "fs-extra";
|
|
384
|
+
import path2 from "path";
|
|
385
|
+
import chalk3 from "chalk";
|
|
386
|
+
import ora2 from "ora";
|
|
387
|
+
import inquirer2 from "inquirer";
|
|
388
|
+
var moduleInfo = {
|
|
389
|
+
payments: {
|
|
390
|
+
name: "Payments",
|
|
391
|
+
package: "@saas-kit/payments",
|
|
392
|
+
description: "Lemon Squeezy integration for subscriptions and payments"
|
|
393
|
+
},
|
|
394
|
+
admin: {
|
|
395
|
+
name: "Admin Panel",
|
|
396
|
+
package: "@saas-kit/admin",
|
|
397
|
+
description: "Admin dashboard components and layouts"
|
|
398
|
+
},
|
|
399
|
+
blog: {
|
|
400
|
+
name: "Blog",
|
|
401
|
+
package: "@saas-kit/blog",
|
|
402
|
+
description: "MDX-powered blog with components"
|
|
403
|
+
},
|
|
404
|
+
landing: {
|
|
405
|
+
name: "Landing Pages",
|
|
406
|
+
package: "@saas-kit/landing",
|
|
407
|
+
description: "Marketing page components (Hero, Features, Pricing, etc.)"
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
async function add(moduleName) {
|
|
411
|
+
const packageJsonPath = path2.join(process.cwd(), "package.json");
|
|
412
|
+
if (!await fs2.pathExists(packageJsonPath)) {
|
|
413
|
+
logger.error("No package.json found. Are you in a project directory?");
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
let module = moduleName;
|
|
417
|
+
if (!module) {
|
|
418
|
+
const { selectedModule } = await inquirer2.prompt([
|
|
419
|
+
{
|
|
420
|
+
type: "list",
|
|
421
|
+
name: "selectedModule",
|
|
422
|
+
message: "Which module would you like to add?",
|
|
423
|
+
choices: Object.entries(moduleInfo).map(([key, value]) => ({
|
|
424
|
+
name: `${value.name} - ${value.description}`,
|
|
425
|
+
value: key
|
|
426
|
+
}))
|
|
427
|
+
}
|
|
428
|
+
]);
|
|
429
|
+
module = selectedModule;
|
|
430
|
+
}
|
|
431
|
+
if (!moduleInfo[module]) {
|
|
432
|
+
logger.error(`Unknown module: ${module}`);
|
|
433
|
+
logger.info(
|
|
434
|
+
`Available modules: ${Object.keys(moduleInfo).join(", ")}`
|
|
435
|
+
);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const info = moduleInfo[module];
|
|
439
|
+
const spinner = ora2(`Adding ${info.name} module...`).start();
|
|
440
|
+
try {
|
|
441
|
+
const packageJson = await fs2.readJSON(packageJsonPath);
|
|
442
|
+
if (packageJson.dependencies?.[info.package] || packageJson.devDependencies?.[info.package]) {
|
|
443
|
+
spinner.warn(`${info.name} is already installed`);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
447
|
+
packageJson.dependencies[info.package] = "^0.1.0";
|
|
448
|
+
await fs2.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
449
|
+
spinner.succeed(`Added ${info.name} module`);
|
|
450
|
+
logger.log("");
|
|
451
|
+
logger.log(chalk3.bold(`Usage example for ${info.name}:`));
|
|
452
|
+
logger.log("");
|
|
453
|
+
switch (module) {
|
|
454
|
+
case "payments":
|
|
455
|
+
logger.log(chalk3.cyan(`import { createCheckoutUrl, createWebhookHandler } from "${info.package}";`));
|
|
456
|
+
logger.log("");
|
|
457
|
+
logger.log("// Create a checkout URL");
|
|
458
|
+
logger.log("const url = await createCheckoutUrl({");
|
|
459
|
+
logger.log(' variantId: "your-variant-id",');
|
|
460
|
+
logger.log(" email: user.email,");
|
|
461
|
+
logger.log(" userId: user.id,");
|
|
462
|
+
logger.log("});");
|
|
463
|
+
break;
|
|
464
|
+
case "admin":
|
|
465
|
+
logger.log(chalk3.cyan(`import { AdminLayout, StatsCard } from "${info.package}";`));
|
|
466
|
+
logger.log("");
|
|
467
|
+
logger.log("// Use in your admin pages");
|
|
468
|
+
logger.log("<AdminLayout user={user}>");
|
|
469
|
+
logger.log(' <StatsCard title="Users" value={1234} />');
|
|
470
|
+
logger.log("</AdminLayout>");
|
|
471
|
+
break;
|
|
472
|
+
case "blog":
|
|
473
|
+
logger.log(chalk3.cyan(`import { getAllPosts, PostCard } from "${info.package}";`));
|
|
474
|
+
logger.log("");
|
|
475
|
+
logger.log("// Get all blog posts");
|
|
476
|
+
logger.log("const posts = getAllPosts();");
|
|
477
|
+
break;
|
|
478
|
+
case "landing":
|
|
479
|
+
logger.log(chalk3.cyan(`import { Hero, Features, Pricing } from "${info.package}";`));
|
|
480
|
+
logger.log("");
|
|
481
|
+
logger.log("// Build your landing page");
|
|
482
|
+
logger.log('<Hero title="..." subtitle="..." />');
|
|
483
|
+
logger.log("<Features features={[...]} />");
|
|
484
|
+
logger.log("<Pricing plans={[...]} />");
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
logger.log("");
|
|
488
|
+
logger.log(chalk3.yellow("Don't forget to run: npm install"));
|
|
489
|
+
logger.log("");
|
|
490
|
+
} catch (error) {
|
|
491
|
+
spinner.fail(`Failed to add ${info.name} module`);
|
|
492
|
+
logger.error(String(error));
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/commands/generate.ts
|
|
498
|
+
import fs3 from "fs-extra";
|
|
499
|
+
import path3 from "path";
|
|
500
|
+
import chalk4 from "chalk";
|
|
501
|
+
import ora3 from "ora";
|
|
502
|
+
import inquirer3 from "inquirer";
|
|
503
|
+
var generators = {
|
|
504
|
+
auth: {
|
|
505
|
+
name: "Auth Routes",
|
|
506
|
+
description: "Login, signup, and verify-request pages",
|
|
507
|
+
files: ["login/page.tsx", "signup/page.tsx", "verify-request/page.tsx", "auth-error/page.tsx"]
|
|
508
|
+
},
|
|
509
|
+
dashboard: {
|
|
510
|
+
name: "Dashboard Routes",
|
|
511
|
+
description: "Dashboard layout and basic pages",
|
|
512
|
+
files: ["dashboard/layout.tsx", "dashboard/page.tsx", "dashboard/settings/page.tsx"]
|
|
513
|
+
},
|
|
514
|
+
api: {
|
|
515
|
+
name: "API Routes",
|
|
516
|
+
description: "Auth and webhook API routes",
|
|
517
|
+
files: ["api/auth/[...nextauth]/route.ts", "api/webhooks/lemonsqueezy/route.ts"]
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
async function generate(type) {
|
|
521
|
+
let selectedType = type;
|
|
522
|
+
if (!selectedType) {
|
|
523
|
+
const { generatorType } = await inquirer3.prompt([
|
|
524
|
+
{
|
|
525
|
+
type: "list",
|
|
526
|
+
name: "generatorType",
|
|
527
|
+
message: "What would you like to generate?",
|
|
528
|
+
choices: Object.entries(generators).map(([key, value]) => ({
|
|
529
|
+
name: `${value.name} - ${value.description}`,
|
|
530
|
+
value: key
|
|
531
|
+
}))
|
|
532
|
+
}
|
|
533
|
+
]);
|
|
534
|
+
selectedType = generatorType;
|
|
535
|
+
}
|
|
536
|
+
if (!generators[selectedType]) {
|
|
537
|
+
logger.error(`Unknown generator: ${selectedType}`);
|
|
538
|
+
logger.info(`Available generators: ${Object.keys(generators).join(", ")}`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
const generator = generators[selectedType];
|
|
542
|
+
const spinner = ora3(`Generating ${generator.name}...`).start();
|
|
543
|
+
try {
|
|
544
|
+
const appDir = path3.join(process.cwd(), "src/app");
|
|
545
|
+
if (!await fs3.pathExists(appDir)) {
|
|
546
|
+
await fs3.ensureDir(appDir);
|
|
547
|
+
}
|
|
548
|
+
for (const file of generator.files) {
|
|
549
|
+
const filePath = path3.join(appDir, file);
|
|
550
|
+
const dirPath = path3.dirname(filePath);
|
|
551
|
+
await fs3.ensureDir(dirPath);
|
|
552
|
+
const content = generateFileContent(selectedType, file);
|
|
553
|
+
await fs3.writeFile(filePath, content);
|
|
554
|
+
}
|
|
555
|
+
spinner.succeed(`Generated ${generator.name}`);
|
|
556
|
+
logger.log("");
|
|
557
|
+
logger.log(chalk4.bold("Files created:"));
|
|
558
|
+
for (const file of generator.files) {
|
|
559
|
+
logger.log(chalk4.cyan(` src/app/${file}`));
|
|
560
|
+
}
|
|
561
|
+
logger.log("");
|
|
562
|
+
} catch (error) {
|
|
563
|
+
spinner.fail(`Failed to generate ${generator.name}`);
|
|
564
|
+
logger.error(String(error));
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function generateFileContent(type, file) {
|
|
569
|
+
if (type === "auth") {
|
|
570
|
+
if (file.includes("login")) {
|
|
571
|
+
return `import { AuthForm } from "@saas-kit/core/components/auth";
|
|
572
|
+
|
|
573
|
+
export default function LoginPage() {
|
|
574
|
+
return (
|
|
575
|
+
<div className="flex min-h-screen items-center justify-center">
|
|
576
|
+
<div className="w-full max-w-md p-8">
|
|
577
|
+
<AuthForm mode="login" />
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
if (file.includes("signup")) {
|
|
585
|
+
return `import { AuthForm } from "@saas-kit/core/components/auth";
|
|
586
|
+
|
|
587
|
+
export default function SignupPage() {
|
|
588
|
+
return (
|
|
589
|
+
<div className="flex min-h-screen items-center justify-center">
|
|
590
|
+
<div className="w-full max-w-md p-8">
|
|
591
|
+
<AuthForm mode="signup" />
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
`;
|
|
597
|
+
}
|
|
598
|
+
if (file.includes("verify-request")) {
|
|
599
|
+
return `export default function VerifyRequestPage() {
|
|
600
|
+
return (
|
|
601
|
+
<div className="flex min-h-screen items-center justify-center">
|
|
602
|
+
<div className="w-full max-w-md p-8 text-center">
|
|
603
|
+
<h1 className="text-2xl font-bold">Check your email</h1>
|
|
604
|
+
<p className="mt-4 text-muted-foreground">
|
|
605
|
+
A sign in link has been sent to your email address.
|
|
606
|
+
</p>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
`;
|
|
612
|
+
}
|
|
613
|
+
if (file.includes("auth-error")) {
|
|
614
|
+
return `export default function AuthErrorPage() {
|
|
615
|
+
return (
|
|
616
|
+
<div className="flex min-h-screen items-center justify-center">
|
|
617
|
+
<div className="w-full max-w-md p-8 text-center">
|
|
618
|
+
<h1 className="text-2xl font-bold text-destructive">Authentication Error</h1>
|
|
619
|
+
<p className="mt-4 text-muted-foreground">
|
|
620
|
+
There was an error signing you in. Please try again.
|
|
621
|
+
</p>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
`;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (type === "dashboard") {
|
|
630
|
+
if (file.includes("layout")) {
|
|
631
|
+
return `import { DashboardLayout } from "@saas-kit/core/components/dashboard";
|
|
632
|
+
import { UserButton } from "@saas-kit/core/components/auth";
|
|
633
|
+
import { LayoutDashboard, Settings, User } from "lucide-react";
|
|
634
|
+
|
|
635
|
+
const sidebarConfig = [
|
|
636
|
+
{
|
|
637
|
+
title: "Overview",
|
|
638
|
+
items: [
|
|
639
|
+
{ title: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
title: "Settings",
|
|
644
|
+
items: [
|
|
645
|
+
{ title: "Profile", href: "/dashboard/settings", icon: User },
|
|
646
|
+
{ title: "Settings", href: "/dashboard/settings", icon: Settings },
|
|
647
|
+
],
|
|
648
|
+
},
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
export default function DashboardLayoutPage({
|
|
652
|
+
children,
|
|
653
|
+
}: {
|
|
654
|
+
children: React.ReactNode;
|
|
655
|
+
}) {
|
|
656
|
+
return (
|
|
657
|
+
<DashboardLayout
|
|
658
|
+
sidebarConfig={sidebarConfig}
|
|
659
|
+
headerContent={<UserButton />}
|
|
660
|
+
>
|
|
661
|
+
{children}
|
|
662
|
+
</DashboardLayout>
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
`;
|
|
666
|
+
}
|
|
667
|
+
if (file === "dashboard/page.tsx") {
|
|
668
|
+
return `export default function DashboardPage() {
|
|
669
|
+
return (
|
|
670
|
+
<div>
|
|
671
|
+
<h1 className="text-3xl font-bold">Dashboard</h1>
|
|
672
|
+
<p className="mt-2 text-muted-foreground">Welcome to your dashboard.</p>
|
|
673
|
+
</div>
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
if (file.includes("settings")) {
|
|
679
|
+
return `export default function SettingsPage() {
|
|
680
|
+
return (
|
|
681
|
+
<div>
|
|
682
|
+
<h1 className="text-3xl font-bold">Settings</h1>
|
|
683
|
+
<p className="mt-2 text-muted-foreground">Manage your account settings.</p>
|
|
684
|
+
</div>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (type === "api") {
|
|
691
|
+
if (file.includes("nextauth")) {
|
|
692
|
+
return `import NextAuth from "next-auth";
|
|
693
|
+
import { createAuthConfig } from "@saas-kit/core";
|
|
694
|
+
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
695
|
+
import { prisma } from "@/lib/prisma";
|
|
696
|
+
|
|
697
|
+
const authConfig = createAuthConfig({
|
|
698
|
+
adapter: PrismaAdapter(prisma),
|
|
699
|
+
providers: ["google", "email"],
|
|
700
|
+
callbacks: {
|
|
701
|
+
getUserRole: async (userId) => {
|
|
702
|
+
const user = await prisma.user.findUnique({
|
|
703
|
+
where: { id: userId },
|
|
704
|
+
select: { role: true },
|
|
705
|
+
});
|
|
706
|
+
return user?.role || "USER";
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
const handler = NextAuth(authConfig);
|
|
712
|
+
|
|
713
|
+
export { handler as GET, handler as POST };
|
|
714
|
+
`;
|
|
715
|
+
}
|
|
716
|
+
if (file.includes("lemonsqueezy")) {
|
|
717
|
+
return `import { createWebhookHandler } from "@saas-kit/payments";
|
|
718
|
+
import { prisma } from "@/lib/prisma";
|
|
719
|
+
|
|
720
|
+
export const POST = createWebhookHandler({
|
|
721
|
+
onSubscriptionCreated: async (event) => {
|
|
722
|
+
const userId = event.meta.custom_data?.user_id;
|
|
723
|
+
if (!userId) return;
|
|
724
|
+
|
|
725
|
+
await prisma.subscription.create({
|
|
726
|
+
data: {
|
|
727
|
+
userId,
|
|
728
|
+
lemonSqueezyId: event.data.id,
|
|
729
|
+
status: event.data.attributes.status,
|
|
730
|
+
// ... other fields
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
},
|
|
734
|
+
onSubscriptionUpdated: async (event) => {
|
|
735
|
+
await prisma.subscription.update({
|
|
736
|
+
where: { lemonSqueezyId: event.data.id },
|
|
737
|
+
data: { status: event.data.attributes.status },
|
|
738
|
+
});
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
`;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return "// Generated file\n";
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/index.ts
|
|
748
|
+
var program = new Command();
|
|
749
|
+
program.name("saas-kit").description("CLI tool for scaffolding SaaS Kit projects").version("0.1.0");
|
|
750
|
+
program.command("init [name]").description("Create a new SaaS Kit project").option("-t, --template <template>", "Use a specific template").action((name, options) => {
|
|
751
|
+
printBanner();
|
|
752
|
+
init(name, options);
|
|
753
|
+
});
|
|
754
|
+
program.command("add [module]").description("Add a module to your project (payments, admin, blog, landing)").action((module) => {
|
|
755
|
+
add(module);
|
|
756
|
+
});
|
|
757
|
+
program.command("generate [type]").alias("g").description("Generate routes and files (auth, dashboard, api)").action((type) => {
|
|
758
|
+
generate(type);
|
|
759
|
+
});
|
|
760
|
+
program.parse();
|
|
761
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils/logger.ts","../src/commands/init.ts","../src/commands/add.ts","../src/commands/generate.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { printBanner } from \"./utils/logger.js\";\nimport { init } from \"./commands/init.js\";\nimport { add } from \"./commands/add.js\";\nimport { generate } from \"./commands/generate.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"saas-kit\")\n .description(\"CLI tool for scaffolding SaaS Kit projects\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"init [name]\")\n .description(\"Create a new SaaS Kit project\")\n .option(\"-t, --template <template>\", \"Use a specific template\")\n .action((name, options) => {\n printBanner();\n init(name, options);\n });\n\nprogram\n .command(\"add [module]\")\n .description(\"Add a module to your project (payments, admin, blog, landing)\")\n .action((module) => {\n add(module);\n });\n\nprogram\n .command(\"generate [type]\")\n .alias(\"g\")\n .description(\"Generate routes and files (auth, dashboard, api)\")\n .action((type) => {\n generate(type);\n });\n\nprogram.parse();\n","import chalk from \"chalk\";\n\nexport const logger = {\n info: (message: string) => console.log(chalk.blue(\"info\"), message),\n success: (message: string) => console.log(chalk.green(\"✓\"), message),\n warn: (message: string) => console.log(chalk.yellow(\"⚠\"), message),\n error: (message: string) => console.log(chalk.red(\"✗\"), message),\n log: (message: string) => console.log(message),\n};\n\nexport function printBanner() {\n console.log();\n console.log(chalk.bold.cyan(\" ███████╗ █████╗ █████╗ ███████╗\"));\n console.log(chalk.bold.cyan(\" ██╔════╝██╔══██╗██╔══██╗██╔════╝\"));\n console.log(chalk.bold.cyan(\" ███████╗███████║███████║███████╗\"));\n console.log(chalk.bold.cyan(\" ╚════██║██╔══██║██╔══██║╚════██║\"));\n console.log(chalk.bold.cyan(\" ███████║██║ ██║██║ ██║███████║\"));\n console.log(chalk.bold.cyan(\" ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝\"));\n console.log();\n console.log(chalk.bold(\" SaaS Kit CLI - Build your SaaS faster\"));\n console.log();\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport inquirer from \"inquirer\";\nimport { logger } from \"../utils/logger.js\";\n\ninterface InitOptions {\n name?: string;\n template?: string;\n}\n\nexport async function init(projectName?: string, options: InitOptions = {}) {\n logger.log(chalk.bold(\"\\nLet's create your new SaaS project!\\n\"));\n\n // Get project name\n let name = projectName;\n if (!name) {\n const answers = await inquirer.prompt([\n {\n type: \"input\",\n name: \"projectName\",\n message: \"What is your project name?\",\n default: \"my-saas-app\",\n validate: (input: string) => {\n if (/^[a-z0-9-]+$/.test(input)) return true;\n return \"Project name can only contain lowercase letters, numbers, and hyphens\";\n },\n },\n ]);\n name = answers.projectName;\n }\n\n // Get configuration\n const config = await inquirer.prompt([\n {\n type: \"checkbox\",\n name: \"modules\",\n message: \"Which modules would you like to include?\",\n choices: [\n { name: \"Payments (Lemon Squeezy)\", value: \"payments\", checked: true },\n { name: \"Admin Panel\", value: \"admin\", checked: true },\n { name: \"Blog (MDX)\", value: \"blog\", checked: false },\n { name: \"Landing Pages\", value: \"landing\", checked: true },\n ],\n },\n {\n type: \"checkbox\",\n name: \"providers\",\n message: \"Which auth providers do you want?\",\n choices: [\n { name: \"Google\", value: \"google\", checked: true },\n { name: \"GitHub\", value: \"github\", checked: false },\n { name: \"Email (Magic Links)\", value: \"email\", checked: true },\n ],\n },\n {\n type: \"list\",\n name: \"database\",\n message: \"Which database will you use?\",\n choices: [\n { name: \"PostgreSQL\", value: \"postgresql\" },\n { name: \"MySQL\", value: \"mysql\" },\n { name: \"SQLite\", value: \"sqlite\" },\n ],\n default: \"postgresql\",\n },\n ]);\n\n const projectDir = path.resolve(process.cwd(), name!);\n\n // Check if directory exists\n if (await fs.pathExists(projectDir)) {\n const { overwrite } = await inquirer.prompt([\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory ${name} already exists. Overwrite?`,\n default: false,\n },\n ]);\n if (!overwrite) {\n logger.info(\"Aborted.\");\n return;\n }\n await fs.remove(projectDir);\n }\n\n const spinner = ora(\"Creating project...\").start();\n\n try {\n // Create project directory\n await fs.ensureDir(projectDir);\n\n // Create package.json\n const packageJson = {\n name: name,\n version: \"0.1.0\",\n private: true,\n scripts: {\n dev: \"next dev\",\n build: \"next build\",\n start: \"next start\",\n lint: \"next lint\",\n \"db:push\": \"prisma db push\",\n \"db:generate\": \"prisma generate\",\n \"db:studio\": \"prisma studio\",\n },\n dependencies: {\n \"@saas-kit/core\": \"^0.1.0\",\n ...(config.modules.includes(\"payments\") && {\n \"@saas-kit/payments\": \"^0.1.0\",\n }),\n ...(config.modules.includes(\"admin\") && {\n \"@saas-kit/admin\": \"^0.1.0\",\n }),\n ...(config.modules.includes(\"blog\") && { \"@saas-kit/blog\": \"^0.1.0\" }),\n ...(config.modules.includes(\"landing\") && {\n \"@saas-kit/landing\": \"^0.1.0\",\n }),\n next: \"^14.0.0\",\n react: \"^18.0.0\",\n \"react-dom\": \"^18.0.0\",\n \"next-auth\": \"^5.0.0-beta.0\",\n },\n devDependencies: {\n \"@types/node\": \"^20\",\n \"@types/react\": \"^18\",\n \"@types/react-dom\": \"^18\",\n typescript: \"^5\",\n prisma: \"^5.0.0\",\n tailwindcss: \"^3.4.0\",\n postcss: \"^8.4.0\",\n autoprefixer: \"^10.4.0\",\n },\n };\n\n await fs.writeJSON(path.join(projectDir, \"package.json\"), packageJson, {\n spaces: 2,\n });\n\n // Create basic structure\n await fs.ensureDir(path.join(projectDir, \"src/app\"));\n await fs.ensureDir(path.join(projectDir, \"src/components\"));\n await fs.ensureDir(path.join(projectDir, \"src/lib\"));\n await fs.ensureDir(path.join(projectDir, \"prisma\"));\n\n // Create tsconfig.json\n const tsconfig = {\n compilerOptions: {\n target: \"ES2022\",\n lib: [\"dom\", \"dom.iterable\", \"esnext\"],\n allowJs: true,\n skipLibCheck: true,\n strict: true,\n noEmit: true,\n esModuleInterop: true,\n module: \"esnext\",\n moduleResolution: \"bundler\",\n resolveJsonModule: true,\n isolatedModules: true,\n jsx: \"preserve\",\n incremental: true,\n plugins: [{ name: \"next\" }],\n paths: { \"@/*\": [\"./src/*\"] },\n },\n include: [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n exclude: [\"node_modules\"],\n };\n await fs.writeJSON(path.join(projectDir, \"tsconfig.json\"), tsconfig, {\n spaces: 2,\n });\n\n // Create .env.example\n const envExample = `# Database\nDATABASE_URL=\"postgresql://user:password@localhost:5432/mydb\"\n\n# NextAuth\nAUTH_SECRET=\"your-secret-here\"\nNEXTAUTH_URL=\"http://localhost:3000\"\n\n# Google OAuth\nGOOGLE_CLIENT_ID=\"\"\nGOOGLE_CLIENT_SECRET=\"\"\n\n# GitHub OAuth (if using)\nGITHUB_CLIENT_ID=\"\"\nGITHUB_CLIENT_SECRET=\"\"\n\n# Email (Resend)\nAUTH_RESEND_KEY=\"\"\nEMAIL_FROM=\"noreply@yourdomain.com\"\n\n# Lemon Squeezy (if using payments)\nLEMONSQUEEZY_API_KEY=\"\"\nLEMONSQUEEZY_STORE_ID=\"\"\nLEMONSQUEEZY_WEBHOOK_SECRET=\"\"\n\n# App\nNEXT_PUBLIC_APP_URL=\"http://localhost:3000\"\n`;\n await fs.writeFile(path.join(projectDir, \".env.example\"), envExample);\n\n // Create saas-kit.config.ts\n const configFile = `import { defineConfig } from \"@saas-kit/core\";\n\nexport default defineConfig({\n app: {\n name: \"${name}\",\n url: process.env.NEXT_PUBLIC_APP_URL,\n description: \"Your SaaS application\",\n },\n auth: {\n providers: [${config.providers.map((p: string) => `\"${p}\"`).join(\", \")}],\n pages: {\n signIn: \"/login\",\n error: \"/auth-error\",\n verifyRequest: \"/verify-request\",\n },\n },\n features: {\n payments: ${config.modules.includes(\"payments\")},\n admin: ${config.modules.includes(\"admin\")},\n blog: ${config.modules.includes(\"blog\")},\n },\n theme: {\n defaultTheme: \"system\",\n enableSystem: true,\n },\n});\n`;\n await fs.writeFile(path.join(projectDir, \"saas-kit.config.ts\"), configFile);\n\n // Create basic layout\n const layoutFile = `import { SaasKitProvider } from \"@saas-kit/core/providers\";\nimport \"./globals.css\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n <html lang=\"en\" suppressHydrationWarning>\n <body>\n <SaasKitProvider>\n {children}\n </SaasKitProvider>\n </body>\n </html>\n );\n}\n`;\n await fs.writeFile(\n path.join(projectDir, \"src/app/layout.tsx\"),\n layoutFile\n );\n\n // Create globals.css\n const globalsCss = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n --background: 0 0% 100%;\n --foreground: 222.2 47.4% 11.2%;\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n}\n\n.dark {\n --background: 224 71% 4%;\n --foreground: 213 31% 91%;\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 1.2%;\n}\n`;\n await fs.writeFile(\n path.join(projectDir, \"src/app/globals.css\"),\n globalsCss\n );\n\n // Create basic page\n const pageTsx = `export default function Home() {\n return (\n <main className=\"flex min-h-screen flex-col items-center justify-center p-24\">\n <h1 className=\"text-4xl font-bold\">Welcome to ${name}</h1>\n <p className=\"mt-4 text-muted-foreground\">\n Your SaaS application is ready!\n </p>\n </main>\n );\n}\n`;\n await fs.writeFile(path.join(projectDir, \"src/app/page.tsx\"), pageTsx);\n\n // Create tailwind.config.js\n const tailwindConfig = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: [\"class\"],\n content: [\n \"./src/**/*.{js,ts,jsx,tsx,mdx}\",\n \"./node_modules/@saas-kit/*/dist/**/*.{js,ts,jsx,tsx}\",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};\n`;\n await fs.writeFile(\n path.join(projectDir, \"tailwind.config.js\"),\n tailwindConfig\n );\n\n // Create postcss.config.js\n const postcssConfig = `module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n`;\n await fs.writeFile(\n path.join(projectDir, \"postcss.config.js\"),\n postcssConfig\n );\n\n // Create next.config.js\n const nextConfig = `/** @type {import('next').NextConfig} */\nconst nextConfig = {\n experimental: {\n serverActions: true,\n },\n};\n\nmodule.exports = nextConfig;\n`;\n await fs.writeFile(path.join(projectDir, \"next.config.js\"), nextConfig);\n\n // Create .gitignore\n const gitignore = `# dependencies\nnode_modules\n.pnpm-store\n\n# next.js\n.next/\nout/\n\n# production\nbuild\ndist\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n`;\n await fs.writeFile(path.join(projectDir, \".gitignore\"), gitignore);\n\n spinner.succeed(\"Project created successfully!\");\n\n // Print next steps\n logger.log(\"\");\n logger.log(chalk.bold(\"Next steps:\"));\n logger.log(\"\");\n logger.log(chalk.cyan(` cd ${name}`));\n logger.log(chalk.cyan(\" npm install\"));\n logger.log(chalk.cyan(\" cp .env.example .env.local\"));\n logger.log(chalk.cyan(\" # Edit .env.local with your credentials\"));\n logger.log(chalk.cyan(\" npm run dev\"));\n logger.log(\"\");\n logger.success(\"Happy building!\");\n } catch (error) {\n spinner.fail(\"Failed to create project\");\n logger.error(String(error));\n process.exit(1);\n }\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport inquirer from \"inquirer\";\nimport { logger } from \"../utils/logger.js\";\n\ntype Module = \"payments\" | \"admin\" | \"blog\" | \"landing\";\n\nconst moduleInfo: Record<\n Module,\n { name: string; package: string; description: string }\n> = {\n payments: {\n name: \"Payments\",\n package: \"@saas-kit/payments\",\n description: \"Lemon Squeezy integration for subscriptions and payments\",\n },\n admin: {\n name: \"Admin Panel\",\n package: \"@saas-kit/admin\",\n description: \"Admin dashboard components and layouts\",\n },\n blog: {\n name: \"Blog\",\n package: \"@saas-kit/blog\",\n description: \"MDX-powered blog with components\",\n },\n landing: {\n name: \"Landing Pages\",\n package: \"@saas-kit/landing\",\n description: \"Marketing page components (Hero, Features, Pricing, etc.)\",\n },\n};\n\nexport async function add(moduleName?: string) {\n // Check if we're in a project directory\n const packageJsonPath = path.join(process.cwd(), \"package.json\");\n if (!(await fs.pathExists(packageJsonPath))) {\n logger.error(\"No package.json found. Are you in a project directory?\");\n process.exit(1);\n }\n\n let module = moduleName as Module | undefined;\n\n if (!module) {\n const { selectedModule } = await inquirer.prompt([\n {\n type: \"list\",\n name: \"selectedModule\",\n message: \"Which module would you like to add?\",\n choices: Object.entries(moduleInfo).map(([key, value]) => ({\n name: `${value.name} - ${value.description}`,\n value: key,\n })),\n },\n ]);\n module = selectedModule as Module;\n }\n\n if (!moduleInfo[module]) {\n logger.error(`Unknown module: ${module}`);\n logger.info(\n `Available modules: ${Object.keys(moduleInfo).join(\", \")}`\n );\n process.exit(1);\n }\n\n const info = moduleInfo[module];\n const spinner = ora(`Adding ${info.name} module...`).start();\n\n try {\n // Read package.json\n const packageJson = await fs.readJSON(packageJsonPath);\n\n // Check if already installed\n if (\n packageJson.dependencies?.[info.package] ||\n packageJson.devDependencies?.[info.package]\n ) {\n spinner.warn(`${info.name} is already installed`);\n return;\n }\n\n // Add dependency\n packageJson.dependencies = packageJson.dependencies || {};\n packageJson.dependencies[info.package] = \"^0.1.0\";\n\n // Write package.json\n await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });\n\n spinner.succeed(`Added ${info.name} module`);\n\n // Print usage instructions\n logger.log(\"\");\n logger.log(chalk.bold(`Usage example for ${info.name}:`));\n logger.log(\"\");\n\n switch (module) {\n case \"payments\":\n logger.log(chalk.cyan(`import { createCheckoutUrl, createWebhookHandler } from \"${info.package}\";`));\n logger.log(\"\");\n logger.log(\"// Create a checkout URL\");\n logger.log(\"const url = await createCheckoutUrl({\");\n logger.log(' variantId: \"your-variant-id\",');\n logger.log(\" email: user.email,\");\n logger.log(\" userId: user.id,\");\n logger.log(\"});\");\n break;\n case \"admin\":\n logger.log(chalk.cyan(`import { AdminLayout, StatsCard } from \"${info.package}\";`));\n logger.log(\"\");\n logger.log(\"// Use in your admin pages\");\n logger.log(\"<AdminLayout user={user}>\");\n logger.log(\" <StatsCard title=\\\"Users\\\" value={1234} />\");\n logger.log(\"</AdminLayout>\");\n break;\n case \"blog\":\n logger.log(chalk.cyan(`import { getAllPosts, PostCard } from \"${info.package}\";`));\n logger.log(\"\");\n logger.log(\"// Get all blog posts\");\n logger.log(\"const posts = getAllPosts();\");\n break;\n case \"landing\":\n logger.log(chalk.cyan(`import { Hero, Features, Pricing } from \"${info.package}\";`));\n logger.log(\"\");\n logger.log(\"// Build your landing page\");\n logger.log(\"<Hero title=\\\"...\\\" subtitle=\\\"...\\\" />\");\n logger.log(\"<Features features={[...]} />\");\n logger.log(\"<Pricing plans={[...]} />\");\n break;\n }\n\n logger.log(\"\");\n logger.log(chalk.yellow(\"Don't forget to run: npm install\"));\n logger.log(\"\");\n } catch (error) {\n spinner.fail(`Failed to add ${info.name} module`);\n logger.error(String(error));\n process.exit(1);\n }\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport inquirer from \"inquirer\";\nimport { logger } from \"../utils/logger.js\";\n\ntype GenerateType = \"auth\" | \"dashboard\" | \"api\";\n\nconst generators: Record<\n GenerateType,\n { name: string; description: string; files: string[] }\n> = {\n auth: {\n name: \"Auth Routes\",\n description: \"Login, signup, and verify-request pages\",\n files: [\"login/page.tsx\", \"signup/page.tsx\", \"verify-request/page.tsx\", \"auth-error/page.tsx\"],\n },\n dashboard: {\n name: \"Dashboard Routes\",\n description: \"Dashboard layout and basic pages\",\n files: [\"dashboard/layout.tsx\", \"dashboard/page.tsx\", \"dashboard/settings/page.tsx\"],\n },\n api: {\n name: \"API Routes\",\n description: \"Auth and webhook API routes\",\n files: [\"api/auth/[...nextauth]/route.ts\", \"api/webhooks/lemonsqueezy/route.ts\"],\n },\n};\n\nexport async function generate(type?: string) {\n let selectedType = type as GenerateType | undefined;\n\n if (!selectedType) {\n const { generatorType } = await inquirer.prompt([\n {\n type: \"list\",\n name: \"generatorType\",\n message: \"What would you like to generate?\",\n choices: Object.entries(generators).map(([key, value]) => ({\n name: `${value.name} - ${value.description}`,\n value: key,\n })),\n },\n ]);\n selectedType = generatorType as GenerateType;\n }\n\n if (!generators[selectedType]) {\n logger.error(`Unknown generator: ${selectedType}`);\n logger.info(`Available generators: ${Object.keys(generators).join(\", \")}`);\n process.exit(1);\n }\n\n const generator = generators[selectedType];\n const spinner = ora(`Generating ${generator.name}...`).start();\n\n try {\n const appDir = path.join(process.cwd(), \"src/app\");\n\n // Check if app directory exists\n if (!(await fs.pathExists(appDir))) {\n await fs.ensureDir(appDir);\n }\n\n for (const file of generator.files) {\n const filePath = path.join(appDir, file);\n const dirPath = path.dirname(filePath);\n\n await fs.ensureDir(dirPath);\n\n // Generate file content based on type\n const content = generateFileContent(selectedType, file);\n await fs.writeFile(filePath, content);\n }\n\n spinner.succeed(`Generated ${generator.name}`);\n\n logger.log(\"\");\n logger.log(chalk.bold(\"Files created:\"));\n for (const file of generator.files) {\n logger.log(chalk.cyan(` src/app/${file}`));\n }\n logger.log(\"\");\n } catch (error) {\n spinner.fail(`Failed to generate ${generator.name}`);\n logger.error(String(error));\n process.exit(1);\n }\n}\n\nfunction generateFileContent(type: GenerateType, file: string): string {\n if (type === \"auth\") {\n if (file.includes(\"login\")) {\n return `import { AuthForm } from \"@saas-kit/core/components/auth\";\n\nexport default function LoginPage() {\n return (\n <div className=\"flex min-h-screen items-center justify-center\">\n <div className=\"w-full max-w-md p-8\">\n <AuthForm mode=\"login\" />\n </div>\n </div>\n );\n}\n`;\n }\n if (file.includes(\"signup\")) {\n return `import { AuthForm } from \"@saas-kit/core/components/auth\";\n\nexport default function SignupPage() {\n return (\n <div className=\"flex min-h-screen items-center justify-center\">\n <div className=\"w-full max-w-md p-8\">\n <AuthForm mode=\"signup\" />\n </div>\n </div>\n );\n}\n`;\n }\n if (file.includes(\"verify-request\")) {\n return `export default function VerifyRequestPage() {\n return (\n <div className=\"flex min-h-screen items-center justify-center\">\n <div className=\"w-full max-w-md p-8 text-center\">\n <h1 className=\"text-2xl font-bold\">Check your email</h1>\n <p className=\"mt-4 text-muted-foreground\">\n A sign in link has been sent to your email address.\n </p>\n </div>\n </div>\n );\n}\n`;\n }\n if (file.includes(\"auth-error\")) {\n return `export default function AuthErrorPage() {\n return (\n <div className=\"flex min-h-screen items-center justify-center\">\n <div className=\"w-full max-w-md p-8 text-center\">\n <h1 className=\"text-2xl font-bold text-destructive\">Authentication Error</h1>\n <p className=\"mt-4 text-muted-foreground\">\n There was an error signing you in. Please try again.\n </p>\n </div>\n </div>\n );\n}\n`;\n }\n }\n\n if (type === \"dashboard\") {\n if (file.includes(\"layout\")) {\n return `import { DashboardLayout } from \"@saas-kit/core/components/dashboard\";\nimport { UserButton } from \"@saas-kit/core/components/auth\";\nimport { LayoutDashboard, Settings, User } from \"lucide-react\";\n\nconst sidebarConfig = [\n {\n title: \"Overview\",\n items: [\n { title: \"Dashboard\", href: \"/dashboard\", icon: LayoutDashboard },\n ],\n },\n {\n title: \"Settings\",\n items: [\n { title: \"Profile\", href: \"/dashboard/settings\", icon: User },\n { title: \"Settings\", href: \"/dashboard/settings\", icon: Settings },\n ],\n },\n];\n\nexport default function DashboardLayoutPage({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n <DashboardLayout\n sidebarConfig={sidebarConfig}\n headerContent={<UserButton />}\n >\n {children}\n </DashboardLayout>\n );\n}\n`;\n }\n if (file === \"dashboard/page.tsx\") {\n return `export default function DashboardPage() {\n return (\n <div>\n <h1 className=\"text-3xl font-bold\">Dashboard</h1>\n <p className=\"mt-2 text-muted-foreground\">Welcome to your dashboard.</p>\n </div>\n );\n}\n`;\n }\n if (file.includes(\"settings\")) {\n return `export default function SettingsPage() {\n return (\n <div>\n <h1 className=\"text-3xl font-bold\">Settings</h1>\n <p className=\"mt-2 text-muted-foreground\">Manage your account settings.</p>\n </div>\n );\n}\n`;\n }\n }\n\n if (type === \"api\") {\n if (file.includes(\"nextauth\")) {\n return `import NextAuth from \"next-auth\";\nimport { createAuthConfig } from \"@saas-kit/core\";\nimport { PrismaAdapter } from \"@auth/prisma-adapter\";\nimport { prisma } from \"@/lib/prisma\";\n\nconst authConfig = createAuthConfig({\n adapter: PrismaAdapter(prisma),\n providers: [\"google\", \"email\"],\n callbacks: {\n getUserRole: async (userId) => {\n const user = await prisma.user.findUnique({\n where: { id: userId },\n select: { role: true },\n });\n return user?.role || \"USER\";\n },\n },\n});\n\nconst handler = NextAuth(authConfig);\n\nexport { handler as GET, handler as POST };\n`;\n }\n if (file.includes(\"lemonsqueezy\")) {\n return `import { createWebhookHandler } from \"@saas-kit/payments\";\nimport { prisma } from \"@/lib/prisma\";\n\nexport const POST = createWebhookHandler({\n onSubscriptionCreated: async (event) => {\n const userId = event.meta.custom_data?.user_id;\n if (!userId) return;\n\n await prisma.subscription.create({\n data: {\n userId,\n lemonSqueezyId: event.data.id,\n status: event.data.attributes.status,\n // ... other fields\n },\n });\n },\n onSubscriptionUpdated: async (event) => {\n await prisma.subscription.update({\n where: { lemonSqueezyId: event.data.id },\n data: { status: event.data.attributes.status },\n });\n },\n});\n`;\n }\n }\n\n return \"// Generated file\\n\";\n}\n"],"mappings":"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","names":["chalk","chalk","fs","path","chalk","ora","inquirer","path","fs","inquirer","ora","chalk","fs","path","chalk","ora","inquirer","inquirer","ora","path","fs","chalk"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@victusvinceere/saas-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for scaffolding SaaS Kit projects",
|
|
5
|
+
"bin": {
|
|
6
|
+
"saas-kit": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"clean": "rm -rf dist"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"chalk": "^5.3.0",
|
|
22
|
+
"commander": "^12.0.0",
|
|
23
|
+
"fs-extra": "^11.2.0",
|
|
24
|
+
"inquirer": "^9.2.15",
|
|
25
|
+
"ora": "^8.0.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/fs-extra": "^11.0.4",
|
|
29
|
+
"@types/inquirer": "^9.0.7",
|
|
30
|
+
"@types/node": "^20",
|
|
31
|
+
"tsup": "^8.0.2",
|
|
32
|
+
"typescript": "^5"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"saas",
|
|
36
|
+
"cli",
|
|
37
|
+
"scaffolding",
|
|
38
|
+
"next.js"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT"
|
|
41
|
+
}
|