stackkit 0.2.3 → 0.2.7

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 (99) hide show
  1. package/dist/cli/add.js +2 -16
  2. package/dist/cli/create.js +2 -5
  3. package/dist/cli/doctor.js +0 -4
  4. package/dist/cli/list.js +73 -83
  5. package/dist/index.js +25 -44
  6. package/dist/lib/constants.d.ts +110 -0
  7. package/dist/lib/constants.js +112 -0
  8. package/dist/lib/conversion/js-conversion.js +27 -11
  9. package/dist/lib/discovery/installed-detection.js +3 -3
  10. package/dist/lib/discovery/module-discovery.d.ts +1 -1
  11. package/dist/lib/discovery/module-discovery.js +22 -6
  12. package/dist/lib/env/env-editor.js +41 -47
  13. package/dist/lib/fs/files.d.ts +0 -1
  14. package/dist/lib/fs/files.js +12 -40
  15. package/dist/lib/generation/code-generator.d.ts +4 -1
  16. package/dist/lib/generation/code-generator.js +39 -13
  17. package/dist/lib/pm/package-manager.d.ts +1 -1
  18. package/dist/lib/pm/package-manager.js +130 -14
  19. package/dist/lib/ui/logger.d.ts +8 -1
  20. package/dist/lib/ui/logger.js +60 -3
  21. package/dist/lib/utils/fs-helpers.d.ts +12 -0
  22. package/dist/lib/utils/fs-helpers.js +61 -0
  23. package/dist/lib/utils/json-loader.d.ts +6 -0
  24. package/dist/lib/utils/json-loader.js +34 -0
  25. package/dist/lib/utils/module-loader.d.ts +9 -0
  26. package/dist/lib/utils/module-loader.js +98 -0
  27. package/dist/lib/utils/package-root.d.ts +1 -0
  28. package/dist/lib/utils/package-root.js +75 -2
  29. package/dist/lib/utils/path-resolver.d.ts +9 -0
  30. package/dist/lib/utils/path-resolver.js +44 -0
  31. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +3 -0
  32. package/modules/auth/authjs/files/nextjs/proxy.ts +1 -0
  33. package/modules/auth/authjs/files/shared/lib/auth.ts +119 -0
  34. package/modules/auth/authjs/files/{prisma → shared/prisma}/schema.prisma +11 -1
  35. package/modules/auth/authjs/generator.json +18 -8
  36. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +54 -0
  37. package/modules/auth/better-auth/files/express/types/express.d.ts +16 -0
  38. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +31 -0
  39. package/modules/auth/better-auth/files/nextjs/proxy.ts +34 -0
  40. package/modules/auth/better-auth/files/{lib → shared/lib}/auth-client.ts +1 -1
  41. package/modules/auth/better-auth/files/{lib → shared/lib}/auth.ts +46 -20
  42. package/modules/auth/better-auth/files/{prisma → shared/prisma}/schema.prisma +11 -2
  43. package/modules/auth/better-auth/generator.json +74 -19
  44. package/modules/database/mongoose/generator.json +16 -2
  45. package/modules/database/prisma/files/lib/prisma.ts +1 -1
  46. package/modules/database/prisma/files/prisma/schema.prisma +1 -2
  47. package/modules/database/prisma/generator.json +8 -1
  48. package/package.json +7 -7
  49. package/templates/express/env.example +2 -1
  50. package/templates/express/package.json +3 -4
  51. package/templates/express/src/app.ts +18 -25
  52. package/templates/express/src/config/cors.ts +12 -0
  53. package/templates/express/src/config/helmet.ts +5 -0
  54. package/templates/express/src/config/logger.ts +6 -0
  55. package/templates/express/src/config/rate-limit.ts +11 -0
  56. package/templates/express/src/{features → modules}/health/health.route.ts +1 -1
  57. package/templates/express/src/routes/index.ts +12 -0
  58. package/templates/express/src/shared/errors/api-error.ts +14 -0
  59. package/templates/express/src/shared/errors/error-codes.ts +9 -0
  60. package/templates/express/src/shared/logger/logger.ts +20 -0
  61. package/templates/express/src/{middlewares → shared/middlewares}/error.middleware.ts +1 -1
  62. package/templates/express/src/shared/middlewares/not-found.middleware.ts +9 -0
  63. package/templates/express/src/shared/utils/async-handler.ts +9 -0
  64. package/templates/express/src/shared/utils/pagination.ts +6 -0
  65. package/templates/express/src/shared/utils/response.ts +9 -0
  66. package/templates/express/tsconfig.json +9 -3
  67. package/templates/nextjs/next-env.d.ts +6 -0
  68. package/templates/react/dist/assets/index-D4AHT4dU.js +193 -0
  69. package/templates/react/dist/assets/index-rpwj5ZOX.css +1 -0
  70. package/templates/react/dist/index.html +14 -0
  71. package/templates/react/dist/vite.svg +1 -0
  72. package/templates/react/src/app/layouts/dashboard-layout.tsx +8 -0
  73. package/templates/react/src/app/layouts/public-layout.tsx +5 -0
  74. package/templates/react/src/app/providers.tsx +20 -0
  75. package/templates/react/src/app/router.tsx +21 -0
  76. package/templates/react/src/{pages/About.tsx → features/about/pages/about.tsx} +1 -1
  77. package/templates/react/src/{pages/Home.tsx → features/home/pages/home.tsx} +1 -1
  78. package/templates/react/src/main.tsx +2 -2
  79. package/templates/react/src/{api/client.ts → shared/api/http.ts} +1 -1
  80. package/templates/react/src/{pages/NotFound.tsx → shared/pages/not-found.tsx} +1 -1
  81. package/dist/lib/git-utils.d.ts +0 -1
  82. package/dist/lib/git-utils.js +0 -29
  83. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +0 -2
  84. package/modules/auth/authjs/files/lib/auth.ts +0 -22
  85. package/templates/express/.env.example +0 -2
  86. package/templates/nextjs/.env.example +0 -1
  87. package/templates/react/.env.example +0 -1
  88. package/templates/react/.prettierignore +0 -4
  89. package/templates/react/.prettierrc +0 -9
  90. /package/modules/auth/better-auth/files/{api → nextjs/api}/auth/[...all]/route.ts +0 -0
  91. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-service.ts +0 -0
  92. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-templates.ts +0 -0
  93. /package/templates/express/src/{features → modules}/health/health.controller.ts +0 -0
  94. /package/templates/express/src/{features → modules}/health/health.service.ts +0 -0
  95. /package/templates/react/src/{components/ErrorBoundary.tsx → shared/components/error-boundary.tsx} +0 -0
  96. /package/templates/react/src/{components/Layout.tsx → shared/components/layout.tsx} +0 -0
  97. /package/templates/react/src/{components/Loading.tsx → shared/components/loading.tsx} +0 -0
  98. /package/templates/react/src/{components/SEO.tsx → shared/components/seo.tsx} +0 -0
  99. /package/templates/react/src/{lib/queryClient.ts → shared/lib/query-client.ts} +0 -0
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LANGUAGE = exports.DEFAULT_PACKAGE_MANAGER = exports.PACKAGE_NAME = exports.CLI_COLORS = exports.SUCCESS_MESSAGES = exports.ERROR_MESSAGES = exports.DISPLAY_NAMES = exports.ROUTER_TYPES = exports.ENV_PATTERNS = exports.RETRY_CONFIG = exports.TIMEOUTS = exports.EXCLUDE_FROM_COPY = exports.ENV_FILES = exports.FILE_NAMES = exports.DIRECTORY_NAMES = exports.MODULE_CATEGORIES = exports.LANGUAGES = exports.LOCK_FILES_ARRAY = exports.LOCK_FILES = exports.PACKAGE_MANAGERS = void 0;
4
+ exports.PACKAGE_MANAGERS = {
5
+ PNPM: "pnpm",
6
+ NPM: "npm",
7
+ YARN: "yarn",
8
+ BUN: "bun",
9
+ };
10
+ exports.LOCK_FILES = {
11
+ [exports.PACKAGE_MANAGERS.PNPM]: "pnpm-lock.yaml",
12
+ [exports.PACKAGE_MANAGERS.YARN]: "yarn.lock",
13
+ [exports.PACKAGE_MANAGERS.BUN]: "bun.lockb",
14
+ [exports.PACKAGE_MANAGERS.NPM]: "package-lock.json",
15
+ };
16
+ // Array of lock files with package managers for iteration
17
+ exports.LOCK_FILES_ARRAY = [
18
+ { file: exports.LOCK_FILES.pnpm, pm: exports.PACKAGE_MANAGERS.PNPM },
19
+ { file: exports.LOCK_FILES.yarn, pm: exports.PACKAGE_MANAGERS.YARN },
20
+ { file: exports.LOCK_FILES.bun, pm: exports.PACKAGE_MANAGERS.BUN },
21
+ { file: exports.LOCK_FILES.npm, pm: exports.PACKAGE_MANAGERS.NPM },
22
+ ];
23
+ exports.LANGUAGES = {
24
+ TYPESCRIPT: "typescript",
25
+ JAVASCRIPT: "javascript",
26
+ };
27
+ exports.MODULE_CATEGORIES = {
28
+ DATABASE: "database",
29
+ AUTH: "auth",
30
+ FRAMEWORK: "framework",
31
+ };
32
+ exports.DIRECTORY_NAMES = {
33
+ MODULES: "modules",
34
+ TEMPLATES: "templates",
35
+ NODE_MODULES: "node_modules",
36
+ GIT: ".git",
37
+ PRISMA: "prisma",
38
+ FILES: "files",
39
+ SRC: "src",
40
+ DIST: "dist",
41
+ BIN: "bin",
42
+ };
43
+ exports.FILE_NAMES = {
44
+ PACKAGE_JSON: "package.json",
45
+ TSCONFIG_JSON: "tsconfig.json",
46
+ MODULE_JSON: "module.json",
47
+ GENERATOR_JSON: "generator.json",
48
+ TEMPLATE_JSON: "template.json",
49
+ CONFIG_JSON: "config.json",
50
+ ENV: ".env",
51
+ ENV_LOCAL: ".env.local",
52
+ ENV_EXAMPLE: ".env.example",
53
+ GITIGNORE: ".gitignore",
54
+ README: "README.md",
55
+ SCHEMA_PRISMA: "schema.prisma",
56
+ };
57
+ exports.ENV_FILES = [exports.FILE_NAMES.ENV, exports.FILE_NAMES.ENV_LOCAL];
58
+ exports.EXCLUDE_FROM_COPY = [
59
+ exports.FILE_NAMES.TEMPLATE_JSON,
60
+ exports.FILE_NAMES.CONFIG_JSON,
61
+ exports.DIRECTORY_NAMES.NODE_MODULES,
62
+ exports.DIRECTORY_NAMES.GIT,
63
+ ];
64
+ exports.TIMEOUTS = {
65
+ PACKAGE_INSTALL: 300000, // 5 minutes in milliseconds
66
+ GIT_INIT: 30000, // 30 seconds
67
+ RETRY_DELAY_BASE: 1000, // 1 second base delay for retries
68
+ };
69
+ exports.RETRY_CONFIG = {
70
+ MAX_ATTEMPTS: 2,
71
+ PACKAGE_ROOT_MAX_ATTEMPTS: 10,
72
+ };
73
+ exports.ENV_PATTERNS = {
74
+ KEY: /^[A-Z_][A-Z0-9_]*$/,
75
+ COMMENT: /^\s*#/,
76
+ };
77
+ exports.ROUTER_TYPES = {
78
+ APP: "app",
79
+ PAGES: "pages",
80
+ };
81
+ exports.DISPLAY_NAMES = {
82
+ [exports.MODULE_CATEGORIES.DATABASE]: "Database",
83
+ [exports.MODULE_CATEGORIES.AUTH]: "Auth",
84
+ [exports.MODULE_CATEGORIES.FRAMEWORK]: "Framework",
85
+ };
86
+ exports.ERROR_MESSAGES = {
87
+ NO_PACKAGE_JSON: "No package.json found in current directory or any parent directory.",
88
+ INVALID_DIRECTORY: "Target directory already exists and is not empty.",
89
+ INVALID_PROJECT_NAME: "Invalid project name. Please use a valid npm package name.",
90
+ UNKNOWN_MODULE_TYPE: (module) => `Unknown module type "${module}". Use "${exports.MODULE_CATEGORIES.DATABASE}" or "${exports.MODULE_CATEGORIES.AUTH}", or specify a provider directly.`,
91
+ MODULE_NOT_FOUND: (moduleName) => `Module "${moduleName}" not found.`,
92
+ TEMPLATE_NOT_FOUND: (framework) => `Base template not found for framework: ${framework}`,
93
+ GIT_INIT_FAILED: "Failed to initialize git repository",
94
+ PACKAGE_INSTALL_FAILED: "Failed to install dependencies",
95
+ };
96
+ exports.SUCCESS_MESSAGES = {
97
+ PROJECT_CREATED: "✨ Project created successfully!",
98
+ MODULE_ADDED: "✨ Module added successfully!",
99
+ DEPENDENCIES_INSTALLED: "✓ Dependencies installed",
100
+ GIT_INITIALIZED: "✓ Git repository initialized",
101
+ };
102
+ exports.CLI_COLORS = {
103
+ PRIMARY: "cyan",
104
+ SUCCESS: "green",
105
+ WARNING: "yellow",
106
+ ERROR: "red",
107
+ INFO: "blue",
108
+ HEADER: "magenta",
109
+ };
110
+ exports.PACKAGE_NAME = "stackkit";
111
+ exports.DEFAULT_PACKAGE_MANAGER = exports.PACKAGE_MANAGERS.NPM;
112
+ exports.DEFAULT_LANGUAGE = exports.LANGUAGES.TYPESCRIPT;
@@ -7,6 +7,7 @@ exports.convertToJavaScript = convertToJavaScript;
7
7
  /* eslint-disable @typescript-eslint/no-require-imports */
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const logger_1 = require("../ui/logger");
10
11
  const package_root_1 = require("../utils/package-root");
11
12
  const baseDirs = {
12
13
  express: "./src",
@@ -41,6 +42,32 @@ async function convertToJavaScript(targetDir, framework) {
41
42
  };
42
43
  await removeDtsFiles(targetDir);
43
44
  const babel = require("@babel/core");
45
+ const checkModule = (name) => {
46
+ try {
47
+ require.resolve(name);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ };
54
+ const hasRecast = checkModule("recast");
55
+ const hasBabelParser = checkModule("@babel/parser");
56
+ const hasTransformTypescript = checkModule("@babel/plugin-transform-typescript");
57
+ if (!hasRecast || !hasBabelParser || !hasTransformTypescript) {
58
+ const packageRoot = (0, package_root_1.getPackageRoot)();
59
+ const missing = [];
60
+ if (!hasRecast)
61
+ missing.push("recast");
62
+ if (!hasBabelParser)
63
+ missing.push("@babel/parser");
64
+ if (!hasTransformTypescript)
65
+ missing.push("@babel/plugin-transform-typescript");
66
+ logger_1.logger.warn(`Optional tooling missing: ${missing.join(", ")}. Generated JavaScript may contain transformed JSX.`);
67
+ if (process.env.STACKKIT_VERBOSE === "1" || process.env.DEBUG) {
68
+ logger_1.logger.info(`To ensure generated JavaScript preserves JSX, add these to the CLI package dependencies or run 'pnpm install' in ${packageRoot}`);
69
+ }
70
+ }
44
71
  const transpileAllTsFiles = async (dir) => {
45
72
  const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
46
73
  for (const entry of entries) {
@@ -73,20 +100,10 @@ async function convertToJavaScript(targetDir, framework) {
73
100
  },
74
101
  ],
75
102
  ];
76
- if (isTsx) {
77
- presets.push([
78
- require.resolve("@babel/preset-react"),
79
- {
80
- runtime: "automatic",
81
- },
82
- ]);
83
- }
84
- // Use recast + Babel AST transform (same approach as transform.tools)
85
103
  try {
86
104
  const recast = require("recast");
87
105
  const { transformFromAstSync } = require("@babel/core");
88
106
  const transformTypescript = require("@babel/plugin-transform-typescript");
89
- // getBabelOptions may be exported as default or directly
90
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
108
  let getBabelOptions = require("recast/parsers/_babel_options");
92
109
  if (getBabelOptions && getBabelOptions.default)
@@ -96,7 +113,6 @@ async function convertToJavaScript(targetDir, framework) {
96
113
  parser: {
97
114
  parse: (source, options) => {
98
115
  const babelOptions = getBabelOptions(options || {});
99
- // ensure typescript and jsx handling
100
116
  if (isTsx) {
101
117
  babelOptions.plugins.push("typescript", "jsx");
102
118
  }
@@ -44,7 +44,7 @@ async function collectModulePkgNames(modulePath) {
44
44
  }
45
45
  }
46
46
  catch {
47
- // ignore
47
+ // ignore malformed module.json
48
48
  }
49
49
  }
50
50
  return Array.from(new Set(pkgNames));
@@ -70,7 +70,7 @@ async function detectAuthModules(packageJson) {
70
70
  moduleName = m.name;
71
71
  }
72
72
  catch {
73
- /* ignore */
73
+ // ignore malformed module.json
74
74
  }
75
75
  }
76
76
  for (const pkg of pkgNames) {
@@ -111,7 +111,7 @@ async function detectDatabaseModules(packageJson) {
111
111
  moduleName = m.name;
112
112
  }
113
113
  catch {
114
- /* ignore */
114
+ // ignore malformed module.json
115
115
  }
116
116
  }
117
117
  for (const pkg of pkgNames) {
@@ -41,7 +41,7 @@ export declare function getValidAuthOptions(authModules: ModuleMetadata[]): stri
41
41
  /**
42
42
  * Get compatible auth options for given framework and database
43
43
  */
44
- export declare function getCompatibleAuthOptions(authModules: ModuleMetadata[], framework: string, database: string): Array<{
44
+ export declare function getCompatibleAuthOptions(authModules: ModuleMetadata[], framework: string, database: string, frameworksMeta?: ModuleMetadata[]): Array<{
45
45
  name: string;
46
46
  value: string;
47
47
  }>;
@@ -52,7 +52,7 @@ async function discoverModules(modulesDir) {
52
52
  });
53
53
  }
54
54
  catch {
55
- // Silently skip invalid templates
55
+ // ignore invalid template
56
56
  }
57
57
  }
58
58
  }
@@ -83,7 +83,7 @@ async function discoverModules(modulesDir) {
83
83
  discovered.databases.push(metadata);
84
84
  }
85
85
  catch {
86
- // Silently skip invalid modules
86
+ // ignore invalid module
87
87
  }
88
88
  }
89
89
  }
@@ -105,7 +105,7 @@ async function discoverModules(modulesDir) {
105
105
  discovered.auth.push(metadata);
106
106
  }
107
107
  catch {
108
- // Silently skip invalid modules
108
+ // ignore invalid module
109
109
  }
110
110
  }
111
111
  }
@@ -149,7 +149,7 @@ function getValidAuthOptions(authModules) {
149
149
  /**
150
150
  * Get compatible auth options for given framework and database
151
151
  */
152
- function getCompatibleAuthOptions(authModules, framework, database) {
152
+ function getCompatibleAuthOptions(authModules, framework, database, frameworksMeta) {
153
153
  const compatible = [];
154
154
  for (const auth of authModules) {
155
155
  // Check if auth supports the framework
@@ -159,10 +159,26 @@ function getCompatibleAuthOptions(authModules, framework, database) {
159
159
  // Normalize database option (handle prisma-<provider> values)
160
160
  const parsedDb = (0, shared_1.parseDatabaseOption)(database || "").database;
161
161
  // If module provides explicit compatibility matrix, use it
162
+ let dbCompatible = true;
162
163
  if (auth.compatibility && auth.compatibility.databases) {
163
- if (!auth.compatibility.databases.includes(parsedDb))
164
- continue;
164
+ dbCompatible = auth.compatibility.databases.includes(parsedDb);
165
165
  }
166
+ // If the framework template explicitly lists this auth as compatible,
167
+ // allow it even if the auth module's database compatibility would normally
168
+ // exclude the current database selection (covers cases like React where
169
+ // the framework can support auth without a DB).
170
+ let explicitlyAllowedByFramework = false;
171
+ if (frameworksMeta && Array.isArray(frameworksMeta)) {
172
+ const fw = frameworksMeta.find((f) => f.name === framework);
173
+ if (fw && fw.compatibility) {
174
+ const authList = fw.compatibility.auth;
175
+ if (Array.isArray(authList) && authList.includes(auth.name)) {
176
+ explicitlyAllowedByFramework = true;
177
+ }
178
+ }
179
+ }
180
+ if (!dbCompatible && !explicitlyAllowedByFramework)
181
+ continue;
166
182
  compatible.push({
167
183
  name: auth.displayName,
168
184
  value: auth.name,
@@ -7,94 +7,88 @@ exports.addEnvVariables = addEnvVariables;
7
7
  exports.removeEnvVariables = removeEnvVariables;
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const constants_1 = require("../constants");
10
11
  const logger_1 = require("../ui/logger");
11
12
  async function addEnvVariables(projectRoot, variables, options = {}) {
12
- const envExamplePath = path_1.default.join(projectRoot, ".env.example");
13
- const envPath = path_1.default.join(projectRoot, ".env");
14
- await appendToEnvFile(envExamplePath, variables, "example", options);
15
- const envExists = await fs_extra_1.default.pathExists(envPath);
16
- if (envExists || options.force) {
17
- await appendToEnvFile(envPath, variables, "local", options);
13
+ validateEnvVariables(variables);
14
+ const envExamplePath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV_EXAMPLE);
15
+ const envPath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV);
16
+ await appendToEnvFile(envExamplePath, variables, options);
17
+ if ((await fs_extra_1.default.pathExists(envPath)) || options.force) {
18
+ await appendToEnvFile(envPath, variables, options);
18
19
  }
19
20
  logger_1.logger.success("Environment variables added");
20
21
  }
21
- async function appendToEnvFile(filePath, variables, fileType, options = {}) {
22
- // Validate environment variable keys
22
+ async function removeEnvVariables(projectRoot, keys) {
23
+ const envExamplePath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV_EXAMPLE);
24
+ const envPath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV);
25
+ await removeFromEnvFile(envExamplePath, keys);
26
+ if (await fs_extra_1.default.pathExists(envPath)) {
27
+ await removeFromEnvFile(envPath, keys);
28
+ }
29
+ }
30
+ function validateEnvVariables(variables) {
23
31
  for (const variable of variables) {
24
- if (!/^[A-Z_][A-Z0-9_]*$/.test(variable.key)) {
25
- throw new Error(`Invalid environment variable key: ${variable.key}`);
32
+ if (!constants_1.ENV_PATTERNS.KEY.test(variable.key)) {
33
+ throw new Error(`Invalid environment variable key: ${variable.key}. ` +
34
+ `Must match pattern: ${constants_1.ENV_PATTERNS.KEY}`);
26
35
  }
27
36
  }
37
+ }
38
+ async function appendToEnvFile(filePath, variables, options = {}) {
28
39
  let content = "";
29
40
  if (await fs_extra_1.default.pathExists(filePath)) {
30
41
  content = await fs_extra_1.default.readFile(filePath, "utf-8");
31
42
  }
32
- // If force, remove existing keys first to avoid duplicates
33
43
  if (options.force) {
34
44
  const keysToRemove = variables.map((v) => v.key);
35
45
  await removeFromEnvFile(filePath, keysToRemove);
36
- if (await fs_extra_1.default.pathExists(filePath)) {
37
- content = await fs_extra_1.default.readFile(filePath, "utf-8");
38
- }
39
- else {
40
- content = "";
41
- }
42
- }
43
- // Check if variables already exist (after potential removal)
44
- const existingKeys = new Set();
45
- const lines = content.split("\n");
46
- for (const line of lines) {
47
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
48
- if (match) {
49
- existingKeys.add(match[1]);
50
- }
46
+ content = (await fs_extra_1.default.pathExists(filePath)) ? await fs_extra_1.default.readFile(filePath, "utf-8") : "";
51
47
  }
48
+ const existingKeys = extractExistingKeys(content);
52
49
  const newVariables = variables.filter((v) => !existingKeys.has(v.key));
53
- if (newVariables.length === 0) {
50
+ if (newVariables.length === 0)
54
51
  return;
55
- }
56
- // Ensure file ends with newline
57
52
  if (content && !content.endsWith("\n")) {
58
53
  content += "\n";
59
54
  }
60
- // Append variables
61
55
  for (const variable of newVariables) {
62
- const value = fileType === "example" ? variable.value || "" : variable.value || "";
63
- content += `${variable.key}=${value}\n`;
56
+ content += `${variable.key}=${variable.value || ""}\n`;
64
57
  }
65
58
  await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
66
59
  await fs_extra_1.default.writeFile(filePath, content, "utf-8");
67
60
  }
68
- async function removeEnvVariables(projectRoot, keys) {
69
- const envExamplePath = path_1.default.join(projectRoot, ".env.example");
70
- const envPath = path_1.default.join(projectRoot, ".env");
71
- await removeFromEnvFile(envExamplePath, keys);
72
- if (await fs_extra_1.default.pathExists(envPath)) {
73
- await removeFromEnvFile(envPath, keys);
74
- }
75
- }
76
61
  async function removeFromEnvFile(filePath, keys) {
77
- if (!(await fs_extra_1.default.pathExists(filePath))) {
62
+ if (!(await fs_extra_1.default.pathExists(filePath)))
78
63
  return;
79
- }
80
64
  try {
81
65
  const content = await fs_extra_1.default.readFile(filePath, "utf-8");
82
66
  const lines = content.split("\n");
83
67
  const newLines = [];
84
68
  for (const line of lines) {
85
69
  const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
86
- if (match && keys.includes(match[1])) {
87
- continue;
70
+ if (!match || !keys.includes(match[1])) {
71
+ newLines.push(line);
88
72
  }
89
- newLines.push(line);
90
73
  }
91
74
  while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
92
75
  newLines.pop();
93
76
  }
94
- await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
77
+ await fs_extra_1.default.writeFile(filePath, newLines.join("\n") + (newLines.length > 0 ? "\n" : ""), "utf-8");
95
78
  }
96
79
  catch (error) {
97
- logger_1.logger.error(`Failed to remove env variables from ${filePath}: ${error}`);
80
+ logger_1.logger.error(`Failed to remove env variables from ${filePath}`);
98
81
  throw error;
99
82
  }
100
83
  }
84
+ function extractExistingKeys(content) {
85
+ const existingKeys = new Set();
86
+ const lines = content.split("\n");
87
+ for (const line of lines) {
88
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
89
+ if (match) {
90
+ existingKeys.add(match[1]);
91
+ }
92
+ }
93
+ return existingKeys;
94
+ }
@@ -6,7 +6,6 @@ export interface PackageJsonConfig {
6
6
  export declare function copyBaseFramework(templatesDir: string, targetDir: string, framework: string): Promise<void>;
7
7
  export declare function mergePackageJson(targetDir: string, config: PackageJsonConfig): Promise<void>;
8
8
  export declare function mergeEnvFile(targetDir: string, envVars: Record<string, string>): Promise<void>;
9
- export declare function copyTemplate(templatePath: string, targetPath: string, projectName: string): Promise<void>;
10
9
  export declare function createFile(targetPath: string, content: string, options?: {
11
10
  force?: boolean;
12
11
  }): Promise<void>;
@@ -6,12 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.copyBaseFramework = copyBaseFramework;
7
7
  exports.mergePackageJson = mergePackageJson;
8
8
  exports.mergeEnvFile = mergeEnvFile;
9
- exports.copyTemplate = copyTemplate;
10
9
  exports.createFile = createFile;
11
10
  exports.readFile = readFile;
12
11
  exports.fileExists = fileExists;
13
12
  const fs_extra_1 = __importDefault(require("fs-extra"));
14
13
  const path_1 = __importDefault(require("path"));
14
+ const constants_1 = require("../constants");
15
15
  const logger_1 = require("../ui/logger");
16
16
  async function copyBaseFramework(templatesDir, targetDir, framework) {
17
17
  const baseDir = path_1.default.join(templatesDir, framework);
@@ -19,17 +19,13 @@ async function copyBaseFramework(templatesDir, targetDir, framework) {
19
19
  throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
20
20
  }
21
21
  await fs_extra_1.default.copy(baseDir, targetDir, {
22
- filter: (src) => {
23
- const basename = path_1.default.basename(src);
24
- return !["template.json", "config.json", "node_modules", ".git"].includes(basename);
25
- },
22
+ filter: (src) => !constants_1.EXCLUDE_FROM_COPY.includes(path_1.default.basename(src)),
26
23
  });
27
24
  }
28
25
  async function mergePackageJson(targetDir, config) {
29
- const pkgPath = path_1.default.join(targetDir, "package.json");
30
- if (!(await fs_extra_1.default.pathExists(pkgPath))) {
26
+ const pkgPath = path_1.default.join(targetDir, constants_1.FILE_NAMES.PACKAGE_JSON);
27
+ if (!(await fs_extra_1.default.pathExists(pkgPath)))
31
28
  return;
32
- }
33
29
  const pkg = await fs_extra_1.default.readJson(pkgPath);
34
30
  if (config.dependencies) {
35
31
  pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
@@ -43,46 +39,22 @@ async function mergePackageJson(targetDir, config) {
43
39
  await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
44
40
  }
45
41
  async function mergeEnvFile(targetDir, envVars) {
46
- const envPath = path_1.default.join(targetDir, ".env");
47
- let existingEnv = "";
42
+ const envPath = path_1.default.join(targetDir, constants_1.FILE_NAMES.ENV);
43
+ let content = "";
48
44
  if (await fs_extra_1.default.pathExists(envPath)) {
49
- existingEnv = await fs_extra_1.default.readFile(envPath, "utf-8");
45
+ content = await fs_extra_1.default.readFile(envPath, "utf-8");
50
46
  }
51
- const envLines = existingEnv.split("\n").filter((line) => line.trim() !== "");
52
- // Add new variables
47
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
53
48
  for (const [key, value] of Object.entries(envVars)) {
54
- const existingIndex = envLines.findIndex((line) => line.startsWith(`${key}=`));
49
+ const existingIndex = lines.findIndex((line) => line.startsWith(`${key}=`));
55
50
  if (existingIndex !== -1) {
56
- envLines[existingIndex] = `${key}=${value}`;
51
+ lines[existingIndex] = `${key}=${value}`;
57
52
  }
58
53
  else {
59
- envLines.push(`${key}=${value}`);
54
+ lines.push(`${key}=${value}`);
60
55
  }
61
56
  }
62
- await fs_extra_1.default.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
63
- }
64
- async function copyTemplate(templatePath, targetPath, projectName) {
65
- if (!(await fs_extra_1.default.pathExists(templatePath))) {
66
- throw new Error(`Template not found: ${templatePath}`);
67
- }
68
- // Create target directory
69
- await fs_extra_1.default.ensureDir(targetPath);
70
- // Copy all files
71
- await fs_extra_1.default.copy(templatePath, targetPath, {
72
- filter: (src) => {
73
- const basename = path_1.default.basename(src);
74
- // Skip template.json metadata file and node_modules
75
- return basename !== "template.json" && basename !== "node_modules";
76
- },
77
- });
78
- // Update package.json with project name
79
- const packageJsonPath = path_1.default.join(targetPath, "package.json");
80
- if (await fs_extra_1.default.pathExists(packageJsonPath)) {
81
- const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
82
- packageJson.name = projectName;
83
- await fs_extra_1.default.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
84
- }
85
- logger_1.logger.success(`Template copied to ${targetPath}`);
57
+ await fs_extra_1.default.writeFile(envPath, lines.join("\n") + "\n", "utf-8");
86
58
  }
87
59
  async function createFile(targetPath, content, options = {}) {
88
60
  const exists = await fs_extra_1.default.pathExists(targetPath);
@@ -4,6 +4,8 @@ export interface GenerationContext {
4
4
  database?: string;
5
5
  auth?: string;
6
6
  features?: string[];
7
+ combo?: string;
8
+ prismaProvider?: string;
7
9
  [key: string]: unknown;
8
10
  }
9
11
  export interface TemplateCondition {
@@ -31,7 +33,7 @@ export interface PatchOperation {
31
33
  type: "add-import" | "add-code" | "replace-code" | "add-to-top" | "add-to-bottom";
32
34
  condition?: TemplateCondition;
33
35
  imports?: string[];
34
- code?: string;
36
+ code?: string | string[];
35
37
  after?: string;
36
38
  before?: string;
37
39
  replace?: string;
@@ -58,6 +60,7 @@ export declare class AdvancedCodeGenerator {
58
60
  loadGenerators(modulesPath: string): Promise<void>;
59
61
  private evaluateCondition;
60
62
  private processTemplate;
63
+ private renderHeadingFromExpr;
61
64
  private processVariableDefinitions;
62
65
  private processTemplateRecursive;
63
66
  generate(selectedModules: {