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