stackkit 0.2.2 → 0.2.3
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/bin/stackkit.js +10 -3
- package/dist/cli/add.js +201 -60
- package/dist/cli/create.js +74 -32
- package/dist/cli/doctor.js +38 -159
- package/dist/cli/list.js +10 -40
- package/dist/index.js +4 -4
- package/dist/lib/discovery/installed-detection.d.ts +7 -0
- package/dist/lib/discovery/installed-detection.js +134 -0
- package/dist/lib/discovery/module-discovery.js +7 -14
- package/dist/lib/discovery/shared.js +9 -7
- package/dist/lib/generation/code-generator.d.ts +2 -0
- package/dist/lib/generation/code-generator.js +96 -2
- package/dist/lib/generation/generator-utils.d.ts +1 -1
- package/dist/lib/generation/generator-utils.js +5 -3
- package/dist/lib/project/detect.js +59 -29
- package/dist/types/index.d.ts +21 -17
- package/modules/auth/better-auth/files/lib/auth.ts +2 -2
- package/package.json +6 -3
- package/templates/express/env.example +2 -0
- package/templates/express/gitignore +23 -0
- package/templates/nextjs/env.example +1 -0
- package/templates/nextjs/gitignore +42 -0
- package/templates/react/env.example +1 -0
- package/templates/react/gitignore +44 -0
- package/templates/react/prettierignore +4 -0
- package/templates/react/prettierrc +9 -0
- package/templates/react/src/utils/utils.ts +1 -1
package/bin/stackkit.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require("../dist/index.js");
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
try {
|
|
4
|
+
require("../dist/index.js");
|
|
5
|
+
} catch (err) {
|
|
6
|
+
console.error(
|
|
7
|
+
"Failed to load compiled CLI (did you run 'npm run build'?)",
|
|
8
|
+
err && err.message ? err.message : err,
|
|
9
|
+
);
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
package/dist/cli/add.js
CHANGED
|
@@ -26,9 +26,20 @@ async function addCommand(module, options) {
|
|
|
26
26
|
const projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
27
27
|
spinner.succeed(`Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`);
|
|
28
28
|
const config = await getAddConfig(module, options, projectInfo);
|
|
29
|
-
|
|
29
|
+
// Refresh project detection in case getAddConfig performed a pre-add (database)
|
|
30
|
+
const refreshedProjectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
31
|
+
await addModuleToProject(projectRoot, refreshedProjectInfo, config, options);
|
|
30
32
|
logger_1.logger.newLine();
|
|
31
|
-
|
|
33
|
+
if (config.preAdded && config.preAdded.length > 0) {
|
|
34
|
+
const addedNames = [
|
|
35
|
+
...config.preAdded.map((p) => p.displayName),
|
|
36
|
+
config.displayName,
|
|
37
|
+
].map((s) => chalk_1.default.bold(s));
|
|
38
|
+
logger_1.logger.success(`Added ${addedNames.join(" and ")}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
|
|
42
|
+
}
|
|
32
43
|
logger_1.logger.newLine();
|
|
33
44
|
}
|
|
34
45
|
catch (error) {
|
|
@@ -42,7 +53,7 @@ async function addCommand(module, options) {
|
|
|
42
53
|
async function getAddConfig(module, options, projectInfo) {
|
|
43
54
|
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
|
|
44
55
|
if (!module) {
|
|
45
|
-
return await getInteractiveConfig(modulesDir, projectInfo);
|
|
56
|
+
return await getInteractiveConfig(modulesDir, projectInfo, options);
|
|
46
57
|
}
|
|
47
58
|
if (module === "database" || module === "auth") {
|
|
48
59
|
if (!options?.provider) {
|
|
@@ -67,8 +78,8 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
67
78
|
module: "database",
|
|
68
79
|
provider: adapterProvider,
|
|
69
80
|
displayName: parsed.database === "prisma" && parsed.provider
|
|
70
|
-
? `${moduleMetadata.displayName} (${parsed.provider})`
|
|
71
|
-
: moduleMetadata.displayName,
|
|
81
|
+
? `${moduleMetadata.displayName || baseProvider} (${parsed.provider})`
|
|
82
|
+
: moduleMetadata.displayName || baseProvider,
|
|
72
83
|
metadata: moduleMetadata,
|
|
73
84
|
};
|
|
74
85
|
}
|
|
@@ -78,24 +89,26 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
78
89
|
if (!moduleMetadata) {
|
|
79
90
|
throw new Error(`Auth provider "${provider}" not found`);
|
|
80
91
|
}
|
|
81
|
-
// Validate compatibility with existing project
|
|
82
92
|
if (projectInfo) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
throw new Error("Auth.js is only supported with Next.js and Prisma database in this project");
|
|
93
|
+
if (moduleMetadata.supportedFrameworks &&
|
|
94
|
+
!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
95
|
+
throw new Error(`${moduleMetadata.displayName} is not supported on ${projectInfo.framework}`);
|
|
87
96
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const dbName = projectInfo.hasPrisma
|
|
98
|
+
? "prisma"
|
|
99
|
+
: projectInfo.hasDatabase
|
|
100
|
+
? "other"
|
|
101
|
+
: "none";
|
|
102
|
+
if (moduleMetadata.compatibility &&
|
|
103
|
+
moduleMetadata.compatibility.databases &&
|
|
104
|
+
!moduleMetadata.compatibility.databases.includes(dbName)) {
|
|
105
|
+
throw new Error(`${moduleMetadata.displayName} is not compatible with the project's database configuration`);
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
return {
|
|
96
109
|
module: "auth",
|
|
97
110
|
provider,
|
|
98
|
-
displayName: moduleMetadata.displayName,
|
|
111
|
+
displayName: moduleMetadata.displayName || provider,
|
|
99
112
|
metadata: moduleMetadata,
|
|
100
113
|
};
|
|
101
114
|
}
|
|
@@ -103,15 +116,17 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
103
116
|
// Unknown module type
|
|
104
117
|
throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
|
|
105
118
|
}
|
|
106
|
-
async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
107
|
-
|
|
119
|
+
async function getInteractiveConfig(modulesDir, projectInfo, options) {
|
|
120
|
+
const projectRoot = process.cwd();
|
|
108
121
|
const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
|
|
109
|
-
|
|
122
|
+
// Prefer discovered framework, then projectInfo.framework; leave empty if unknown
|
|
123
|
+
const defaultFramework = (discovered.frameworks && discovered.frameworks[0]?.name) || projectInfo?.framework || "";
|
|
124
|
+
const compatibleAuths = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || defaultFramework, projectInfo?.hasPrisma ? "prisma" : "none");
|
|
110
125
|
const categories = [
|
|
111
126
|
{ name: "Database", value: "database" },
|
|
112
127
|
];
|
|
113
|
-
//
|
|
114
|
-
if (compatibleAuths.length >
|
|
128
|
+
// Offer Auth category when there's at least one compatible auth option
|
|
129
|
+
if (compatibleAuths.length > 0) {
|
|
115
130
|
categories.push({ name: "Auth", value: "auth" });
|
|
116
131
|
}
|
|
117
132
|
const answers = await inquirer_1.default.prompt([
|
|
@@ -124,7 +139,7 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
124
139
|
]);
|
|
125
140
|
const category = answers.category;
|
|
126
141
|
if (category === "database") {
|
|
127
|
-
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework ||
|
|
142
|
+
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || defaultFramework);
|
|
128
143
|
const dbAnswers = await inquirer_1.default.prompt([
|
|
129
144
|
{
|
|
130
145
|
type: "list",
|
|
@@ -155,9 +170,57 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
155
170
|
};
|
|
156
171
|
}
|
|
157
172
|
else if (category === "auth") {
|
|
158
|
-
|
|
173
|
+
let preAddedForReturn;
|
|
174
|
+
// If no database detected, require the user to select/add a database first
|
|
175
|
+
if (!projectInfo?.hasDatabase) {
|
|
176
|
+
logger_1.logger.warn("No database detected in the project. Authentication requires a database.");
|
|
177
|
+
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || defaultFramework);
|
|
178
|
+
const dbAnswer = await inquirer_1.default.prompt([
|
|
179
|
+
{
|
|
180
|
+
type: "list",
|
|
181
|
+
name: "database",
|
|
182
|
+
message: "Select a database to add before authentication:",
|
|
183
|
+
choices: dbChoices,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
const selectedDb = dbAnswer.database;
|
|
187
|
+
if (!selectedDb || selectedDb === "none") {
|
|
188
|
+
logger_1.logger.info("Cancelled — authentication requires a database");
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
// Build a database AddConfig and add it immediately, then refresh projectInfo
|
|
192
|
+
let dbConfig;
|
|
193
|
+
if (selectedDb.startsWith("prisma-")) {
|
|
194
|
+
const provider = selectedDb.split("-")[1];
|
|
195
|
+
dbConfig = {
|
|
196
|
+
module: "database",
|
|
197
|
+
provider: `prisma-${provider}`,
|
|
198
|
+
displayName: `Prisma (${provider})`,
|
|
199
|
+
metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const meta = (await loadModuleMetadata(modulesDir, selectedDb, selectedDb));
|
|
204
|
+
if (!meta)
|
|
205
|
+
throw new Error(`Database provider "${selectedDb}" not found`);
|
|
206
|
+
dbConfig = {
|
|
207
|
+
module: "database",
|
|
208
|
+
provider: selectedDb,
|
|
209
|
+
displayName: meta.displayName || selectedDb,
|
|
210
|
+
metadata: meta,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// Add the database first (suppress its top-level success message)
|
|
214
|
+
await addModuleToProject(projectRoot, projectInfo || (await (0, detect_1.detectProjectInfo)(projectRoot)), dbConfig, options);
|
|
215
|
+
// Refresh project info after database install and record pre-added
|
|
216
|
+
projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
217
|
+
// attach preAdded so caller can summarize chained additions
|
|
218
|
+
dbConfig.preAdded = dbConfig.preAdded || [];
|
|
219
|
+
// store for returning later
|
|
220
|
+
preAddedForReturn = dbConfig;
|
|
221
|
+
}
|
|
159
222
|
const dbString = projectInfo?.hasPrisma ? "prisma" : "none";
|
|
160
|
-
const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework ||
|
|
223
|
+
const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || defaultFramework, dbString);
|
|
161
224
|
const authAnswers = await inquirer_1.default.prompt([
|
|
162
225
|
{
|
|
163
226
|
type: "list",
|
|
@@ -180,12 +243,17 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
180
243
|
!metadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
181
244
|
throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
|
|
182
245
|
}
|
|
183
|
-
|
|
246
|
+
const result = {
|
|
184
247
|
module: "auth",
|
|
185
248
|
provider: selectedAuth,
|
|
186
249
|
displayName: metadata.displayName || selectedAuth,
|
|
187
250
|
metadata,
|
|
188
251
|
};
|
|
252
|
+
// If we added a DB earlier in this flow, include it for grouped messaging
|
|
253
|
+
if (typeof preAddedForReturn !== "undefined" && preAddedForReturn) {
|
|
254
|
+
result.preAdded = [preAddedForReturn];
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
189
257
|
}
|
|
190
258
|
throw new Error("Invalid selection");
|
|
191
259
|
}
|
|
@@ -261,6 +329,32 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
261
329
|
}
|
|
262
330
|
}
|
|
263
331
|
const selectedModules = { framework: projectInfo.framework };
|
|
332
|
+
// Populate database context from detected project state so generator conditions work
|
|
333
|
+
try {
|
|
334
|
+
const pkg = await fs_extra_1.default.readJson(path_1.default.join(projectRoot, "package.json"));
|
|
335
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
336
|
+
if (projectInfo.hasPrisma) {
|
|
337
|
+
selectedModules.database = "prisma";
|
|
338
|
+
// Parse provider specifically from the `datasource` block to avoid
|
|
339
|
+
// capturing the generator block (which uses provider = "prisma-client-js").
|
|
340
|
+
const prismaSchema = path_1.default.join(projectRoot, "prisma", "schema.prisma");
|
|
341
|
+
if (await fs_extra_1.default.pathExists(prismaSchema)) {
|
|
342
|
+
const content = await fs_extra_1.default.readFile(prismaSchema, "utf-8");
|
|
343
|
+
const dsMatch = content.match(/datasource\s+\w+\s*\{([\s\S]*?)\}/i);
|
|
344
|
+
if (dsMatch && dsMatch[1]) {
|
|
345
|
+
const provMatch = dsMatch[1].match(/provider\s*=\s*["']([^"']+)["']/i);
|
|
346
|
+
if (provMatch && provMatch[1])
|
|
347
|
+
selectedModules.prismaProvider = provMatch[1];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (deps["mongoose"]) {
|
|
352
|
+
selectedModules.database = "mongoose";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// ignore detection errors
|
|
357
|
+
}
|
|
264
358
|
if (config.module === "database" && config.provider) {
|
|
265
359
|
const parsed = (0, shared_1.parseDatabaseOption)(config.provider);
|
|
266
360
|
selectedModules.database = parsed.database;
|
|
@@ -272,7 +366,6 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
272
366
|
selectedModules.auth = config.provider;
|
|
273
367
|
}
|
|
274
368
|
const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
|
|
275
|
-
// Install dependencies first, then run post-install commands (e.g. prisma generate)
|
|
276
369
|
if (!options?.dryRun && options?.install !== false) {
|
|
277
370
|
const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
|
|
278
371
|
try {
|
|
@@ -285,30 +378,39 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
285
378
|
}
|
|
286
379
|
}
|
|
287
380
|
if (postInstall && postInstall.length > 0 && !options?.dryRun) {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
292
|
-
}
|
|
293
|
-
postInstallSpinner.succeed("Post-install commands completed");
|
|
381
|
+
const createdFiles = gen.getCreatedFiles ? gen.getCreatedFiles() : [];
|
|
382
|
+
if (createdFiles.length === 0) {
|
|
383
|
+
logger_1.logger.warn("Skipping post-install commands — no files were created by generators to act upon.");
|
|
294
384
|
}
|
|
295
|
-
|
|
296
|
-
postInstallSpinner.
|
|
297
|
-
|
|
385
|
+
else {
|
|
386
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
387
|
+
try {
|
|
388
|
+
for (const command of postInstall) {
|
|
389
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
390
|
+
}
|
|
391
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
298
397
|
}
|
|
299
398
|
}
|
|
300
399
|
return;
|
|
301
400
|
}
|
|
302
401
|
const mergedDeps = {};
|
|
303
402
|
const mergedDevDeps = {};
|
|
304
|
-
|
|
305
|
-
|
|
403
|
+
try {
|
|
404
|
+
const shared = moduleMetadata.frameworkConfigs
|
|
405
|
+
?.shared;
|
|
406
|
+
if (shared && shared.dependencies)
|
|
407
|
+
Object.assign(mergedDeps, shared.dependencies);
|
|
408
|
+
if (shared && shared.devDependencies)
|
|
409
|
+
Object.assign(mergedDevDeps, shared.devDependencies);
|
|
306
410
|
}
|
|
307
|
-
|
|
308
|
-
|
|
411
|
+
catch {
|
|
412
|
+
// ignore malformed frameworkConfigs
|
|
309
413
|
}
|
|
310
|
-
// Adapter-specific dependencies are applied via generator metadata; frameworkConfigs still merge above.
|
|
311
|
-
// Do not mutate the loaded module metadata here; use mergedDeps/mergedDevDeps for installation.
|
|
312
414
|
const variables = {};
|
|
313
415
|
if (selectedProvider) {
|
|
314
416
|
variables.provider = selectedProvider;
|
|
@@ -344,16 +446,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
344
446
|
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
345
447
|
}
|
|
346
448
|
if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options?.dryRun) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
for (const
|
|
350
|
-
(
|
|
449
|
+
const createdFiles = [];
|
|
450
|
+
if (Array.isArray(moduleMetadata.patches)) {
|
|
451
|
+
for (const p of moduleMetadata.patches) {
|
|
452
|
+
if (p.type === "create-file") {
|
|
453
|
+
const destPath = p.destination;
|
|
454
|
+
if (typeof destPath === "string") {
|
|
455
|
+
const dest = path_1.default.join(projectRoot, destPath);
|
|
456
|
+
if (await fs_extra_1.default.pathExists(dest))
|
|
457
|
+
createdFiles.push(destPath);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
351
460
|
}
|
|
352
|
-
postInstallSpinner.succeed("Post-install commands completed");
|
|
353
461
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
462
|
+
if (createdFiles.length === 0) {
|
|
463
|
+
logger_1.logger.warn("Skipping module post-install commands — no files were created by module patches to act upon.");
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
467
|
+
try {
|
|
468
|
+
for (const command of moduleMetadata.postInstall) {
|
|
469
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
470
|
+
}
|
|
471
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
357
477
|
}
|
|
358
478
|
}
|
|
359
479
|
if (Object.keys(mergedDeps).length > 0 && options?.install !== false) {
|
|
@@ -374,16 +494,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
374
494
|
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
|
|
375
495
|
}
|
|
376
496
|
}
|
|
377
|
-
if (moduleMetadata.envVars
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}));
|
|
382
|
-
if (!options?.dryRun) {
|
|
383
|
-
await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options?.force });
|
|
497
|
+
if (moduleMetadata.envVars) {
|
|
498
|
+
let envArray = [];
|
|
499
|
+
if (Array.isArray(moduleMetadata.envVars)) {
|
|
500
|
+
envArray = moduleMetadata.envVars;
|
|
384
501
|
}
|
|
385
|
-
else {
|
|
386
|
-
|
|
502
|
+
else if (typeof moduleMetadata.envVars === "object") {
|
|
503
|
+
envArray = Object.entries(moduleMetadata.envVars).map(([k, v]) => ({
|
|
504
|
+
key: k,
|
|
505
|
+
value: String(v),
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
if (envArray.length > 0) {
|
|
509
|
+
const processedEnvVars = envArray.map((envVar) => ({
|
|
510
|
+
key: envVar.key || "",
|
|
511
|
+
value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
|
|
512
|
+
required: false,
|
|
513
|
+
}));
|
|
514
|
+
if (!options?.dryRun) {
|
|
515
|
+
const envVarsToAdd = processedEnvVars.map((e) => ({
|
|
516
|
+
key: e.key,
|
|
517
|
+
value: e.value,
|
|
518
|
+
required: !!e.required,
|
|
519
|
+
}));
|
|
520
|
+
await (0, env_editor_1.addEnvVariables)(projectRoot, envVarsToAdd, { force: options?.force });
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
|
|
524
|
+
}
|
|
387
525
|
}
|
|
388
526
|
}
|
|
389
527
|
}
|
|
@@ -475,8 +613,11 @@ async function findModulePath(modulesDir, moduleName, provider) {
|
|
|
475
613
|
const metadataPath = path_1.default.join(modulePath, "module.json");
|
|
476
614
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
477
615
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
478
|
-
if (provider
|
|
479
|
-
|
|
616
|
+
if (provider) {
|
|
617
|
+
const baseProvider = String(provider).split("-")[0];
|
|
618
|
+
if (moduleDir === provider || moduleDir === baseProvider) {
|
|
619
|
+
return modulePath;
|
|
620
|
+
}
|
|
480
621
|
}
|
|
481
622
|
if (!provider && metadata.name === moduleName) {
|
|
482
623
|
return modulePath;
|
package/dist/cli/create.js
CHANGED
|
@@ -45,12 +45,23 @@ async function getProjectConfig(projectName, options) {
|
|
|
45
45
|
const optionsProvided = flagsProvided || !!(options && (options.yes || options.y));
|
|
46
46
|
if (optionsProvided) {
|
|
47
47
|
if (options && (options.yes || options.y) && !flagsProvided) {
|
|
48
|
+
const defaultFramework = discoveredModules.frameworks && discoveredModules.frameworks.length > 0
|
|
49
|
+
? discoveredModules.frameworks[0].name
|
|
50
|
+
: "";
|
|
51
|
+
const defaultDatabase = discoveredModules.databases && discoveredModules.databases.length > 0
|
|
52
|
+
? discoveredModules.databases[0].name
|
|
53
|
+
: "none";
|
|
54
|
+
const prismaProviders = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
55
|
+
const defaultPrismaProvider = prismaProviders.length > 0 ? prismaProviders[0] : undefined;
|
|
56
|
+
const defaultAuth = discoveredModules.auth && discoveredModules.auth.length > 0
|
|
57
|
+
? discoveredModules.auth[0].name
|
|
58
|
+
: "none";
|
|
48
59
|
return {
|
|
49
60
|
projectName: projectName || "my-app",
|
|
50
|
-
framework:
|
|
51
|
-
database:
|
|
52
|
-
prismaProvider:
|
|
53
|
-
auth:
|
|
61
|
+
framework: defaultFramework,
|
|
62
|
+
database: defaultDatabase,
|
|
63
|
+
prismaProvider: defaultPrismaProvider,
|
|
64
|
+
auth: defaultAuth,
|
|
54
65
|
language: "typescript",
|
|
55
66
|
packageManager: "pnpm",
|
|
56
67
|
};
|
|
@@ -100,12 +111,22 @@ async function getProjectConfig(projectName, options) {
|
|
|
100
111
|
if (authOpt && authOpt !== "none") {
|
|
101
112
|
auth = authOpt;
|
|
102
113
|
}
|
|
103
|
-
const finalFramework = (framework || "
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
const finalFramework = (framework || (discoveredModules.frameworks[0]?.name ?? ""));
|
|
115
|
+
// Validate auth compatibility using discovered module metadata when available
|
|
116
|
+
if (auth && auth !== "none" && discoveredModules.auth) {
|
|
117
|
+
const authMeta = discoveredModules.auth.find((a) => a.name === auth);
|
|
118
|
+
if (authMeta) {
|
|
119
|
+
if (authMeta.supportedFrameworks &&
|
|
120
|
+
!authMeta.supportedFrameworks.includes(finalFramework)) {
|
|
121
|
+
throw new Error(`${authMeta.displayName || auth} is not supported on ${finalFramework}`);
|
|
122
|
+
}
|
|
123
|
+
const dbName = database === "prisma" ? "prisma" : database === "none" ? "none" : "other";
|
|
124
|
+
if (authMeta.compatibility &&
|
|
125
|
+
authMeta.compatibility.databases &&
|
|
126
|
+
!authMeta.compatibility.databases.includes(dbName)) {
|
|
127
|
+
throw new Error(`${authMeta.displayName || auth} is not compatible with the selected database configuration`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
109
130
|
}
|
|
110
131
|
return {
|
|
111
132
|
projectName: projectName || "my-app",
|
|
@@ -117,6 +138,7 @@ async function getProjectConfig(projectName, options) {
|
|
|
117
138
|
packageManager: (pm || "pnpm"),
|
|
118
139
|
};
|
|
119
140
|
}
|
|
141
|
+
const prismaProviders = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
120
142
|
const answers = (await inquirer_1.default.prompt([
|
|
121
143
|
{
|
|
122
144
|
type: "input",
|
|
@@ -139,26 +161,49 @@ async function getProjectConfig(projectName, options) {
|
|
|
139
161
|
type: "list",
|
|
140
162
|
name: "framework",
|
|
141
163
|
message: "Select framework:",
|
|
142
|
-
choices:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
choices: (() => {
|
|
165
|
+
if (discoveredModules.frameworks && discoveredModules.frameworks.length > 0) {
|
|
166
|
+
return discoveredModules.frameworks.map((f) => ({ name: f.displayName, value: f.name }));
|
|
167
|
+
}
|
|
168
|
+
// Fallback: read templates dir directly
|
|
169
|
+
try {
|
|
170
|
+
const templatesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "templates");
|
|
171
|
+
if (fs_extra_1.default.existsSync(templatesDir)) {
|
|
172
|
+
const dirs = fs_extra_1.default.readdirSync(templatesDir).filter((d) => d !== "node_modules");
|
|
173
|
+
return dirs.map((d) => ({ name: d.charAt(0).toUpperCase() + d.slice(1), value: d }));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// ignore
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
})(),
|
|
149
181
|
},
|
|
150
182
|
{
|
|
151
183
|
type: "list",
|
|
152
184
|
name: "database",
|
|
153
185
|
message: "Select database/ORM:",
|
|
154
186
|
when: (answers) => answers.framework !== "react",
|
|
155
|
-
choices: (answers) =>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
187
|
+
choices: (answers) => {
|
|
188
|
+
if (discoveredModules.databases && discoveredModules.databases.length > 0) {
|
|
189
|
+
return (0, module_discovery_1.getDatabaseChoices)(discoveredModules.databases, answers.framework);
|
|
190
|
+
}
|
|
191
|
+
// Fallback: scan modules/database directory
|
|
192
|
+
try {
|
|
193
|
+
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules", "database");
|
|
194
|
+
if (fs_extra_1.default.existsSync(modulesDir)) {
|
|
195
|
+
const dbs = fs_extra_1.default
|
|
196
|
+
.readdirSync(modulesDir)
|
|
197
|
+
.map((d) => ({ name: d.charAt(0).toUpperCase() + d.slice(1), value: d }));
|
|
198
|
+
dbs.push({ name: "None", value: "none" });
|
|
199
|
+
return dbs;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// ignore
|
|
204
|
+
}
|
|
205
|
+
return [{ name: "None", value: "none" }];
|
|
206
|
+
},
|
|
162
207
|
},
|
|
163
208
|
// If a prisma-* choice is selected above, `prismaProvider` will be derived from it,
|
|
164
209
|
// otherwise prompt for provider when `prisma` is selected directly.
|
|
@@ -166,13 +211,11 @@ async function getProjectConfig(projectName, options) {
|
|
|
166
211
|
type: "list",
|
|
167
212
|
name: "prismaProvider",
|
|
168
213
|
message: "Select database provider for Prisma:",
|
|
169
|
-
when: (answers) => answers.database === "prisma",
|
|
170
|
-
choices:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{ name: "SQLite", value: "sqlite" },
|
|
175
|
-
],
|
|
214
|
+
when: (answers) => answers.database === "prisma" && prismaProviders.length > 0,
|
|
215
|
+
choices: prismaProviders.map((p) => ({
|
|
216
|
+
name: p.charAt(0).toUpperCase() + p.slice(1),
|
|
217
|
+
value: p,
|
|
218
|
+
})),
|
|
176
219
|
},
|
|
177
220
|
{
|
|
178
221
|
type: "list",
|
|
@@ -204,7 +247,6 @@ async function getProjectConfig(projectName, options) {
|
|
|
204
247
|
default: "pnpm",
|
|
205
248
|
},
|
|
206
249
|
]));
|
|
207
|
-
// Normalize database answer (interactive flow): handle values like `prisma-postgresql`
|
|
208
250
|
let databaseAnswer = answers.framework === "react" ? "none" : answers.database;
|
|
209
251
|
let prismaProviderAnswer = answers.prismaProvider;
|
|
210
252
|
if (typeof databaseAnswer === "string" && databaseAnswer.startsWith("prisma-")) {
|