better-ts-stack 2.0.0 → 2.0.2
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 +1 -1
- package/dist/args.js +2 -2
- package/dist/builder/buildPlanner.js +71 -0
- package/dist/builder/configGenerator.js +9 -7
- package/dist/builder/dependencyInstaller.js +0 -1
- package/dist/builder/fileProcessor.js +47 -44
- package/dist/builder/gitInitializer.js +20 -1
- package/dist/builder/index.js +19 -38
- package/dist/builder/moduleSelector.js +0 -1
- package/dist/builder/templateContext.js +0 -22
- package/dist/index.js +4 -3
- package/dist/modules/registry.js +4 -6
- package/dist/output/nextSteps.js +4 -3
- package/dist/prompts/index.js +0 -1
- package/dist/prompts/stack.js +4 -1
- package/dist/types/index.js +8 -4
- package/dist/ui/index.js +0 -1
- package/dist/ui/messages.js +25 -1
- package/dist/ui/prompts.js +0 -1
- package/dist/ui/theme.js +0 -1
- package/dist/validators/index.js +10 -41
- package/package.json +2 -1
- package/templates/backend/express/.prettierrc +2 -2
- package/templates/backend/express/config.json +1 -5
- package/templates/backend/express/eslint.config.mjs +40 -40
- package/templates/backend/express/gitignore +36 -36
- package/templates/backend/express/src/{index.ts → index.ts.hbs} +28 -29
- package/templates/backend/express/src/routes/health.ts +12 -12
- package/templates/backend/express/tsconfig.json +25 -25
- package/templates/backend/nestjs/.prettierrc +16 -16
- package/templates/backend/nestjs/config.json +1 -4
- package/templates/backend/nestjs/eslint.config.mjs +40 -40
- package/templates/backend/nestjs/gitignore +36 -36
- package/templates/backend/nestjs/nest-cli.json +9 -9
- package/templates/backend/nestjs/src/app.module.ts.hbs +34 -34
- package/templates/backend/nestjs/src/health/health.controller.ts +12 -12
- package/templates/backend/nestjs/src/main.ts +23 -23
- package/templates/backend/nestjs/tsconfig.json +25 -25
- package/templates/frontend/nextjs/components.json +19 -19
- package/templates/frontend/nextjs/config.json +1 -2
- package/templates/frontend/nextjs/gitignore +42 -42
- package/templates/frontend/nextjs/lib/utils.ts +6 -6
- package/templates/frontend/nextjs/proxy.ts.hbs +24 -24
- package/templates/frontend/nextjs/tsconfig.json +34 -34
- package/templates/frontend/tanstack/config.json +45 -48
- package/templates/frontend/tanstack/src/routeTree.gen.ts +68 -68
- package/templates/frontend/tanstack/src/styles/app.css +85 -85
- package/templates/modules/auth/express/config.json +1 -8
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +116 -116
- package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +26 -26
- package/templates/modules/auth/express/src/services/userStore.ts.hbs +117 -117
- package/templates/modules/auth/nestjs/config.json +1 -5
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -46
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -36
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -22
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -35
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -70
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -27
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -144
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -16
- package/templates/modules/auth/nextjs/app/dashboard/page.tsx +96 -96
- package/templates/modules/auth/nextjs/app/sign-in/page.tsx +35 -35
- package/templates/modules/auth/nextjs/app/sign-up/page.tsx +35 -35
- package/templates/modules/auth/nextjs/components/auth/sign-in-form.tsx +132 -132
- package/templates/modules/auth/nextjs/components/auth/sign-out-button.tsx +50 -50
- package/templates/modules/auth/nextjs/components/auth/sign-up-form.tsx +152 -152
- package/templates/modules/auth/nextjs/config.json +2 -15
- package/templates/modules/auth/nextjs/lib/auth-schema.ts +39 -39
- package/templates/modules/auth/nextjs/lib/auth.ts.hbs +37 -37
- package/templates/modules/auth/tanstack/config.json +2 -16
- package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +4 -4
- package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +38 -38
- package/templates/modules/auth/tanstack/src/routeTree.gen.ts +140 -140
- package/templates/modules/docker/.dockerignore +14 -14
- package/templates/modules/docker/Dockerfile.hbs +1 -3
- package/templates/modules/docker/config.json +1 -5
- package/templates/modules/docker/docker-compose.yml.hbs +9 -9
- package/templates/modules/drizzle/express/config.json +1 -2
- package/templates/modules/drizzle/express/drizzle.config.ts +13 -13
- package/templates/modules/drizzle/express/src/lib/db.ts +13 -13
- package/templates/modules/drizzle/express/src/lib/schema.ts +12 -10
- package/templates/modules/drizzle/nestjs/config.json +1 -2
- package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -13
- package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -30
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +12 -10
- package/templates/modules/drizzle/nextjs/config.json +1 -4
- package/templates/modules/drizzle/nextjs/lib/db.ts +13 -13
- package/templates/modules/drizzle/nextjs/lib/schema.ts.hbs +90 -84
- package/templates/modules/drizzle/tanstack/config.json +5 -5
- package/templates/modules/drizzle/tanstack/src/lib/schema.ts.hbs +90 -84
- package/templates/modules/mongoose/express/config.json +1 -2
- package/templates/modules/mongoose/nestjs/config.json +1 -2
- package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -21
- package/templates/modules/mongoose/nextjs/config.json +1 -2
- package/templates/modules/mongoose/nextjs/lib/db.ts +36 -36
- package/templates/modules/mongoose/tanstack/config.json +1 -2
- package/templates/modules/mongoose/tanstack/src/lib/db.ts +37 -37
- package/templates/modules/prisma/express/config.json +0 -1
- package/templates/modules/prisma/express/prisma/schema.prisma +1 -1
- package/templates/modules/prisma/express/prisma.config.ts +12 -12
- package/templates/modules/prisma/express/src/lib/prisma.ts +24 -24
- package/templates/modules/prisma/nestjs/config.json +0 -1
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -23
- package/templates/modules/prisma/nestjs/prisma.config.ts +12 -12
- package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -11
- package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -17
- package/templates/modules/prisma/nextjs/config.json +0 -1
- package/templates/modules/prisma/nextjs/prisma/schema.prisma.hbs +91 -91
- package/templates/modules/prisma/tanstack/config.json +0 -1
- package/dist/args.d.ts +0 -12
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- 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 -6
- package/dist/builder/moduleSelector.d.ts.map +0 -1
- package/dist/builder/moduleSelector.js.map +0 -1
- package/dist/builder/templateContext.d.ts +0 -6
- 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/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 -3
- 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 -4
- package/dist/prompts/stack.d.ts.map +0 -1
- package/dist/prompts/stack.js.map +0 -1
- package/dist/types/index.d.ts +0 -137
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/index.d.ts +0 -3
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/messages.d.ts +0 -9
- package/dist/ui/messages.d.ts.map +0 -1
- package/dist/ui/messages.js.map +0 -1
- package/dist/ui/prompts.d.ts +0 -20
- package/dist/ui/prompts.d.ts.map +0 -1
- package/dist/ui/prompts.js.map +0 -1
- package/dist/ui/theme.d.ts +0 -9
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
- package/dist/validators/index.d.ts +0 -6
- package/dist/validators/index.d.ts.map +0 -1
- package/dist/validators/index.js.map +0 -1
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
|
@@ -39,7 +39,7 @@ npx better-ts-stack . --framework nextjs --database drizzle --pm pnpm
|
|
|
39
39
|
| `--git` / `--no-git` | Initialize a git repository |
|
|
40
40
|
| `--install` / `--no-install` | Install dependencies |
|
|
41
41
|
| `-y`, `--yes` | Use defaults for missing options, no prompts |
|
|
42
|
-
| `--dry-run` |
|
|
42
|
+
| `--dry-run` | List what would be created, write nothing |
|
|
43
43
|
| `-h`, `--help` | Show help |
|
|
44
44
|
| `-v`, `--version` | Show the version |
|
|
45
45
|
|
package/dist/args.js
CHANGED
|
@@ -23,7 +23,8 @@ Options left out are asked interactively, unless --yes is given.
|
|
|
23
23
|
--git, --no-git Initialize a git repository
|
|
24
24
|
--install, --no-install Install dependencies
|
|
25
25
|
-y, --yes Use defaults for missing options and skip confirmation
|
|
26
|
-
--dry-run
|
|
26
|
+
--dry-run List the files, dependencies, scripts, environment
|
|
27
|
+
variables, and commands without writing anything
|
|
27
28
|
-h, --help Show this help
|
|
28
29
|
-v, --version Show the version
|
|
29
30
|
|
|
@@ -87,4 +88,3 @@ function parseCliArgs(argv) {
|
|
|
87
88
|
version: values.version ?? false,
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
|
-
//# sourceMappingURL=args.js.map
|
|
@@ -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,7 +13,7 @@ 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");
|
|
17
18
|
// Frameworks whose generated project uses ES modules
|
|
18
19
|
const ESM_FRAMEWORKS = ["tanstack", "nestjs"];
|
|
@@ -88,7 +89,7 @@ async function generatePackageJson(targetDir, mergedConfig, config) {
|
|
|
88
89
|
devDependencies: mergedConfig.devDependencies,
|
|
89
90
|
overrides,
|
|
90
91
|
};
|
|
91
|
-
const packageJsonPath =
|
|
92
|
+
const packageJsonPath = node_path_1.default.join(targetDir, "package.json");
|
|
92
93
|
await fs_extra_1.default.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
@@ -124,12 +125,14 @@ async function generatePnpmWorkspace(targetDir, overrides) {
|
|
|
124
125
|
lines.push(` - "${name}"`);
|
|
125
126
|
}
|
|
126
127
|
try {
|
|
127
|
-
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");
|
|
128
129
|
}
|
|
129
130
|
catch (error) {
|
|
130
131
|
throw (0, types_1.buildError)(error, "PNPM_WORKSPACE_ERROR", "Failed to generate pnpm-workspace.yaml");
|
|
131
132
|
}
|
|
132
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"];
|
|
133
136
|
// Generates .env and .env.example files from merged environment variables
|
|
134
137
|
async function generateEnvFile(targetDir, envVars) {
|
|
135
138
|
try {
|
|
@@ -140,21 +143,20 @@ async function generateEnvFile(targetDir, envVars) {
|
|
|
140
143
|
let localEnvContent = envContent;
|
|
141
144
|
// Add each environment variable
|
|
142
145
|
for (const [key, value] of Object.entries(envVars)) {
|
|
143
|
-
const resolvedValue =
|
|
146
|
+
const resolvedValue = exports.GENERATED_SECRETS.includes(key)
|
|
144
147
|
? (0, node_crypto_1.randomBytes)(32).toString("hex")
|
|
145
148
|
: value;
|
|
146
149
|
envContent += `${key}=${value}\n`;
|
|
147
150
|
localEnvContent += `${key}=${resolvedValue}\n`;
|
|
148
151
|
}
|
|
149
152
|
// Write .env.example
|
|
150
|
-
const envExamplePath =
|
|
153
|
+
const envExamplePath = node_path_1.default.join(targetDir, ".env.example");
|
|
151
154
|
await fs_extra_1.default.writeFile(envExamplePath, envContent, "utf-8");
|
|
152
155
|
// Keep generated secrets out of the shareable example file.
|
|
153
|
-
const envPath =
|
|
156
|
+
const envPath = node_path_1.default.join(targetDir, ".env");
|
|
154
157
|
await fs_extra_1.default.writeFile(envPath, localEnvContent, "utf-8");
|
|
155
158
|
}
|
|
156
159
|
catch (error) {
|
|
157
160
|
throw (0, types_1.buildError)(error, "ENV_FILE_ERROR", "Failed to generate environment files");
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
|
-
//# sourceMappingURL=configGenerator.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
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInsideGitRepository = isInsideGitRepository;
|
|
3
4
|
exports.initializeGitRepository = initializeGitRepository;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
const ui_1 = require("../ui");
|
|
7
|
+
// True when the directory is already tracked by a git repository above it
|
|
8
|
+
function isInsideGitRepository(cwd) {
|
|
9
|
+
try {
|
|
10
|
+
const output = (0, node_child_process_1.execSync)("git rev-parse --is-inside-work-tree", {
|
|
11
|
+
cwd,
|
|
12
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
13
|
+
});
|
|
14
|
+
return output.toString().trim() === "true";
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
6
20
|
// Initializes a git repository in the given directory
|
|
7
21
|
function initializeGitRepository(cwd) {
|
|
22
|
+
// A project created inside a repository (apps/api in a monorepo) must not
|
|
23
|
+
// get a nested repository of its own
|
|
24
|
+
if (isInsideGitRepository(cwd)) {
|
|
25
|
+
(0, ui_1.info)("Already inside a git repository, skipped git init");
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
8
28
|
try {
|
|
9
29
|
(0, node_child_process_1.execSync)("git init", { cwd, stdio: "ignore" });
|
|
10
30
|
(0, ui_1.success)("Git repository initialized");
|
|
@@ -16,4 +36,3 @@ function initializeGitRepository(cwd) {
|
|
|
16
36
|
return false;
|
|
17
37
|
}
|
|
18
38
|
}
|
|
19
|
-
//# sourceMappingURL=gitInitializer.js.map
|
package/dist/builder/index.js
CHANGED
|
@@ -3,18 +3,19 @@ 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.planBuild = void 0;
|
|
6
7
|
exports.build = build;
|
|
7
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const registry_1 = require("../modules/registry");
|
|
9
9
|
const nextSteps_1 = require("../output/nextSteps");
|
|
10
10
|
const types_1 = require("../types");
|
|
11
11
|
const validators_1 = require("../validators");
|
|
12
|
+
const buildPlanner_1 = require("./buildPlanner");
|
|
12
13
|
const configGenerator_1 = require("./configGenerator");
|
|
13
14
|
const dependencyInstaller_1 = require("./dependencyInstaller");
|
|
14
15
|
const fileProcessor_1 = require("./fileProcessor");
|
|
15
16
|
const gitInitializer_1 = require("./gitInitializer");
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
var buildPlanner_2 = require("./buildPlanner");
|
|
18
|
+
Object.defineProperty(exports, "planBuild", { enumerable: true, get: function () { return buildPlanner_2.planBuild; } });
|
|
18
19
|
// Main function to orchestrate the project construction process
|
|
19
20
|
async function build(config, targetDir) {
|
|
20
21
|
// 1. Validate target directory
|
|
@@ -27,51 +28,29 @@ async function build(config, targetDir) {
|
|
|
27
28
|
await fs_extra_1.default.ensureDir(targetDir).catch((e) => {
|
|
28
29
|
throw (0, types_1.buildError)(e, "DIRECTORY_CREATE_ERROR", "Failed to create directory");
|
|
29
30
|
});
|
|
30
|
-
// 3.
|
|
31
|
-
|
|
32
|
-
const modules =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
31
|
+
// 3. Load the selected modules, build the template context, and merge
|
|
32
|
+
// the module configurations
|
|
33
|
+
const { modules, templateContext, mergedConfig } = await (0, buildPlanner_1.loadBuildInputs)(config);
|
|
34
|
+
// 4. Copy module files
|
|
35
|
+
for (const module of modules) {
|
|
36
|
+
await (0, fileProcessor_1.copyModuleFiles)(module.path, targetDir);
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// 4. Build template context and merge module configurations
|
|
42
|
-
const templateContext = (0, templateContext_1.buildTemplateContext)(config);
|
|
43
|
-
const mergedConfig = (0, configGenerator_1.mergeConfigurations)(modules.map((m) => m.config), templateContext);
|
|
44
|
-
// 5. Copy module files
|
|
45
|
-
try {
|
|
46
|
-
for (const module of modules) {
|
|
47
|
-
await (0, fileProcessor_1.copyModuleFiles)(module.path, targetDir);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
throw (0, types_1.buildError)(e, "FILE_COPY_ERROR", "Failed to copy module files");
|
|
52
|
-
}
|
|
53
|
-
// 6. Process Handlebars templates
|
|
54
|
-
try {
|
|
55
|
-
await (0, fileProcessor_1.processTemplateFiles)(targetDir, templateContext, modules.flatMap((m) => m.config.templateFiles));
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
throw (0, types_1.buildError)(e, "TEMPLATE_PROCESSING_ERROR", "Failed to process template files");
|
|
59
|
-
}
|
|
60
|
-
// 7. Generate package.json
|
|
38
|
+
// 5. Process Handlebars templates
|
|
39
|
+
await (0, fileProcessor_1.processTemplateFiles)(targetDir, templateContext);
|
|
40
|
+
// 6. Generate package.json
|
|
61
41
|
await (0, configGenerator_1.generatePackageJson)(targetDir, mergedConfig, config);
|
|
62
42
|
if (config.packageManager === "pnpm") {
|
|
63
43
|
await (0, configGenerator_1.generatePnpmWorkspace)(targetDir, mergedConfig.overrides);
|
|
64
44
|
}
|
|
65
|
-
//
|
|
45
|
+
// 7. Generate environment files
|
|
66
46
|
await (0, configGenerator_1.generateEnvFile)(targetDir, mergedConfig.envVars);
|
|
67
|
-
//
|
|
47
|
+
// 8. Install dependencies and initialize git (both optional); a failure
|
|
68
48
|
// in either is reported as a warning and does not fail the build
|
|
69
49
|
const depsInstalled = config.installDeps &&
|
|
70
50
|
(0, dependencyInstaller_1.installDependencies)(config.packageManager, targetDir);
|
|
71
51
|
if (config.initGit)
|
|
72
52
|
(0, gitInitializer_1.initializeGitRepository)(targetDir);
|
|
73
53
|
return {
|
|
74
|
-
success: true,
|
|
75
54
|
projectPath: targetDir,
|
|
76
55
|
nextSteps: (0, nextSteps_1.generateNextSteps)(config, targetDir, depsInstalled),
|
|
77
56
|
};
|
|
@@ -80,7 +59,9 @@ async function build(config, targetDir) {
|
|
|
80
59
|
// Remove the partial project so a retry is not blocked by a non-empty
|
|
81
60
|
// directory; a directory that already existed is emptied, not deleted
|
|
82
61
|
await (didDirectoryExist ? fs_extra_1.default.emptyDir(targetDir) : fs_extra_1.default.remove(targetDir)).catch(() => undefined);
|
|
83
|
-
|
|
62
|
+
// Errors raised by the build steps already say what failed
|
|
63
|
+
throw (0, types_1.isBuildError)(error)
|
|
64
|
+
? error
|
|
65
|
+
: (0, types_1.buildError)(error, "BUILDING_ERROR", "Building failed");
|
|
84
66
|
}
|
|
85
67
|
}
|
|
86
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.lowercase = lowercase;
|
|
4
|
-
exports.uppercase = uppercase;
|
|
5
|
-
exports.kebabCase = kebabCase;
|
|
6
3
|
exports.buildTemplateContext = buildTemplateContext;
|
|
7
4
|
// Context builder for Handlebars, providing variables and helpers for templates
|
|
8
5
|
const types_1 = require("../types");
|
|
9
|
-
// Converts string to lowercase
|
|
10
|
-
function lowercase(str) {
|
|
11
|
-
return str.toLowerCase();
|
|
12
|
-
}
|
|
13
|
-
// Converts string to uppercase
|
|
14
|
-
function uppercase(str) {
|
|
15
|
-
return str.toUpperCase();
|
|
16
|
-
}
|
|
17
|
-
// Converts string to kebab-case
|
|
18
|
-
function kebabCase(str) {
|
|
19
|
-
return str
|
|
20
|
-
.replace(/([a-z])([A-Z])/g, "$1-$2") // camelCase to kebab-case
|
|
21
|
-
.replace(/[\s_]+/g, "-") // spaces and underscores to hyphens
|
|
22
|
-
.toLowerCase();
|
|
23
|
-
}
|
|
24
6
|
function buildTemplateContext(config) {
|
|
25
7
|
return {
|
|
26
8
|
projectName: config.projectName,
|
|
@@ -31,9 +13,6 @@ function buildTemplateContext(config) {
|
|
|
31
13
|
useDocker: config.useDocker,
|
|
32
14
|
useAuth: config.useAuth,
|
|
33
15
|
helpers: {
|
|
34
|
-
lowercase,
|
|
35
|
-
uppercase,
|
|
36
|
-
kebabCase,
|
|
37
16
|
eq: (a, b) => a === b,
|
|
38
17
|
runner: () => {
|
|
39
18
|
if (config.packageManager === "bun")
|
|
@@ -43,4 +22,3 @@ function buildTemplateContext(config) {
|
|
|
43
22
|
},
|
|
44
23
|
};
|
|
45
24
|
}
|
|
46
|
-
//# sourceMappingURL=templateContext.js.map
|
package/dist/index.js
CHANGED
|
@@ -40,10 +40,12 @@ async function main() {
|
|
|
40
40
|
};
|
|
41
41
|
(0, ui_1.showSummary)(config, targetDir);
|
|
42
42
|
if (args.dryRun) {
|
|
43
|
+
(0, ui_1.showPlan)(await (0, builder_1.planBuild)(config, targetDir));
|
|
43
44
|
(0, ui_1.info)("Dry run: no files were written.");
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
// Without a terminal nobody can answer, and every option came from a flag
|
|
48
|
+
if (!args.yes && process.stdin.isTTY)
|
|
47
49
|
await (0, prompts_1.confirmBuild)();
|
|
48
50
|
const result = await (0, builder_1.build)(config, targetDir);
|
|
49
51
|
(0, ui_1.success)("Project created");
|
|
@@ -53,7 +55,6 @@ main()
|
|
|
53
55
|
.then(() => process.exit(0))
|
|
54
56
|
.catch((err) => {
|
|
55
57
|
// Errors raised by the CLI carry a code and a message meant for the user
|
|
56
|
-
(0, ui_1.error)(
|
|
58
|
+
(0, ui_1.error)((0, types_1.isBuildError)(err) ? err.message : String(err));
|
|
57
59
|
process.exit(1);
|
|
58
60
|
});
|
|
59
|
-
//# sourceMappingURL=index.js.map
|
package/dist/modules/registry.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getModule = getModule;
|
|
7
7
|
// Module registry for accessing base templates and feature modules
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
-
const
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const zod_1 = require("zod");
|
|
11
11
|
const types_1 = require("../types");
|
|
12
12
|
const moduleConfigSchema = zod_1.z.object({
|
|
@@ -19,20 +19,19 @@ const moduleConfigSchema = zod_1.z.object({
|
|
|
19
19
|
overrides: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
|
|
20
20
|
scripts: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
|
|
21
21
|
envVars: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
|
|
22
|
-
templateFiles: zod_1.z.array(zod_1.z.string()).default([]),
|
|
23
22
|
});
|
|
24
23
|
// Determines the absolute filesystem path for a given module ID
|
|
25
24
|
function resolveModulePath(id) {
|
|
26
25
|
// Base templates: backend/express, frontend/nextjs
|
|
27
26
|
if (id.startsWith("backend/") || id.startsWith("frontend/")) {
|
|
28
|
-
return
|
|
27
|
+
return node_path_1.default.join(__dirname, `../../templates/${id}`);
|
|
29
28
|
}
|
|
30
29
|
// Feature modules: auth/express, prisma/nextjs, docker, mongoose, etc.
|
|
31
|
-
return
|
|
30
|
+
return node_path_1.default.join(__dirname, `../../templates/modules/${id}`);
|
|
32
31
|
}
|
|
33
32
|
// Reads and parses a module's config.json file
|
|
34
33
|
async function loadModuleConfig(modulePath) {
|
|
35
|
-
const configPath =
|
|
34
|
+
const configPath = node_path_1.default.join(modulePath, "config.json");
|
|
36
35
|
try {
|
|
37
36
|
const configContent = await fs_extra_1.default.readFile(configPath, "utf-8");
|
|
38
37
|
const rawConfig = JSON.parse(configContent);
|
|
@@ -58,4 +57,3 @@ async function getModule(id) {
|
|
|
58
57
|
path: modulePath,
|
|
59
58
|
};
|
|
60
59
|
}
|
|
61
|
-
//# sourceMappingURL=registry.js.map
|
package/dist/output/nextSteps.js
CHANGED
|
@@ -21,9 +21,11 @@ function generateNextSteps(config, targetDir, depsInstalled) {
|
|
|
21
21
|
const steps = [];
|
|
22
22
|
const isFullstack = config.applicationType === "fullstack";
|
|
23
23
|
// Step 1: cd into project directory, unless it was created in place
|
|
24
|
+
// Quoted when it has spaces, so the command can be pasted as it is
|
|
24
25
|
const relativeDir = node_path_1.default.relative((0, node_process_1.cwd)(), targetDir);
|
|
25
|
-
if (relativeDir)
|
|
26
|
-
steps.push(`cd ${relativeDir}`);
|
|
26
|
+
if (relativeDir) {
|
|
27
|
+
steps.push(/\s/.test(relativeDir) ? `cd "${relativeDir}"` : `cd ${relativeDir}`);
|
|
28
|
+
}
|
|
27
29
|
// Step 2: Set environment variables
|
|
28
30
|
if (config.database !== "none") {
|
|
29
31
|
steps.push("Review the generated .env and set your database connection details");
|
|
@@ -70,4 +72,3 @@ function generateNextSteps(config, targetDir, depsInstalled) {
|
|
|
70
72
|
}
|
|
71
73
|
return steps;
|
|
72
74
|
}
|
|
73
|
-
//# sourceMappingURL=nextSteps.js.map
|
package/dist/prompts/index.js
CHANGED
package/dist/prompts/stack.js
CHANGED
|
@@ -9,6 +9,10 @@ const ui_1 = require("../ui");
|
|
|
9
9
|
async function ask(preset, prompt) {
|
|
10
10
|
if (preset !== undefined)
|
|
11
11
|
return preset;
|
|
12
|
+
// Without a terminal a prompt reads end-of-input and looks like a cancel
|
|
13
|
+
if (!process.stdin.isTTY) {
|
|
14
|
+
throw (0, types_1.buildError)("Cannot ask questions without an interactive terminal. Pass --yes, or give every option as a flag (see --help)", "NOT_INTERACTIVE");
|
|
15
|
+
}
|
|
12
16
|
const answer = await prompt();
|
|
13
17
|
if ((0, core_1.isCancel)(answer)) {
|
|
14
18
|
(0, ui_1.info)("Cancelled. Nothing was created.");
|
|
@@ -85,4 +89,3 @@ async function collectStackChoices(preset) {
|
|
|
85
89
|
installDeps,
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
|
-
//# sourceMappingURL=stack.js.map
|