better-ts-stack 2.0.1 → 2.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/README.md +114 -113
- package/dist/args.js +24 -20
- package/dist/builder/buildPlanner.js +1 -1
- package/dist/builder/configGenerator.js +1 -5
- package/dist/builder/index.js +15 -3
- package/dist/builder/moduleSelector.js +4 -2
- package/dist/builder/readmeGenerator.js +101 -0
- package/dist/builder/templateContext.js +30 -0
- package/dist/index.js +7 -1
- package/dist/output/nextSteps.js +22 -23
- package/dist/prompts/index.js +2 -1
- package/dist/prompts/stack.js +21 -2
- package/dist/types/index.js +2 -1
- package/dist/ui/messages.js +2 -1
- package/dist/validators/index.js +5 -2
- package/package.json +1 -1
- package/templates/backend/express/config.json +42 -40
- package/templates/backend/express/src/env.ts.hbs +39 -0
- package/templates/backend/express/src/index.ts.hbs +111 -75
- package/templates/backend/nestjs/config.json +43 -41
- package/templates/backend/nestjs/src/app.module.ts.hbs +13 -0
- package/templates/backend/nestjs/src/env.ts.hbs +39 -0
- package/templates/backend/nestjs/src/main.ts +3 -2
- package/templates/frontend/nextjs/app/page.tsx.hbs +98 -97
- package/templates/frontend/tanstack/config.json +45 -45
- package/templates/frontend/tanstack/src/routes/index.tsx.hbs +102 -101
- package/templates/frontend/tanstack/vite.config.ts +22 -14
- package/templates/modules/auth/express/config.json +20 -20
- package/templates/modules/auth/express/src/routes/auth.ts.hbs +27 -19
- package/templates/modules/auth/nestjs/config.json +20 -20
- package/templates/modules/auth/nestjs/src/auth/{auth.controller.ts → auth.controller.ts.hbs} +15 -0
- package/templates/modules/auth/nextjs/lib/auth-schema.ts +1 -1
- package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +132 -134
- package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +46 -48
- package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +150 -152
- package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +38 -38
- package/templates/modules/docker/.dockerignore +1 -0
- package/templates/modules/docker/Dockerfile.hbs +147 -136
- package/templates/modules/docker/docker-compose.yml.hbs +96 -1
- package/templates/modules/drizzle/express/src/lib/schema.ts +3 -1
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +3 -1
- package/templates/modules/drizzle/nextjs/lib/schema.ts.hbs +11 -5
- package/templates/modules/drizzle/tanstack/src/lib/schema.ts.hbs +90 -84
- package/templates/modules/rate-limit/express/config.json +12 -0
- package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +21 -0
- 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
|
|
33
|
-
|
|
|
34
|
-
| `--framework <name>`
|
|
35
|
-
| `--database <name>`
|
|
36
|
-
| `--pm <name>`
|
|
37
|
-
| `--auth` / `--no-auth`
|
|
38
|
-
| `--docker` / `--no-docker`
|
|
39
|
-
| `--
|
|
40
|
-
| `--
|
|
41
|
-
|
|
|
42
|
-
| `--
|
|
43
|
-
|
|
|
44
|
-
| `-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- [
|
|
109
|
-
- [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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,28 @@ 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
|
-
--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
--
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
--git, --no-git Initialize a git repository
|
|
26
|
+
--install, --no-install Install dependencies
|
|
27
|
+
-y, --yes Use defaults for missing options and skip confirmation
|
|
28
|
+
--dry-run List the files, dependencies, scripts, environment
|
|
29
|
+
variables, and commands without writing anything
|
|
30
|
+
-h, --help Show this help
|
|
31
|
+
-v, --version Show the version
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
better-ts-stack my-api --framework nestjs --database prisma --auth --yes
|
|
35
|
+
better-ts-stack . --framework nextjs --database drizzle --pm pnpm
|
|
34
36
|
`;
|
|
35
37
|
function parseChoice(flag, value, allowed) {
|
|
36
38
|
if (value === undefined)
|
|
@@ -53,6 +55,7 @@ function parseCliArgs(argv) {
|
|
|
53
55
|
pm: { type: "string" },
|
|
54
56
|
auth: { type: "boolean" },
|
|
55
57
|
docker: { type: "boolean" },
|
|
58
|
+
"rate-limit": { type: "boolean" },
|
|
56
59
|
git: { type: "boolean" },
|
|
57
60
|
install: { type: "boolean" },
|
|
58
61
|
yes: { type: "boolean", short: "y" },
|
|
@@ -75,6 +78,7 @@ function parseCliArgs(argv) {
|
|
|
75
78
|
packageManager: parseChoice("pm", values.pm, PACKAGE_MANAGERS),
|
|
76
79
|
useAuth: values.auth,
|
|
77
80
|
useDocker: values.docker,
|
|
81
|
+
useRateLimit: values["rate-limit"],
|
|
78
82
|
initGit: values.git,
|
|
79
83
|
installDeps: values.install,
|
|
80
84
|
};
|
|
@@ -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
|
-
|
|
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] }),
|
package/dist/builder/index.js
CHANGED
|
@@ -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
|
|
61
|
-
|
|
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,15 @@ function selectModules(config) {
|
|
|
9
9
|
? `backend/${framework}`
|
|
10
10
|
: `frontend/${framework}`;
|
|
11
11
|
const modules = [];
|
|
12
|
-
// Database and
|
|
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 module is 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}`);
|
|
20
22
|
return { base, modules };
|
|
21
23
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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.useDocker)
|
|
56
|
+
lines.push("- **Docker:** Dockerfile and Compose file");
|
|
57
|
+
lines.push(`- **Package manager:** ${config.packageManager}`, "");
|
|
58
|
+
const steps = (0, nextSteps_1.generateNextSteps)(config, targetDir, false, true);
|
|
59
|
+
lines.push("## Getting started", "", ...steps.map((step, index) => {
|
|
60
|
+
const isCommand = /^(npm|pnpm|bun|docker) /.test(step);
|
|
61
|
+
return `${index + 1}. ${isCommand ? `\`${step}\`` : step}`;
|
|
62
|
+
}), "");
|
|
63
|
+
// "pre" and "post" hooks run on their own, so only the commands to type
|
|
64
|
+
// are listed
|
|
65
|
+
const scripts = Object.entries(mergedConfig.scripts).filter(([name]) => !/^(pre|post)./.test(name));
|
|
66
|
+
if (scripts.length > 0) {
|
|
67
|
+
lines.push("## Scripts", "", ...table(["Command", "Runs"], scripts.map(([name, command]) => [
|
|
68
|
+
`\`${config.packageManager} run ${name}\``,
|
|
69
|
+
`\`${command}\``,
|
|
70
|
+
])), "");
|
|
71
|
+
}
|
|
72
|
+
const envVars = Object.entries(mergedConfig.envVars);
|
|
73
|
+
if (envVars.length > 0) {
|
|
74
|
+
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]) => [
|
|
75
|
+
`\`${key}\``,
|
|
76
|
+
configGenerator_1.GENERATED_SECRETS.includes(key)
|
|
77
|
+
? "A random value generated in `.env`"
|
|
78
|
+
: `\`${value}\``,
|
|
79
|
+
])), "");
|
|
80
|
+
}
|
|
81
|
+
lines.push("## Learn more", "", `- [${isFullstack ? "Full-stack" : "Backend"} guide](${DOCS_URL}/modules/${isFullstack ? "frontend" : "backend"})`);
|
|
82
|
+
if (config.database !== "none") {
|
|
83
|
+
lines.push(`- [Database guide](${DOCS_URL}/modules/database)`);
|
|
84
|
+
}
|
|
85
|
+
if (config.useAuth)
|
|
86
|
+
lines.push(`- [Auth guide](${DOCS_URL}/modules/auth)`);
|
|
87
|
+
if (config.useDocker) {
|
|
88
|
+
lines.push(`- [Docker guide](${DOCS_URL}/modules/docker)`);
|
|
89
|
+
}
|
|
90
|
+
lines.push(`- [Troubleshooting](${DOCS_URL}/troubleshooting)`, "");
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
// Writes README.md into the project
|
|
94
|
+
async function generateReadme(targetDir, config, mergedConfig) {
|
|
95
|
+
try {
|
|
96
|
+
await fs_extra_1.default.writeFile(node_path_1.default.join(targetDir, "README.md"), renderReadme(config, mergedConfig, targetDir), "utf-8");
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw (0, types_1.buildError)(error, "README_ERROR", "Failed to generate README.md");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -3,6 +3,34 @@ 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
|
+
};
|
|
6
34
|
function buildTemplateContext(config) {
|
|
7
35
|
return {
|
|
8
36
|
projectName: config.projectName,
|
|
@@ -12,6 +40,8 @@ function buildTemplateContext(config) {
|
|
|
12
40
|
port: types_1.DEFAULT_PORT,
|
|
13
41
|
useDocker: config.useDocker,
|
|
14
42
|
useAuth: config.useAuth,
|
|
43
|
+
useRateLimit: config.useRateLimit,
|
|
44
|
+
docker: DOCKER_COMMANDS[config.packageManager],
|
|
15
45
|
helpers: {
|
|
16
46
|
eq: (a, b) => a === b,
|
|
17
47
|
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
|
|
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),
|
package/dist/output/nextSteps.js
CHANGED
|
@@ -7,23 +7,18 @@ exports.generateNextSteps = generateNextSteps;
|
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const node_process_1 = require("node:process");
|
|
9
9
|
const MONGODB_AUTH_SETUP_URL = "https://better-ts-stack.abdullahtech.me/docs/modules/database#mongoose-and-mongodb";
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
const [currentMajor, currentMinor] = process.versions.node
|
|
15
|
-
.split(".")
|
|
16
|
-
.map(Number);
|
|
17
|
-
return (currentMajor < major || (currentMajor === major && currentMinor < minor));
|
|
18
|
-
}
|
|
19
|
-
// Generates a list of instructions for the user to follow after project creation
|
|
20
|
-
function generateNextSteps(config, targetDir, depsInstalled) {
|
|
10
|
+
// Generates a list of instructions for the user to follow after project
|
|
11
|
+
// creation. The README version leaves out the cd step, which only makes
|
|
12
|
+
// sense in the terminal
|
|
13
|
+
function generateNextSteps(config, targetDir, depsInstalled, isForReadme = false) {
|
|
21
14
|
const steps = [];
|
|
22
15
|
const isFullstack = config.applicationType === "fullstack";
|
|
23
16
|
// Step 1: cd into project directory, unless it was created in place
|
|
17
|
+
// Quoted when it has spaces, so the command can be pasted as it is
|
|
24
18
|
const relativeDir = node_path_1.default.relative((0, node_process_1.cwd)(), targetDir);
|
|
25
|
-
if (relativeDir)
|
|
26
|
-
steps.push(`cd ${relativeDir}`);
|
|
19
|
+
if (relativeDir && !isForReadme) {
|
|
20
|
+
steps.push(/\s/.test(relativeDir) ? `cd "${relativeDir}"` : `cd ${relativeDir}`);
|
|
21
|
+
}
|
|
27
22
|
// Step 2: Set environment variables
|
|
28
23
|
if (config.database !== "none") {
|
|
29
24
|
steps.push("Review the generated .env and set your database connection details");
|
|
@@ -33,17 +28,15 @@ function generateNextSteps(config, targetDir, depsInstalled) {
|
|
|
33
28
|
? "Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env"
|
|
34
29
|
: "Review the generated JWT_SECRET in .env");
|
|
35
30
|
}
|
|
36
|
-
if (config.framework === "tanstack" &&
|
|
37
|
-
process.platform === "win32" &&
|
|
38
|
-
isNodeBelow(MIN_TANSTACK_WINDOWS_NODE)) {
|
|
39
|
-
steps.push(`Upgrade Node.js to ${MIN_TANSTACK_WINDOWS_NODE.join(".")} or newer: on Windows, ` +
|
|
40
|
-
`Node ${process.versions.node} makes "npm start" return 404 for CSS, JS, and other static files`);
|
|
41
|
-
}
|
|
42
31
|
// Step 3: Install dependencies if not already done
|
|
43
32
|
if (!depsInstalled) {
|
|
44
33
|
steps.push(`${config.packageManager} install`);
|
|
45
34
|
}
|
|
46
|
-
// Step 4: Database-specific steps
|
|
35
|
+
// Step 4: Database-specific steps. With Docker, Compose runs the database
|
|
36
|
+
const hasDockerDatabase = config.useDocker && config.database !== "none";
|
|
37
|
+
if (hasDockerDatabase) {
|
|
38
|
+
steps.push("docker compose up -d db");
|
|
39
|
+
}
|
|
47
40
|
if (config.database === "prisma") {
|
|
48
41
|
steps.push(`${config.packageManager} run prisma:generate`);
|
|
49
42
|
steps.push(`${config.packageManager} run prisma:migrate`);
|
|
@@ -53,9 +46,12 @@ function generateNextSteps(config, targetDir, depsInstalled) {
|
|
|
53
46
|
steps.push(`${config.packageManager} run db:migrate`);
|
|
54
47
|
}
|
|
55
48
|
else if (config.database === "mongoose") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
if (!hasDockerDatabase) {
|
|
50
|
+
steps.push("Ensure MongoDB is running locally or update MONGODB_URI in .env");
|
|
51
|
+
}
|
|
52
|
+
// Better Auth's MongoDB adapter uses transactions, which need a replica
|
|
53
|
+
// set; the Compose database already runs as one
|
|
54
|
+
if (config.useAuth && isFullstack && !hasDockerDatabase) {
|
|
59
55
|
steps.push("Better Auth uses MongoDB transactions: run MongoDB as a replica set (or use Atlas) " +
|
|
60
56
|
'and create the "user", "session", "account", and "verification" collections before signing up. ' +
|
|
61
57
|
`See ${MONGODB_AUTH_SETUP_URL}`);
|
|
@@ -68,5 +64,8 @@ function generateNextSteps(config, targetDir, depsInstalled) {
|
|
|
68
64
|
? "Visit /sign-up to create your first account"
|
|
69
65
|
: "Create a user via POST /auth/register then login with /auth/login");
|
|
70
66
|
}
|
|
67
|
+
if (config.useDocker) {
|
|
68
|
+
steps.push(`To run the app in Docker instead: ${config.packageManager} run docker:up`);
|
|
69
|
+
}
|
|
71
70
|
return steps;
|
|
72
71
|
}
|
package/dist/prompts/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.collectStackChoices = void 0;
|
|
3
|
+
exports.detectPackageManager = exports.collectStackChoices = void 0;
|
|
4
4
|
exports.collectProjectPath = collectProjectPath;
|
|
5
5
|
exports.confirmBuild = confirmBuild;
|
|
6
6
|
const types_1 = require("../types");
|
|
@@ -9,6 +9,7 @@ const validators_1 = require("../validators");
|
|
|
9
9
|
const stack_1 = require("./stack");
|
|
10
10
|
var stack_2 = require("./stack");
|
|
11
11
|
Object.defineProperty(exports, "collectStackChoices", { enumerable: true, get: function () { return stack_2.collectStackChoices; } });
|
|
12
|
+
Object.defineProperty(exports, "detectPackageManager", { enumerable: true, get: function () { return stack_2.detectPackageManager; } });
|
|
12
13
|
// Returns the project path from the command line, or asks for it
|
|
13
14
|
async function collectProjectPath(preset, isNonInteractive) {
|
|
14
15
|
if (preset !== undefined) {
|