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 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
- sentryNext: "^8.35.0"
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
- const gitignore = await readTextFile(join3(templatesRoot, "shared", ".gitignore"));
594
- await writeTextFile(join3(projectRoot, ".gitignore"), gitignore, ctx);
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 as fileURLToPath2 } from "url";
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 = fileURLToPath2(new URL("../../../templates", import.meta.url));
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
- await appendEnvLine(envPath, 'DATABASE_URL=""', ctx);
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 fileURLToPath3 } from "url";
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 = fileURLToPath3(new URL("../../../templates", import.meta.url));
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 fileURLToPath4 } from "url";
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 = fileURLToPath4(new URL("../../../templates", import.meta.url));
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 fileURLToPath5 } from "url";
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 = fileURLToPath5(new URL("../../../templates", import.meta.url));
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 fileURLToPath6 } from "url";
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 = fileURLToPath6(new URL("../../../templates", import.meta.url));
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 fileURLToPath7 } from "url";
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 = fileURLToPath7(new URL("../../../templates", import.meta.url));
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") {