stackkit 0.2.6 → 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 (83) hide show
  1. package/dist/cli/create.js +1 -2
  2. package/dist/cli/list.js +73 -83
  3. package/dist/index.js +25 -44
  4. package/dist/lib/constants.d.ts +110 -0
  5. package/dist/lib/constants.js +112 -0
  6. package/dist/lib/env/env-editor.js +41 -47
  7. package/dist/lib/fs/files.d.ts +0 -1
  8. package/dist/lib/fs/files.js +12 -40
  9. package/dist/lib/generation/code-generator.d.ts +4 -1
  10. package/dist/lib/generation/code-generator.js +36 -10
  11. package/dist/lib/pm/package-manager.d.ts +1 -1
  12. package/dist/lib/pm/package-manager.js +130 -14
  13. package/dist/lib/ui/logger.d.ts +8 -1
  14. package/dist/lib/ui/logger.js +60 -3
  15. package/dist/lib/utils/fs-helpers.d.ts +12 -0
  16. package/dist/lib/utils/fs-helpers.js +61 -0
  17. package/dist/lib/utils/json-loader.d.ts +6 -0
  18. package/dist/lib/utils/json-loader.js +34 -0
  19. package/dist/lib/utils/module-loader.d.ts +9 -0
  20. package/dist/lib/utils/module-loader.js +98 -0
  21. package/dist/lib/utils/package-root.d.ts +1 -0
  22. package/dist/lib/utils/package-root.js +75 -2
  23. package/dist/lib/utils/path-resolver.d.ts +9 -0
  24. package/dist/lib/utils/path-resolver.js +44 -0
  25. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +3 -0
  26. package/modules/auth/authjs/files/nextjs/proxy.ts +1 -0
  27. package/modules/auth/authjs/files/shared/lib/auth.ts +119 -0
  28. package/modules/auth/authjs/files/{prisma → shared/prisma}/schema.prisma +11 -1
  29. package/modules/auth/authjs/generator.json +18 -8
  30. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +54 -0
  31. package/modules/auth/better-auth/files/express/types/express.d.ts +16 -0
  32. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +31 -0
  33. package/modules/auth/better-auth/files/nextjs/proxy.ts +34 -0
  34. package/modules/auth/better-auth/files/{lib → shared/lib}/auth-client.ts +1 -1
  35. package/modules/auth/better-auth/files/{lib → shared/lib}/auth.ts +46 -20
  36. package/modules/auth/better-auth/files/{prisma → shared/prisma}/schema.prisma +11 -2
  37. package/modules/auth/better-auth/generator.json +74 -19
  38. package/modules/database/mongoose/generator.json +16 -2
  39. package/modules/database/prisma/files/lib/prisma.ts +1 -1
  40. package/modules/database/prisma/files/prisma/schema.prisma +1 -2
  41. package/modules/database/prisma/generator.json +8 -1
  42. package/package.json +2 -2
  43. package/templates/express/env.example +2 -1
  44. package/templates/express/package.json +3 -4
  45. package/templates/express/src/app.ts +18 -25
  46. package/templates/express/src/config/cors.ts +12 -0
  47. package/templates/express/src/config/helmet.ts +5 -0
  48. package/templates/express/src/config/logger.ts +6 -0
  49. package/templates/express/src/config/rate-limit.ts +11 -0
  50. package/templates/express/src/{features → modules}/health/health.route.ts +1 -1
  51. package/templates/express/src/routes/index.ts +12 -0
  52. package/templates/express/src/shared/errors/api-error.ts +14 -0
  53. package/templates/express/src/shared/errors/error-codes.ts +9 -0
  54. package/templates/express/src/shared/logger/logger.ts +20 -0
  55. package/templates/express/src/{middlewares → shared/middlewares}/error.middleware.ts +1 -1
  56. package/templates/express/src/shared/middlewares/not-found.middleware.ts +9 -0
  57. package/templates/express/src/shared/utils/async-handler.ts +9 -0
  58. package/templates/express/src/shared/utils/pagination.ts +6 -0
  59. package/templates/express/src/shared/utils/response.ts +9 -0
  60. package/templates/express/tsconfig.json +9 -3
  61. package/templates/react/src/app/layouts/dashboard-layout.tsx +8 -0
  62. package/templates/react/src/app/layouts/public-layout.tsx +5 -0
  63. package/templates/react/src/app/providers.tsx +20 -0
  64. package/templates/react/src/app/router.tsx +21 -0
  65. package/templates/react/src/{pages/About.tsx → features/about/pages/about.tsx} +1 -1
  66. package/templates/react/src/{pages/Home.tsx → features/home/pages/home.tsx} +1 -1
  67. package/templates/react/src/main.tsx +2 -2
  68. package/templates/react/src/{api/client.ts → shared/api/http.ts} +1 -1
  69. package/templates/react/src/{pages/NotFound.tsx → shared/pages/not-found.tsx} +1 -1
  70. package/dist/lib/git-utils.d.ts +0 -1
  71. package/dist/lib/git-utils.js +0 -29
  72. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +0 -2
  73. package/modules/auth/authjs/files/lib/auth.ts +0 -22
  74. /package/modules/auth/better-auth/files/{api → nextjs/api}/auth/[...all]/route.ts +0 -0
  75. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-service.ts +0 -0
  76. /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-templates.ts +0 -0
  77. /package/templates/express/src/{features → modules}/health/health.controller.ts +0 -0
  78. /package/templates/express/src/{features → modules}/health/health.service.ts +0 -0
  79. /package/templates/react/src/{components/ErrorBoundary.tsx → shared/components/error-boundary.tsx} +0 -0
  80. /package/templates/react/src/{components/Layout.tsx → shared/components/layout.tsx} +0 -0
  81. /package/templates/react/src/{components/Loading.tsx → shared/components/loading.tsx} +0 -0
  82. /package/templates/react/src/{components/SEO.tsx → shared/components/seo.tsx} +0 -0
  83. /package/templates/react/src/{lib/queryClient.ts → shared/lib/query-client.ts} +0 -0
@@ -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: {
@@ -108,6 +108,13 @@ class AdvancedCodeGenerator {
108
108
  content = content.replace(/\n{3,}/g, "\n\n");
109
109
  return content;
110
110
  }
111
+ renderHeadingFromExpr(expr, context) {
112
+ const depthVar = expr.substring(8).trim();
113
+ const depthVal = context[depthVar];
114
+ const n = parseInt(String(depthVal || "1"), 10) || 1;
115
+ const level = Math.max(1, Math.min(n, 6));
116
+ return "#".repeat(level);
117
+ }
111
118
  processVariableDefinitions(content, context) {
112
119
  let result = content;
113
120
  let index = 0;
@@ -223,7 +230,8 @@ class AdvancedCodeGenerator {
223
230
  break;
224
231
  }
225
232
  }
226
- return (result || defaultCase || "").trim();
233
+ const chosen = (result || defaultCase || "").trim();
234
+ return this.processTemplateRecursive(chosen, context);
227
235
  });
228
236
  // Handle variable replacement with advanced expressions
229
237
  content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
@@ -252,15 +260,19 @@ class AdvancedCodeGenerator {
252
260
  const [caseVal, result] = caseStr.split(":").map((s) => s.trim());
253
261
  const cleanCaseVal = caseVal.replace(/['"]/g, "");
254
262
  if (cleanCaseVal === actualVal || cleanCaseVal === "default") {
255
- return result.replace(/['"]/g, "");
263
+ return this.processTemplateRecursive(result.replace(/['"]/g, ""), context);
256
264
  }
257
265
  }
258
266
  return "";
259
267
  }
268
+ // Handle heading helper {{heading:depthVar}} -> '#', '##', ... up to '######'
269
+ if (trimmedExpr.startsWith("heading:")) {
270
+ return this.renderHeadingFromExpr(trimmedExpr, context);
271
+ }
260
272
  // Handle feature flags {{feature:name}}
261
273
  if (trimmedExpr.startsWith("feature:")) {
262
- const featureName = trimmedExpr.substring(8);
263
- const features = context.features || [];
274
+ const featureName = trimmedExpr.substring(8).trim();
275
+ const features = Array.isArray(context.features) ? context.features : [];
264
276
  return features.includes(featureName) ? "true" : "false";
265
277
  }
266
278
  // Handle conditional expressions {{if condition then:value else:value}}
@@ -290,6 +302,8 @@ class AdvancedCodeGenerator {
290
302
  ...selectedModules,
291
303
  features,
292
304
  };
305
+ // Derived combined key to simplify template conditionals (e.g. "prisma:express")
306
+ context.combo = `${context.database || ""}:${context.framework || ""}`;
293
307
  // Set default prismaProvider if database is prisma but no provider specified
294
308
  if (selectedModules.database === "prisma" && !context.prismaProvider) {
295
309
  const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
@@ -505,9 +519,6 @@ class AdvancedCodeGenerator {
505
519
  if (processed.content) {
506
520
  processed.content = this.processTemplate(processed.content, context);
507
521
  }
508
- if (processed.destination) {
509
- processed.destination = this.processTemplate(processed.destination, context);
510
- }
511
522
  // Process templates in patch operations
512
523
  if (processed.operations) {
513
524
  processed.operations = processed.operations.map((op) => {
@@ -516,7 +527,14 @@ class AdvancedCodeGenerator {
516
527
  processedOp.imports = processedOp.imports.map((imp) => this.processTemplate(imp, context));
517
528
  }
518
529
  if (processedOp.code) {
519
- processedOp.code = this.processTemplate(processedOp.code, context);
530
+ if (Array.isArray(processedOp.code)) {
531
+ processedOp.code = processedOp.code
532
+ .map((c) => this.processTemplate(c, context))
533
+ .join("\n");
534
+ }
535
+ else {
536
+ processedOp.code = this.processTemplate(processedOp.code, context);
537
+ }
520
538
  }
521
539
  if (processedOp.after) {
522
540
  processedOp.after = this.processTemplate(processedOp.after, context);
@@ -648,7 +666,10 @@ class AdvancedCodeGenerator {
648
666
  break;
649
667
  case "add-code":
650
668
  if (patchOp.code) {
651
- const processedCode = this.processTemplate(patchOp.code, context);
669
+ let codeValue = patchOp.code;
670
+ if (Array.isArray(codeValue))
671
+ codeValue = codeValue.join("\n");
672
+ const processedCode = this.processTemplate(codeValue, context);
652
673
  // Skip insertion if the exact code already exists in the file
653
674
  const codeTrimmed = processedCode.trim();
654
675
  if (codeTrimmed && content.includes(codeTrimmed)) {
@@ -696,7 +717,10 @@ class AdvancedCodeGenerator {
696
717
  break;
697
718
  case "replace-code":
698
719
  if (patchOp.code && patchOp.replace) {
699
- const processedCode = this.processTemplate(patchOp.code, context);
720
+ let codeValue2 = patchOp.code;
721
+ if (Array.isArray(codeValue2))
722
+ codeValue2 = codeValue2.join("\n");
723
+ const processedCode = this.processTemplate(codeValue2, context);
700
724
  const replacePattern = this.processTemplate(patchOp.replace, context);
701
725
  content = content.replace(replacePattern, processedCode);
702
726
  }
@@ -845,6 +869,8 @@ class AdvancedCodeGenerator {
845
869
  ...selectedModules,
846
870
  features,
847
871
  };
872
+ // Derived combined key to simplify template conditionals (e.g. "prisma:express")
873
+ context.combo = `${context.database || ""}:${context.framework || ""}`;
848
874
  if (selectedModules.database === "prisma" && !context.prismaProvider) {
849
875
  const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
850
876
  if (providers && providers.length > 0) {
@@ -1,5 +1,5 @@
1
1
  export type PackageManager = "npm" | "yarn" | "pnpm" | "bun";
2
2
  export declare function detectPackageManager(cwd: string): Promise<PackageManager>;
3
- export declare function installDependencies(cwd: string, pm: PackageManager): Promise<void>;
3
+ export declare function installDependencies(cwd: string, pm: PackageManager, maxRetries?: number): Promise<void>;
4
4
  export declare function addDependencies(cwd: string, pm: PackageManager, packages: string[], dev?: boolean): Promise<void>;
5
5
  export declare function initGit(cwd: string): Promise<void>;
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -9,24 +42,64 @@ exports.addDependencies = addDependencies;
9
42
  exports.initGit = initGit;
10
43
  const detect_package_manager_1 = require("detect-package-manager");
11
44
  const execa_1 = __importDefault(require("execa"));
45
+ const fs = __importStar(require("fs-extra"));
46
+ const path = __importStar(require("path"));
47
+ const constants_1 = require("../constants");
12
48
  const logger_1 = require("../ui/logger");
13
49
  async function detectPackageManager(cwd) {
14
50
  try {
51
+ for (const { file, pm } of constants_1.LOCK_FILES_ARRAY) {
52
+ if (await fs.pathExists(path.join(cwd, file))) {
53
+ logger_1.logger.debug(`Detected ${pm} from ${file}`);
54
+ return pm;
55
+ }
56
+ }
15
57
  const pm = await (0, detect_package_manager_1.detect)({ cwd });
58
+ logger_1.logger.debug(`Detected ${pm} using detect-package-manager`);
16
59
  return pm;
17
60
  }
18
- catch {
19
- return "npm";
61
+ catch (error) {
62
+ logger_1.logger.debug(`Package manager detection failed, defaulting to npm: ${error}`);
63
+ return constants_1.PACKAGE_MANAGERS.NPM;
20
64
  }
21
65
  }
22
- async function installDependencies(cwd, pm) {
66
+ async function installDependencies(cwd, pm, maxRetries = 2) {
23
67
  const args = ["install"];
24
68
  const stdio = "pipe";
25
- await (0, execa_1.default)(pm, args, { cwd, stdio });
69
+ let lastError = null;
70
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
71
+ try {
72
+ if (attempt > 0) {
73
+ logger_1.logger.debug(`Retry attempt ${attempt} for installing dependencies`);
74
+ await new Promise((resolve) => setTimeout(resolve, constants_1.TIMEOUTS.RETRY_DELAY_BASE * attempt));
75
+ }
76
+ await (0, execa_1.default)(pm, args, { cwd, stdio, timeout: constants_1.TIMEOUTS.PACKAGE_INSTALL });
77
+ return;
78
+ }
79
+ catch (error) {
80
+ lastError = error;
81
+ logger_1.logger.debug(`Installation attempt ${attempt + 1} failed: ${lastError.message}`);
82
+ const err = error;
83
+ const errorMsg = err.message || "";
84
+ if (errorMsg.includes("ECONNRESET") ||
85
+ errorMsg.includes("ETIMEDOUT") ||
86
+ errorMsg.includes("ENOTFOUND")) {
87
+ continue;
88
+ }
89
+ throw error;
90
+ }
91
+ }
92
+ throw new Error(`Failed to install dependencies after ${maxRetries + 1} attempts: ${lastError?.message || "Unknown error"}`);
26
93
  }
27
94
  async function addDependencies(cwd, pm, packages, dev = false) {
28
- if (packages.length === 0)
95
+ if (packages.length === 0) {
96
+ logger_1.logger.debug("No packages to add, skipping");
29
97
  return;
98
+ }
99
+ const invalidPackages = packages.filter((pkg) => !isValidPackageName(pkg));
100
+ if (invalidPackages.length > 0) {
101
+ throw new Error(`Invalid package names: ${invalidPackages.join(", ")}`);
102
+ }
30
103
  const spinner = logger_1.logger.startSpinner(`Adding ${dev ? "dev " : ""}dependencies: ${packages.join(", ")}...`);
31
104
  try {
32
105
  const stdio = "pipe";
@@ -44,21 +117,63 @@ async function addDependencies(cwd, pm, packages, dev = false) {
44
117
  case "bun":
45
118
  args = ["add", ...(dev ? ["-d"] : []), ...packages];
46
119
  break;
120
+ default:
121
+ throw new Error(`Unsupported package manager: ${pm}`);
47
122
  }
48
- await (0, execa_1.default)(pm, args, { cwd, stdio });
123
+ logger_1.logger.debug(`Running: ${pm} ${args.join(" ")}`);
124
+ await (0, execa_1.default)(pm, args, { cwd, stdio, timeout: constants_1.TIMEOUTS.PACKAGE_INSTALL });
49
125
  spinner.succeed(`Dependencies added successfully`);
50
126
  }
51
127
  catch (error) {
52
128
  spinner.fail(`Failed to add dependencies`);
53
- throw new Error(`Failed to add dependencies: ${error instanceof Error ? error.message : String(error)}`);
129
+ const errorMessage = error instanceof Error ? error.message : String(error);
130
+ logger_1.logger.debug(`Full error: ${errorMessage}`);
131
+ if (errorMessage.includes("ENOTFOUND") || errorMessage.includes("ETIMEDOUT")) {
132
+ throw new Error(`Network error while adding dependencies. Please check your internet connection and try again.`);
133
+ }
134
+ else if (errorMessage.includes("404")) {
135
+ throw new Error(`One or more packages not found. Please verify package names: ${packages.join(", ")}`);
136
+ }
137
+ else {
138
+ throw new Error(`Failed to add dependencies: ${errorMessage}`);
139
+ }
54
140
  }
55
141
  }
142
+ function isValidPackageName(packageName) {
143
+ const nameOnly = packageName.split("@").filter(Boolean)[0] || packageName;
144
+ const validPattern = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
145
+ return validPattern.test(nameOnly);
146
+ }
56
147
  async function initGit(cwd) {
57
148
  const spinner = logger_1.logger.startSpinner("Initializing git repository...");
149
+ try {
150
+ await (0, execa_1.default)("git", ["--version"], { cwd, stdio: "pipe" });
151
+ }
152
+ catch {
153
+ spinner.fail("Git is not installed");
154
+ logger_1.logger.warn("Skipping git initialization. Please install git to enable version control.");
155
+ return;
156
+ }
157
+ try {
158
+ if (await fs.pathExists(path.join(cwd, ".git"))) {
159
+ spinner.succeed("Git repository already exists");
160
+ return;
161
+ }
162
+ }
163
+ catch {
164
+ return;
165
+ }
58
166
  const run = async (stdio) => {
59
167
  await (0, execa_1.default)("git", ["init"], { cwd, stdio });
60
168
  await (0, execa_1.default)("git", ["add", "."], { cwd, stdio });
61
- await (0, execa_1.default)("git", ["commit", "-m", "Initial commit from StackKit"], { cwd, stdio });
169
+ try {
170
+ await (0, execa_1.default)("git", ["config", "user.name"], { cwd, stdio: "pipe" });
171
+ await (0, execa_1.default)("git", ["config", "user.email"], { cwd, stdio: "pipe" });
172
+ await (0, execa_1.default)("git", ["commit", "-m", "Initial commit from StackKit"], { cwd, stdio });
173
+ }
174
+ catch {
175
+ logger_1.logger.debug("Git config not found, skipping initial commit");
176
+ }
62
177
  };
63
178
  try {
64
179
  await run("pipe");
@@ -67,7 +182,7 @@ async function initGit(cwd) {
67
182
  }
68
183
  catch (error) {
69
184
  const err = error;
70
- spinner.fail(`Git init failed: ${err.message}`);
185
+ logger_1.logger.debug(`Git init error: ${err.message}`);
71
186
  const isENOBUFS = (e) => {
72
187
  if (!e || typeof e !== "object")
73
188
  return false;
@@ -77,19 +192,20 @@ async function initGit(cwd) {
77
192
  String(obj.message ?? "").includes("ENOBUFS"));
78
193
  };
79
194
  if (isENOBUFS(err)) {
80
- logger_1.logger.warn("ENOBUFS detected; skipping git initialization.");
81
- logger_1.logger.info("Skipped git initialization due to system resource limits.");
195
+ spinner.warn("Skipped git initialization due to system resource limits");
196
+ logger_1.logger.info("You can manually initialize git later with: git init");
82
197
  return;
83
198
  }
84
199
  try {
85
200
  await run("inherit");
86
- spinner.succeed("Git repository initialized (fallback)");
201
+ spinner.succeed("Git repository initialized");
87
202
  return;
88
203
  }
89
204
  catch (fallbackErr) {
90
205
  const fe = fallbackErr;
91
- logger_1.logger.warn(`Git init fallback failed: ${fe.message}`);
92
- spinner.fail("Git initialization skipped");
206
+ logger_1.logger.debug(`Git init fallback error: ${fe.message}`);
207
+ spinner.warn("Git initialization skipped");
208
+ logger_1.logger.info("You can manually initialize git later with: git init");
93
209
  return;
94
210
  }
95
211
  }
@@ -1,10 +1,15 @@
1
1
  import { Ora } from "ora";
2
2
  export declare class Logger {
3
3
  private spinner;
4
+ private debugMode;
5
+ private silentMode;
6
+ setDebugMode(enabled: boolean): void;
7
+ setSilentMode(enabled: boolean): void;
4
8
  info(message: string): void;
5
9
  success(message: string): void;
6
- error(message: string): void;
10
+ error(message: string, error?: Error): void;
7
11
  warn(message: string): void;
12
+ debug(message: string): void;
8
13
  log(message: string): void;
9
14
  newLine(): void;
10
15
  startSpinner(text: string): Ora;
@@ -12,5 +17,7 @@ export declare class Logger {
12
17
  updateSpinner(text: string): void;
13
18
  header(text: string): void;
14
19
  footer(): void;
20
+ box(text: string, color?: "cyan" | "green" | "yellow" | "red"): void;
21
+ logWithPrefix(prefix: string, message: string, color?: "blue" | "green" | "yellow" | "red" | "cyan"): void;
15
22
  }
16
23
  export declare const logger: Logger;