stackkit 0.2.0 → 0.2.1

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.
package/README.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  CLI for scaffolding and composing modular JavaScript applications.
4
4
 
5
- Quick Start
6
- -----------
5
+ ## Quick Start
7
6
 
8
7
  Create a new project without installing globally:
9
8
 
@@ -23,15 +22,13 @@ Check project health:
23
22
  npx stackkit@latest doctor
24
23
  ```
25
24
 
26
- Supported technologies
27
- ----------------------
25
+ ## Supported technologies
28
26
 
29
27
  - Frameworks: Next.js, React, Express
30
28
  - Databases: Prisma, Mongoose
31
29
  - Auth: Auth.js / Better Auth
32
30
 
33
- Links
34
- -----
31
+ ## Links
35
32
 
36
33
  - Website: https://stackkit.tariqul.dev
37
- - Repo: https://github.com/tariqul420/stackkit
34
+ - Repo: https://github.com/tariqul420/stackkit
package/dist/cli/add.js CHANGED
@@ -9,14 +9,16 @@ 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");
13
- const detect_1 = require("../lib/project/detect");
12
+ const module_discovery_1 = require("../lib/discovery/module-discovery");
13
+ const shared_1 = require("../lib/discovery/shared");
14
14
  const env_editor_1 = require("../lib/env/env-editor");
15
+ const framework_utils_1 = require("../lib/framework/framework-utils");
15
16
  const files_1 = require("../lib/fs/files");
16
- const logger_1 = require("../lib/ui/logger");
17
+ const code_generator_1 = require("../lib/generation/code-generator");
17
18
  const package_manager_1 = require("../lib/pm/package-manager");
19
+ const detect_1 = require("../lib/project/detect");
20
+ const logger_1 = require("../lib/ui/logger");
18
21
  const package_root_1 = require("../lib/utils/package-root");
19
- const framework_utils_1 = require("../lib/framework/framework-utils");
20
22
  async function addCommand(module, options) {
21
23
  try {
22
24
  const projectRoot = process.cwd();
@@ -39,13 +41,9 @@ async function addCommand(module, options) {
39
41
  }
40
42
  async function getAddConfig(module, options, projectInfo) {
41
43
  const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
42
- // If no module provided, go interactive
43
44
  if (!module) {
44
45
  return await getInteractiveConfig(modulesDir, projectInfo);
45
46
  }
46
- // Only allow: no-arg interactive, or explicit category + --provider.
47
- // Disallow positional provider names like `npx stackkit@latest add better-auth` or
48
- // `npx stackkit@latest add auth prisma-postgresql` — require `--provider` flag.
49
47
  if (module === "database" || module === "auth") {
50
48
  if (!options?.provider) {
51
49
  if (module === "database") {
@@ -56,13 +54,11 @@ async function getAddConfig(module, options, projectInfo) {
56
54
  }
57
55
  }
58
56
  if (module === "database") {
59
- let baseProvider = options.provider;
60
- let adapterProvider = options.provider;
61
- if (options.provider.includes("-")) {
62
- const parts = options.provider.split("-");
63
- baseProvider = parts[0]; // e.g., "prisma"
64
- adapterProvider = options.provider; // e.g., "prisma-postgresql"
65
- }
57
+ const parsed = (0, shared_1.parseDatabaseOption)(options.provider || "");
58
+ const baseProvider = parsed.database;
59
+ const adapterProvider = parsed.provider
60
+ ? `${parsed.database}-${parsed.provider}`
61
+ : parsed.database;
66
62
  const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
67
63
  if (!moduleMetadata) {
68
64
  throw new Error(`Database provider "${baseProvider}" not found`);
@@ -70,7 +66,9 @@ async function getAddConfig(module, options, projectInfo) {
70
66
  return {
71
67
  module: "database",
72
68
  provider: adapterProvider,
73
- displayName: `${moduleMetadata.displayName} (${adapterProvider.split("-")[1] || adapterProvider})`,
69
+ displayName: parsed.database === "prisma" && parsed.provider
70
+ ? `${moduleMetadata.displayName} (${parsed.provider})`
71
+ : moduleMetadata.displayName,
74
72
  metadata: moduleMetadata,
75
73
  };
76
74
  }
@@ -104,59 +102,46 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
104
102
  },
105
103
  ]);
106
104
  const category = answers.category;
105
+ const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
107
106
  if (category === "database") {
107
+ const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || "nextjs");
108
108
  const dbAnswers = await inquirer_1.default.prompt([
109
109
  {
110
110
  type: "list",
111
111
  name: "database",
112
112
  message: "Select database:",
113
- choices: [
114
- { name: "Prisma", value: "prisma" },
115
- { name: "Mongoose", value: "mongoose" },
116
- ],
113
+ choices: dbChoices,
117
114
  },
118
115
  ]);
119
116
  const selectedDb = dbAnswers.database;
120
- if (selectedDb === "prisma") {
121
- const providerAnswers = await inquirer_1.default.prompt([
122
- {
123
- type: "list",
124
- name: "provider",
125
- message: "Select Prisma provider:",
126
- choices: [
127
- { name: "PostgreSQL", value: "postgresql" },
128
- { name: "MongoDB", value: "mongodb" },
129
- { name: "MySQL", value: "mysql" },
130
- { name: "SQLite", value: "sqlite" },
131
- ],
132
- },
133
- ]);
117
+ if (selectedDb.startsWith("prisma-")) {
118
+ const provider = selectedDb.split("-")[1];
134
119
  return {
135
120
  module: "database",
136
- provider: `prisma-${providerAnswers.provider}`,
137
- displayName: `Prisma (${providerAnswers.provider})`,
121
+ provider: `prisma-${provider}`,
122
+ displayName: `Prisma (${provider})`,
138
123
  metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
139
124
  };
140
125
  }
141
- else {
142
- return {
143
- module: "database",
144
- provider: "mongoose",
145
- displayName: "Mongoose",
146
- metadata: (await loadModuleMetadata(modulesDir, "mongoose", "mongoose")),
147
- };
148
- }
126
+ // Other databases (mongoose, etc.)
127
+ const meta = (await loadModuleMetadata(modulesDir, selectedDb, selectedDb));
128
+ if (!meta)
129
+ throw new Error(`Database provider "${selectedDb}" not found`);
130
+ return {
131
+ module: "database",
132
+ provider: selectedDb,
133
+ displayName: meta.displayName || selectedDb,
134
+ metadata: meta,
135
+ };
149
136
  }
150
137
  else if (category === "auth") {
138
+ const authChoices = (discovered.auth || []).map((a) => ({ name: a.displayName, value: a.name }));
151
139
  const authAnswers = await inquirer_1.default.prompt([
152
140
  {
153
141
  type: "list",
154
142
  name: "auth",
155
143
  message: "Select authentication:",
156
- choices: [
157
- { name: "Better Auth", value: "better-auth" },
158
- { name: "Auth.js", value: "authjs" },
159
- ],
144
+ choices: authChoices,
160
145
  },
161
146
  ]);
162
147
  const selectedAuth = authAnswers.auth;
@@ -164,67 +149,19 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
164
149
  if (!metadata) {
165
150
  throw new Error(`Auth provider "${selectedAuth}" not found`);
166
151
  }
167
- if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
152
+ if (projectInfo && metadata.supportedFrameworks && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
168
153
  throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
169
154
  }
170
155
  return {
171
156
  module: "auth",
172
157
  provider: selectedAuth,
173
- displayName: selectedAuth === "better-auth" ? "Better Auth" : "Auth.js",
158
+ displayName: metadata.displayName || selectedAuth,
174
159
  metadata,
175
160
  };
176
161
  }
177
162
  throw new Error("Invalid selection");
178
163
  }
179
- async function getProviderConfig(modulesDir, provider, projectInfo) {
180
- if (provider.includes("-")) {
181
- const parts = provider.split("-");
182
- const baseProvider = parts[0];
183
- const specificProvider = provider;
184
- if (baseProvider === "prisma") {
185
- const metadata = await loadModuleMetadata(modulesDir, "database", baseProvider);
186
- if (!metadata) {
187
- throw new Error(`Database provider "${baseProvider}" not found`);
188
- }
189
- return {
190
- module: "database",
191
- provider: specificProvider,
192
- displayName: `Prisma (${parts[1]})`,
193
- metadata,
194
- };
195
- }
196
- }
197
- else {
198
- if (provider === "mongoose") {
199
- const metadata = await loadModuleMetadata(modulesDir, "database", "mongoose");
200
- if (!metadata) {
201
- throw new Error(`Database provider "${provider}" not found`);
202
- }
203
- return {
204
- module: "database",
205
- provider: "mongoose",
206
- displayName: "Mongoose",
207
- metadata,
208
- };
209
- }
210
- else if (provider === "better-auth" || provider === "authjs") {
211
- const metadata = await loadModuleMetadata(modulesDir, provider, provider);
212
- if (!metadata) {
213
- throw new Error(`Auth provider "${provider}" not found`);
214
- }
215
- if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
216
- throw new Error(`Auth provider "${provider}" does not support ${projectInfo.framework}`);
217
- }
218
- return {
219
- module: "auth",
220
- provider,
221
- displayName: provider === "better-auth" ? "Better Auth" : "Auth.js",
222
- metadata,
223
- };
224
- }
225
- }
226
- throw new Error(`Unknown provider "${provider}". Available providers: better-auth, authjs, mongoose, prisma-postgresql, prisma-mongodb, prisma-mysql, prisma-sqlite`);
227
- }
164
+ /* removed unused getProviderConfig discovery-based flows handle providers */
228
165
  async function addModuleToProject(projectRoot, projectInfo, config, options) {
229
166
  const moduleMetadata = config.metadata;
230
167
  const selectedProvider = config.provider;
@@ -297,12 +234,10 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
297
234
  }
298
235
  const selectedModules = { framework: projectInfo.framework };
299
236
  if (config.module === "database" && config.provider) {
300
- if (config.provider.startsWith("prisma-")) {
301
- selectedModules.database = "prisma";
302
- selectedModules.prismaProvider = config.provider.split("-")[1];
303
- }
304
- else {
305
- selectedModules.database = config.provider;
237
+ const parsed = (0, shared_1.parseDatabaseOption)(config.provider);
238
+ selectedModules.database = parsed.database;
239
+ if (parsed.database === "prisma" && parsed.provider) {
240
+ selectedModules.prismaProvider = parsed.provider;
306
241
  }
307
242
  }
308
243
  if (config.module === "auth" && config.provider) {
@@ -10,15 +10,16 @@ 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
12
  const validate_npm_package_name_1 = __importDefault(require("validate-npm-package-name"));
13
- const git_utils_1 = require("../lib/git-utils");
14
13
  const js_conversion_1 = require("../lib/conversion/js-conversion");
15
- const package_manager_1 = require("../lib/pm/package-manager");
16
- const logger_1 = require("../lib/ui/logger");
17
- const framework_utils_1 = require("../lib/framework/framework-utils");
18
14
  const module_discovery_1 = require("../lib/discovery/module-discovery");
15
+ const shared_1 = require("../lib/discovery/shared");
16
+ const env_editor_1 = require("../lib/env/env-editor");
17
+ const framework_utils_1 = require("../lib/framework/framework-utils");
19
18
  const code_generator_1 = require("../lib/generation/code-generator");
19
+ const git_utils_1 = require("../lib/git-utils");
20
+ const package_manager_1 = require("../lib/pm/package-manager");
21
+ const logger_1 = require("../lib/ui/logger");
20
22
  const package_root_1 = require("../lib/utils/package-root");
21
- const env_editor_1 = require("../lib/env/env-editor");
22
23
  async function createProject(projectName, options) {
23
24
  logger_1.logger.newLine();
24
25
  logger_1.logger.log(chalk_1.default.bold.cyan("📦 Create StackKit App"));
@@ -91,7 +92,7 @@ async function getProjectConfig(projectName, options) {
91
92
  let database = "none";
92
93
  let prismaProvider;
93
94
  if (db && db !== "none") {
94
- const parsed = (0, module_discovery_1.parseDatabaseOption)(db);
95
+ const parsed = (0, shared_1.parseDatabaseOption)(db);
95
96
  database = parsed.database;
96
97
  prismaProvider = parsed.provider;
97
98
  }
@@ -151,12 +152,16 @@ async function getProjectConfig(projectName, options) {
151
152
  name: "database",
152
153
  message: "Select database/ORM:",
153
154
  when: (answers) => answers.framework !== "react",
154
- choices: [
155
- { name: "Prisma", value: "prisma" },
156
- { name: "Mongoose", value: "mongoose" },
157
- { name: "None", value: "none" },
158
- ],
155
+ choices: (answers) => discoveredModules.databases && discoveredModules.databases.length > 0
156
+ ? (0, module_discovery_1.getDatabaseChoices)(discoveredModules.databases, answers.framework)
157
+ : [
158
+ { name: "Prisma", value: "prisma" },
159
+ { name: "Mongoose", value: "mongoose" },
160
+ { name: "None", value: "none" },
161
+ ],
159
162
  },
163
+ // If a prisma-* choice is selected above, `prismaProvider` will be derived from it,
164
+ // otherwise prompt for provider when `prisma` is selected directly.
160
165
  {
161
166
  type: "list",
162
167
  name: "prismaProvider",
@@ -199,13 +204,21 @@ async function getProjectConfig(projectName, options) {
199
204
  default: "pnpm",
200
205
  },
201
206
  ]));
207
+ // Normalize database answer (interactive flow): handle values like `prisma-postgresql`
208
+ let databaseAnswer = answers.framework === "react" ? "none" : answers.database;
209
+ let prismaProviderAnswer = answers.prismaProvider;
210
+ if (typeof databaseAnswer === "string" && databaseAnswer.startsWith("prisma-")) {
211
+ const parts = databaseAnswer.split("-");
212
+ if (parts.length >= 2) {
213
+ prismaProviderAnswer = parts[1];
214
+ databaseAnswer = "prisma";
215
+ }
216
+ }
202
217
  return {
203
218
  projectName: (projectName || answers.projectName),
204
219
  framework: answers.framework,
205
- database: (answers.framework === "react"
206
- ? "none"
207
- : answers.database),
208
- prismaProvider: answers.prismaProvider,
220
+ database: databaseAnswer,
221
+ prismaProvider: prismaProviderAnswer,
209
222
  auth: answers.auth || "none",
210
223
  language: answers.language,
211
224
  packageManager: answers.packageManager,
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const logger_1 = require("../lib/ui/logger");
11
+ const package_root_1 = require("../lib/utils/package-root");
11
12
  // Constants for consistent messaging
12
13
  const MESSAGES = {
13
14
  NO_PACKAGE_JSON: "No package.json found in current directory or any parent directory.",
@@ -211,24 +212,125 @@ function checkNodeVersion() {
211
212
  function detectAuthModules(packageJson) {
212
213
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
213
214
  const modules = [];
214
- if (deps["better-auth"]) {
215
- modules.push("better-auth");
215
+ try {
216
+ const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules", "auth");
217
+ if (fs_extra_1.default.existsSync(modulesDir)) {
218
+ const authDirs = fs_extra_1.default.readdirSync(modulesDir);
219
+ for (const authDir of authDirs) {
220
+ try {
221
+ const genPath = path_1.default.join(modulesDir, authDir, "generator.json");
222
+ const modJson = path_1.default.join(modulesDir, authDir, "module.json");
223
+ let pkgNames = [];
224
+ if (fs_extra_1.default.existsSync(genPath)) {
225
+ const gen = JSON.parse(fs_extra_1.default.readFileSync(genPath, "utf-8"));
226
+ if (Array.isArray(gen.operations)) {
227
+ for (const op of gen.operations) {
228
+ if (op.dependencies && typeof op.dependencies === "object") {
229
+ pkgNames.push(...Object.keys(op.dependencies));
230
+ }
231
+ if (op.devDependencies && typeof op.devDependencies === "object") {
232
+ pkgNames.push(...Object.keys(op.devDependencies));
233
+ }
234
+ }
235
+ }
236
+ }
237
+ // Fallback: check module.json provider/name
238
+ let moduleName = authDir;
239
+ if (fs_extra_1.default.existsSync(modJson)) {
240
+ try {
241
+ const m = JSON.parse(fs_extra_1.default.readFileSync(modJson, "utf-8"));
242
+ if (m && m.name)
243
+ moduleName = m.name;
244
+ }
245
+ catch {
246
+ /* ignore */
247
+ }
248
+ }
249
+ for (const pkg of pkgNames) {
250
+ if (deps[pkg]) {
251
+ modules.push(moduleName);
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ catch {
257
+ // ignore per-module errors
258
+ }
259
+ }
260
+ }
216
261
  }
217
- if (deps["next-auth"]) {
218
- modules.push("authjs");
262
+ catch {
263
+ // ignore discovery errors
219
264
  }
220
- return modules;
265
+ // Fallback to original simple checks if nothing found
266
+ if (modules.length === 0) {
267
+ if (deps["better-auth"])
268
+ modules.push("better-auth");
269
+ if (deps["next-auth"])
270
+ modules.push("authjs");
271
+ }
272
+ return Array.from(new Set(modules));
221
273
  }
222
274
  function detectDatabaseModules(packageJson) {
223
275
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
224
276
  const modules = [];
225
- if (deps["@prisma/client"] || deps["prisma"]) {
226
- modules.push("prisma");
277
+ try {
278
+ const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules", "database");
279
+ if (fs_extra_1.default.existsSync(modulesDir)) {
280
+ const dbDirs = fs_extra_1.default.readdirSync(modulesDir);
281
+ for (const dbDir of dbDirs) {
282
+ try {
283
+ const genPath = path_1.default.join(modulesDir, dbDir, "generator.json");
284
+ const modJson = path_1.default.join(modulesDir, dbDir, "module.json");
285
+ let pkgNames = [];
286
+ if (fs_extra_1.default.existsSync(genPath)) {
287
+ const gen = JSON.parse(fs_extra_1.default.readFileSync(genPath, "utf-8"));
288
+ if (Array.isArray(gen.operations)) {
289
+ for (const op of gen.operations) {
290
+ if (op.dependencies && typeof op.dependencies === "object") {
291
+ pkgNames.push(...Object.keys(op.dependencies));
292
+ }
293
+ if (op.devDependencies && typeof op.devDependencies === "object") {
294
+ pkgNames.push(...Object.keys(op.devDependencies));
295
+ }
296
+ }
297
+ }
298
+ }
299
+ let moduleName = dbDir;
300
+ if (fs_extra_1.default.existsSync(modJson)) {
301
+ try {
302
+ const m = JSON.parse(fs_extra_1.default.readFileSync(modJson, "utf-8"));
303
+ if (m && m.name)
304
+ moduleName = m.name;
305
+ }
306
+ catch {
307
+ /* ignore */
308
+ }
309
+ }
310
+ for (const pkg of pkgNames) {
311
+ if (deps[pkg]) {
312
+ modules.push(moduleName);
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ catch {
318
+ // ignore per-module errors
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // ignore discovery errors
227
325
  }
228
- if (deps["mongoose"]) {
229
- modules.push("mongoose");
326
+ // Fallback to original checks if nothing found
327
+ if (modules.length === 0) {
328
+ if (deps["@prisma/client"] || deps["prisma"])
329
+ modules.push("prisma");
330
+ if (deps["mongoose"])
331
+ modules.push("mongoose");
230
332
  }
231
- return modules;
333
+ return Array.from(new Set(modules));
232
334
  }
233
335
  async function checkKeyFiles(projectRoot, projectType, authModules, databaseModules) {
234
336
  const checks = [];
@@ -260,8 +362,42 @@ async function checkKeyFiles(projectRoot, projectType, authModules, databaseModu
260
362
  async function checkAuthRoutesExist(projectRoot, projectType) {
261
363
  if (projectType !== "nextjs")
262
364
  return true; // Skip for non-Next.js
263
- const possiblePaths = [
264
- // NextAuth routes
365
+ // Build candidate auth route paths from generator.json files in modules/auth
366
+ const candidates = new Set();
367
+ try {
368
+ const authModulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules", "auth");
369
+ if (await fs_extra_1.default.pathExists(authModulesDir)) {
370
+ const authDirs = await fs_extra_1.default.readdir(authModulesDir);
371
+ for (const dir of authDirs) {
372
+ const genPath = path_1.default.join(authModulesDir, dir, "generator.json");
373
+ if (!(await fs_extra_1.default.pathExists(genPath)))
374
+ continue;
375
+ try {
376
+ const gen = await fs_extra_1.default.readJson(genPath);
377
+ if (Array.isArray(gen.operations)) {
378
+ for (const op of gen.operations) {
379
+ if (typeof op.destination === "string")
380
+ candidates.add(op.destination);
381
+ if (Array.isArray(op.operations)) {
382
+ for (const sub of op.operations) {
383
+ if (typeof sub.destination === "string")
384
+ candidates.add(sub.destination);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ catch {
391
+ // ignore malformed generator
392
+ }
393
+ }
394
+ }
395
+ }
396
+ catch {
397
+ // ignore discovery errors
398
+ }
399
+ // Fallback to known common paths if generators don't provide any
400
+ const fallback = [
265
401
  "app/api/auth/[...nextauth]/route.ts",
266
402
  "app/api/auth/[...nextauth]/route.js",
267
403
  "src/app/api/auth/[...nextauth]/route.ts",
@@ -270,13 +406,14 @@ async function checkAuthRoutesExist(projectRoot, projectType) {
270
406
  "pages/api/auth/[...nextauth].js",
271
407
  "src/pages/api/auth/[...nextauth].ts",
272
408
  "src/pages/api/auth/[...nextauth].js",
273
- // Better Auth routes
274
409
  "app/api/auth/[...all]/route.ts",
275
410
  "app/api/auth/[...all]/route.js",
276
411
  "src/app/api/auth/[...all]/route.ts",
277
412
  "src/app/api/auth/[...all]/route.js",
278
413
  ];
279
- for (const routePath of possiblePaths) {
414
+ for (const p of fallback)
415
+ candidates.add(p);
416
+ for (const routePath of candidates) {
280
417
  if (await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, routePath))) {
281
418
  return true;
282
419
  }
@@ -288,14 +425,59 @@ async function checkEnvFiles(projectRoot, authModules, databaseModules) {
288
425
  const requiredKeys = [];
289
426
  const missing = [];
290
427
  const present = [];
291
- if (databaseModules.includes("prisma")) {
292
- requiredKeys.push("DATABASE_URL");
293
- }
294
- if (authModules.includes("authjs")) {
295
- requiredKeys.push("NEXTAUTH_SECRET", "NEXTAUTH_URL");
428
+ // Dynamically collect required env keys from generator.json for detected modules
429
+ try {
430
+ const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
431
+ async function collectEnvKeys(category, name) {
432
+ const genPath = path_1.default.join(modulesDir, category, name, "generator.json");
433
+ if (!(await fs_extra_1.default.pathExists(genPath)))
434
+ return;
435
+ try {
436
+ const gen = await fs_extra_1.default.readJson(genPath);
437
+ if (Array.isArray(gen.operations)) {
438
+ for (const op of gen.operations) {
439
+ if (op.type === "add-env" && op.envVars && typeof op.envVars === "object") {
440
+ for (const k of Object.keys(op.envVars)) {
441
+ if (!requiredKeys.includes(k))
442
+ requiredKeys.push(k);
443
+ }
444
+ }
445
+ // Also check nested operations (e.g., patch-file -> operations)
446
+ if (Array.isArray(op.operations)) {
447
+ for (const sub of op.operations) {
448
+ if (sub.type === "add-env" && sub.envVars && typeof sub.envVars === "object") {
449
+ for (const k of Object.keys(sub.envVars)) {
450
+ if (!requiredKeys.includes(k))
451
+ requiredKeys.push(k);
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
459
+ catch {
460
+ // ignore malformed generator
461
+ }
462
+ }
463
+ for (const db of databaseModules) {
464
+ await collectEnvKeys("database", db);
465
+ }
466
+ for (const auth of authModules) {
467
+ await collectEnvKeys("auth", auth);
468
+ }
296
469
  }
297
- if (authModules.includes("better-auth")) {
298
- requiredKeys.push("BETTER_AUTH_SECRET", "BETTER_AUTH_URL");
470
+ catch {
471
+ // fallback to previous minimal checks if discovery fails
472
+ if (databaseModules.includes("prisma")) {
473
+ requiredKeys.push("DATABASE_URL");
474
+ }
475
+ if (authModules.includes("authjs")) {
476
+ requiredKeys.push("NEXTAUTH_SECRET", "NEXTAUTH_URL");
477
+ }
478
+ if (authModules.includes("better-auth")) {
479
+ requiredKeys.push("BETTER_AUTH_SECRET", "BETTER_AUTH_URL");
480
+ }
299
481
  }
300
482
  const envPaths = [".env", ".env.local"];
301
483
  let envContent = "";