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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -82
  3. package/dist/args.js +90 -0
  4. package/dist/builder/buildPlanner.js +71 -0
  5. package/dist/builder/configGenerator.js +19 -10
  6. package/dist/builder/dependencyInstaller.js +8 -15
  7. package/dist/builder/fileProcessor.js +47 -44
  8. package/dist/builder/gitInitializer.js +21 -35
  9. package/dist/builder/index.js +33 -91
  10. package/dist/builder/moduleSelector.js +11 -16
  11. package/dist/builder/templateContext.js +6 -27
  12. package/dist/index.js +47 -21
  13. package/dist/modules/registry.js +4 -6
  14. package/dist/output/nextSteps.js +10 -20
  15. package/dist/prompts/index.js +22 -59
  16. package/dist/prompts/stack.js +85 -71
  17. package/dist/types/index.js +47 -41
  18. package/dist/ui/index.js +18 -0
  19. package/dist/ui/messages.js +124 -0
  20. package/dist/ui/prompts.js +70 -0
  21. package/dist/ui/theme.js +23 -0
  22. package/dist/validators/index.js +15 -40
  23. package/package.json +71 -66
  24. package/templates/backend/express/.prettierrc +2 -2
  25. package/templates/backend/express/config.json +40 -44
  26. package/templates/backend/express/src/{index.ts → index.ts.hbs} +75 -76
  27. package/templates/backend/nestjs/.prettierignore +52 -0
  28. package/templates/backend/nestjs/.prettierrc +16 -0
  29. package/templates/backend/nestjs/config.json +41 -0
  30. package/templates/backend/nestjs/eslint.config.mjs +40 -0
  31. package/templates/backend/nestjs/gitignore +36 -0
  32. package/templates/backend/nestjs/nest-cli.json +9 -0
  33. package/templates/backend/nestjs/src/app.module.ts.hbs +34 -0
  34. package/templates/backend/nestjs/src/health/health.controller.ts +12 -0
  35. package/templates/backend/nestjs/src/main.ts +23 -0
  36. package/templates/backend/nestjs/tsconfig.eslint.json +9 -0
  37. package/templates/backend/nestjs/tsconfig.json +25 -0
  38. package/templates/frontend/nextjs/app/layout.tsx +13 -13
  39. package/templates/frontend/nextjs/app/page.tsx.hbs +97 -97
  40. package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
  41. package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
  42. package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
  43. package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
  44. package/templates/frontend/nextjs/config.json +37 -38
  45. package/templates/frontend/nextjs/eslint.config.mjs +11 -11
  46. package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
  47. package/templates/frontend/tanstack/config.json +45 -48
  48. package/templates/modules/auth/express/config.json +20 -26
  49. package/templates/modules/auth/express/src/controllers/authController.ts.hbs +43 -57
  50. package/templates/modules/auth/nestjs/config.json +20 -0
  51. package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -0
  52. package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -0
  53. package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -0
  54. package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -0
  55. package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -0
  56. package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -0
  57. package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -0
  58. package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -0
  59. package/templates/modules/auth/nextjs/config.json +18 -31
  60. package/templates/modules/auth/tanstack/config.json +2 -16
  61. package/templates/modules/docker/Dockerfile.hbs +135 -133
  62. package/templates/modules/docker/config.json +15 -19
  63. package/templates/modules/drizzle/express/config.json +26 -27
  64. package/templates/modules/drizzle/nestjs/config.json +26 -0
  65. package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -0
  66. package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -0
  67. package/templates/modules/drizzle/nestjs/src/database/schema.ts +10 -0
  68. package/templates/modules/drizzle/nextjs/config.json +26 -29
  69. package/templates/modules/drizzle/tanstack/config.json +5 -5
  70. package/templates/modules/mongoose/express/config.json +15 -16
  71. package/templates/modules/mongoose/nestjs/config.json +15 -0
  72. package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -0
  73. package/templates/modules/mongoose/nextjs/config.json +15 -16
  74. package/templates/modules/mongoose/tanstack/config.json +16 -17
  75. package/templates/modules/prisma/express/config.json +27 -28
  76. package/templates/modules/prisma/nestjs/config.json +27 -0
  77. package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -0
  78. package/templates/modules/prisma/nestjs/prisma.config.ts +12 -0
  79. package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -0
  80. package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -0
  81. package/templates/modules/prisma/nextjs/config.json +28 -29
  82. package/templates/modules/prisma/tanstack/config.json +5 -6
  83. package/dist/builder/configGenerator.d.ts +0 -7
  84. package/dist/builder/configGenerator.d.ts.map +0 -1
  85. package/dist/builder/configGenerator.js.map +0 -1
  86. package/dist/builder/dependencyInstaller.d.ts +0 -3
  87. package/dist/builder/dependencyInstaller.d.ts.map +0 -1
  88. package/dist/builder/dependencyInstaller.js.map +0 -1
  89. package/dist/builder/fileProcessor.d.ts +0 -6
  90. package/dist/builder/fileProcessor.d.ts.map +0 -1
  91. package/dist/builder/fileProcessor.js.map +0 -1
  92. package/dist/builder/gitInitializer.d.ts +0 -2
  93. package/dist/builder/gitInitializer.d.ts.map +0 -1
  94. package/dist/builder/gitInitializer.js.map +0 -1
  95. package/dist/builder/index.d.ts +0 -3
  96. package/dist/builder/index.d.ts.map +0 -1
  97. package/dist/builder/index.js.map +0 -1
  98. package/dist/builder/moduleSelector.d.ts +0 -9
  99. package/dist/builder/moduleSelector.d.ts.map +0 -1
  100. package/dist/builder/moduleSelector.js.map +0 -1
  101. package/dist/builder/templateContext.d.ts +0 -21
  102. package/dist/builder/templateContext.d.ts.map +0 -1
  103. package/dist/builder/templateContext.js.map +0 -1
  104. package/dist/index.d.ts +0 -2
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.js.map +0 -1
  107. package/dist/intro.d.ts +0 -2
  108. package/dist/intro.d.ts.map +0 -1
  109. package/dist/intro.js +0 -27
  110. package/dist/intro.js.map +0 -1
  111. package/dist/modules/registry.d.ts +0 -6
  112. package/dist/modules/registry.d.ts.map +0 -1
  113. package/dist/modules/registry.js.map +0 -1
  114. package/dist/output/nextSteps.d.ts +0 -4
  115. package/dist/output/nextSteps.d.ts.map +0 -1
  116. package/dist/output/nextSteps.js.map +0 -1
  117. package/dist/prompts/index.d.ts +0 -4
  118. package/dist/prompts/index.d.ts.map +0 -1
  119. package/dist/prompts/index.js.map +0 -1
  120. package/dist/prompts/stack.d.ts +0 -3
  121. package/dist/prompts/stack.d.ts.map +0 -1
  122. package/dist/prompts/stack.js.map +0 -1
  123. package/dist/types/index.d.ts +0 -159
  124. package/dist/types/index.d.ts.map +0 -1
  125. package/dist/types/index.js.map +0 -1
  126. package/dist/validators/index.d.ts +0 -5
  127. package/dist/validators/index.d.ts.map +0 -1
  128. package/dist/validators/index.js.map +0 -1
  129. package/templates/backend/express/.eslintrc.js +0 -24
@@ -1,52 +1,38 @@
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 });
3
+ exports.isInsideGitRepository = isInsideGitRepository;
6
4
  exports.initializeGitRepository = initializeGitRepository;
7
- const child_process_1 = require("child_process");
8
- const consola_1 = __importDefault(require("consola"));
9
- // Checks if git is installed and available in the system PATH
10
- function isGitAvailable() {
5
+ const node_child_process_1 = require("node:child_process");
6
+ const ui_1 = require("../ui");
7
+ // True when the directory is already tracked by a git repository above it
8
+ function isInsideGitRepository(cwd) {
11
9
  try {
12
- (0, child_process_1.execSync)("git --version", { stdio: "ignore" });
13
- return true;
14
- }
15
- catch {
16
- consola_1.default.warn("Git is not installed or not available in PATH");
17
- return false;
18
- }
19
- }
20
- // Initializes a new git repository in the given directory
21
- function initGit(cwd) {
22
- consola_1.default.info("Initializing git repository...");
23
- try {
24
- (0, child_process_1.execSync)("git init", {
10
+ const output = (0, node_child_process_1.execSync)("git rev-parse --is-inside-work-tree", {
25
11
  cwd,
26
- stdio: "ignore",
12
+ stdio: ["ignore", "pipe", "ignore"],
27
13
  });
28
- consola_1.default.success("Git repository initialized");
29
- return true;
14
+ return output.toString().trim() === "true";
30
15
  }
31
16
  catch {
32
- consola_1.default.warn("Failed to initialize git repository");
33
17
  return false;
34
18
  }
35
19
  }
36
- // Coordinates the git initialization process
20
+ // Initializes a git repository in the given directory
37
21
  function initializeGitRepository(cwd) {
38
- // Check if git is available
39
- if (!isGitAvailable()) {
40
- consola_1.default.warn("Git is not installed or not available in PATH");
41
- consola_1.default.info("Skipping git initialization. You can initialize git manually later.");
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");
42
26
  return false;
43
27
  }
44
- // Initialize git repository
45
- const gitInitSuccess = initGit(cwd);
46
- if (!gitInitSuccess) {
47
- consola_1.default.info("Continuing with building...");
28
+ try {
29
+ (0, node_child_process_1.execSync)("git init", { cwd, stdio: "ignore" });
30
+ (0, ui_1.success)("Git repository initialized");
31
+ return true;
32
+ }
33
+ catch {
34
+ // Not fatal: git may not be installed
35
+ (0, ui_1.warn)('Could not initialize git. Run "git init" yourself.');
48
36
  return false;
49
37
  }
50
- return true;
51
38
  }
52
- //# sourceMappingURL=gitInitializer.js.map
@@ -3,123 +3,65 @@ 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
- const consola_1 = __importDefault(require("consola"));
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
- const registry_1 = require("../modules/registry");
10
9
  const nextSteps_1 = require("../output/nextSteps");
11
10
  const types_1 = require("../types");
12
11
  const validators_1 = require("../validators");
12
+ const buildPlanner_1 = require("./buildPlanner");
13
13
  const configGenerator_1 = require("./configGenerator");
14
14
  const dependencyInstaller_1 = require("./dependencyInstaller");
15
15
  const fileProcessor_1 = require("./fileProcessor");
16
16
  const gitInitializer_1 = require("./gitInitializer");
17
- const moduleSelector_1 = require("./moduleSelector");
18
- const templateContext_1 = require("./templateContext");
17
+ var buildPlanner_2 = require("./buildPlanner");
18
+ Object.defineProperty(exports, "planBuild", { enumerable: true, get: function () { return buildPlanner_2.planBuild; } });
19
19
  // Main function to orchestrate the project construction process
20
20
  async function build(config, targetDir) {
21
- consola_1.default.start("Starting project building...");
21
+ // 1. Validate target directory
22
+ const validationResult = await (0, validators_1.validateDirectoryEmpty)(targetDir);
23
+ if (validationResult)
24
+ throw (0, types_1.buildError)(validationResult, "DIRECTORY_NOT_EMPTY");
25
+ const didDirectoryExist = await fs_extra_1.default.pathExists(targetDir);
22
26
  try {
23
- // 1. Validate target directory
24
- consola_1.default.info("Validating target directory...");
25
- const validationResult = await (0, validators_1.validateDirectoryEmpty)(targetDir);
26
- if (validationResult)
27
- throw (0, types_1.buildError)(validationResult, "DIRECTORY_NOT_EMPTY");
28
27
  // 2. Create target directory
29
- consola_1.default.info("Creating project directory...");
30
28
  await fs_extra_1.default.ensureDir(targetDir).catch((e) => {
31
29
  throw (0, types_1.buildError)(e, "DIRECTORY_CREATE_ERROR", "Failed to create directory");
32
30
  });
33
- // 3. Select modules
34
- consola_1.default.info("Selecting modules...");
35
- const selection = (0, moduleSelector_1.selectModules)(config);
36
- // 4. Load modules
37
- consola_1.default.info("Loading module configurations...");
38
- const modules = [];
39
- try {
40
- const baseModule = await (0, registry_1.getModule)(selection.base);
41
- modules.push({ ...baseModule, framework: config.framework });
42
- for (const moduleWithFramework of selection.modules) {
43
- const module = await (0, registry_1.getModule)(moduleWithFramework.id);
44
- modules.push({ ...module, framework: moduleWithFramework.framework });
45
- }
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);
46
37
  }
47
- catch (e) {
48
- throw (0, types_1.buildError)(e, "MODULE_LOADING_ERROR", "Failed to load modules");
49
- }
50
- consola_1.default.success(`Selected ${modules.length} modules: ${modules.map((m) => m.config.id).join(", ")}`);
51
- // 5. Build template context
52
- consola_1.default.info("Building template context...");
53
- const templateContext = (0, templateContext_1.buildTemplateContext)(config);
54
- // 6. Merge configurations
55
- const mergedConfig = (0, configGenerator_1.mergeConfigurations)(modules.map((m) => m.config), templateContext);
56
- // 7. Copy module files
57
- consola_1.default.info("Copying module files...");
58
- try {
59
- for (const module of modules) {
60
- consola_1.default.debug(` - Copying ${module.config.id}...`);
61
- await (0, fileProcessor_1.copyModuleFiles)(module.path, targetDir);
62
- }
63
- consola_1.default.success("Module files copied");
64
- }
65
- catch (e) {
66
- throw (0, types_1.buildError)(e, "FILE_COPY_ERROR", "Failed to copy module files");
67
- }
68
- // 8. Process Handlebars templates
69
- consola_1.default.info("Processing template files...");
70
- try {
71
- const allTemplateFiles = modules.flatMap((m) => m.config.templateFiles);
72
- if (allTemplateFiles.length > 0) {
73
- await (0, fileProcessor_1.processTemplateFiles)(targetDir, templateContext, allTemplateFiles);
74
- consola_1.default.success(`Processed ${allTemplateFiles.length} template file(s)`);
75
- }
76
- else {
77
- consola_1.default.info(" No template files to process");
78
- }
79
- }
80
- catch (e) {
81
- throw (0, types_1.buildError)(e, "TEMPLATE_PROCESSING_ERROR", "Failed to process template files");
82
- }
83
- // 9. Generate package.json
84
- consola_1.default.info("Generating package.json...");
38
+ // 5. Process Handlebars templates
39
+ await (0, fileProcessor_1.processTemplateFiles)(targetDir, templateContext);
40
+ // 6. Generate package.json
85
41
  await (0, configGenerator_1.generatePackageJson)(targetDir, mergedConfig, config);
86
42
  if (config.packageManager === "pnpm") {
87
43
  await (0, configGenerator_1.generatePnpmWorkspace)(targetDir, mergedConfig.overrides);
88
44
  }
89
- consola_1.default.success("package.json generated");
90
- // 10. Generate environment files
91
- consola_1.default.info("Generating environment files...");
45
+ // 7. Generate environment files
92
46
  await (0, configGenerator_1.generateEnvFile)(targetDir, mergedConfig.envVars);
93
- consola_1.default.success("Environment files generated");
94
- // 11. Install dependencies (optional)
95
- let depsInstalled = false;
96
- if (config.installDeps) {
97
- try {
98
- depsInstalled = (0, dependencyInstaller_1.installDependencies)(config.packageManager, targetDir);
99
- }
100
- catch {
101
- consola_1.default.warn("Continuing despite dependency installation issues");
102
- }
103
- }
104
- // 12. Initialize git (optional)
105
- if (config.initGit) {
106
- try {
107
- (0, gitInitializer_1.initializeGitRepository)(targetDir);
108
- }
109
- catch {
110
- consola_1.default.warn("Continuing despite git initialization issues");
111
- }
112
- }
113
- // 13. Done!
114
- consola_1.default.success("Project building completed successfully!");
47
+ // 8. Install dependencies and initialize git (both optional); a failure
48
+ // in either is reported as a warning and does not fail the build
49
+ const depsInstalled = config.installDeps &&
50
+ (0, dependencyInstaller_1.installDependencies)(config.packageManager, targetDir);
51
+ if (config.initGit)
52
+ (0, gitInitializer_1.initializeGitRepository)(targetDir);
115
53
  return {
116
- success: true,
117
54
  projectPath: targetDir,
118
- nextSteps: (0, nextSteps_1.generateNextSteps)(config, depsInstalled),
55
+ nextSteps: (0, nextSteps_1.generateNextSteps)(config, targetDir, depsInstalled),
119
56
  };
120
57
  }
121
58
  catch (error) {
122
- throw (0, types_1.buildError)(error, "BUILDING_ERROR", "Building failed");
59
+ // Remove the partial project so a retry is not blocked by a non-empty
60
+ // directory; a directory that already existed is emptied, not deleted
61
+ await (didDirectoryExist ? fs_extra_1.default.emptyDir(targetDir) : fs_extra_1.default.remove(targetDir)).catch(() => undefined);
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");
123
66
  }
124
67
  }
125
- //# sourceMappingURL=index.js.map
@@ -3,24 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.selectModules = selectModules;
4
4
  // Determines which base and feature modules to include based on user configuration
5
5
  function selectModules(config) {
6
- // Determine framework for module integration
7
- const framework = config.framework;
6
+ const { framework } = config;
8
7
  // Select base template based on app type
9
8
  const base = config.applicationType === "backend"
10
- ? `backend/${config.framework}`
11
- : `frontend/${config.framework}`;
9
+ ? `backend/${framework}`
10
+ : `frontend/${framework}`;
12
11
  const modules = [];
13
- // Add database module if selected (compound ids: prisma/express, drizzle/nextjs, mongoose/tanstack, ...)
14
- if (config.database !== "none") {
15
- modules.push({ id: `${config.database}/${framework}`, framework });
16
- }
17
- // Add feature modules based on configuration
18
- if (config.useDocker) {
19
- modules.push({ id: "docker", framework });
20
- }
21
- if (config.useAuth) {
22
- modules.push({ id: `auth/${framework}`, framework });
23
- }
12
+ // Database and auth modules have one variant per framework (prisma/express,
13
+ // auth/nextjs, ...); the Docker module is shared
14
+ if (config.database !== "none")
15
+ modules.push(`${config.database}/${framework}`);
16
+ if (config.useDocker)
17
+ modules.push("docker");
18
+ if (config.useAuth)
19
+ modules.push(`auth/${framework}`);
24
20
  return { base, modules };
25
21
  }
26
- //# sourceMappingURL=moduleSelector.js.map
@@ -1,38 +1,18 @@
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
- // Converts string to lowercase
8
- function lowercase(str) {
9
- return str.toLowerCase();
10
- }
11
- // Converts string to uppercase
12
- function uppercase(str) {
13
- return str.toUpperCase();
14
- }
15
- // Converts string to kebab-case
16
- function kebabCase(str) {
17
- return str
18
- .replace(/([a-z])([A-Z])/g, "$1-$2") // camelCase to kebab-case
19
- .replace(/[\s_]+/g, "-") // spaces and underscores to hyphens
20
- .toLowerCase();
21
- }
4
+ // Context builder for Handlebars, providing variables and helpers for templates
5
+ const types_1 = require("../types");
22
6
  function buildTemplateContext(config) {
23
- const framework = config.framework;
24
7
  return {
25
8
  projectName: config.projectName,
26
9
  packageManager: config.packageManager,
27
- database: config.database ?? "none",
28
- framework,
29
- port: 3000, // Default port
30
- useDocker: config.useDocker ?? false,
10
+ database: config.database,
11
+ framework: config.framework,
12
+ port: types_1.DEFAULT_PORT,
13
+ useDocker: config.useDocker,
31
14
  useAuth: config.useAuth,
32
15
  helpers: {
33
- lowercase,
34
- uppercase,
35
- kebabCase,
36
16
  eq: (a, b) => a === b,
37
17
  runner: () => {
38
18
  if (config.packageManager === "bun")
@@ -42,4 +22,3 @@ function buildTemplateContext(config) {
42
22
  },
43
23
  };
44
24
  }
45
- //# sourceMappingURL=templateContext.js.map
package/dist/index.js CHANGED
@@ -4,31 +4,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const prompts_1 = require("@clack/prompts");
8
- const consola_1 = __importDefault(require("consola"));
9
- const path_1 = __importDefault(require("path"));
10
- const process_1 = require("process");
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_process_1 = require("node:process");
10
+ const args_1 = require("./args");
11
11
  const builder_1 = require("./builder");
12
- const intro_1 = require("./intro");
13
- const nextSteps_1 = require("./output/nextSteps");
14
- const prompts_2 = require("./prompts");
12
+ const prompts_1 = require("./prompts");
13
+ const types_1 = require("./types");
14
+ const ui_1 = require("./ui");
15
+ const validators_1 = require("./validators");
15
16
  // Main entry point for the CLI tool
16
17
  async function main() {
17
- // Show interactive intro
18
- await (0, intro_1.showInteractiveIntro)();
19
- const config = await (0, prompts_2.collectUserChoices)();
20
- const targetDir = path_1.default.resolve((0, process_1.cwd)(), config.projectName);
21
- await (0, prompts_2.confirmBuild)(config, targetDir);
22
- const result = await (0, builder_1.build)(config, targetDir);
23
- if (result.success) {
24
- const nextStepsMessage = (0, nextSteps_1.displayNextSteps)(config.projectName, result.nextSteps);
25
- consola_1.default.success(nextStepsMessage);
26
- (0, prompts_1.outro)("Happy coding!");
27
- process.exit(0);
18
+ const args = (0, args_1.parseCliArgs)(process.argv.slice(2));
19
+ if (args.help) {
20
+ console.log(args_1.HELP_TEXT);
21
+ return;
22
+ }
23
+ const { version } = (await fs_extra_1.default.readJson(node_path_1.default.join(__dirname, "../package.json")));
24
+ if (args.version) {
25
+ console.log(version);
26
+ return;
28
27
  }
28
+ (0, ui_1.showBanner)(version, !args.yes);
29
+ const projectPath = await (0, prompts_1.collectProjectPath)(args.projectPath, args.yes);
30
+ const targetDir = node_path_1.default.resolve((0, node_process_1.cwd)(), projectPath);
31
+ // Fail before the remaining prompts when the directory cannot be used
32
+ const directoryError = await (0, validators_1.validateDirectoryEmpty)(targetDir);
33
+ if (directoryError)
34
+ throw (0, types_1.buildError)(directoryError, "DIRECTORY_NOT_EMPTY");
35
+ const choices = await (0, prompts_1.collectStackChoices)(args.yes ? { ...types_1.DEFAULT_CHOICES, ...args.choices } : args.choices);
36
+ const config = {
37
+ projectName: node_path_1.default.basename(targetDir),
38
+ applicationType: (0, types_1.getApplicationType)(choices.framework),
39
+ ...choices,
40
+ };
41
+ (0, ui_1.showSummary)(config, targetDir);
42
+ if (args.dryRun) {
43
+ (0, ui_1.showPlan)(await (0, builder_1.planBuild)(config, targetDir));
44
+ (0, ui_1.info)("Dry run: no files were written.");
45
+ return;
46
+ }
47
+ // Without a terminal nobody can answer, and every option came from a flag
48
+ if (!args.yes && process.stdin.isTTY)
49
+ await (0, prompts_1.confirmBuild)();
50
+ const result = await (0, builder_1.build)(config, targetDir);
51
+ (0, ui_1.success)("Project created");
52
+ (0, ui_1.showNextSteps)(result.nextSteps);
29
53
  }
30
- main().catch((err) => {
31
- consola_1.default.error("❌ Error:", err);
54
+ main()
55
+ .then(() => process.exit(0))
56
+ .catch((err) => {
57
+ // Errors raised by the CLI carry a code and a message meant for the user
58
+ (0, ui_1.error)((0, types_1.isBuildError)(err) ? err.message : String(err));
32
59
  process.exit(1);
33
60
  });
34
- //# sourceMappingURL=index.js.map
@@ -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 path_1 = __importDefault(require("path"));
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 path_1.default.join(__dirname, `../../templates/${id}`);
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 path_1.default.join(__dirname, `../../templates/modules/${id}`);
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 = path_1.default.join(modulePath, "config.json");
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
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.generateNextSteps = generateNextSteps;
4
- exports.displayNextSteps = displayNextSteps;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_process_1 = require("node:process");
5
9
  const MONGODB_AUTH_SETUP_URL = "https://better-ts-stack.abdullahtech.me/docs/modules/database#mongoose-and-mongodb";
6
10
  // Node 24 releases before 24.20 report mismatched file stats on Windows, which
7
11
  // makes srvx (TanStack's production server) reject every static asset
@@ -13,11 +17,13 @@ function isNodeBelow([major, minor]) {
13
17
  return (currentMajor < major || (currentMajor === major && currentMinor < minor));
14
18
  }
15
19
  // Generates a list of instructions for the user to follow after project creation
16
- function generateNextSteps(config, depsInstalled) {
20
+ function generateNextSteps(config, targetDir, depsInstalled) {
17
21
  const steps = [];
18
22
  const isFullstack = config.applicationType === "fullstack";
19
- // Step 1: cd into project directory
20
- steps.push(`cd ${config.projectName}`);
23
+ // Step 1: cd into project directory, unless it was created in place
24
+ const relativeDir = node_path_1.default.relative((0, node_process_1.cwd)(), targetDir);
25
+ if (relativeDir)
26
+ steps.push(`cd ${relativeDir}`);
21
27
  // Step 2: Set environment variables
22
28
  if (config.database !== "none") {
23
29
  steps.push("Review the generated .env and set your database connection details");
@@ -64,19 +70,3 @@ function generateNextSteps(config, depsInstalled) {
64
70
  }
65
71
  return steps;
66
72
  }
67
- // Formats the next steps into a success message string
68
- function displayNextSteps(_projectName, steps) {
69
- const formattedSteps = steps
70
- .map((step, index) => ` ${index + 1}. ${step}`)
71
- .join("\n");
72
- const message = `
73
- ✨ Project created successfully!
74
-
75
- Next steps:
76
- ${formattedSteps}
77
-
78
- Your server will be running at http://localhost:3000
79
- `;
80
- return message;
81
- }
82
- //# sourceMappingURL=nextSteps.js.map
@@ -1,71 +1,34 @@
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
- exports.collectUserChoices = collectUserChoices;
3
+ exports.collectStackChoices = void 0;
4
+ exports.collectProjectPath = collectProjectPath;
7
5
  exports.confirmBuild = confirmBuild;
8
- const prompts_1 = require("@clack/prompts");
9
- const consola_1 = __importDefault(require("consola"));
10
6
  const types_1 = require("../types");
7
+ const ui_1 = require("../ui");
11
8
  const validators_1 = require("../validators");
12
9
  const stack_1 = require("./stack");
13
- // Collects user input via interactive prompts
14
- async function collectUserChoices() {
15
- const projectName = await (0, prompts_1.text)({
16
- message: "Project name:",
17
- placeholder: "my-awesome-project",
18
- validate: (value) => (0, validators_1.validateProjectName)(value ?? ""),
19
- });
20
- if ((0, prompts_1.isCancel)(projectName)) {
21
- (0, prompts_1.cancel)("Operation cancelled.");
22
- process.exit(0);
10
+ var stack_2 = require("./stack");
11
+ Object.defineProperty(exports, "collectStackChoices", { enumerable: true, get: function () { return stack_2.collectStackChoices; } });
12
+ // Returns the project path from the command line, or asks for it
13
+ async function collectProjectPath(preset, isNonInteractive) {
14
+ if (preset !== undefined) {
15
+ const error = (0, validators_1.validateProjectPath)(preset);
16
+ if (error)
17
+ throw (0, types_1.buildError)(error, "INVALID_ARGUMENT");
23
18
  }
24
- const applicationType = await (0, prompts_1.select)({
25
- message: "Application type:",
26
- options: types_1.applicationTypeOptions,
27
- initialValue: "backend",
28
- });
29
- if ((0, prompts_1.isCancel)(applicationType)) {
30
- (0, prompts_1.cancel)("Operation cancelled.");
31
- process.exit(0);
19
+ else if (isNonInteractive) {
20
+ throw (0, types_1.buildError)("A project path is required with --yes, for example: better-ts-stack my-app --yes", "INVALID_ARGUMENT");
32
21
  }
33
- const userChoices = await (0, stack_1.collectStackChoices)(applicationType);
34
- // Derive database field from databaseType + orm for backward compatibility
35
- const database = (0, types_1.deriveDatabase)(userChoices.databaseType, userChoices.orm);
36
- const choices = {
37
- projectName,
38
- applicationType,
39
- ...userChoices,
40
- database,
41
- };
42
- return choices;
22
+ return (0, stack_1.ask)(preset, () => (0, ui_1.text)({
23
+ message: "Project name",
24
+ placeholder: "my-awesome-project",
25
+ validate: (value) => (0, validators_1.validateProjectPath)(value ?? ""),
26
+ }));
43
27
  }
44
- // Displays a summary and asks for final confirmation before building
45
- async function confirmBuild(config, targetDir) {
46
- consola_1.default.info("Project Summary:");
47
- const appTypeLabel = config.applicationType === "backend" ? "Backend API" : "Full-stack App";
48
- consola_1.default.box(`Project Name: ${config.projectName}\n` +
49
- `Target Dir: ${targetDir}\n` +
50
- `App Type: ${appTypeLabel}\n` +
51
- `Framework: ${config.framework}\n` +
52
- `Database: ${config.database}\n` +
53
- `Auth: ${config.useAuth ? "Yes" : "No"}\n` +
54
- `Docker: ${config.useDocker ? "Yes" : "No"}\n` +
55
- `Package Mgr: ${config.packageManager}\n` +
56
- `Git Init: ${config.initGit ? "Yes" : "No"}\n` +
57
- `Install Deps: ${config.installDeps ? "Yes" : "No"}`);
58
- const shouldContinue = await (0, prompts_1.confirm)({
59
- message: "Looks good? Ready to build?",
60
- initialValue: true,
61
- });
62
- if ((0, prompts_1.isCancel)(shouldContinue)) {
63
- (0, prompts_1.cancel)("Operation cancelled.");
64
- process.exit(0);
65
- }
66
- if (!shouldContinue) {
67
- (0, prompts_1.cancel)("Building cancelled by user.");
28
+ // Asks for final confirmation before building; declining ends the session
29
+ async function confirmBuild() {
30
+ const shouldBuild = await (0, stack_1.ask)(undefined, () => (0, ui_1.confirm)({ message: "Create this project?", initialValue: true }));
31
+ if (!shouldBuild) {
68
32
  process.exit(0);
69
33
  }
70
34
  }
71
- //# sourceMappingURL=index.js.map