stackkit-cli 0.4.4 → 0.4.5

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 (73) hide show
  1. package/README.md +26 -2
  2. package/bin/stackkit.js +1 -1
  3. package/dist/commands/add.js +20 -39
  4. package/dist/commands/doctor.d.ts +11 -0
  5. package/dist/commands/doctor.js +483 -0
  6. package/dist/commands/list.d.ts +1 -1
  7. package/dist/commands/list.js +59 -38
  8. package/dist/index.js +11 -13
  9. package/dist/types/index.d.ts +15 -0
  10. package/dist/utils/config-utils.d.ts +2 -0
  11. package/dist/utils/config-utils.js +88 -0
  12. package/dist/utils/detect.js +12 -2
  13. package/dist/utils/env-editor.d.ts +0 -1
  14. package/dist/utils/env-editor.js +50 -25
  15. package/dist/utils/files.d.ts +8 -0
  16. package/dist/utils/files.js +51 -0
  17. package/dist/utils/js-conversion.d.ts +1 -0
  18. package/dist/utils/js-conversion.js +244 -0
  19. package/dist/utils/module-utils.d.ts +2 -0
  20. package/dist/utils/module-utils.js +461 -0
  21. package/dist/utils/package-manager.js +15 -31
  22. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
  23. package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
  24. package/modules/auth/authjs/files/lib/auth.ts +41 -0
  25. package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
  26. package/modules/auth/authjs/module.json +95 -0
  27. package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
  28. package/modules/auth/better-auth/files/lib/auth.ts +62 -0
  29. package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
  30. package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
  31. package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +1 -1
  32. package/modules/auth/better-auth/module.json +164 -27
  33. package/modules/database/mongoose/files/lib/db.ts +68 -0
  34. package/modules/database/{mongoose-mongodb → mongoose}/files/models/User.ts +0 -5
  35. package/modules/database/{mongoose-mongodb → mongoose}/module.json +9 -24
  36. package/modules/database/prisma/files/lib/prisma.ts +1 -3
  37. package/modules/database/prisma/files/prisma/schema.prisma +1 -1
  38. package/modules/database/prisma/files/prisma.config.ts +2 -2
  39. package/modules/database/prisma/module.json +5 -23
  40. package/package.json +1 -1
  41. package/templates/express/.env.example +0 -1
  42. package/templates/express/package.json +4 -4
  43. package/templates/express/src/app.ts +2 -2
  44. package/templates/express/src/features/health/health.controller.ts +18 -0
  45. package/templates/express/src/features/health/health.route.ts +9 -0
  46. package/templates/express/src/features/health/health.service.ts +6 -0
  47. package/templates/nextjs/lib/env.ts +8 -0
  48. package/templates/nextjs/package.json +7 -7
  49. package/templates/react-vite/.env.example +1 -2
  50. package/templates/react-vite/.prettierignore +4 -0
  51. package/templates/react-vite/.prettierrc +9 -0
  52. package/templates/react-vite/README.md +22 -0
  53. package/templates/react-vite/package.json +16 -16
  54. package/templates/react-vite/src/router.tsx +0 -12
  55. package/templates/react-vite/vite.config.ts +0 -6
  56. package/dist/commands/init.d.ts +0 -10
  57. package/dist/commands/init.js +0 -157
  58. package/dist/utils/code-inject.d.ts +0 -14
  59. package/dist/utils/code-inject.js +0 -70
  60. package/dist/utils/json-editor.d.ts +0 -8
  61. package/dist/utils/json-editor.js +0 -45
  62. package/modules/auth/clerk/files/express/auth.ts +0 -7
  63. package/modules/auth/clerk/files/nextjs/auth-provider.tsx +0 -5
  64. package/modules/auth/clerk/files/nextjs/middleware.ts +0 -9
  65. package/modules/auth/clerk/files/react/auth-provider.tsx +0 -15
  66. package/modules/auth/clerk/module.json +0 -115
  67. package/modules/database/mongoose-mongodb/files/lib/db.ts +0 -78
  68. package/templates/express/src/features/auth/auth.controller.ts +0 -48
  69. package/templates/express/src/features/auth/auth.route.ts +0 -10
  70. package/templates/express/src/features/auth/auth.service.ts +0 -21
  71. package/templates/react-vite/src/api/services/user.service.ts +0 -18
  72. package/templates/react-vite/src/pages/UserProfile.tsx +0 -40
  73. package/templates/react-vite/src/types/user.d.ts +0 -6
@@ -9,37 +9,35 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const logger_1 = require("../utils/logger");
11
11
  async function listCommand(options) {
12
- const showTemplates = !options.modules || options.templates;
13
- const showModules = !options.templates || options.modules;
12
+ const showFrameworks = !options.modules || options.frameworks;
13
+ const showModules = !options.frameworks || options.modules;
14
14
  try {
15
+ logger_1.logger.header("StackKit Resources");
15
16
  logger_1.logger.newLine();
16
- // List templates
17
- if (showTemplates) {
17
+ let hasFrameworks = false;
18
+ let hasModules = false;
19
+ // List frameworks
20
+ if (showFrameworks) {
18
21
  const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
19
- const templates = await getAvailableTemplates(templatesDir);
20
- logger_1.logger.log(chalk_1.default.bold.cyan("▸ TEMPLATES") + chalk_1.default.gray(` (${templates.length})`));
21
- logger_1.logger.newLine();
22
- if (templates.length === 0) {
23
- logger_1.logger.log(chalk_1.default.dim(" No templates available"));
24
- }
25
- else {
26
- templates.forEach((template) => {
27
- logger_1.logger.log(` ${chalk_1.default.cyan("•")} ${template.displayName}`);
22
+ const frameworks = await getAvailableFrameworks(templatesDir);
23
+ if (frameworks.length > 0) {
24
+ hasFrameworks = true;
25
+ logger_1.logger.log(chalk_1.default.bold.blue("FRAMEWORKS"));
26
+ frameworks.forEach((framework, index) => {
27
+ const isLast = index === frameworks.length - 1;
28
+ const prefix = isLast ? "└──" : "├──";
29
+ logger_1.logger.log(` ${chalk_1.default.gray(prefix)} ${chalk_1.default.cyan(framework.displayName)}`);
28
30
  });
31
+ logger_1.logger.newLine();
29
32
  }
30
- logger_1.logger.newLine();
31
33
  }
32
34
  // List modules
33
35
  if (showModules) {
34
36
  const modulesDir = path_1.default.join(__dirname, "..", "..", "modules");
35
37
  const modules = await getAvailableModules(modulesDir);
36
- logger_1.logger.log(chalk_1.default.bold.cyan("▸ MODULES") + chalk_1.default.gray(` (${modules.length})`));
37
- if (modules.length === 0) {
38
- logger_1.logger.newLine();
39
- logger_1.logger.log(chalk_1.default.dim(" No modules available"));
40
- logger_1.logger.newLine();
41
- }
42
- else {
38
+ if (modules.length > 0) {
39
+ hasModules = true;
40
+ logger_1.logger.log(chalk_1.default.bold.magenta("MODULES"));
43
41
  // Group by category
44
42
  const grouped = modules.reduce((acc, mod) => {
45
43
  if (!acc[mod.category]) {
@@ -48,36 +46,59 @@ async function listCommand(options) {
48
46
  acc[mod.category].push(mod);
49
47
  return acc;
50
48
  }, {});
51
- for (const [category, mods] of Object.entries(grouped)) {
52
- logger_1.logger.newLine();
53
- logger_1.logger.log(` ${chalk_1.default.yellow("→")} ${chalk_1.default.bold.yellow(category.toUpperCase())} ${chalk_1.default.dim(`(${mods.length})`)}`);
54
- mods.forEach((mod) => {
55
- logger_1.logger.log(` ${chalk_1.default.cyan("")} ${mod.displayName}`);
49
+ const categories = Object.keys(grouped);
50
+ categories.forEach((category, categoryIndex) => {
51
+ const mods = grouped[category];
52
+ const isLastCategory = categoryIndex === categories.length - 1;
53
+ const categoryPrefix = isLastCategory ? "└──" : "├──";
54
+ logger_1.logger.log(` ${chalk_1.default.gray(categoryPrefix)} ${chalk_1.default.yellow(formatCategoryName(category))} ${chalk_1.default.dim(`(${mods.length})`)}`);
55
+ mods.forEach((mod, modIndex) => {
56
+ const isLastMod = modIndex === mods.length - 1;
57
+ const modPrefix = isLastCategory ? (isLastMod ? " └──" : " ├──") : (isLastMod ? "│ └──" : "│ ├──");
58
+ logger_1.logger.log(` ${chalk_1.default.gray(modPrefix)} ${chalk_1.default.green(mod.displayName)}`);
59
+ // Show additional details for database modules
60
+ if (mod.category === "database" && mod.name === "prisma") {
61
+ const providerPrefix = isLastCategory ? (isLastMod ? " └──" : " ├──") : (isLastMod ? "│ └──" : "│ ├──");
62
+ logger_1.logger.log(` ${chalk_1.default.gray(providerPrefix)} ${chalk_1.default.dim("Providers: PostgreSQL, MongoDB, MySQL, SQLite")}`);
63
+ }
56
64
  });
57
- }
65
+ });
58
66
  logger_1.logger.newLine();
59
67
  }
60
68
  }
69
+ if (!hasFrameworks && !hasModules) {
70
+ logger_1.logger.log(chalk_1.default.dim("No resources available"));
71
+ logger_1.logger.newLine();
72
+ }
73
+ logger_1.logger.log(chalk_1.default.dim("Use 'stackkit add <module>' to add modules to your project"));
74
+ logger_1.logger.newLine();
61
75
  }
62
76
  catch (error) {
63
77
  logger_1.logger.error(`Failed to list resources: ${error.message}`);
64
78
  process.exit(1);
65
79
  }
66
80
  }
67
- async function getAvailableTemplates(templatesDir) {
81
+ async function getAvailableFrameworks(templatesDir) {
68
82
  if (!(await fs_extra_1.default.pathExists(templatesDir))) {
69
83
  return [];
70
84
  }
71
- const templateDirs = await fs_extra_1.default.readdir(templatesDir);
72
- const templates = [];
73
- for (const dir of templateDirs) {
74
- const metadataPath = path_1.default.join(templatesDir, dir, "template.json");
75
- if (await fs_extra_1.default.pathExists(metadataPath)) {
76
- const metadata = await fs_extra_1.default.readJSON(metadataPath);
77
- templates.push(metadata);
78
- }
79
- }
80
- return templates;
85
+ const frameworkDirs = await fs_extra_1.default.readdir(templatesDir);
86
+ const frameworks = frameworkDirs
87
+ .filter((dir) => dir !== "node_modules" && dir !== ".git")
88
+ .map((dir) => ({
89
+ name: dir,
90
+ displayName: formatFrameworkName(dir),
91
+ }));
92
+ return frameworks;
93
+ }
94
+ function formatFrameworkName(name) {
95
+ return name
96
+ .split("-")
97
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
98
+ .join(" ");
99
+ }
100
+ function formatCategoryName(name) {
101
+ return name.charAt(0).toUpperCase() + name.slice(1);
81
102
  }
82
103
  async function getAvailableModules(modulesDir) {
83
104
  if (!(await fs_extra_1.default.pathExists(modulesDir))) {
package/dist/index.js CHANGED
@@ -7,28 +7,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const commander_1 = require("commander");
9
9
  const add_1 = require("./commands/add");
10
- const init_1 = require("./commands/init");
10
+ const doctor_1 = require("./commands/doctor");
11
11
  const list_1 = require("./commands/list");
12
12
  const program = new commander_1.Command();
13
13
  program
14
14
  .name("stackkit")
15
15
  .description("Production-ready project generator and module CLI")
16
16
  .version("0.3.2");
17
- // Init command
18
- program
19
- .command("init [project-name]")
20
- .description("Create a new project from a template")
21
- .option("-t, --template <template>", "Template to use")
22
- .option("--pm <pm>", "Package manager to use (npm, yarn, pnpm, bun)")
23
- .option("--no-install", "Skip installing dependencies")
24
- .option("--no-git", "Skip git initialization")
25
- .option("-y, --yes", "Skip prompts and use defaults")
26
- .action(init_1.initCommand);
27
17
  // List command
28
18
  program
29
19
  .command("list")
30
- .description("List available templates and modules")
31
- .option("-t, --templates", "List only templates")
20
+ .description("List available frameworks and modules")
21
+ .option("-f, --frameworks", "List only frameworks")
32
22
  .option("-m, --modules", "List only modules")
33
23
  .action(list_1.listCommand);
34
24
  // Add command
@@ -40,6 +30,14 @@ program
40
30
  .option("--dry-run", "Show what would be changed without making changes")
41
31
  .option("--no-install", "Skip installing dependencies")
42
32
  .action(add_1.addCommand);
33
+ // Doctor command
34
+ program
35
+ .command("doctor")
36
+ .description("Check project health and compatibility with StackKit modules")
37
+ .option("--json", "Output results in JSON format")
38
+ .option("--verbose", "Show detailed information")
39
+ .option("--strict", "Treat warnings as errors")
40
+ .action(doctor_1.doctorCommand);
43
41
  // Error handling
44
42
  program.on("command:*", () => {
45
43
  // Use logger for error and info
@@ -28,6 +28,21 @@ export interface ModuleMetadata {
28
28
  };
29
29
  }>;
30
30
  postInstall?: string[];
31
+ databaseAdapters?: {
32
+ common?: {
33
+ dependencies?: Record<string, string>;
34
+ devDependencies?: Record<string, string>;
35
+ };
36
+ providers?: Record<string, Record<string, {
37
+ dependencies?: Record<string, string>;
38
+ devDependencies?: Record<string, string>;
39
+ }>>;
40
+ };
41
+ frameworkConfigs?: Record<string, {
42
+ dependencies?: Record<string, string>;
43
+ devDependencies?: Record<string, string>;
44
+ patches?: ModulePatch[];
45
+ }>;
31
46
  }
32
47
  export interface EnvVar {
33
48
  key: string;
@@ -0,0 +1,2 @@
1
+ export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
2
+ export declare function applyFrameworkPatches(targetDir: string, patches: Record<string, unknown>): Promise<void>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deepMerge = deepMerge;
7
+ exports.applyFrameworkPatches = applyFrameworkPatches;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = require("path");
10
+ function deepMerge(target, source) {
11
+ const result = { ...target };
12
+ for (const key in source) {
13
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
14
+ result[key] = deepMerge(result[key] || {}, source[key]);
15
+ }
16
+ else {
17
+ result[key] = source[key];
18
+ }
19
+ }
20
+ return result;
21
+ }
22
+ async function applyFrameworkPatches(targetDir, patches) {
23
+ for (const [filename, patchConfig] of Object.entries(patches)) {
24
+ const filePath = (0, path_1.join)(targetDir, filename);
25
+ if (await fs_extra_1.default.pathExists(filePath)) {
26
+ if (patchConfig && typeof patchConfig === "object" && "merge" in patchConfig) {
27
+ const fileContent = await fs_extra_1.default.readJson(filePath);
28
+ const merged = deepMerge(fileContent, patchConfig.merge);
29
+ await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
30
+ }
31
+ else if (patchConfig && typeof patchConfig === "object" && "replace" in patchConfig) {
32
+ let fileContent = await fs_extra_1.default.readFile(filePath, "utf-8");
33
+ const replaceConfig = patchConfig;
34
+ for (const [oldStr, newStr] of Object.entries(replaceConfig.replace)) {
35
+ fileContent = fileContent.replace(new RegExp(oldStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newStr);
36
+ }
37
+ await fs_extra_1.default.writeFile(filePath, fileContent);
38
+ }
39
+ else if (patchConfig &&
40
+ typeof patchConfig === "object" &&
41
+ ("operations" in patchConfig ||
42
+ ("type" in patchConfig && patchConfig.type === "patch-file"))) {
43
+ let fileContent = await fs_extra_1.default.readFile(filePath, "utf-8");
44
+ const operations = patchConfig.operations;
45
+ for (const operation of operations) {
46
+ if (operation && typeof operation === "object" && "type" in operation) {
47
+ const op = operation;
48
+ if (op.type === "add-import" && "imports" in op && Array.isArray(op.imports)) {
49
+ const imports = op.imports;
50
+ // Add imports at the top after existing imports
51
+ const importLines = imports.join("\n");
52
+ // Find the last import statement
53
+ const lines = fileContent.split("\n");
54
+ let lastImportIndex = -1;
55
+ for (let i = 0; i < lines.length; i++) {
56
+ if (lines[i].trim().startsWith("import")) {
57
+ lastImportIndex = i;
58
+ }
59
+ else if (lines[i].trim() !== "" && !lines[i].trim().startsWith("//")) {
60
+ break;
61
+ }
62
+ }
63
+ if (lastImportIndex >= 0) {
64
+ lines.splice(lastImportIndex + 1, 0, "", importLines);
65
+ fileContent = lines.join("\n");
66
+ }
67
+ }
68
+ else if (op.type === "add-code" && "after" in op && "code" in op) {
69
+ const after = op.after;
70
+ const code = op.code;
71
+ const afterIndex = fileContent.indexOf(after);
72
+ if (afterIndex >= 0) {
73
+ const insertPos = afterIndex + after.length;
74
+ fileContent = fileContent.slice(0, insertPos) + code + fileContent.slice(insertPos);
75
+ }
76
+ }
77
+ else if (op.type === "replace" && "oldString" in op && "newString" in op) {
78
+ const oldString = op.oldString;
79
+ const newString = op.newString;
80
+ fileContent = fileContent.replace(oldString, newString);
81
+ }
82
+ }
83
+ }
84
+ await fs_extra_1.default.writeFile(filePath, fileContent);
85
+ }
86
+ }
87
+ }
88
+ }
@@ -54,7 +54,18 @@ async function detectProjectInfo(targetDir) {
54
54
  }
55
55
  // Detect TypeScript vs JavaScript
56
56
  const tsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "tsconfig.json"));
57
- const language = tsconfigExists ? "ts" : "js";
57
+ const jsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "jsconfig.json"));
58
+ let language;
59
+ if (tsconfigExists) {
60
+ language = "ts";
61
+ }
62
+ else if (jsconfigExists) {
63
+ language = "js";
64
+ }
65
+ else {
66
+ // Default to TypeScript if neither exists
67
+ language = "ts";
68
+ }
58
69
  // Detect package manager
59
70
  const yarnLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "yarn.lock"));
60
71
  const pnpmLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "pnpm-lock.yaml"));
@@ -73,7 +84,6 @@ async function detectProjectInfo(targetDir) {
73
84
  const hasAuth = !!(packageJson.dependencies?.["next-auth"] ||
74
85
  packageJson.dependencies?.["better-auth"] ||
75
86
  packageJson.dependencies?.["@auth/core"] ||
76
- packageJson.dependencies?.["@clerk/nextjs"] ||
77
87
  packageJson.dependencies?.["@kinde-oss/kinde-auth-nextjs"] ||
78
88
  packageJson.dependencies?.["passport"]);
79
89
  const hasPrisma = !!(packageJson.dependencies?.["@prisma/client"] || packageJson.devDependencies?.["prisma"]);
@@ -1,7 +1,6 @@
1
1
  export interface EnvVariable {
2
2
  key: string;
3
3
  value?: string;
4
- description: string;
5
4
  required: boolean;
6
5
  }
7
6
  export declare function addEnvVariables(projectRoot: string, variables: EnvVariable[], options?: {
@@ -13,9 +13,7 @@ const ENV_MARKER_END = "# End StackKit";
13
13
  async function addEnvVariables(projectRoot, variables, options = {}) {
14
14
  const envExamplePath = path_1.default.join(projectRoot, ".env.example");
15
15
  const envPath = path_1.default.join(projectRoot, ".env");
16
- // Add to .env.example
17
16
  await appendToEnvFile(envExamplePath, variables, "example", options);
18
- // Add to .env if it exists or create it
19
17
  const envExists = await fs_extra_1.default.pathExists(envPath);
20
18
  if (envExists || options.force) {
21
19
  await appendToEnvFile(envPath, variables, "local", options);
@@ -23,11 +21,28 @@ async function addEnvVariables(projectRoot, variables, options = {}) {
23
21
  logger_1.logger.success("Environment variables added");
24
22
  }
25
23
  async function appendToEnvFile(filePath, variables, fileType, options = {}) {
24
+ // Validate environment variable keys
25
+ for (const variable of variables) {
26
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(variable.key)) {
27
+ throw new Error(`Invalid environment variable key: ${variable.key}`);
28
+ }
29
+ }
26
30
  let content = "";
27
31
  if (await fs_extra_1.default.pathExists(filePath)) {
28
32
  content = await fs_extra_1.default.readFile(filePath, "utf-8");
29
33
  }
30
- // Check if variables already exist
34
+ // If force, remove existing keys first to avoid duplicates
35
+ if (options.force) {
36
+ const keysToRemove = variables.map((v) => v.key);
37
+ await removeFromEnvFile(filePath, keysToRemove);
38
+ if (await fs_extra_1.default.pathExists(filePath)) {
39
+ content = await fs_extra_1.default.readFile(filePath, "utf-8");
40
+ }
41
+ else {
42
+ content = "";
43
+ }
44
+ }
45
+ // Check if variables already exist (after potential removal)
31
46
  const existingKeys = new Set();
32
47
  const lines = content.split("\n");
33
48
  for (const line of lines) {
@@ -36,15 +51,7 @@ async function appendToEnvFile(filePath, variables, fileType, options = {}) {
36
51
  existingKeys.add(match[1]);
37
52
  }
38
53
  }
39
- const newVariables = variables.filter((v) => {
40
- if (existingKeys.has(v.key)) {
41
- if (!options.force) {
42
- logger_1.logger.warn(`Variable ${v.key} already exists in ${filePath}`);
43
- return false;
44
- }
45
- }
46
- return true;
47
- });
54
+ const newVariables = variables.filter((v) => !existingKeys.has(v.key));
48
55
  if (newVariables.length === 0) {
49
56
  return;
50
57
  }
@@ -56,13 +63,11 @@ async function appendToEnvFile(filePath, variables, fileType, options = {}) {
56
63
  content += "\n";
57
64
  content += `${ENV_MARKER_START} Added by StackKit\n`;
58
65
  for (const variable of newVariables) {
59
- if (variable.description) {
60
- content += `# ${variable.description}\n`;
61
- }
62
- const value = fileType === "example" ? variable.value || "" : variable.value || "";
66
+ const value = fileType === "example" ? (variable.value || "") : (variable.value || "");
63
67
  content += `${variable.key}=${value}\n`;
64
68
  }
65
69
  content += `${ENV_MARKER_END}\n`;
70
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
66
71
  await fs_extra_1.default.writeFile(filePath, content, "utf-8");
67
72
  }
68
73
  async function removeEnvVariables(projectRoot, keys) {
@@ -77,15 +82,35 @@ async function removeFromEnvFile(filePath, keys) {
77
82
  if (!(await fs_extra_1.default.pathExists(filePath))) {
78
83
  return;
79
84
  }
80
- const content = await fs_extra_1.default.readFile(filePath, "utf-8");
81
- const lines = content.split("\n");
82
- const newLines = [];
83
- for (const line of lines) {
84
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
85
- if (match && keys.includes(match[1])) {
86
- continue; // Skip this line
85
+ try {
86
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
87
+ const lines = content.split("\n");
88
+ const newLines = [];
89
+ let inStackKitBlock = false;
90
+ for (const line of lines) {
91
+ if (line.includes(ENV_MARKER_START)) {
92
+ inStackKitBlock = true;
93
+ continue;
94
+ }
95
+ if (line.includes(ENV_MARKER_END)) {
96
+ inStackKitBlock = false;
97
+ continue;
98
+ }
99
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
100
+ if (match && keys.includes(match[1])) {
101
+ continue;
102
+ }
103
+ if (!inStackKitBlock || !line.startsWith("#")) {
104
+ newLines.push(line);
105
+ }
87
106
  }
88
- newLines.push(line);
107
+ while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
108
+ newLines.pop();
109
+ }
110
+ await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
111
+ }
112
+ catch (error) {
113
+ logger_1.logger.error(`Failed to remove env variables from ${filePath}: ${error}`);
114
+ throw error;
89
115
  }
90
- await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
91
116
  }
@@ -1,3 +1,11 @@
1
+ export interface PackageJsonConfig {
2
+ dependencies?: Record<string, string>;
3
+ devDependencies?: Record<string, string>;
4
+ scripts?: Record<string, string>;
5
+ }
6
+ export declare function copyBaseFramework(templatesDir: string, targetDir: string, framework: string): Promise<void>;
7
+ export declare function mergePackageJson(targetDir: string, config: PackageJsonConfig): Promise<void>;
8
+ export declare function mergeEnvFile(targetDir: string, envVars: Record<string, string>): Promise<void>;
1
9
  export declare function copyTemplate(templatePath: string, targetPath: string, projectName: string): Promise<void>;
2
10
  export declare function createFile(targetPath: string, content: string, options?: {
3
11
  force?: boolean;
@@ -3,6 +3,9 @@ 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.copyBaseFramework = copyBaseFramework;
7
+ exports.mergePackageJson = mergePackageJson;
8
+ exports.mergeEnvFile = mergeEnvFile;
6
9
  exports.copyTemplate = copyTemplate;
7
10
  exports.createFile = createFile;
8
11
  exports.readFile = readFile;
@@ -10,6 +13,54 @@ exports.fileExists = fileExists;
10
13
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
14
  const path_1 = __importDefault(require("path"));
12
15
  const logger_1 = require("./logger");
16
+ async function copyBaseFramework(templatesDir, targetDir, framework) {
17
+ const baseDir = path_1.default.join(templatesDir, framework);
18
+ if (!(await fs_extra_1.default.pathExists(baseDir))) {
19
+ throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
20
+ }
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
+ },
26
+ });
27
+ }
28
+ async function mergePackageJson(targetDir, config) {
29
+ const pkgPath = path_1.default.join(targetDir, "package.json");
30
+ if (!(await fs_extra_1.default.pathExists(pkgPath))) {
31
+ return;
32
+ }
33
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
34
+ if (config.dependencies) {
35
+ pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
36
+ }
37
+ if (config.devDependencies) {
38
+ pkg.devDependencies = { ...pkg.devDependencies, ...config.devDependencies };
39
+ }
40
+ if (config.scripts) {
41
+ pkg.scripts = { ...pkg.scripts, ...config.scripts };
42
+ }
43
+ await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
44
+ }
45
+ async function mergeEnvFile(targetDir, envVars) {
46
+ const envPath = path_1.default.join(targetDir, ".env");
47
+ let existingEnv = "";
48
+ if (await fs_extra_1.default.pathExists(envPath)) {
49
+ existingEnv = await fs_extra_1.default.readFile(envPath, "utf-8");
50
+ }
51
+ const envLines = existingEnv.split("\n").filter((line) => line.trim() !== "");
52
+ // Add new variables
53
+ for (const [key, value] of Object.entries(envVars)) {
54
+ const existingIndex = envLines.findIndex((line) => line.startsWith(`${key}=`));
55
+ if (existingIndex !== -1) {
56
+ envLines[existingIndex] = `${key}=${value}`;
57
+ }
58
+ else {
59
+ envLines.push(`${key}=${value}`);
60
+ }
61
+ }
62
+ await fs_extra_1.default.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
63
+ }
13
64
  async function copyTemplate(templatePath, targetPath, projectName) {
14
65
  if (!(await fs_extra_1.default.pathExists(templatePath))) {
15
66
  throw new Error(`Template not found: ${templatePath}`);
@@ -0,0 +1 @@
1
+ export declare function convertToJavaScript(targetDir: string, framework: string): Promise<void>;