create-stackforge 0.1.1 → 0.1.3
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/cli.js +140 -30
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/auth/better-auth-client.js +7 -0
- package/templates/auth/better-auth-client.ts +7 -0
- package/templates/auth/better-auth-protected-page.jsx +12 -0
- package/templates/auth/better-auth-protected-page.tsx +12 -0
- package/templates/auth/better-auth-route.js +4 -0
- package/templates/auth/better-auth-route.ts +4 -0
- package/templates/auth/better-auth-server.js +9 -0
- package/templates/auth/better-auth-server.ts +9 -0
- package/templates/auth/better-auth-signin.jsx +25 -0
- package/templates/auth/better-auth-signin.tsx +25 -0
- package/templates/auth/better-auth.README.md +10 -0
- package/templates/shared/_gitignore +7 -0
package/dist/cli.js
CHANGED
|
@@ -123,15 +123,58 @@ function defaultConfig() {
|
|
|
123
123
|
var supported = {
|
|
124
124
|
frontend: ["nextjs", "vite"],
|
|
125
125
|
ui: ["none", "tailwind", "shadcn", "mui", "chakra", "mantine", "antd", "nextui"],
|
|
126
|
-
database: ["none", "postgres", "mysql", "sqlite", "neon", "supabase"],
|
|
126
|
+
database: ["none", "postgres", "mysql", "sqlite", "mongodb", "neon", "supabase"],
|
|
127
127
|
orm: ["drizzle", "prisma", "mongoose", "typeorm"],
|
|
128
|
-
auth: ["none", "nextauth", "clerk", "supabase"],
|
|
128
|
+
auth: ["none", "nextauth", "clerk", "better-auth", "supabase"],
|
|
129
129
|
api: ["none", "rest", "trpc", "graphql"],
|
|
130
130
|
agents: ["claude", "copilot", "codex", "gemini", "cursor", "codeium", "windsurf", "tabnine"],
|
|
131
131
|
features: ["email", "storage", "payments", "analytics", "error-tracking"]
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
// src/cli/prompts/index.ts
|
|
135
|
+
var displayNames = {
|
|
136
|
+
// Frontend
|
|
137
|
+
nextjs: "Next.js",
|
|
138
|
+
vite: "Vite",
|
|
139
|
+
// UI
|
|
140
|
+
none: "None",
|
|
141
|
+
tailwind: "Tailwind CSS",
|
|
142
|
+
shadcn: "shadcn/ui",
|
|
143
|
+
mui: "Material UI",
|
|
144
|
+
chakra: "Chakra UI",
|
|
145
|
+
mantine: "Mantine",
|
|
146
|
+
antd: "Ant Design",
|
|
147
|
+
nextui: "NextUI",
|
|
148
|
+
// Database
|
|
149
|
+
postgres: "PostgreSQL",
|
|
150
|
+
mysql: "MySQL",
|
|
151
|
+
sqlite: "SQLite",
|
|
152
|
+
mongodb: "MongoDB",
|
|
153
|
+
neon: "Neon",
|
|
154
|
+
supabase: "Supabase",
|
|
155
|
+
// ORM
|
|
156
|
+
drizzle: "Drizzle",
|
|
157
|
+
prisma: "Prisma",
|
|
158
|
+
mongoose: "Mongoose",
|
|
159
|
+
typeorm: "TypeORM",
|
|
160
|
+
// Auth
|
|
161
|
+
nextauth: "NextAuth",
|
|
162
|
+
clerk: "Clerk",
|
|
163
|
+
"better-auth": "Better Auth",
|
|
164
|
+
// API
|
|
165
|
+
rest: "REST",
|
|
166
|
+
trpc: "tRPC",
|
|
167
|
+
graphql: "GraphQL",
|
|
168
|
+
// Features
|
|
169
|
+
email: "Email (Resend)",
|
|
170
|
+
storage: "File Storage (Cloudinary)",
|
|
171
|
+
payments: "Payments (Stripe)",
|
|
172
|
+
analytics: "Analytics (PostHog)",
|
|
173
|
+
"error-tracking": "Error Tracking (Sentry)"
|
|
174
|
+
};
|
|
175
|
+
function label(value) {
|
|
176
|
+
return displayNames[value] || value;
|
|
177
|
+
}
|
|
135
178
|
async function promptForConfig(input) {
|
|
136
179
|
if (input.skipPrompts) {
|
|
137
180
|
const base2 = defaultConfig();
|
|
@@ -166,7 +209,7 @@ async function promptForConfig(input) {
|
|
|
166
209
|
type: "list",
|
|
167
210
|
name: "frontend",
|
|
168
211
|
message: "Frontend framework",
|
|
169
|
-
choices: supported.frontend.map((v) => ({ name: v, value: v }))
|
|
212
|
+
choices: supported.frontend.map((v) => ({ name: label(v), value: v }))
|
|
170
213
|
},
|
|
171
214
|
{
|
|
172
215
|
type: "list",
|
|
@@ -181,38 +224,38 @@ async function promptForConfig(input) {
|
|
|
181
224
|
type: "list",
|
|
182
225
|
name: "uiLibrary",
|
|
183
226
|
message: "UI library",
|
|
184
|
-
choices: supported.ui.map((v) => ({ name: v, value: v }))
|
|
227
|
+
choices: supported.ui.map((v) => ({ name: label(v), value: v }))
|
|
185
228
|
},
|
|
186
229
|
{
|
|
187
230
|
type: "list",
|
|
188
231
|
name: "databaseProvider",
|
|
189
232
|
message: "Database provider",
|
|
190
|
-
choices: supported.database.map((v) => ({ name: v, value: v }))
|
|
233
|
+
choices: supported.database.map((v) => ({ name: label(v), value: v }))
|
|
191
234
|
},
|
|
192
235
|
{
|
|
193
236
|
type: "list",
|
|
194
237
|
name: "orm",
|
|
195
238
|
message: "ORM",
|
|
196
239
|
when: (ans) => ans.databaseProvider !== "none",
|
|
197
|
-
choices: supported.orm.map((v) => ({ name: v, value: v }))
|
|
240
|
+
choices: supported.orm.map((v) => ({ name: label(v), value: v }))
|
|
198
241
|
},
|
|
199
242
|
{
|
|
200
243
|
type: "list",
|
|
201
244
|
name: "authProvider",
|
|
202
245
|
message: "Authentication",
|
|
203
|
-
choices: supported.auth.map((v) => ({ name: v, value: v }))
|
|
246
|
+
choices: supported.auth.map((v) => ({ name: label(v), value: v }))
|
|
204
247
|
},
|
|
205
248
|
{
|
|
206
249
|
type: "list",
|
|
207
250
|
name: "apiType",
|
|
208
251
|
message: "API type",
|
|
209
|
-
choices: supported.api.map((v) => ({ name: v, value: v }))
|
|
252
|
+
choices: supported.api.map((v) => ({ name: label(v), value: v }))
|
|
210
253
|
},
|
|
211
254
|
{
|
|
212
255
|
type: "checkbox",
|
|
213
256
|
name: "features",
|
|
214
257
|
message: "Additional features",
|
|
215
|
-
choices: supported.features.map((v) => ({ name: v, value: v }))
|
|
258
|
+
choices: supported.features.map((v) => ({ name: label(v), value: v }))
|
|
216
259
|
}
|
|
217
260
|
]);
|
|
218
261
|
const base = {
|
|
@@ -232,7 +275,6 @@ async function promptForConfig(input) {
|
|
|
232
275
|
|
|
233
276
|
// src/generators/core/project-creator.ts
|
|
234
277
|
import { join as join3 } from "path";
|
|
235
|
-
import { fileURLToPath } from "url";
|
|
236
278
|
|
|
237
279
|
// src/utils/file-system.ts
|
|
238
280
|
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
@@ -358,11 +400,24 @@ var versions = {
|
|
|
358
400
|
graphql: "^16.9.0",
|
|
359
401
|
graphqlRequest: "^6.1.0",
|
|
360
402
|
graphqlYoga: "^5.7.0",
|
|
403
|
+
mongodb: "^6.8.0",
|
|
404
|
+
betterAuth: "^1.2.0",
|
|
361
405
|
resend: "^3.5.0",
|
|
406
|
+
sendgrid: "^8.1.0",
|
|
407
|
+
nodemailer: "^6.9.0",
|
|
408
|
+
typesNodemailer: "^6.4.0",
|
|
409
|
+
awsSes: "^3.700.0",
|
|
362
410
|
cloudinary: "^2.0.0",
|
|
411
|
+
awsS3: "^3.700.0",
|
|
412
|
+
vercelBlob: "^0.24.0",
|
|
363
413
|
stripe: "^14.0.0",
|
|
414
|
+
paypal: "^1.0.0",
|
|
415
|
+
razorpay: "^2.9.0",
|
|
364
416
|
posthog: "^1.165.0",
|
|
365
|
-
|
|
417
|
+
reactGa4: "^2.1.0",
|
|
418
|
+
vercelAnalytics: "^1.3.0",
|
|
419
|
+
sentryNext: "^8.35.0",
|
|
420
|
+
logrocket: "^8.1.0"
|
|
366
421
|
};
|
|
367
422
|
|
|
368
423
|
// src/generators/deps/deps-registry.ts
|
|
@@ -434,6 +489,9 @@ function collectDependencies(config) {
|
|
|
434
489
|
if (config.database.provider === "sqlite") {
|
|
435
490
|
dependencies["better-sqlite3"] = versions.betterSqlite3;
|
|
436
491
|
}
|
|
492
|
+
if (config.database.provider === "mongodb") {
|
|
493
|
+
dependencies["mongodb"] = versions.mongodb;
|
|
494
|
+
}
|
|
437
495
|
if (config.database.provider === "neon") {
|
|
438
496
|
dependencies["@neondatabase/serverless"] = versions.neonServerless;
|
|
439
497
|
}
|
|
@@ -461,6 +519,9 @@ function collectDependencies(config) {
|
|
|
461
519
|
if (config.auth.provider === "clerk") {
|
|
462
520
|
dependencies["@clerk/nextjs"] = versions.clerkNext;
|
|
463
521
|
}
|
|
522
|
+
if (config.auth.provider === "better-auth") {
|
|
523
|
+
dependencies["better-auth"] = versions.betterAuth;
|
|
524
|
+
}
|
|
464
525
|
if (config.auth.provider === "supabase") {
|
|
465
526
|
dependencies["@supabase/supabase-js"] = versions.supabaseJs;
|
|
466
527
|
if (config.frontend.type === "nextjs") {
|
|
@@ -581,19 +642,33 @@ async function readProjectConfig(root) {
|
|
|
581
642
|
}
|
|
582
643
|
|
|
583
644
|
// src/generators/core/project-creator.ts
|
|
645
|
+
var GITIGNORE_CONTENT = `node_modules/
|
|
646
|
+
.next/
|
|
647
|
+
dist/
|
|
648
|
+
.env
|
|
649
|
+
.env.local
|
|
650
|
+
.env.*.local
|
|
651
|
+
.DS_Store
|
|
652
|
+
`;
|
|
653
|
+
var EDITORCONFIG_CONTENT = `root = true
|
|
654
|
+
|
|
655
|
+
[*]
|
|
656
|
+
charset = utf-8
|
|
657
|
+
end_of_line = lf
|
|
658
|
+
insert_final_newline = true
|
|
659
|
+
indent_style = space
|
|
660
|
+
indent_size = 2
|
|
661
|
+
`;
|
|
584
662
|
async function createProjectSkeleton(root, config, ctx) {
|
|
585
663
|
const projectRoot = join3(root, config.projectName);
|
|
586
|
-
const templatesRoot = fileURLToPath(new URL("../../../templates", import.meta.url));
|
|
587
664
|
await ensureDir(projectRoot, ctx);
|
|
588
665
|
const readme = buildProjectReadme(config);
|
|
589
666
|
await writeTextFile(join3(projectRoot, "README.md"), readme + "\n", ctx);
|
|
590
667
|
const envExample = `# Environment Variables
|
|
591
668
|
`;
|
|
592
669
|
await writeTextFile(join3(projectRoot, ".env.example"), envExample, ctx);
|
|
593
|
-
|
|
594
|
-
await writeTextFile(join3(projectRoot, ".
|
|
595
|
-
const editorconfig = await readTextFile(join3(templatesRoot, "shared", ".editorconfig"));
|
|
596
|
-
await writeTextFile(join3(projectRoot, ".editorconfig"), editorconfig, ctx);
|
|
670
|
+
await writeTextFile(join3(projectRoot, ".gitignore"), GITIGNORE_CONTENT, ctx);
|
|
671
|
+
await writeTextFile(join3(projectRoot, ".editorconfig"), EDITORCONFIG_CONTENT, ctx);
|
|
597
672
|
const pkg = {
|
|
598
673
|
name: config.projectName,
|
|
599
674
|
version: "0.0.0",
|
|
@@ -637,7 +712,7 @@ async function createProjectSkeleton(root, config, ctx) {
|
|
|
637
712
|
|
|
638
713
|
// src/generators/database/database-files.ts
|
|
639
714
|
import { join as join4 } from "path";
|
|
640
|
-
import { fileURLToPath
|
|
715
|
+
import { fileURLToPath } from "url";
|
|
641
716
|
|
|
642
717
|
// src/utils/env-file.ts
|
|
643
718
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -681,10 +756,14 @@ async function removeEnvKey(path, key, ctx) {
|
|
|
681
756
|
// src/generators/database/database-files.ts
|
|
682
757
|
async function generateDatabaseFiles(root, config, ctx) {
|
|
683
758
|
const projectRoot = join4(root, config.projectName);
|
|
684
|
-
const templatesRoot =
|
|
759
|
+
const templatesRoot = fileURLToPath(new URL("../../../templates", import.meta.url));
|
|
685
760
|
if (config.database.provider !== "none") {
|
|
686
761
|
const envPath = join4(projectRoot, ".env.example");
|
|
687
|
-
|
|
762
|
+
if (config.database.provider === "mongodb") {
|
|
763
|
+
await appendEnvLine(envPath, 'MONGODB_URI=""', ctx);
|
|
764
|
+
} else {
|
|
765
|
+
await appendEnvLine(envPath, 'DATABASE_URL=""', ctx);
|
|
766
|
+
}
|
|
688
767
|
if (config.database.provider === "neon") {
|
|
689
768
|
await appendEnvLine(envPath, 'NEON_API_KEY=""', ctx);
|
|
690
769
|
await appendEnvLine(envPath, 'NEON_PROJECT_ID=""', ctx);
|
|
@@ -772,7 +851,7 @@ async function generateDatabaseFiles(root, config, ctx) {
|
|
|
772
851
|
|
|
773
852
|
// src/generators/frontend/frontend-files.ts
|
|
774
853
|
import { join as join5 } from "path";
|
|
775
|
-
import { fileURLToPath as
|
|
854
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
776
855
|
|
|
777
856
|
// src/generators/templates/template-engine.ts
|
|
778
857
|
function applyTemplate(content, vars) {
|
|
@@ -786,7 +865,7 @@ function applyTemplate(content, vars) {
|
|
|
786
865
|
// src/generators/frontend/frontend-files.ts
|
|
787
866
|
async function generateFrontendFiles(root, config, ctx) {
|
|
788
867
|
const projectRoot = join5(root, config.projectName);
|
|
789
|
-
const templatesRoot =
|
|
868
|
+
const templatesRoot = fileURLToPath2(new URL("../../../templates", import.meta.url));
|
|
790
869
|
if (config.frontend.type === "nextjs") {
|
|
791
870
|
const appDir = join5(projectRoot, "app");
|
|
792
871
|
await ensureDir(appDir, ctx);
|
|
@@ -1123,10 +1202,10 @@ function buildPageLinks(config) {
|
|
|
1123
1202
|
|
|
1124
1203
|
// src/generators/ui/ui-files.ts
|
|
1125
1204
|
import { join as join6 } from "path";
|
|
1126
|
-
import { fileURLToPath as
|
|
1205
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1127
1206
|
async function generateUiFiles(root, config, ctx) {
|
|
1128
1207
|
const projectRoot = join6(root, config.projectName);
|
|
1129
|
-
const templatesRoot =
|
|
1208
|
+
const templatesRoot = fileURLToPath3(new URL("../../../templates", import.meta.url));
|
|
1130
1209
|
if (config.ui.library === "tailwind") {
|
|
1131
1210
|
const tailwindTemplate = await readTextFile(join6(templatesRoot, "ui", "tailwind.config.js"));
|
|
1132
1211
|
const postcssTemplate = await readTextFile(join6(templatesRoot, "ui", "postcss.config.js"));
|
|
@@ -1194,10 +1273,10 @@ async function generateUiFiles(root, config, ctx) {
|
|
|
1194
1273
|
|
|
1195
1274
|
// src/generators/auth/auth-files.ts
|
|
1196
1275
|
import { join as join7 } from "path";
|
|
1197
|
-
import { fileURLToPath as
|
|
1276
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1198
1277
|
async function generateAuthFiles(root, config, ctx) {
|
|
1199
1278
|
const projectRoot = join7(root, config.projectName);
|
|
1200
|
-
const templatesRoot =
|
|
1279
|
+
const templatesRoot = fileURLToPath4(new URL("../../../templates", import.meta.url));
|
|
1201
1280
|
if (config.auth.provider === "nextauth") {
|
|
1202
1281
|
await appendEnvLine(join7(projectRoot, ".env.example"), 'NEXTAUTH_SECRET=""', ctx);
|
|
1203
1282
|
await appendEnvLine(join7(projectRoot, ".env.example"), 'NEXTAUTH_URL=""', ctx);
|
|
@@ -1272,6 +1351,37 @@ export const config = {
|
|
|
1272
1351
|
await writeTextFile(join7(authDir, `protected.${ext}x`), protectedPage, ctx);
|
|
1273
1352
|
}
|
|
1274
1353
|
}
|
|
1354
|
+
if (config.auth.provider === "better-auth") {
|
|
1355
|
+
await appendEnvLine(join7(projectRoot, ".env.example"), 'BETTER_AUTH_SECRET=""', ctx);
|
|
1356
|
+
await appendEnvLine(join7(projectRoot, ".env.example"), 'BETTER_AUTH_URL=""', ctx);
|
|
1357
|
+
const ext = config.frontend.language === "ts" ? "ts" : "js";
|
|
1358
|
+
const authDir = join7(projectRoot, "auth");
|
|
1359
|
+
await ensureDir(authDir, ctx);
|
|
1360
|
+
const readme = await readTextFile(join7(templatesRoot, "auth", "better-auth.README.md"));
|
|
1361
|
+
await writeTextFile(join7(authDir, "README.md"), readme, ctx);
|
|
1362
|
+
const serverConfig = await readTextFile(join7(templatesRoot, "auth", `better-auth-server.${ext}`));
|
|
1363
|
+
await writeTextFile(join7(authDir, `auth.${ext}`), serverConfig, ctx);
|
|
1364
|
+
const libDir = join7(projectRoot, "src", "lib");
|
|
1365
|
+
await ensureDir(libDir, ctx);
|
|
1366
|
+
const clientConfig = await readTextFile(join7(templatesRoot, "auth", `better-auth-client.${ext}`));
|
|
1367
|
+
await writeTextFile(join7(libDir, `auth-client.${ext}`), clientConfig, ctx);
|
|
1368
|
+
if (config.frontend.type === "nextjs") {
|
|
1369
|
+
const routeDir = join7(projectRoot, "app", "api", "auth", "[...all]");
|
|
1370
|
+
await ensureDir(routeDir, ctx);
|
|
1371
|
+
const route = await readTextFile(join7(templatesRoot, "auth", `better-auth-route.${ext}`));
|
|
1372
|
+
await writeTextFile(join7(routeDir, `route.${ext}`), route, ctx);
|
|
1373
|
+
const protectedDir = join7(projectRoot, "app", "auth", "protected");
|
|
1374
|
+
await ensureDir(protectedDir, ctx);
|
|
1375
|
+
const protectedPage = await readTextFile(
|
|
1376
|
+
join7(templatesRoot, "auth", `better-auth-protected-page.${ext}x`)
|
|
1377
|
+
);
|
|
1378
|
+
await writeTextFile(join7(protectedDir, `page.${ext}x`), protectedPage, ctx);
|
|
1379
|
+
const signInDir = join7(projectRoot, "app", "auth", "signin");
|
|
1380
|
+
await ensureDir(signInDir, ctx);
|
|
1381
|
+
const signInPage = await readTextFile(join7(templatesRoot, "auth", `better-auth-signin.${ext}x`));
|
|
1382
|
+
await writeTextFile(join7(signInDir, `page.${ext}x`), signInPage, ctx);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1275
1385
|
if (config.auth.provider === "supabase") {
|
|
1276
1386
|
await appendEnvLine(join7(projectRoot, ".env.example"), 'NEXT_PUBLIC_SUPABASE_URL=""', ctx);
|
|
1277
1387
|
await appendEnvLine(join7(projectRoot, ".env.example"), 'NEXT_PUBLIC_SUPABASE_ANON_KEY=""', ctx);
|
|
@@ -1337,10 +1447,10 @@ export function createSupabaseServerClient() {
|
|
|
1337
1447
|
|
|
1338
1448
|
// src/generators/api/api-files.ts
|
|
1339
1449
|
import { join as join8 } from "path";
|
|
1340
|
-
import { fileURLToPath as
|
|
1450
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1341
1451
|
async function generateApiFiles(root, config, ctx) {
|
|
1342
1452
|
const projectRoot = join8(root, config.projectName);
|
|
1343
|
-
const templatesRoot =
|
|
1453
|
+
const templatesRoot = fileURLToPath5(new URL("../../../templates", import.meta.url));
|
|
1344
1454
|
if (config.api.type === "rest") {
|
|
1345
1455
|
const apiDir = join8(projectRoot, "api");
|
|
1346
1456
|
await ensureDir(apiDir, ctx);
|
|
@@ -2017,10 +2127,10 @@ server.listen(port, () => {
|
|
|
2017
2127
|
|
|
2018
2128
|
// src/generators/features/feature-files.ts
|
|
2019
2129
|
import { join as join10 } from "path";
|
|
2020
|
-
import { fileURLToPath as
|
|
2130
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
2021
2131
|
async function generateFeatureFiles(root, config, ctx) {
|
|
2022
2132
|
const projectRoot = join10(root, config.projectName);
|
|
2023
|
-
const templatesRoot =
|
|
2133
|
+
const templatesRoot = fileURLToPath6(new URL("../../../templates", import.meta.url));
|
|
2024
2134
|
const libDir = join10(projectRoot, "src", "lib");
|
|
2025
2135
|
const isTs = config.frontend.language === "ts";
|
|
2026
2136
|
if (config.features.includes("email")) {
|
|
@@ -2139,7 +2249,7 @@ function validateCompatibility(config) {
|
|
|
2139
2249
|
if (config.api.type === "trpc" && config.frontend.language !== "ts") {
|
|
2140
2250
|
throw new Error("tRPC requires TypeScript.");
|
|
2141
2251
|
}
|
|
2142
|
-
if (config.database.orm === "mongoose") {
|
|
2252
|
+
if (config.database.orm === "mongoose" && config.database.provider !== "mongodb") {
|
|
2143
2253
|
throw new Error("Mongoose requires MongoDB. Use drizzle, prisma, or typeorm with SQL databases.");
|
|
2144
2254
|
}
|
|
2145
2255
|
if (config.database.orm === "typeorm") {
|