better-ts-stack 2.0.2 → 2.1.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 (68) hide show
  1. package/README.md +114 -113
  2. package/dist/args.js +27 -20
  3. package/dist/builder/buildPlanner.js +1 -1
  4. package/dist/builder/configGenerator.js +4 -6
  5. package/dist/builder/index.js +15 -3
  6. package/dist/builder/moduleSelector.js +6 -2
  7. package/dist/builder/readmeGenerator.js +107 -0
  8. package/dist/builder/templateContext.js +53 -0
  9. package/dist/index.js +7 -1
  10. package/dist/output/nextSteps.js +23 -22
  11. package/dist/prompts/index.js +5 -2
  12. package/dist/prompts/stack.js +23 -2
  13. package/dist/types/index.js +2 -0
  14. package/dist/ui/messages.js +3 -1
  15. package/dist/validators/index.js +5 -2
  16. package/package.json +3 -3
  17. package/templates/backend/express/config.json +42 -40
  18. package/templates/backend/express/gitignore +3 -0
  19. package/templates/backend/express/src/env.ts.hbs +39 -0
  20. package/templates/backend/express/src/index.ts.hbs +111 -75
  21. package/templates/backend/nestjs/config.json +43 -41
  22. package/templates/backend/nestjs/gitignore +3 -0
  23. package/templates/backend/nestjs/src/app.module.ts.hbs +13 -0
  24. package/templates/backend/nestjs/src/env.ts.hbs +39 -0
  25. package/templates/backend/nestjs/src/health/{health.controller.ts → health.controller.ts.hbs} +8 -0
  26. package/templates/backend/nestjs/src/main.ts +3 -2
  27. package/templates/frontend/nextjs/app/page.tsx.hbs +98 -97
  28. package/templates/frontend/nextjs/config.json +12 -14
  29. package/templates/frontend/nextjs/gitignore +3 -0
  30. package/templates/frontend/tanstack/config.json +16 -16
  31. package/templates/frontend/tanstack/gitignore +3 -0
  32. package/templates/frontend/tanstack/src/routes/index.tsx.hbs +102 -101
  33. package/templates/frontend/tanstack/vite.config.ts +23 -14
  34. package/templates/modules/auth/express/config.json +20 -20
  35. package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +1 -2
  36. package/templates/modules/auth/express/src/routes/auth.ts.hbs +27 -19
  37. package/templates/modules/auth/nestjs/config.json +20 -20
  38. package/templates/modules/auth/nestjs/src/auth/{auth.controller.ts → auth.controller.ts.hbs} +15 -0
  39. package/templates/modules/auth/nextjs/config.json +4 -4
  40. package/templates/{frontend/nextjs/proxy.ts.hbs → modules/auth/nextjs/proxy.ts} +1 -8
  41. package/templates/modules/auth/tanstack/config.json +4 -4
  42. package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +132 -134
  43. package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +46 -48
  44. package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +150 -152
  45. package/templates/modules/ci/.github/workflows/ci.yml.hbs +43 -0
  46. package/templates/modules/ci/config.json +10 -0
  47. package/templates/modules/docker/.dockerignore +2 -0
  48. package/templates/modules/docker/Dockerfile.hbs +147 -136
  49. package/templates/modules/docker/docker-compose.yml.hbs +97 -2
  50. package/templates/modules/drizzle/express/config.json +3 -3
  51. package/templates/modules/drizzle/nestjs/config.json +3 -3
  52. package/templates/modules/drizzle/nextjs/config.json +3 -3
  53. package/templates/modules/drizzle/tanstack/config.json +3 -3
  54. package/templates/modules/mongoose/express/config.json +2 -2
  55. package/templates/modules/mongoose/nestjs/config.json +1 -1
  56. package/templates/modules/mongoose/nextjs/config.json +2 -2
  57. package/templates/modules/mongoose/tanstack/config.json +2 -2
  58. package/templates/modules/prisma/express/config.json +4 -4
  59. package/templates/modules/prisma/express/prisma.config.ts +4 -2
  60. package/templates/modules/prisma/nestjs/config.json +4 -4
  61. package/templates/modules/prisma/nestjs/prisma.config.ts +4 -2
  62. package/templates/modules/prisma/nextjs/config.json +4 -4
  63. package/templates/modules/prisma/nextjs/prisma.config.ts +4 -2
  64. package/templates/modules/prisma/tanstack/config.json +4 -4
  65. package/templates/modules/prisma/tanstack/prisma.config.ts +4 -2
  66. package/templates/modules/rate-limit/express/config.json +12 -0
  67. package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +23 -0
  68. package/templates/modules/rate-limit/nestjs/config.json +12 -0
package/README.md CHANGED
@@ -1,113 +1,114 @@
1
- # better-ts-stack
2
-
3
- > A CLI that scaffolds TypeScript backend and full-stack projects.
4
-
5
- `better-ts-stack` creates an Express, NestJS, Next.js, or TanStack Start project with TypeScript, ESLint, and Prettier set up. Database, authentication, Docker, and git are optional.
6
-
7
- ## Quick Start
8
-
9
- ```bash
10
- # Run instantly with npx
11
- npx better-ts-stack
12
-
13
- # Or install globally
14
- npm install -g better-ts-stack
15
- better-ts-stack
16
- ```
17
-
18
- Run the command and answer the prompts. The CLI creates the project folder.
19
-
20
- ## Flags
21
-
22
- Every prompt has a flag. Options you leave out are asked interactively, unless you pass `--yes`.
23
-
24
- ```bash
25
- # No prompts at all
26
- npx better-ts-stack my-api --framework nestjs --database prisma --auth --yes
27
-
28
- # Create the project in the current (empty) directory
29
- npx better-ts-stack . --framework nextjs --database drizzle --pm pnpm
30
- ```
31
-
32
- | Flag | Values |
33
- | ---------------------------- | -------------------------------------------- |
34
- | `--framework <name>` | `express`, `nestjs`, `nextjs`, `tanstack` |
35
- | `--database <name>` | `none`, `prisma`, `drizzle`, `mongoose` |
36
- | `--pm <name>` | `npm`, `pnpm`, `bun` |
37
- | `--auth` / `--no-auth` | Add authentication |
38
- | `--docker` / `--no-docker` | Add Docker files |
39
- | `--git` / `--no-git` | Initialize a git repository |
40
- | `--install` / `--no-install` | Install dependencies |
41
- | `-y`, `--yes` | Use defaults for missing options, no prompts |
42
- | `--dry-run` | List what would be created, write nothing |
43
- | `-h`, `--help` | Show help |
44
- | `-v`, `--version` | Show the version |
45
-
46
- `--database` names the ORM, which also selects the database: `prisma` and `drizzle` use PostgreSQL, `mongoose` uses MongoDB.
47
-
48
- With `--yes`, the defaults are Express, no database, no auth, no Docker, npm, git initialized, and dependencies not installed.
49
-
50
- The project path can be a name (`my-app`), a nested path (`apps/api`), or `.` for the current directory.
51
-
52
- ## What It Can Scaffold
53
-
54
- | Project type | Stack | Optional setup |
55
- | -------------- | --------------------------------------- | ------------------------------------------------------------------------ |
56
- | Backend API | Express 5 or NestJS 12 + TypeScript | PostgreSQL, MongoDB, Prisma, Drizzle, Mongoose, JWT auth, Docker, git |
57
- | Full-stack app | Next.js 16 or TanStack Start + React 19 | PostgreSQL, MongoDB, Prisma, Drizzle, Mongoose, Better Auth, Docker, git |
58
-
59
- Notes:
60
-
61
- - Better Auth is only offered for full-stack projects that use a database.
62
-
63
- ## Interactive Prompt Flow
64
-
65
- Without flags, the CLI asks, in order:
66
-
67
- 1. Project name or path
68
- 2. Application type: `Backend API` or `Full-stack App`
69
- 3. Framework
70
- - Backend: `Express` or `NestJS`
71
- - Full-stack: `Next.js` or `TanStack Start`
72
- 4. Database: `none`, `PostgreSQL`, or `MongoDB`
73
- 5. ORM/ODM when a database is selected
74
- - PostgreSQL: `Prisma` or `Drizzle`
75
- - MongoDB: `Mongoose`
76
- 6. Package manager: `npm`, `pnpm`, or `bun`
77
- 7. Docker setup
78
- 8. Authentication
79
- - Express and NestJS apps: JWT-based auth
80
- - Next.js and TanStack Start apps: Better Auth, only when a database is selected
81
- 9. Git initialization
82
- 10. Dependency installation
83
-
84
- The CLI does not ask for a port. Backend projects default to `PORT=3000`.
85
-
86
- ## What You Get
87
-
88
- Every generated project includes:
89
-
90
- - TypeScript with strict mode enabled
91
- - ESLint and Prettier pre-configured
92
- - `.env.example` and `.env` setup
93
- - Ready-to-run project scripts
94
- - Health check endpoint at `/health` for backend projects
95
- - shadcn-compatible UI components for Next.js and TanStack Start projects
96
- - Database wiring and schema setup when selected
97
- - Authentication scaffolding when selected
98
- - Docker files when selected
99
- - Optional git repository initialization
100
-
101
- ## Requirements
102
-
103
- - Node.js 24 or later
104
-
105
- ## Links
106
-
107
- - [Documentation](https://better-ts-stack.abdullahtech.me/docs)
108
- - [GitHub Repository](https://github.com/Abdullah-dev0/better-ts-stack)
109
- - [Issue Tracker](https://github.com/Abdullah-dev0/better-ts-stack/issues)
110
-
111
- ## License
112
-
113
- MIT
1
+ # better-ts-stack
2
+
3
+ > A CLI that scaffolds TypeScript backend and full-stack projects.
4
+
5
+ `better-ts-stack` creates an Express, NestJS, Next.js, or TanStack Start project with TypeScript, ESLint, and Prettier set up. Database, authentication, Docker, and git are optional.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Run instantly with npx
11
+ npx better-ts-stack
12
+
13
+ # Or install globally
14
+ npm install -g better-ts-stack
15
+ better-ts-stack
16
+ ```
17
+
18
+ Run the command and answer the prompts. The CLI creates the project folder.
19
+
20
+ ## Flags
21
+
22
+ Every prompt has a flag. Options you leave out are asked interactively, unless you pass `--yes`.
23
+
24
+ ```bash
25
+ # No prompts at all
26
+ npx better-ts-stack my-api --framework nestjs --database prisma --auth --yes
27
+
28
+ # Create the project in the current (empty) directory
29
+ npx better-ts-stack . --framework nextjs --database drizzle --pm pnpm
30
+ ```
31
+
32
+ | Flag | Values |
33
+ | ---------------------------------- | -------------------------------------------- |
34
+ | `--framework <name>` | `express`, `nestjs`, `nextjs`, `tanstack` |
35
+ | `--database <name>` | `none`, `prisma`, `drizzle`, `mongoose` |
36
+ | `--pm <name>` | `npm`, `pnpm`, `bun` |
37
+ | `--auth` / `--no-auth` | Add authentication |
38
+ | `--docker` / `--no-docker` | Add Docker files |
39
+ | `--rate-limit` / `--no-rate-limit` | Add rate limiting (Express and NestJS) |
40
+ | `--git` / `--no-git` | Initialize a git repository |
41
+ | `--install` / `--no-install` | Install dependencies |
42
+ | `-y`, `--yes` | Use defaults for missing options, no prompts |
43
+ | `--dry-run` | List what would be created, write nothing |
44
+ | `-h`, `--help` | Show help |
45
+ | `-v`, `--version` | Show the version |
46
+
47
+ `--database` names the ORM, which also selects the database: `prisma` and `drizzle` use PostgreSQL, `mongoose` uses MongoDB.
48
+
49
+ With `--yes`, the defaults are Express, no database, no auth, no rate limiting, no Docker, git initialized, and dependencies not installed. The package manager defaults to the one that ran the CLI: `pnpm` for `pnpm dlx`, `bun` for `bunx`, and `npm` otherwise.
50
+
51
+ The project path can be a name (`my-app`), a nested path (`apps/api`), or `.` for the current directory.
52
+
53
+ ## What It Can Scaffold
54
+
55
+ | Project type | Stack | Optional setup |
56
+ | -------------- | --------------------------------------- | ------------------------------------------------------------------------ |
57
+ | Backend API | Express 5 or NestJS 12 + TypeScript | PostgreSQL, MongoDB, Prisma, Drizzle, Mongoose, JWT auth, Docker, git |
58
+ | Full-stack app | Next.js 16 or TanStack Start + React 19 | PostgreSQL, MongoDB, Prisma, Drizzle, Mongoose, Better Auth, Docker, git |
59
+
60
+ Notes:
61
+
62
+ - Better Auth is only offered for full-stack projects that use a database.
63
+
64
+ ## Interactive Prompt Flow
65
+
66
+ Without flags, the CLI asks, in order:
67
+
68
+ 1. Project name or path
69
+ 2. Application type: `Backend API` or `Full-stack App`
70
+ 3. Framework
71
+ - Backend: `Express` or `NestJS`
72
+ - Full-stack: `Next.js` or `TanStack Start`
73
+ 4. Database: `none`, `PostgreSQL`, or `MongoDB`
74
+ 5. ORM/ODM when a database is selected
75
+ - PostgreSQL: `Prisma` or `Drizzle`
76
+ - MongoDB: `Mongoose`
77
+ 6. Package manager: `npm`, `pnpm`, or `bun`
78
+ 7. Docker setup
79
+ 8. Authentication
80
+ - Express and NestJS apps: JWT-based auth
81
+ - Next.js and TanStack Start apps: Better Auth, only when a database is selected
82
+ 9. Git initialization
83
+ 10. Dependency installation
84
+
85
+ The CLI does not ask for a port. Backend projects default to `PORT=3000`.
86
+
87
+ ## What You Get
88
+
89
+ Every generated project includes:
90
+
91
+ - TypeScript with strict mode enabled
92
+ - ESLint and Prettier pre-configured
93
+ - `.env.example` and `.env` setup
94
+ - Ready-to-run project scripts
95
+ - Health check endpoint at `/health` for backend projects
96
+ - shadcn-compatible UI components for Next.js and TanStack Start projects
97
+ - Database wiring and schema setup when selected
98
+ - Authentication scaffolding when selected
99
+ - Docker files when selected
100
+ - Optional git repository initialization
101
+
102
+ ## Requirements
103
+
104
+ - Node.js 24 or later
105
+
106
+ ## Links
107
+
108
+ - [Documentation](https://better-ts-stack.abdullahtech.me/docs)
109
+ - [GitHub Repository](https://github.com/Abdullah-dev0/better-ts-stack)
110
+ - [Issue Tracker](https://github.com/Abdullah-dev0/better-ts-stack/issues)
111
+
112
+ ## License
113
+
114
+ MIT
package/dist/args.js CHANGED
@@ -11,26 +11,29 @@ const FRAMEWORKS = [
11
11
  ].map((option) => option.value);
12
12
  const DATABASES = ["none", "prisma", "drizzle", "mongoose"];
13
13
  const PACKAGE_MANAGERS = types_1.packageManagerOptions.map((option) => option.value);
14
- exports.HELP_TEXT = `Usage: better-ts-stack [project-path] [options]
15
-
16
- Options left out are asked interactively, unless --yes is given.
17
-
18
- --framework <name> ${FRAMEWORKS.join(" | ")}
19
- --database <name> ${DATABASES.join(" | ")}
20
- --pm <name> ${PACKAGE_MANAGERS.join(" | ")}
21
- --auth, --no-auth Add authentication
22
- --docker, --no-docker Add Docker files
23
- --git, --no-git Initialize a git repository
24
- --install, --no-install Install dependencies
25
- -y, --yes Use defaults for missing options and skip confirmation
26
- --dry-run List the files, dependencies, scripts, environment
27
- variables, and commands without writing anything
28
- -h, --help Show this help
29
- -v, --version Show the version
30
-
31
- Examples:
32
- better-ts-stack my-api --framework nestjs --database prisma --auth --yes
33
- better-ts-stack . --framework nextjs --database drizzle --pm pnpm
14
+ exports.HELP_TEXT = `Usage: better-ts-stack [project-path] [options]
15
+
16
+ Options left out are asked interactively, unless --yes is given.
17
+
18
+ --framework <name> ${FRAMEWORKS.join(" | ")}
19
+ --database <name> ${DATABASES.join(" | ")}
20
+ --pm <name> ${PACKAGE_MANAGERS.join(" | ")}
21
+ --auth, --no-auth Add authentication
22
+ --docker, --no-docker Add Docker files
23
+ --rate-limit, --no-rate-limit
24
+ Add rate limiting (Express and NestJS)
25
+ --ci, --no-ci Add a GitHub Actions workflow
26
+ --git, --no-git Initialize a git repository
27
+ --install, --no-install Install dependencies
28
+ -y, --yes Use defaults for missing options and skip confirmation
29
+ --dry-run List the files, dependencies, scripts, environment
30
+ variables, and commands without writing anything
31
+ -h, --help Show this help
32
+ -v, --version Show the version
33
+
34
+ Examples:
35
+ better-ts-stack my-api --framework nestjs --database prisma --auth --yes
36
+ better-ts-stack . --framework nextjs --database drizzle --pm pnpm
34
37
  `;
35
38
  function parseChoice(flag, value, allowed) {
36
39
  if (value === undefined)
@@ -53,6 +56,8 @@ function parseCliArgs(argv) {
53
56
  pm: { type: "string" },
54
57
  auth: { type: "boolean" },
55
58
  docker: { type: "boolean" },
59
+ "rate-limit": { type: "boolean" },
60
+ ci: { type: "boolean" },
56
61
  git: { type: "boolean" },
57
62
  install: { type: "boolean" },
58
63
  yes: { type: "boolean", short: "y" },
@@ -75,6 +80,8 @@ function parseCliArgs(argv) {
75
80
  packageManager: parseChoice("pm", values.pm, PACKAGE_MANAGERS),
76
81
  useAuth: values.auth,
77
82
  useDocker: values.docker,
83
+ useRateLimit: values["rate-limit"],
84
+ useCi: values.ci,
78
85
  initGit: values.git,
79
86
  installDeps: values.install,
80
87
  };
@@ -15,7 +15,7 @@ const gitInitializer_1 = require("./gitInitializer");
15
15
  const moduleSelector_1 = require("./moduleSelector");
16
16
  const templateContext_1 = require("./templateContext");
17
17
  // Files the builder writes itself instead of copying them from a module
18
- const GENERATED_FILES = ["package.json", ".env", ".env.example"];
18
+ const GENERATED_FILES = ["package.json", ".env", ".env.example", "README.md"];
19
19
  // Loads the selected modules and merges their configurations
20
20
  async function loadBuildInputs(config) {
21
21
  const selection = (0, moduleSelector_1.selectModules)(config);
@@ -69,11 +69,7 @@ async function generatePackageJson(targetDir, mergedConfig, config) {
69
69
  name: config.projectName,
70
70
  version: "1.0.0",
71
71
  private: true,
72
- // TanStack's srvx static serving needs Node 24.20+ on Windows (earlier
73
- // 24.x reports mismatched file stats there, so every asset 404s)
74
- engines: {
75
- node: config.framework === "tanstack" ? ">=24.20.0" : ">=24.0.0",
76
- },
72
+ engines: { node: ">=24.0.0" },
77
73
  description: `Project created with better-ts-stack using ${config.database !== "none" ? config.database : "no database"}`,
78
74
  ...(ESM_FRAMEWORKS.includes(config.framework) ? { type: "module" } : {}),
79
75
  ...(isFullstack ? {} : { main: BACKEND_ENTRY_FILES[config.framework] }),
@@ -140,7 +136,9 @@ async function generateEnvFile(targetDir, envVars) {
140
136
  let envContent = "# Environment Variables\n";
141
137
  envContent +=
142
138
  "# Copy this file to .env and update with your actual values\n\n";
143
- let localEnvContent = envContent;
139
+ let localEnvContent = "# Environment Variables\n";
140
+ localEnvContent +=
141
+ "# Local values for this project. Do not commit them\n\n";
144
142
  // Add each environment variable
145
143
  for (const [key, value] of Object.entries(envVars)) {
146
144
  const resolvedValue = exports.GENERATED_SECRETS.includes(key)
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.planBuild = void 0;
7
7
  exports.build = build;
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
9
10
  const nextSteps_1 = require("../output/nextSteps");
10
11
  const types_1 = require("../types");
11
12
  const validators_1 = require("../validators");
@@ -14,8 +15,17 @@ const configGenerator_1 = require("./configGenerator");
14
15
  const dependencyInstaller_1 = require("./dependencyInstaller");
15
16
  const fileProcessor_1 = require("./fileProcessor");
16
17
  const gitInitializer_1 = require("./gitInitializer");
18
+ const readmeGenerator_1 = require("./readmeGenerator");
17
19
  var buildPlanner_2 = require("./buildPlanner");
18
20
  Object.defineProperty(exports, "planBuild", { enumerable: true, get: function () { return buildPlanner_2.planBuild; } });
21
+ // Empties a directory except for the entries it may have had before the build
22
+ async function removeCreatedEntries(dir) {
23
+ for (const entry of await fs_extra_1.default.readdir(dir)) {
24
+ if (!validators_1.PRESERVED_ENTRIES.includes(entry)) {
25
+ await fs_extra_1.default.remove(node_path_1.default.join(dir, entry));
26
+ }
27
+ }
28
+ }
19
29
  // Main function to orchestrate the project construction process
20
30
  async function build(config, targetDir) {
21
31
  // 1. Validate target directory
@@ -42,8 +52,9 @@ async function build(config, targetDir) {
42
52
  if (config.packageManager === "pnpm") {
43
53
  await (0, configGenerator_1.generatePnpmWorkspace)(targetDir, mergedConfig.overrides);
44
54
  }
45
- // 7. Generate environment files
55
+ // 7. Generate environment files and the README
46
56
  await (0, configGenerator_1.generateEnvFile)(targetDir, mergedConfig.envVars);
57
+ await (0, readmeGenerator_1.generateReadme)(targetDir, config, mergedConfig);
47
58
  // 8. Install dependencies and initialize git (both optional); a failure
48
59
  // in either is reported as a warning and does not fail the build
49
60
  const depsInstalled = config.installDeps &&
@@ -57,8 +68,9 @@ async function build(config, targetDir) {
57
68
  }
58
69
  catch (error) {
59
70
  // Remove the partial project so a retry is not blocked by a non-empty
60
- // directory; a directory that already existed is emptied, not deleted
61
- await (didDirectoryExist ? fs_extra_1.default.emptyDir(targetDir) : fs_extra_1.default.remove(targetDir)).catch(() => undefined);
71
+ // directory. A directory that already existed is emptied, not deleted,
72
+ // and keeps what it had before, such as .git
73
+ await (didDirectoryExist ? removeCreatedEntries(targetDir) : fs_extra_1.default.remove(targetDir)).catch(() => undefined);
62
74
  // Errors raised by the build steps already say what failed
63
75
  throw (0, types_1.isBuildError)(error)
64
76
  ? error
@@ -9,13 +9,17 @@ function selectModules(config) {
9
9
  ? `backend/${framework}`
10
10
  : `frontend/${framework}`;
11
11
  const modules = [];
12
- // Database and auth modules have one variant per framework (prisma/express,
13
- // auth/nextjs, ...); the Docker module is shared
12
+ // Database, auth, and rate-limit modules have one variant per framework
13
+ // (prisma/express, auth/nextjs, ...); the Docker and CI modules are shared
14
14
  if (config.database !== "none")
15
15
  modules.push(`${config.database}/${framework}`);
16
16
  if (config.useDocker)
17
17
  modules.push("docker");
18
18
  if (config.useAuth)
19
19
  modules.push(`auth/${framework}`);
20
+ if (config.useRateLimit)
21
+ modules.push(`rate-limit/${framework}`);
22
+ if (config.useCi)
23
+ modules.push("ci");
20
24
  return { base, modules };
21
25
  }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderReadme = renderReadme;
7
+ exports.generateReadme = generateReadme;
8
+ // Writes the README.md of a generated project
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const nextSteps_1 = require("../output/nextSteps");
12
+ const types_1 = require("../types");
13
+ const configGenerator_1 = require("./configGenerator");
14
+ const DOCS_URL = "https://better-ts-stack.abdullahtech.me/docs";
15
+ const FRAMEWORK_LABELS = {
16
+ express: "Express 5",
17
+ nestjs: "NestJS 12",
18
+ nextjs: "Next.js 16 (App Router)",
19
+ tanstack: "TanStack Start",
20
+ };
21
+ const DATABASE_LABELS = {
22
+ prisma: "Prisma with PostgreSQL",
23
+ drizzle: "Drizzle with PostgreSQL",
24
+ mongoose: "Mongoose with MongoDB",
25
+ };
26
+ // Renders rows as a Markdown table; pipes inside cells are escaped
27
+ function table(headers, rows) {
28
+ const cell = (text) => text.replace(/\|/g, "\\|");
29
+ return [
30
+ `| ${headers.join(" | ")} |`,
31
+ `| ${headers.map(() => "---").join(" | ")} |`,
32
+ ...rows.map((row) => `| ${row.map(cell).join(" | ")} |`),
33
+ ];
34
+ }
35
+ // Builds the README content from the project's choices and merged config
36
+ function renderReadme(config, mergedConfig, targetDir) {
37
+ const isFullstack = config.applicationType === "fullstack";
38
+ const lines = [
39
+ `# ${config.projectName}`,
40
+ "",
41
+ `Created with [better-ts-stack](${DOCS_URL}).`,
42
+ "",
43
+ "## Stack",
44
+ "",
45
+ `- **Framework:** ${FRAMEWORK_LABELS[config.framework]}`,
46
+ ];
47
+ if (config.database !== "none") {
48
+ lines.push(`- **Database:** ${DATABASE_LABELS[config.database]}`);
49
+ }
50
+ if (config.useAuth) {
51
+ lines.push(isFullstack
52
+ ? "- **Auth:** Better Auth with email and password"
53
+ : "- **Auth:** JWT with `/auth/register`, `/auth/login`, and `/auth/me`");
54
+ }
55
+ if (config.useRateLimit) {
56
+ lines.push(`- **Rate limiting:** 100 requests per client IP in 15 minutes${config.useAuth ? ", and 10 sign-up or login attempts" : ""}`);
57
+ }
58
+ if (config.useDocker)
59
+ lines.push("- **Docker:** Dockerfile and Compose file");
60
+ if (config.useCi) {
61
+ lines.push("- **CI:** GitHub Actions builds, type-checks, and lints every push to `main` and every pull request");
62
+ }
63
+ lines.push(`- **Package manager:** ${config.packageManager}`, "");
64
+ const steps = (0, nextSteps_1.generateNextSteps)(config, targetDir, false, true);
65
+ lines.push("## Getting started", "", ...steps.map((step, index) => {
66
+ const isCommand = /^(npm|pnpm|bun|docker) /.test(step);
67
+ return `${index + 1}. ${isCommand ? `\`${step}\`` : step}`;
68
+ }), "");
69
+ // "pre" and "post" hooks run on their own, so only the commands to type
70
+ // are listed
71
+ const scripts = Object.entries(mergedConfig.scripts).filter(([name]) => !/^(pre|post)./.test(name));
72
+ if (scripts.length > 0) {
73
+ lines.push("## Scripts", "", ...table(["Command", "Runs"], scripts.map(([name, command]) => [
74
+ `\`${config.packageManager} run ${name}\``,
75
+ `\`${command}\``,
76
+ ])), "");
77
+ }
78
+ const envVars = Object.entries(mergedConfig.envVars);
79
+ if (envVars.length > 0) {
80
+ lines.push("## Environment variables", "", "`.env` holds your local values and is not committed. `.env.example` lists the same variables for other developers.", "", ...table(["Variable", "Default"], envVars.map(([key, value]) => [
81
+ `\`${key}\``,
82
+ configGenerator_1.GENERATED_SECRETS.includes(key)
83
+ ? "A random value generated in `.env`"
84
+ : `\`${value}\``,
85
+ ])), "");
86
+ }
87
+ lines.push("## Learn more", "", `- [${isFullstack ? "Full-stack" : "Backend"} guide](${DOCS_URL}/modules/${isFullstack ? "frontend" : "backend"})`);
88
+ if (config.database !== "none") {
89
+ lines.push(`- [Database guide](${DOCS_URL}/modules/database)`);
90
+ }
91
+ if (config.useAuth)
92
+ lines.push(`- [Auth guide](${DOCS_URL}/modules/auth)`);
93
+ if (config.useDocker) {
94
+ lines.push(`- [Docker guide](${DOCS_URL}/modules/docker)`);
95
+ }
96
+ lines.push(`- [Troubleshooting](${DOCS_URL}/troubleshooting)`, "");
97
+ return lines.join("\n");
98
+ }
99
+ // Writes README.md into the project
100
+ async function generateReadme(targetDir, config, mergedConfig) {
101
+ try {
102
+ await fs_extra_1.default.writeFile(node_path_1.default.join(targetDir, "README.md"), renderReadme(config, mergedConfig, targetDir), "utf-8");
103
+ }
104
+ catch (error) {
105
+ throw (0, types_1.buildError)(error, "README_ERROR", "Failed to generate README.md");
106
+ }
107
+ }
@@ -3,6 +3,56 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildTemplateContext = buildTemplateContext;
4
4
  // Context builder for Handlebars, providing variables and helpers for templates
5
5
  const types_1 = require("../types");
6
+ // The lockfile is optional: a project built before its first install has
7
+ // none, so each install falls back to a plain install without it
8
+ const DOCKER_COMMANDS = {
9
+ npm: {
10
+ setup: "",
11
+ manifests: "package.json package-lock.json*",
12
+ install: "if [ -f package-lock.json ]; then npm ci; else npm install; fi",
13
+ installProduction: "if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi",
14
+ cleanCache: "npm cache clean --force",
15
+ run: "npm run",
16
+ },
17
+ pnpm: {
18
+ setup: "RUN npm install -g pnpm@11",
19
+ manifests: "package.json pnpm-lock.yaml* pnpm-workspace.yaml",
20
+ install: "if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else pnpm install; fi",
21
+ installProduction: "if [ -f pnpm-lock.yaml ]; then pnpm install --prod --frozen-lockfile; else pnpm install --prod; fi",
22
+ cleanCache: "pnpm store prune",
23
+ run: "pnpm run",
24
+ },
25
+ bun: {
26
+ setup: "COPY --from=oven/bun:1-alpine /usr/local/bin/bun /usr/local/bin/bun",
27
+ manifests: "package.json bun.lock*",
28
+ install: "if [ -f bun.lock ]; then bun install --frozen-lockfile; else bun install; fi",
29
+ installProduction: "if [ -f bun.lock ]; then bun install --production --frozen-lockfile; else bun install --production; fi",
30
+ cleanCache: "rm -rf /root/.bun/install/cache",
31
+ run: "bun run",
32
+ },
33
+ };
34
+ // Placeholders for the CI build, the same ones the Dockerfile uses: a build
35
+ // needs these to be set, but never connects to a database
36
+ function getCiBuildEnv(config) {
37
+ const env = [];
38
+ if (config.database === "prisma" || config.database === "drizzle") {
39
+ env.push({
40
+ name: "DATABASE_URL",
41
+ value: "postgresql://postgres:postgres@localhost:5432/postgres",
42
+ });
43
+ }
44
+ if (config.database === "mongoose") {
45
+ env.push({ name: "MONGODB_URI", value: "mongodb://localhost:27017/build" });
46
+ }
47
+ // next build loads the auth route, which initializes Better Auth
48
+ if (config.framework === "nextjs" && config.useAuth) {
49
+ env.push({
50
+ name: "BETTER_AUTH_SECRET",
51
+ value: "build-only-placeholder-secret-not-used-at-runtime",
52
+ });
53
+ }
54
+ return env;
55
+ }
6
56
  function buildTemplateContext(config) {
7
57
  return {
8
58
  projectName: config.projectName,
@@ -12,6 +62,9 @@ function buildTemplateContext(config) {
12
62
  port: types_1.DEFAULT_PORT,
13
63
  useDocker: config.useDocker,
14
64
  useAuth: config.useAuth,
65
+ useRateLimit: config.useRateLimit,
66
+ docker: DOCKER_COMMANDS[config.packageManager],
67
+ ciBuildEnv: getCiBuildEnv(config),
15
68
  helpers: {
16
69
  eq: (a, b) => a === b,
17
70
  runner: () => {
package/dist/index.js CHANGED
@@ -32,7 +32,13 @@ async function main() {
32
32
  const directoryError = await (0, validators_1.validateDirectoryEmpty)(targetDir);
33
33
  if (directoryError)
34
34
  throw (0, types_1.buildError)(directoryError, "DIRECTORY_NOT_EMPTY");
35
- const choices = await (0, prompts_1.collectStackChoices)(args.yes ? { ...types_1.DEFAULT_CHOICES, ...args.choices } : args.choices);
35
+ const choices = await (0, prompts_1.collectStackChoices)(args.yes
36
+ ? {
37
+ ...types_1.DEFAULT_CHOICES,
38
+ packageManager: (0, prompts_1.detectPackageManager)(),
39
+ ...args.choices,
40
+ }
41
+ : args.choices);
36
42
  const config = {
37
43
  projectName: node_path_1.default.basename(targetDir),
38
44
  applicationType: (0, types_1.getApplicationType)(choices.framework),