stackkit 0.1.2 → 0.1.4

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 (37) hide show
  1. package/README.md +4 -2
  2. package/dist/cli/add.d.ts +2 -1
  3. package/dist/cli/add.js +318 -110
  4. package/dist/cli/create.js +8 -45
  5. package/dist/cli/doctor.js +0 -10
  6. package/dist/cli/list.js +3 -5
  7. package/dist/index.js +23 -4
  8. package/dist/lib/conversion/js-conversion.js +3 -2
  9. package/dist/lib/discovery/module-discovery.d.ts +0 -1
  10. package/dist/lib/discovery/module-discovery.js +27 -5
  11. package/dist/lib/generation/code-generator.d.ts +14 -0
  12. package/dist/lib/generation/code-generator.js +69 -65
  13. package/dist/lib/generation/generator-utils.d.ts +11 -0
  14. package/dist/lib/generation/generator-utils.js +116 -0
  15. package/dist/lib/git-utils.js +20 -0
  16. package/dist/lib/project/detect.js +2 -4
  17. package/dist/lib/utils/package-root.d.ts +1 -0
  18. package/dist/lib/utils/package-root.js +45 -0
  19. package/dist/types/index.d.ts +0 -10
  20. package/modules/auth/authjs/generator.json +10 -0
  21. package/modules/auth/authjs/module.json +0 -9
  22. package/modules/auth/better-auth/files/lib/auth.ts +7 -7
  23. package/modules/auth/better-auth/generator.json +12 -12
  24. package/modules/auth/better-auth/module.json +0 -24
  25. package/modules/database/mongoose/files/models/health.ts +34 -0
  26. package/modules/database/mongoose/generator.json +4 -4
  27. package/modules/database/mongoose/module.json +1 -2
  28. package/modules/database/prisma/files/lib/prisma.ts +2 -1
  29. package/modules/database/prisma/generator.json +33 -8
  30. package/modules/database/prisma/module.json +1 -4
  31. package/package.json +1 -2
  32. package/templates/express/tsconfig.json +2 -23
  33. package/dist/lib/database/database-config.d.ts +0 -6
  34. package/dist/lib/database/database-config.js +0 -9
  35. package/modules/database/mongoose/files/models/User.ts +0 -34
  36. package/templates/react-vite/README.md +0 -56
  37. /package/modules/database/mongoose/files/lib/{db.ts → mongoose.ts} +0 -0
package/README.md CHANGED
@@ -19,6 +19,8 @@ npx stackkit create my-app
19
19
  ## Add Features to Existing Project
20
20
 
21
21
  ```bash
22
+ npx stackkit add
23
+ # or non-interactive
22
24
  npx stackkit add auth
23
25
  npx stackkit add database
24
26
  ```
@@ -32,10 +34,10 @@ npx stackkit doctor
32
34
  ## Supported Technologies
33
35
 
34
36
  - **Frameworks**: Next.js, React, Express
35
- - **Databases**: Prisma (PostgreSQL, MySQL, SQLite), Mongoose (MongoDB)
37
+ - **Databases**: Prisma (PostgreSQL, MongoDB, MySQL, SQLite), Mongoose (MongoDB)
36
38
  - **Auth**: Better Auth, Auth.js
37
39
 
38
40
  ## Documentation
39
41
 
40
- - [StackKit Docs](https://stack-kit.com)[https://stackkit.tariqul.dev]
42
+ - [StackKit Docs](https://stackkit.tariqul.dev)
41
43
  - [GitHub Repository](https://github.com/tariqul420/stackkit)
package/dist/cli/add.d.ts CHANGED
@@ -3,6 +3,7 @@ interface AddOptions {
3
3
  force?: boolean;
4
4
  dryRun?: boolean;
5
5
  install?: boolean;
6
+ yes?: boolean;
6
7
  }
7
- export declare function addCommand(module: string, options: AddOptions): Promise<void>;
8
+ export declare function addCommand(module?: string, options?: AddOptions): Promise<void>;
8
9
  export {};
package/dist/cli/add.js CHANGED
@@ -9,24 +9,95 @@ const child_process_1 = require("child_process");
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
10
10
  const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const path_1 = __importDefault(require("path"));
12
+ const code_generator_1 = require("../lib/generation/code-generator");
12
13
  const detect_1 = require("../lib/project/detect");
13
14
  const env_editor_1 = require("../lib/env/env-editor");
14
15
  const files_1 = require("../lib/fs/files");
15
16
  const logger_1 = require("../lib/ui/logger");
16
17
  const package_manager_1 = require("../lib/pm/package-manager");
17
- const database_config_1 = require("../lib/database/database-config");
18
+ const package_root_1 = require("../lib/utils/package-root");
19
+ const framework_utils_1 = require("../lib/framework/framework-utils");
20
+ const generator_utils_1 = require("../lib/generation/generator-utils");
18
21
  async function addCommand(module, options) {
19
22
  try {
20
23
  const projectRoot = process.cwd();
21
24
  const spinner = logger_1.logger.startSpinner("Detecting project...");
22
25
  const projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
23
26
  spinner.succeed(`Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`);
24
- const moduleMetadata = await loadModuleMetadata(path_1.default.join(__dirname, "..", "..", "..", "modules"), module, options.provider);
27
+ const config = await getAddConfig(module, options, projectInfo);
28
+ await addModuleToProject(projectRoot, projectInfo, config, options);
29
+ logger_1.logger.newLine();
30
+ logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
31
+ logger_1.logger.newLine();
32
+ if (config.metadata.envVars && config.metadata.envVars.length > 0) {
33
+ logger_1.logger.log("Next: Fill in environment variables in .env");
34
+ }
35
+ logger_1.logger.newLine();
36
+ }
37
+ catch (error) {
38
+ logger_1.logger.error(`Failed to add module: ${error.message}`);
39
+ if (error instanceof Error && error.stack) {
40
+ logger_1.logger.log(chalk_1.default.gray(error.stack));
41
+ }
42
+ process.exit(1);
43
+ }
44
+ }
45
+ async function getAddConfig(module, options, projectInfo) {
46
+ const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules');
47
+ const argv = process.argv.slice(2);
48
+ const addIndex = argv.indexOf('add');
49
+ const argsAfterAdd = addIndex >= 0 ? argv.slice(addIndex + 1) : [];
50
+ const flagsProvided = argsAfterAdd.some(arg => arg.startsWith('-'));
51
+ const optionsProvided = flagsProvided || !!(options && (options.yes || options.provider || options.force || options.dryRun));
52
+ if (optionsProvided) {
53
+ if (!module) {
54
+ throw new Error("Module name is required when using flags");
55
+ }
56
+ if (module === "database" || module === "auth") {
57
+ if (module === "database") {
58
+ if (!options?.provider) {
59
+ throw new Error("Provider is required for database. Use --provider <provider>");
60
+ }
61
+ let baseProvider = options.provider;
62
+ let adapterProvider = options.provider;
63
+ if (options.provider.includes('-')) {
64
+ const parts = options.provider.split('-');
65
+ baseProvider = parts[0]; // e.g., "prisma"
66
+ adapterProvider = options.provider; // e.g., "prisma-postgresql"
67
+ }
68
+ const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
69
+ if (!moduleMetadata) {
70
+ throw new Error(`Database provider "${baseProvider}" not found`);
71
+ }
72
+ return {
73
+ module: "database",
74
+ provider: adapterProvider,
75
+ displayName: `${moduleMetadata.displayName} (${adapterProvider.split('-')[1] || adapterProvider})`,
76
+ metadata: moduleMetadata,
77
+ };
78
+ }
79
+ else if (module === "auth") {
80
+ const provider = options?.provider || "better-auth";
81
+ const moduleMetadata = await loadModuleMetadata(modulesDir, provider, provider);
82
+ if (!moduleMetadata) {
83
+ throw new Error(`Auth provider "${provider}" not found`);
84
+ }
85
+ return {
86
+ module: "auth",
87
+ provider,
88
+ displayName: moduleMetadata.displayName,
89
+ metadata: moduleMetadata,
90
+ };
91
+ }
92
+ }
93
+ const moduleMetadata = await loadModuleMetadata(modulesDir, module, options?.provider);
25
94
  if (!moduleMetadata) {
26
- logger_1.logger.error(`Module "${module}" not found`);
27
- process.exit(1);
95
+ throw new Error(`Module "${module}" not found`);
96
+ }
97
+ let selectedProvider = options?.provider;
98
+ if (!selectedProvider && moduleMetadata.category !== module) {
99
+ selectedProvider = module;
28
100
  }
29
- let selectedProvider = options.provider;
30
101
  if (moduleMetadata.category === "database" && !selectedProvider) {
31
102
  if (typeof moduleMetadata.dependencies === "object" && "providers" in moduleMetadata.dependencies) {
32
103
  const providers = Object.keys(moduleMetadata.dependencies.providers || {});
@@ -43,111 +114,272 @@ async function addCommand(module, options) {
43
114
  }
44
115
  }
45
116
  }
46
- const mergedDeps = {};
47
- const mergedDevDeps = {};
48
- if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
49
- Object.assign(mergedDeps, moduleMetadata.frameworkConfigs.shared.dependencies);
117
+ if (projectInfo && !moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
118
+ throw new Error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(", ")}`);
50
119
  }
51
- if (moduleMetadata.frameworkConfigs?.shared?.devDependencies) {
52
- Object.assign(mergedDevDeps, moduleMetadata.frameworkConfigs.shared.devDependencies);
120
+ return {
121
+ module,
122
+ provider: selectedProvider,
123
+ displayName: moduleMetadata.displayName,
124
+ metadata: moduleMetadata,
125
+ };
126
+ }
127
+ const answers = await inquirer_1.default.prompt([
128
+ {
129
+ type: "list",
130
+ name: "category",
131
+ message: "What would you like to add?",
132
+ choices: [
133
+ { name: "Database", value: "database" },
134
+ { name: "Authentication", value: "auth" },
135
+ ],
136
+ },
137
+ ]);
138
+ const category = answers.category;
139
+ if (category === "database") {
140
+ const dbAnswers = await inquirer_1.default.prompt([
141
+ {
142
+ type: "list",
143
+ name: "database",
144
+ message: "Select database:",
145
+ choices: [
146
+ { name: "Prisma", value: "prisma" },
147
+ { name: "Mongoose", value: "mongoose" },
148
+ ],
149
+ },
150
+ ]);
151
+ const selectedDb = dbAnswers.database;
152
+ if (selectedDb === "prisma") {
153
+ const providerAnswers = await inquirer_1.default.prompt([
154
+ {
155
+ type: "list",
156
+ name: "provider",
157
+ message: "Select Prisma provider:",
158
+ choices: [
159
+ { name: "PostgreSQL", value: "postgresql" },
160
+ { name: "MongoDB", value: "mongodb" },
161
+ { name: "MySQL", value: "mysql" },
162
+ { name: "SQLite", value: "sqlite" },
163
+ ],
164
+ },
165
+ ]);
166
+ return {
167
+ module: "database",
168
+ provider: "prisma",
169
+ displayName: `Prisma (${providerAnswers.provider})`,
170
+ metadata: await loadModuleMetadata(modulesDir, "prisma", "prisma"),
171
+ };
53
172
  }
54
- if (selectedProvider && moduleMetadata.databaseAdapters?.providers?.[selectedProvider]?.dependencies) {
55
- Object.assign(mergedDeps, moduleMetadata.databaseAdapters.providers[selectedProvider].dependencies);
173
+ else {
174
+ return {
175
+ module: "database",
176
+ provider: "mongoose",
177
+ displayName: "Mongoose",
178
+ metadata: await loadModuleMetadata(modulesDir, "mongoose", "mongoose"),
179
+ };
56
180
  }
57
- if (selectedProvider && moduleMetadata.databaseAdapters?.providers?.[selectedProvider]?.devDependencies) {
58
- Object.assign(mergedDevDeps, moduleMetadata.databaseAdapters.providers[selectedProvider].devDependencies);
181
+ }
182
+ else if (category === "auth") {
183
+ const authAnswers = await inquirer_1.default.prompt([
184
+ {
185
+ type: "list",
186
+ name: "auth",
187
+ message: "Select authentication:",
188
+ choices: [
189
+ { name: "Better Auth", value: "better-auth" },
190
+ { name: "Auth.js", value: "authjs" },
191
+ ],
192
+ },
193
+ ]);
194
+ const selectedAuth = authAnswers.auth;
195
+ const metadata = await loadModuleMetadata(modulesDir, selectedAuth, selectedAuth);
196
+ if (!metadata) {
197
+ throw new Error(`Auth provider "${selectedAuth}" not found`);
59
198
  }
60
- moduleMetadata.dependencies = mergedDeps;
61
- moduleMetadata.devDependencies = mergedDevDeps;
62
- const variables = {};
63
- if (selectedProvider) {
64
- variables.provider = selectedProvider;
65
- variables.connectionString = database_config_1.DATABASE_CONNECTION_STRINGS[selectedProvider] || "";
199
+ if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
200
+ throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
66
201
  }
67
- if (!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
68
- logger_1.logger.error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(", ")}`);
69
- process.exit(1);
202
+ return {
203
+ module: "auth",
204
+ provider: selectedAuth,
205
+ displayName: selectedAuth === "better-auth" ? "Better Auth" : "Auth.js",
206
+ metadata,
207
+ };
208
+ }
209
+ throw new Error("Invalid selection");
210
+ }
211
+ async function addModuleToProject(projectRoot, projectInfo, config, options) {
212
+ const moduleMetadata = config.metadata;
213
+ const selectedProvider = config.provider;
214
+ if (config.module === "auth" && projectInfo.hasAuth && !options?.force) {
215
+ logger_1.logger.warn("Auth library already detected in this project");
216
+ const { proceed } = await inquirer_1.default.prompt([
217
+ {
218
+ type: "confirm",
219
+ name: "proceed",
220
+ message: "Continue anyway? (use --force to skip this prompt)",
221
+ default: false,
222
+ },
223
+ ]);
224
+ if (!proceed) {
225
+ logger_1.logger.info("Cancelled");
226
+ return;
70
227
  }
71
- if (module === "auth" && projectInfo.hasAuth && !options.force) {
72
- logger_1.logger.warn("Auth library already detected in this project");
73
- const { proceed } = await inquirer_1.default.prompt([
74
- {
75
- type: "confirm",
76
- name: "proceed",
77
- message: "Continue anyway? (use --force to skip this prompt)",
78
- default: false,
79
- },
80
- ]);
81
- if (!proceed) {
82
- logger_1.logger.info("Cancelled");
83
- process.exit(0);
228
+ }
229
+ if (options?.dryRun) {
230
+ logger_1.logger.warn("Dry run mode - no changes will be made");
231
+ logger_1.logger.newLine();
232
+ }
233
+ const moduleBasePath = await findModulePath(path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules'), config.module, config.provider);
234
+ if (moduleBasePath) {
235
+ const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(projectInfo.framework, path_1.default.join((0, package_root_1.getPackageRoot)(), 'templates'));
236
+ const gen = new code_generator_1.AdvancedCodeGenerator(frameworkConfig);
237
+ await gen.loadGenerators(path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules'));
238
+ const moduleName = path_1.default.basename(moduleBasePath);
239
+ const available = gen.getAvailableGenerators();
240
+ const alreadyRegistered = (config.module === 'database' && available.databases.includes(moduleName)) || (config.module === 'auth' && available.auths.includes(moduleName));
241
+ if (!alreadyRegistered) {
242
+ const ops = [];
243
+ if (Array.isArray(moduleMetadata.patches)) {
244
+ for (const p of moduleMetadata.patches) {
245
+ if (p.type === 'create-file') {
246
+ ops.push({ type: 'create-file', source: p.source, destination: p.destination, condition: p.condition });
247
+ }
248
+ }
249
+ }
250
+ if (ops.length > 0) {
251
+ gen.registerGenerator(config.module, moduleName, {
252
+ name: moduleName,
253
+ type: config.module,
254
+ priority: 0,
255
+ operations: ops,
256
+ dependencies: {},
257
+ });
84
258
  }
85
259
  }
86
- if (options.dryRun) {
87
- logger_1.logger.warn("Dry run mode - no changes will be made");
88
- logger_1.logger.newLine();
260
+ const selectedModules = { framework: projectInfo.framework };
261
+ if (config.module === 'database' && config.provider) {
262
+ if (config.provider.startsWith('prisma-')) {
263
+ selectedModules.database = 'prisma';
264
+ selectedModules.prismaProvider = config.provider.split('-')[1];
265
+ }
266
+ else {
267
+ selectedModules.database = config.provider;
268
+ }
89
269
  }
90
- await applyModulePatches(projectRoot, projectInfo, moduleMetadata, path_1.default.join(__dirname, "..", "..", "modules"), module, options);
91
- if (moduleMetadata.frameworkPatches && !options.dryRun) {
92
- await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
270
+ if (config.module === 'auth' && config.provider) {
271
+ selectedModules.auth = config.provider;
93
272
  }
94
- if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options.dryRun) {
95
- const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
273
+ const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
274
+ if (postInstall && postInstall.length > 0 && !options?.dryRun) {
275
+ const postInstallSpinner = logger_1.logger.startSpinner('Running post-install commands...');
96
276
  try {
97
- for (const command of moduleMetadata.postInstall) {
98
- (0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
277
+ for (const command of postInstall) {
278
+ (0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: 'pipe' });
99
279
  }
100
- postInstallSpinner.succeed("Post-install commands completed");
280
+ postInstallSpinner.succeed('Post-install commands completed');
101
281
  }
102
282
  catch (error) {
103
- postInstallSpinner.fail("Failed to run post-install commands");
283
+ postInstallSpinner.fail('Failed to run post-install commands');
104
284
  throw error;
105
285
  }
106
286
  }
107
- if (Object.keys(mergedDeps).length > 0 && options.install !== false) {
108
- const deps = Object.entries(mergedDeps).map(([name, version]) => `${name}@${version}`);
109
- if (!options.dryRun) {
110
- await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, deps, false);
287
+ if (!options?.dryRun && options?.install !== false) {
288
+ const installSpinner = logger_1.logger.startSpinner('Installing dependencies...');
289
+ try {
290
+ await (0, package_manager_1.installDependencies)(projectRoot, projectInfo.packageManager);
291
+ installSpinner.succeed('Dependencies installed');
111
292
  }
112
- else {
113
- logger_1.logger.info(`Would add dependencies: ${deps.join(", ")}`);
293
+ catch (err) {
294
+ installSpinner.fail('Failed to install dependencies');
295
+ throw err;
114
296
  }
115
297
  }
116
- if (Object.keys(mergedDevDeps).length > 0 && options.install !== false) {
117
- const devDeps = Object.entries(mergedDevDeps).map(([name, version]) => `${name}@${version}`);
118
- if (!options.dryRun) {
119
- await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, devDeps, true);
120
- }
121
- else {
122
- logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
123
- }
298
+ return;
299
+ }
300
+ const mergedDeps = {};
301
+ const mergedDevDeps = {};
302
+ if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
303
+ Object.assign(mergedDeps, moduleMetadata.frameworkConfigs.shared.dependencies);
304
+ }
305
+ if (moduleMetadata.frameworkConfigs?.shared?.devDependencies) {
306
+ Object.assign(mergedDevDeps, moduleMetadata.frameworkConfigs.shared.devDependencies);
307
+ }
308
+ // Adapter-specific dependencies are applied via generator metadata; frameworkConfigs still merge above.
309
+ // Do not mutate the loaded module metadata here; use mergedDeps/mergedDevDeps for installation.
310
+ const variables = {};
311
+ if (selectedProvider) {
312
+ variables.provider = selectedProvider;
313
+ }
314
+ if (moduleMetadata.envVars) {
315
+ const envArray = Array.isArray(moduleMetadata.envVars)
316
+ ? moduleMetadata.envVars
317
+ : Object.entries(moduleMetadata.envVars).map(([k, v]) => ({ key: k, value: String(v) }));
318
+ for (const ev of envArray) {
319
+ if (ev.key && typeof ev.value === 'string')
320
+ variables[ev.key] = ev.value;
124
321
  }
125
- if (moduleMetadata.envVars && moduleMetadata.envVars.length > 0) {
126
- const processedEnvVars = moduleMetadata.envVars.map((envVar) => ({
127
- ...envVar,
128
- value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
129
- }));
130
- if (!options.dryRun) {
131
- await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options.force });
322
+ for (let pass = 0; pass < 5; pass++) {
323
+ let changed = false;
324
+ for (const ev of envArray) {
325
+ if (!ev.key || typeof ev.value !== 'string')
326
+ continue;
327
+ const resolved = ev.value.replace(/\{\{(\w+)\}\}/g, (_m, k) => variables[k] ?? _m);
328
+ if (variables[ev.key] !== resolved) {
329
+ variables[ev.key] = resolved;
330
+ changed = true;
331
+ }
132
332
  }
133
- else {
134
- logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
333
+ if (!changed)
334
+ break;
335
+ }
336
+ }
337
+ await applyModulePatches(projectRoot, projectInfo, moduleMetadata, path_1.default.join((0, package_root_1.getPackageRoot)(), "modules"), config.module, options || {});
338
+ if (moduleMetadata.frameworkPatches && !options?.dryRun) {
339
+ await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
340
+ }
341
+ if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options?.dryRun) {
342
+ const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
343
+ try {
344
+ for (const command of moduleMetadata.postInstall) {
345
+ (0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
135
346
  }
347
+ postInstallSpinner.succeed("Post-install commands completed");
136
348
  }
137
- logger_1.logger.newLine();
138
- logger_1.logger.success(`Added ${chalk_1.default.bold(moduleMetadata.displayName)}`);
139
- logger_1.logger.newLine();
140
- if (moduleMetadata.envVars && moduleMetadata.envVars.some((v) => v.required)) {
141
- logger_1.logger.log("Next: Fill in environment variables in .env");
349
+ catch (error) {
350
+ postInstallSpinner.fail("Failed to run post-install commands");
351
+ throw error;
142
352
  }
143
- logger_1.logger.newLine();
144
353
  }
145
- catch (error) {
146
- logger_1.logger.error(`Failed to add module: ${error.message}`);
147
- if (error instanceof Error && error.stack) {
148
- logger_1.logger.log(chalk_1.default.gray(error.stack));
354
+ if (Object.keys(mergedDeps).length > 0 && options?.install !== false) {
355
+ const deps = Object.entries(mergedDeps).map(([name, version]) => `${name}@${version}`);
356
+ if (!options?.dryRun) {
357
+ await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, deps, false);
358
+ }
359
+ else {
360
+ logger_1.logger.info(`Would add dependencies: ${deps.join(", ")}`);
361
+ }
362
+ }
363
+ if (Object.keys(mergedDevDeps).length > 0 && options?.install !== false) {
364
+ const devDeps = Object.entries(mergedDevDeps).map(([name, version]) => `${name}@${version}`);
365
+ if (!options?.dryRun) {
366
+ await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, devDeps, true);
367
+ }
368
+ else {
369
+ logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
370
+ }
371
+ }
372
+ if (moduleMetadata.envVars && moduleMetadata.envVars.length > 0) {
373
+ const processedEnvVars = moduleMetadata.envVars.map((envVar) => ({
374
+ ...envVar,
375
+ value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
376
+ }));
377
+ if (!options?.dryRun) {
378
+ await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options?.force });
379
+ }
380
+ else {
381
+ logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
149
382
  }
150
- process.exit(1);
151
383
  }
152
384
  }
153
385
  async function loadModuleMetadata(modulesDir, moduleName, provider) {
@@ -170,40 +402,16 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
170
402
  if (await fs_extra_1.default.pathExists(metadataPath)) {
171
403
  const metadata = await fs_extra_1.default.readJSON(metadataPath);
172
404
  if (provider && moduleDir === provider) {
173
- return await loadGeneratorAndMerge(metadata, modulePath);
405
+ return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
174
406
  }
175
- if (!provider && metadata.category === moduleName) {
176
- return await loadGeneratorAndMerge(metadata, modulePath);
407
+ if (!provider && (metadata.category === moduleName || moduleDir === moduleName)) {
408
+ return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
177
409
  }
178
410
  }
179
411
  }
180
412
  }
181
413
  return null;
182
414
  }
183
- async function loadGeneratorAndMerge(metadata, modulePath) {
184
- const generatorPath = path_1.default.join(modulePath, "generator.json");
185
- if (await fs_extra_1.default.pathExists(generatorPath)) {
186
- const generator = await fs_extra_1.default.readJSON(generatorPath);
187
- // Merge envVars, dependencies, etc.
188
- if (generator.envVars) {
189
- metadata.envVars = metadata.envVars || [];
190
- for (const [key, value] of Object.entries(generator.envVars)) {
191
- metadata.envVars.push({ key, value: value, description: `Environment variable for ${key}`, required: true });
192
- }
193
- }
194
- if (generator.dependencies) {
195
- metadata.dependencies = { ...metadata.dependencies, ...generator.dependencies };
196
- }
197
- if (generator.devDependencies) {
198
- metadata.devDependencies = { ...metadata.devDependencies, ...generator.devDependencies };
199
- }
200
- if (generator.scripts) {
201
- // Perhaps add to metadata, but currently not used
202
- }
203
- // For operations, perhaps add to patches or something, but for now, keep manual
204
- }
205
- return metadata;
206
- }
207
415
  async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, moduleName, options) {
208
416
  if (!moduleMetadata.patches || !Array.isArray(moduleMetadata.patches)) {
209
417
  return;