better-ts-stack 1.1.4 → 2.0.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.
- package/LICENSE +21 -0
- package/README.md +113 -82
- package/dist/args.js +90 -0
- package/dist/builder/buildPlanner.js +71 -0
- package/dist/builder/configGenerator.js +19 -10
- package/dist/builder/dependencyInstaller.js +8 -15
- package/dist/builder/fileProcessor.js +47 -44
- package/dist/builder/gitInitializer.js +21 -35
- package/dist/builder/index.js +33 -91
- package/dist/builder/moduleSelector.js +11 -16
- package/dist/builder/templateContext.js +6 -27
- package/dist/index.js +47 -21
- package/dist/modules/registry.js +4 -6
- package/dist/output/nextSteps.js +10 -20
- package/dist/prompts/index.js +22 -59
- package/dist/prompts/stack.js +85 -71
- package/dist/types/index.js +47 -41
- package/dist/ui/index.js +18 -0
- package/dist/ui/messages.js +124 -0
- package/dist/ui/prompts.js +70 -0
- package/dist/ui/theme.js +23 -0
- package/dist/validators/index.js +15 -40
- package/package.json +71 -66
- package/templates/backend/express/.prettierrc +2 -2
- package/templates/backend/express/config.json +40 -44
- package/templates/backend/express/src/{index.ts → index.ts.hbs} +75 -76
- package/templates/backend/nestjs/.prettierignore +52 -0
- package/templates/backend/nestjs/.prettierrc +16 -0
- package/templates/backend/nestjs/config.json +41 -0
- package/templates/backend/nestjs/eslint.config.mjs +40 -0
- package/templates/backend/nestjs/gitignore +36 -0
- package/templates/backend/nestjs/nest-cli.json +9 -0
- package/templates/backend/nestjs/src/app.module.ts.hbs +34 -0
- package/templates/backend/nestjs/src/health/health.controller.ts +12 -0
- package/templates/backend/nestjs/src/main.ts +23 -0
- package/templates/backend/nestjs/tsconfig.eslint.json +9 -0
- package/templates/backend/nestjs/tsconfig.json +25 -0
- package/templates/frontend/nextjs/app/layout.tsx +13 -13
- package/templates/frontend/nextjs/app/page.tsx.hbs +97 -97
- package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
- package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
- package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
- package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
- package/templates/frontend/nextjs/config.json +37 -38
- package/templates/frontend/nextjs/eslint.config.mjs +11 -11
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
- package/templates/frontend/tanstack/config.json +45 -48
- package/templates/modules/auth/express/config.json +20 -26
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +43 -57
- package/templates/modules/auth/nestjs/config.json +20 -0
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -0
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -0
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -0
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -0
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -0
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -0
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -0
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -0
- package/templates/modules/auth/nextjs/config.json +18 -31
- package/templates/modules/auth/tanstack/config.json +2 -16
- package/templates/modules/docker/Dockerfile.hbs +135 -133
- package/templates/modules/docker/config.json +15 -19
- package/templates/modules/drizzle/express/config.json +26 -27
- package/templates/modules/drizzle/nestjs/config.json +26 -0
- package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -0
- package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -0
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +10 -0
- package/templates/modules/drizzle/nextjs/config.json +26 -29
- package/templates/modules/drizzle/tanstack/config.json +5 -5
- package/templates/modules/mongoose/express/config.json +15 -16
- package/templates/modules/mongoose/nestjs/config.json +15 -0
- package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -0
- package/templates/modules/mongoose/nextjs/config.json +15 -16
- package/templates/modules/mongoose/tanstack/config.json +16 -17
- package/templates/modules/prisma/express/config.json +27 -28
- package/templates/modules/prisma/nestjs/config.json +27 -0
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -0
- package/templates/modules/prisma/nestjs/prisma.config.ts +12 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -0
- package/templates/modules/prisma/nextjs/config.json +28 -29
- package/templates/modules/prisma/tanstack/config.json +5 -6
- package/dist/builder/configGenerator.d.ts +0 -7
- package/dist/builder/configGenerator.d.ts.map +0 -1
- package/dist/builder/configGenerator.js.map +0 -1
- package/dist/builder/dependencyInstaller.d.ts +0 -3
- package/dist/builder/dependencyInstaller.d.ts.map +0 -1
- package/dist/builder/dependencyInstaller.js.map +0 -1
- package/dist/builder/fileProcessor.d.ts +0 -6
- package/dist/builder/fileProcessor.d.ts.map +0 -1
- package/dist/builder/fileProcessor.js.map +0 -1
- package/dist/builder/gitInitializer.d.ts +0 -2
- package/dist/builder/gitInitializer.d.ts.map +0 -1
- package/dist/builder/gitInitializer.js.map +0 -1
- package/dist/builder/index.d.ts +0 -3
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/builder/index.js.map +0 -1
- package/dist/builder/moduleSelector.d.ts +0 -9
- package/dist/builder/moduleSelector.d.ts.map +0 -1
- package/dist/builder/moduleSelector.js.map +0 -1
- package/dist/builder/templateContext.d.ts +0 -21
- package/dist/builder/templateContext.d.ts.map +0 -1
- package/dist/builder/templateContext.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/intro.d.ts +0 -2
- package/dist/intro.d.ts.map +0 -1
- package/dist/intro.js +0 -27
- package/dist/intro.js.map +0 -1
- package/dist/modules/registry.d.ts +0 -6
- package/dist/modules/registry.d.ts.map +0 -1
- package/dist/modules/registry.js.map +0 -1
- package/dist/output/nextSteps.d.ts +0 -4
- package/dist/output/nextSteps.d.ts.map +0 -1
- package/dist/output/nextSteps.js.map +0 -1
- package/dist/prompts/index.d.ts +0 -4
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/stack.d.ts +0 -3
- package/dist/prompts/stack.d.ts.map +0 -1
- package/dist/prompts/stack.js.map +0 -1
- package/dist/types/index.d.ts +0 -159
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/validators/index.d.ts +0 -5
- package/dist/validators/index.d.ts.map +0 -1
- package/dist/validators/index.js.map +0 -1
- package/templates/backend/express/.eslintrc.js +0 -24
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdullah
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,82 +1,113 @@
|
|
|
1
|
-
# better-ts-stack
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
`better-ts-stack` creates an Express, 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
|
-
##
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HELP_TEXT = void 0;
|
|
4
|
+
exports.parseCliArgs = parseCliArgs;
|
|
5
|
+
// Command-line flag parsing; every flag is optional and falls back to a prompt
|
|
6
|
+
const node_util_1 = require("node:util");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
const FRAMEWORKS = [
|
|
9
|
+
...types_1.backendFrameworkOptions,
|
|
10
|
+
...types_1.frontendFrameworkOptions,
|
|
11
|
+
].map((option) => option.value);
|
|
12
|
+
const DATABASES = ["none", "prisma", "drizzle", "mongoose"];
|
|
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
|
|
34
|
+
`;
|
|
35
|
+
function parseChoice(flag, value, allowed) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!allowed.includes(value)) {
|
|
39
|
+
throw (0, types_1.buildError)(`Invalid value "${value}" for --${flag}. Use one of: ${allowed.join(", ")}`, "INVALID_ARGUMENT");
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function parseCliArgs(argv) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = (0, node_util_1.parseArgs)({
|
|
47
|
+
args: argv,
|
|
48
|
+
allowPositionals: true,
|
|
49
|
+
allowNegative: true,
|
|
50
|
+
options: {
|
|
51
|
+
framework: { type: "string" },
|
|
52
|
+
database: { type: "string" },
|
|
53
|
+
pm: { type: "string" },
|
|
54
|
+
auth: { type: "boolean" },
|
|
55
|
+
docker: { type: "boolean" },
|
|
56
|
+
git: { type: "boolean" },
|
|
57
|
+
install: { type: "boolean" },
|
|
58
|
+
yes: { type: "boolean", short: "y" },
|
|
59
|
+
"dry-run": { type: "boolean" },
|
|
60
|
+
help: { type: "boolean", short: "h" },
|
|
61
|
+
version: { type: "boolean", short: "v" },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw (0, types_1.buildError)(error, "INVALID_ARGUMENT");
|
|
67
|
+
}
|
|
68
|
+
const { values, positionals } = parsed;
|
|
69
|
+
if (positionals.length > 1) {
|
|
70
|
+
throw (0, types_1.buildError)(`Expected one project path, got: ${positionals.join(" ")}`, "INVALID_ARGUMENT");
|
|
71
|
+
}
|
|
72
|
+
const choices = {
|
|
73
|
+
framework: parseChoice("framework", values.framework, FRAMEWORKS),
|
|
74
|
+
database: parseChoice("database", values.database, DATABASES),
|
|
75
|
+
packageManager: parseChoice("pm", values.pm, PACKAGE_MANAGERS),
|
|
76
|
+
useAuth: values.auth,
|
|
77
|
+
useDocker: values.docker,
|
|
78
|
+
initGit: values.git,
|
|
79
|
+
installDeps: values.install,
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
projectPath: positionals[0],
|
|
83
|
+
// Keep only the flags that were passed, so the rest can take defaults
|
|
84
|
+
choices: Object.fromEntries(Object.entries(choices).filter(([, value]) => value !== undefined)),
|
|
85
|
+
yes: values.yes ?? false,
|
|
86
|
+
dryRun: values["dry-run"] ?? false,
|
|
87
|
+
help: values.help ?? false,
|
|
88
|
+
version: values.version ?? false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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.loadBuildInputs = loadBuildInputs;
|
|
7
|
+
exports.planBuild = planBuild;
|
|
8
|
+
// Works out what a build produces; nothing here writes to disk
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const registry_1 = require("../modules/registry");
|
|
12
|
+
const configGenerator_1 = require("./configGenerator");
|
|
13
|
+
const fileProcessor_1 = require("./fileProcessor");
|
|
14
|
+
const gitInitializer_1 = require("./gitInitializer");
|
|
15
|
+
const moduleSelector_1 = require("./moduleSelector");
|
|
16
|
+
const templateContext_1 = require("./templateContext");
|
|
17
|
+
// Files the builder writes itself instead of copying them from a module
|
|
18
|
+
const GENERATED_FILES = ["package.json", ".env", ".env.example"];
|
|
19
|
+
// Loads the selected modules and merges their configurations
|
|
20
|
+
async function loadBuildInputs(config) {
|
|
21
|
+
const selection = (0, moduleSelector_1.selectModules)(config);
|
|
22
|
+
const modules = [];
|
|
23
|
+
for (const id of [selection.base, ...selection.modules]) {
|
|
24
|
+
modules.push(await (0, registry_1.getModule)(id));
|
|
25
|
+
}
|
|
26
|
+
const templateContext = (0, templateContext_1.buildTemplateContext)(config);
|
|
27
|
+
const mergedConfig = (0, configGenerator_1.mergeConfigurations)(modules.map((m) => m.config), templateContext);
|
|
28
|
+
return { modules, templateContext, mergedConfig };
|
|
29
|
+
}
|
|
30
|
+
// The target directory may not exist yet, so git is asked about the closest
|
|
31
|
+
// directory that does
|
|
32
|
+
async function findExistingDirectory(dir) {
|
|
33
|
+
let current = dir;
|
|
34
|
+
while (!(await fs_extra_1.default.pathExists(current))) {
|
|
35
|
+
const parent = node_path_1.default.dirname(current);
|
|
36
|
+
if (parent === current)
|
|
37
|
+
break;
|
|
38
|
+
current = parent;
|
|
39
|
+
}
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
// Describes the files, package.json content, environment variables, and
|
|
43
|
+
// commands of a build, for --dry-run
|
|
44
|
+
async function planBuild(config, targetDir) {
|
|
45
|
+
const { modules, mergedConfig } = await loadBuildInputs(config);
|
|
46
|
+
// Later modules overwrite files of earlier ones, so each path is listed once
|
|
47
|
+
const files = new Set(GENERATED_FILES);
|
|
48
|
+
if (config.packageManager === "pnpm")
|
|
49
|
+
files.add("pnpm-workspace.yaml");
|
|
50
|
+
for (const module of modules) {
|
|
51
|
+
for (const file of await (0, fileProcessor_1.listModuleFiles)(module.path))
|
|
52
|
+
files.add(file);
|
|
53
|
+
}
|
|
54
|
+
const envVars = Object.fromEntries(Object.entries(mergedConfig.envVars).map(([key, value]) => [
|
|
55
|
+
key,
|
|
56
|
+
configGenerator_1.GENERATED_SECRETS.includes(key) ? "<random secret>" : value,
|
|
57
|
+
]));
|
|
58
|
+
const commands = [];
|
|
59
|
+
if (config.installDeps)
|
|
60
|
+
commands.push(`${config.packageManager} install`);
|
|
61
|
+
if (config.initGit &&
|
|
62
|
+
!(0, gitInitializer_1.isInsideGitRepository)(await findExistingDirectory(targetDir))) {
|
|
63
|
+
commands.push("git init");
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
...mergedConfig,
|
|
67
|
+
envVars,
|
|
68
|
+
files: [...files].sort((a, b) => a.localeCompare(b)),
|
|
69
|
+
commands,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GENERATED_SECRETS = void 0;
|
|
6
7
|
exports.processScriptVariables = processScriptVariables;
|
|
7
8
|
exports.mergeConfigurations = mergeConfigurations;
|
|
8
9
|
exports.generatePackageJson = generatePackageJson;
|
|
@@ -12,8 +13,15 @@ exports.generateEnvFile = generateEnvFile;
|
|
|
12
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
13
14
|
const handlebars_1 = __importDefault(require("handlebars"));
|
|
14
15
|
const node_crypto_1 = require("node:crypto");
|
|
15
|
-
const
|
|
16
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
17
|
const types_1 = require("../types");
|
|
18
|
+
// Frameworks whose generated project uses ES modules
|
|
19
|
+
const ESM_FRAMEWORKS = ["tanstack", "nestjs"];
|
|
20
|
+
// Compiled entry file of each backend framework
|
|
21
|
+
const BACKEND_ENTRY_FILES = {
|
|
22
|
+
express: "dist/index.js",
|
|
23
|
+
nestjs: "dist/main.js",
|
|
24
|
+
};
|
|
17
25
|
// Replaces Handlebars variables within script command strings
|
|
18
26
|
function processScriptVariables(scripts, context) {
|
|
19
27
|
const processedScripts = {};
|
|
@@ -67,21 +75,21 @@ async function generatePackageJson(targetDir, mergedConfig, config) {
|
|
|
67
75
|
node: config.framework === "tanstack" ? ">=24.20.0" : ">=24.0.0",
|
|
68
76
|
},
|
|
69
77
|
description: `Project created with better-ts-stack using ${config.database !== "none" ? config.database : "no database"}`,
|
|
70
|
-
...(config.framework
|
|
71
|
-
...(isFullstack ? {} : { main:
|
|
78
|
+
...(ESM_FRAMEWORKS.includes(config.framework) ? { type: "module" } : {}),
|
|
79
|
+
...(isFullstack ? {} : { main: BACKEND_ENTRY_FILES[config.framework] }),
|
|
72
80
|
scripts: mergedConfig.scripts,
|
|
73
81
|
keywords: isFullstack
|
|
74
82
|
? config.framework === "tanstack"
|
|
75
83
|
? ["tanstack-start", "react", "typescript"]
|
|
76
84
|
: ["nextjs", "react", "typescript"]
|
|
77
|
-
: ["backend", "typescript",
|
|
85
|
+
: ["backend", "typescript", config.framework],
|
|
78
86
|
author: "",
|
|
79
87
|
license: "MIT",
|
|
80
88
|
dependencies: mergedConfig.dependencies,
|
|
81
89
|
devDependencies: mergedConfig.devDependencies,
|
|
82
90
|
overrides,
|
|
83
91
|
};
|
|
84
|
-
const packageJsonPath =
|
|
92
|
+
const packageJsonPath = node_path_1.default.join(targetDir, "package.json");
|
|
85
93
|
await fs_extra_1.default.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
|
|
86
94
|
}
|
|
87
95
|
catch (error) {
|
|
@@ -117,12 +125,14 @@ async function generatePnpmWorkspace(targetDir, overrides) {
|
|
|
117
125
|
lines.push(` - "${name}"`);
|
|
118
126
|
}
|
|
119
127
|
try {
|
|
120
|
-
await fs_extra_1.default.writeFile(
|
|
128
|
+
await fs_extra_1.default.writeFile(node_path_1.default.join(targetDir, "pnpm-workspace.yaml"), lines.join("\n") + "\n", "utf-8");
|
|
121
129
|
}
|
|
122
130
|
catch (error) {
|
|
123
131
|
throw (0, types_1.buildError)(error, "PNPM_WORKSPACE_ERROR", "Failed to generate pnpm-workspace.yaml");
|
|
124
132
|
}
|
|
125
133
|
}
|
|
134
|
+
// Variables that get a random value in .env instead of the module's default
|
|
135
|
+
exports.GENERATED_SECRETS = ["JWT_SECRET", "BETTER_AUTH_SECRET"];
|
|
126
136
|
// Generates .env and .env.example files from merged environment variables
|
|
127
137
|
async function generateEnvFile(targetDir, envVars) {
|
|
128
138
|
try {
|
|
@@ -133,21 +143,20 @@ async function generateEnvFile(targetDir, envVars) {
|
|
|
133
143
|
let localEnvContent = envContent;
|
|
134
144
|
// Add each environment variable
|
|
135
145
|
for (const [key, value] of Object.entries(envVars)) {
|
|
136
|
-
const resolvedValue =
|
|
146
|
+
const resolvedValue = exports.GENERATED_SECRETS.includes(key)
|
|
137
147
|
? (0, node_crypto_1.randomBytes)(32).toString("hex")
|
|
138
148
|
: value;
|
|
139
149
|
envContent += `${key}=${value}\n`;
|
|
140
150
|
localEnvContent += `${key}=${resolvedValue}\n`;
|
|
141
151
|
}
|
|
142
152
|
// Write .env.example
|
|
143
|
-
const envExamplePath =
|
|
153
|
+
const envExamplePath = node_path_1.default.join(targetDir, ".env.example");
|
|
144
154
|
await fs_extra_1.default.writeFile(envExamplePath, envContent, "utf-8");
|
|
145
155
|
// Keep generated secrets out of the shareable example file.
|
|
146
|
-
const envPath =
|
|
156
|
+
const envPath = node_path_1.default.join(targetDir, ".env");
|
|
147
157
|
await fs_extra_1.default.writeFile(envPath, localEnvContent, "utf-8");
|
|
148
158
|
}
|
|
149
159
|
catch (error) {
|
|
150
160
|
throw (0, types_1.buildError)(error, "ENV_FILE_ERROR", "Failed to generate environment files");
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
|
-
//# sourceMappingURL=configGenerator.js.map
|
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.installDependencies = installDependencies;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const ui_1 = require("../ui");
|
|
9
6
|
// Installs project dependencies in the target directory
|
|
10
7
|
function installDependencies(packageManager, cwd) {
|
|
11
8
|
const command = `${packageManager} install`;
|
|
12
|
-
|
|
13
|
-
consola_1.default.debug(`Running: ${command}`);
|
|
9
|
+
(0, ui_1.info)(`Installing dependencies with ${packageManager}`);
|
|
14
10
|
try {
|
|
15
|
-
(0,
|
|
11
|
+
(0, node_child_process_1.execSync)(command, {
|
|
16
12
|
cwd,
|
|
17
13
|
stdio: "inherit", // Show installation progress in real-time
|
|
18
14
|
});
|
|
19
|
-
|
|
15
|
+
(0, ui_1.success)("Dependencies installed");
|
|
20
16
|
return true;
|
|
21
17
|
}
|
|
22
|
-
catch
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
consola_1.default.debug(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
-
consola_1.default.info(`You can install dependencies manually later by running: ${command}`);
|
|
18
|
+
catch {
|
|
19
|
+
// Not fatal: the project is complete and the install can be run later
|
|
20
|
+
(0, ui_1.warn)(`Could not install dependencies. Run "${command}" yourself.`);
|
|
27
21
|
return false;
|
|
28
22
|
}
|
|
29
23
|
}
|
|
30
|
-
//# sourceMappingURL=dependencyInstaller.js.map
|
|
@@ -4,18 +4,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.copyModuleFiles = copyModuleFiles;
|
|
7
|
+
exports.listModuleFiles = listModuleFiles;
|
|
7
8
|
exports.compileTemplate = compileTemplate;
|
|
8
9
|
exports.processTemplateFiles = processTemplateFiles;
|
|
9
|
-
exports.processTemplateFile = processTemplateFile;
|
|
10
10
|
// Utilities for copying files and processing Handlebars templates
|
|
11
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const handlebars_1 = __importDefault(require("handlebars"));
|
|
13
|
-
const
|
|
13
|
+
const promises_1 = require("node:fs/promises");
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
15
|
const types_1 = require("../types");
|
|
16
|
+
const TEMPLATE_EXTENSION = ".hbs";
|
|
17
|
+
const MODULE_CONFIG_FILE = "config.json";
|
|
18
|
+
// Names a template's .gitignore can be stored under, see restoreGitignore()
|
|
19
|
+
const GITIGNORE_TEMPLATE_NAMES = ["gitignore", ".npmignore"];
|
|
15
20
|
async function restoreGitignore(targetDir) {
|
|
16
|
-
const templateGitignorePath =
|
|
17
|
-
const npmIgnorePath =
|
|
18
|
-
const gitIgnorePath =
|
|
21
|
+
const templateGitignorePath = node_path_1.default.join(targetDir, "gitignore");
|
|
22
|
+
const npmIgnorePath = node_path_1.default.join(targetDir, ".npmignore");
|
|
23
|
+
const gitIgnorePath = node_path_1.default.join(targetDir, ".gitignore");
|
|
19
24
|
const [hasTemplateGitignore, hasNpmIgnore, hasGitIgnore] = await Promise.all([
|
|
20
25
|
fs_extra_1.default.pathExists(templateGitignorePath),
|
|
21
26
|
fs_extra_1.default.pathExists(npmIgnorePath),
|
|
@@ -41,8 +46,8 @@ async function copyModuleFiles(moduleDir, targetDir) {
|
|
|
41
46
|
overwrite: true,
|
|
42
47
|
filter: (src) => {
|
|
43
48
|
// Skip config.json files
|
|
44
|
-
const basename =
|
|
45
|
-
return basename !==
|
|
49
|
+
const basename = node_path_1.default.basename(src);
|
|
50
|
+
return basename !== MODULE_CONFIG_FILE;
|
|
46
51
|
},
|
|
47
52
|
});
|
|
48
53
|
await restoreGitignore(targetDir);
|
|
@@ -51,58 +56,56 @@ async function copyModuleFiles(moduleDir, targetDir) {
|
|
|
51
56
|
throw (0, types_1.buildError)(error, "FILE_COPY_ERROR", `Failed to copy module files from ${moduleDir}`);
|
|
52
57
|
}
|
|
53
58
|
}
|
|
59
|
+
// Lists the files a module adds to a project, under the names they end up
|
|
60
|
+
// with: no .hbs extension, and the gitignore template as .gitignore
|
|
61
|
+
async function listModuleFiles(moduleDir) {
|
|
62
|
+
const entries = await (0, promises_1.readdir)(moduleDir, {
|
|
63
|
+
recursive: true,
|
|
64
|
+
withFileTypes: true,
|
|
65
|
+
});
|
|
66
|
+
return entries
|
|
67
|
+
.filter((entry) => entry.isFile() && entry.name !== MODULE_CONFIG_FILE)
|
|
68
|
+
.map((entry) => {
|
|
69
|
+
const file = node_path_1.default
|
|
70
|
+
.relative(moduleDir, node_path_1.default.join(entry.parentPath, entry.name))
|
|
71
|
+
.split(node_path_1.default.sep)
|
|
72
|
+
.join("/");
|
|
73
|
+
if (GITIGNORE_TEMPLATE_NAMES.includes(file))
|
|
74
|
+
return ".gitignore";
|
|
75
|
+
return file.endsWith(TEMPLATE_EXTENSION)
|
|
76
|
+
? file.slice(0, -TEMPLATE_EXTENSION.length)
|
|
77
|
+
: file;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
54
80
|
// Compiles and renders template content with Handlebars and provided context
|
|
55
81
|
function compileTemplate(content, context) {
|
|
56
82
|
try {
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
// Compile the template
|
|
62
|
-
const template = handlebars_1.default.compile(content);
|
|
63
|
-
// Render with context
|
|
64
|
-
return template(context);
|
|
83
|
+
// The output is source code, not HTML, so values are written unescaped
|
|
84
|
+
const template = handlebars_1.default.compile(content, { noEscape: true });
|
|
85
|
+
return template(context, { helpers: context.helpers });
|
|
65
86
|
}
|
|
66
87
|
catch (error) {
|
|
67
88
|
throw (0, types_1.buildError)(error, "TEMPLATE_SYNTAX_ERROR", "Failed to compile Handlebars template");
|
|
68
89
|
}
|
|
69
90
|
}
|
|
70
|
-
//
|
|
71
|
-
async function processTemplateFiles(targetDir, context
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
// Renders every .hbs file in the project and removes the .hbs extension
|
|
92
|
+
async function processTemplateFiles(targetDir, context) {
|
|
93
|
+
const files = await (0, promises_1.readdir)(targetDir, { recursive: true });
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
if (file.endsWith(TEMPLATE_EXTENSION)) {
|
|
96
|
+
await processTemplateFile(node_path_1.default.join(targetDir, file), context);
|
|
97
|
+
}
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
|
-
//
|
|
100
|
+
// Renders a single .hbs file next to itself and deletes the template
|
|
77
101
|
async function processTemplateFile(filePath, context) {
|
|
78
102
|
try {
|
|
79
|
-
// Check if file exists
|
|
80
|
-
const exists = await fs_extra_1.default.pathExists(filePath);
|
|
81
|
-
if (!exists) {
|
|
82
|
-
return; // Skip if file doesn't exist
|
|
83
|
-
}
|
|
84
|
-
// Read template file
|
|
85
103
|
const content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
86
|
-
// Compile and render template with Handlebars
|
|
87
104
|
const rendered = compileTemplate(content, context);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
? filePath.slice(0, -4)
|
|
91
|
-
: filePath;
|
|
92
|
-
// Write processed content
|
|
93
|
-
await fs_extra_1.default.writeFile(outputPath, rendered, "utf-8");
|
|
94
|
-
// Remove original .hbs file if it was renamed
|
|
95
|
-
if (outputPath !== filePath) {
|
|
96
|
-
await fs_extra_1.default.remove(filePath);
|
|
97
|
-
}
|
|
105
|
+
await fs_extra_1.default.writeFile(filePath.slice(0, -TEMPLATE_EXTENSION.length), rendered, "utf-8");
|
|
106
|
+
await fs_extra_1.default.remove(filePath);
|
|
98
107
|
}
|
|
99
108
|
catch (error) {
|
|
100
|
-
|
|
101
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
// Wrap other errors
|
|
105
|
-
throw (0, types_1.buildError)(error, "TEMPLATE_WRITE_ERROR", `Failed to process template file ${filePath}`);
|
|
109
|
+
throw (0, types_1.buildError)(error, "TEMPLATE_PROCESSING_ERROR", `Failed to process template file ${filePath}`);
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
|
-
//# sourceMappingURL=fileProcessor.js.map
|