stackkit 0.2.2 → 0.2.6
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 +188 -61
- package/dist/cli/create.js +75 -35
- package/dist/cli/doctor.js +36 -161
- package/dist/cli/list.js +10 -40
- package/dist/index.js +6 -6
- package/dist/lib/conversion/js-conversion.js +27 -11
- 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.d.ts +1 -1
- package/dist/lib/discovery/module-discovery.js +26 -17
- 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 +98 -4
- 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 +13 -10
- package/templates/express/gitignore +23 -0
- package/templates/nextjs/gitignore +42 -0
- package/templates/nextjs/next-env.d.ts +6 -0
- package/templates/react/dist/assets/index-D4AHT4dU.js +193 -0
- package/templates/react/dist/assets/index-rpwj5ZOX.css +1 -0
- package/templates/react/dist/index.html +14 -0
- package/templates/react/dist/vite.svg +1 -0
- package/templates/react/gitignore +44 -0
- package/templates/react/src/utils/utils.ts +1 -1
- /package/templates/express/{.env.example → env.example} +0 -0
- /package/templates/nextjs/{.env.example → env.example} +0 -0
- /package/templates/react/{.env.example → env.example} +0 -0
- /package/templates/react/{.prettierignore → prettierignore} +0 -0
- /package/templates/react/{.prettierrc → prettierrc} +0 -0
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,17 @@ 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 = [...config.preAdded.map((p) => p.displayName), config.displayName].map((s) => chalk_1.default.bold(s));
|
|
35
|
+
logger_1.logger.success(`Added ${addedNames.join(" and ")}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
|
|
39
|
+
}
|
|
32
40
|
logger_1.logger.newLine();
|
|
33
41
|
}
|
|
34
42
|
catch (error) {
|
|
@@ -42,7 +50,7 @@ async function addCommand(module, options) {
|
|
|
42
50
|
async function getAddConfig(module, options, projectInfo) {
|
|
43
51
|
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
|
|
44
52
|
if (!module) {
|
|
45
|
-
return await getInteractiveConfig(modulesDir, projectInfo);
|
|
53
|
+
return await getInteractiveConfig(modulesDir, projectInfo, options);
|
|
46
54
|
}
|
|
47
55
|
if (module === "database" || module === "auth") {
|
|
48
56
|
if (!options?.provider) {
|
|
@@ -67,8 +75,8 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
67
75
|
module: "database",
|
|
68
76
|
provider: adapterProvider,
|
|
69
77
|
displayName: parsed.database === "prisma" && parsed.provider
|
|
70
|
-
? `${moduleMetadata.displayName} (${parsed.provider})`
|
|
71
|
-
: moduleMetadata.displayName,
|
|
78
|
+
? `${moduleMetadata.displayName || baseProvider} (${parsed.provider})`
|
|
79
|
+
: moduleMetadata.displayName || baseProvider,
|
|
72
80
|
metadata: moduleMetadata,
|
|
73
81
|
};
|
|
74
82
|
}
|
|
@@ -78,24 +86,26 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
78
86
|
if (!moduleMetadata) {
|
|
79
87
|
throw new Error(`Auth provider "${provider}" not found`);
|
|
80
88
|
}
|
|
81
|
-
// Validate compatibility with existing project
|
|
82
89
|
if (projectInfo) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
throw new Error("Auth.js is only supported with Next.js and Prisma database in this project");
|
|
90
|
+
if (moduleMetadata.supportedFrameworks &&
|
|
91
|
+
!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
92
|
+
throw new Error(`${moduleMetadata.displayName} is not supported on ${projectInfo.framework}`);
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const dbName = projectInfo.hasPrisma
|
|
95
|
+
? "prisma"
|
|
96
|
+
: projectInfo.hasDatabase
|
|
97
|
+
? "other"
|
|
98
|
+
: "none";
|
|
99
|
+
if (moduleMetadata.compatibility &&
|
|
100
|
+
moduleMetadata.compatibility.databases &&
|
|
101
|
+
!moduleMetadata.compatibility.databases.includes(dbName)) {
|
|
102
|
+
throw new Error(`${moduleMetadata.displayName} is not compatible with the project's database configuration`);
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
return {
|
|
96
106
|
module: "auth",
|
|
97
107
|
provider,
|
|
98
|
-
displayName: moduleMetadata.displayName,
|
|
108
|
+
displayName: moduleMetadata.displayName || provider,
|
|
99
109
|
metadata: moduleMetadata,
|
|
100
110
|
};
|
|
101
111
|
}
|
|
@@ -103,15 +113,17 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
103
113
|
// Unknown module type
|
|
104
114
|
throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
|
|
105
115
|
}
|
|
106
|
-
async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
107
|
-
|
|
116
|
+
async function getInteractiveConfig(modulesDir, projectInfo, options) {
|
|
117
|
+
const projectRoot = process.cwd();
|
|
108
118
|
const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
|
|
109
|
-
|
|
119
|
+
// Prefer discovered framework, then projectInfo.framework; leave empty if unknown
|
|
120
|
+
const defaultFramework = (discovered.frameworks && discovered.frameworks[0]?.name) || projectInfo?.framework || "";
|
|
121
|
+
const compatibleAuths = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || defaultFramework, projectInfo?.hasPrisma ? "prisma" : "none", discovered.frameworks);
|
|
110
122
|
const categories = [
|
|
111
123
|
{ name: "Database", value: "database" },
|
|
112
124
|
];
|
|
113
|
-
//
|
|
114
|
-
if (compatibleAuths.length >
|
|
125
|
+
// Offer Auth category when there's at least one compatible auth option
|
|
126
|
+
if (compatibleAuths.length > 0) {
|
|
115
127
|
categories.push({ name: "Auth", value: "auth" });
|
|
116
128
|
}
|
|
117
129
|
const answers = await inquirer_1.default.prompt([
|
|
@@ -124,7 +136,7 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
124
136
|
]);
|
|
125
137
|
const category = answers.category;
|
|
126
138
|
if (category === "database") {
|
|
127
|
-
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework ||
|
|
139
|
+
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || defaultFramework);
|
|
128
140
|
const dbAnswers = await inquirer_1.default.prompt([
|
|
129
141
|
{
|
|
130
142
|
type: "list",
|
|
@@ -155,9 +167,51 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
155
167
|
};
|
|
156
168
|
}
|
|
157
169
|
else if (category === "auth") {
|
|
158
|
-
|
|
170
|
+
let preAddedForReturn;
|
|
171
|
+
if (!projectInfo?.hasDatabase) {
|
|
172
|
+
logger_1.logger.warn("No database detected in the project. Authentication requires a database.");
|
|
173
|
+
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || defaultFramework);
|
|
174
|
+
const dbAnswer = await inquirer_1.default.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: "list",
|
|
177
|
+
name: "database",
|
|
178
|
+
message: "Select a database to add before authentication:",
|
|
179
|
+
choices: dbChoices,
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
const selectedDb = dbAnswer.database;
|
|
183
|
+
if (!selectedDb || selectedDb === "none") {
|
|
184
|
+
logger_1.logger.info("Cancelled — authentication requires a database");
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
let dbConfig;
|
|
188
|
+
if (selectedDb.startsWith("prisma-")) {
|
|
189
|
+
const provider = selectedDb.split("-")[1];
|
|
190
|
+
dbConfig = {
|
|
191
|
+
module: "database",
|
|
192
|
+
provider: `prisma-${provider}`,
|
|
193
|
+
displayName: `Prisma (${provider})`,
|
|
194
|
+
metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const meta = (await loadModuleMetadata(modulesDir, selectedDb, selectedDb));
|
|
199
|
+
if (!meta)
|
|
200
|
+
throw new Error(`Database provider "${selectedDb}" not found`);
|
|
201
|
+
dbConfig = {
|
|
202
|
+
module: "database",
|
|
203
|
+
provider: selectedDb,
|
|
204
|
+
displayName: meta.displayName || selectedDb,
|
|
205
|
+
metadata: meta,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
await addModuleToProject(projectRoot, projectInfo || (await (0, detect_1.detectProjectInfo)(projectRoot)), dbConfig, options);
|
|
209
|
+
projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
210
|
+
dbConfig.preAdded = dbConfig.preAdded || [];
|
|
211
|
+
preAddedForReturn = dbConfig;
|
|
212
|
+
}
|
|
159
213
|
const dbString = projectInfo?.hasPrisma ? "prisma" : "none";
|
|
160
|
-
const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework ||
|
|
214
|
+
const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || defaultFramework, dbString);
|
|
161
215
|
const authAnswers = await inquirer_1.default.prompt([
|
|
162
216
|
{
|
|
163
217
|
type: "list",
|
|
@@ -180,16 +234,19 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
180
234
|
!metadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
181
235
|
throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
|
|
182
236
|
}
|
|
183
|
-
|
|
237
|
+
const result = {
|
|
184
238
|
module: "auth",
|
|
185
239
|
provider: selectedAuth,
|
|
186
240
|
displayName: metadata.displayName || selectedAuth,
|
|
187
241
|
metadata,
|
|
188
242
|
};
|
|
243
|
+
if (typeof preAddedForReturn !== "undefined" && preAddedForReturn) {
|
|
244
|
+
result.preAdded = [preAddedForReturn];
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
189
247
|
}
|
|
190
248
|
throw new Error("Invalid selection");
|
|
191
249
|
}
|
|
192
|
-
/* removed unused getProviderConfig — discovery-based flows handle providers */
|
|
193
250
|
async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
194
251
|
const moduleMetadata = config.metadata;
|
|
195
252
|
const selectedProvider = config.provider;
|
|
@@ -261,6 +318,29 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
261
318
|
}
|
|
262
319
|
}
|
|
263
320
|
const selectedModules = { framework: projectInfo.framework };
|
|
321
|
+
try {
|
|
322
|
+
const pkg = await fs_extra_1.default.readJson(path_1.default.join(projectRoot, "package.json"));
|
|
323
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
324
|
+
if (projectInfo.hasPrisma) {
|
|
325
|
+
selectedModules.database = "prisma";
|
|
326
|
+
const prismaSchema = path_1.default.join(projectRoot, "prisma", "schema.prisma");
|
|
327
|
+
if (await fs_extra_1.default.pathExists(prismaSchema)) {
|
|
328
|
+
const content = await fs_extra_1.default.readFile(prismaSchema, "utf-8");
|
|
329
|
+
const dsMatch = content.match(/datasource\s+\w+\s*\{([\s\S]*?)\}/i);
|
|
330
|
+
if (dsMatch && dsMatch[1]) {
|
|
331
|
+
const provMatch = dsMatch[1].match(/provider\s*=\s*["']([^"']+)["']/i);
|
|
332
|
+
if (provMatch && provMatch[1])
|
|
333
|
+
selectedModules.prismaProvider = provMatch[1];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (deps["mongoose"]) {
|
|
338
|
+
selectedModules.database = "mongoose";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// ignore detection errors
|
|
343
|
+
}
|
|
264
344
|
if (config.module === "database" && config.provider) {
|
|
265
345
|
const parsed = (0, shared_1.parseDatabaseOption)(config.provider);
|
|
266
346
|
selectedModules.database = parsed.database;
|
|
@@ -272,7 +352,6 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
272
352
|
selectedModules.auth = config.provider;
|
|
273
353
|
}
|
|
274
354
|
const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
|
|
275
|
-
// Install dependencies first, then run post-install commands (e.g. prisma generate)
|
|
276
355
|
if (!options?.dryRun && options?.install !== false) {
|
|
277
356
|
const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
|
|
278
357
|
try {
|
|
@@ -285,30 +364,39 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
285
364
|
}
|
|
286
365
|
}
|
|
287
366
|
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");
|
|
367
|
+
const createdFiles = gen.getCreatedFiles ? gen.getCreatedFiles() : [];
|
|
368
|
+
if (createdFiles.length === 0) {
|
|
369
|
+
logger_1.logger.warn("Skipping post-install commands — no files were created by generators to act upon.");
|
|
294
370
|
}
|
|
295
|
-
|
|
296
|
-
postInstallSpinner.
|
|
297
|
-
|
|
371
|
+
else {
|
|
372
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
373
|
+
try {
|
|
374
|
+
for (const command of postInstall) {
|
|
375
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
376
|
+
}
|
|
377
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
298
383
|
}
|
|
299
384
|
}
|
|
300
385
|
return;
|
|
301
386
|
}
|
|
302
387
|
const mergedDeps = {};
|
|
303
388
|
const mergedDevDeps = {};
|
|
304
|
-
|
|
305
|
-
|
|
389
|
+
try {
|
|
390
|
+
const shared = moduleMetadata.frameworkConfigs
|
|
391
|
+
?.shared;
|
|
392
|
+
if (shared && shared.dependencies)
|
|
393
|
+
Object.assign(mergedDeps, shared.dependencies);
|
|
394
|
+
if (shared && shared.devDependencies)
|
|
395
|
+
Object.assign(mergedDevDeps, shared.devDependencies);
|
|
306
396
|
}
|
|
307
|
-
|
|
308
|
-
|
|
397
|
+
catch {
|
|
398
|
+
// ignore malformed frameworkConfigs
|
|
309
399
|
}
|
|
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
400
|
const variables = {};
|
|
313
401
|
if (selectedProvider) {
|
|
314
402
|
variables.provider = selectedProvider;
|
|
@@ -344,16 +432,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
344
432
|
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
345
433
|
}
|
|
346
434
|
if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options?.dryRun) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
for (const
|
|
350
|
-
(
|
|
435
|
+
const createdFiles = [];
|
|
436
|
+
if (Array.isArray(moduleMetadata.patches)) {
|
|
437
|
+
for (const p of moduleMetadata.patches) {
|
|
438
|
+
if (p.type === "create-file") {
|
|
439
|
+
const destPath = p.destination;
|
|
440
|
+
if (typeof destPath === "string") {
|
|
441
|
+
const dest = path_1.default.join(projectRoot, destPath);
|
|
442
|
+
if (await fs_extra_1.default.pathExists(dest))
|
|
443
|
+
createdFiles.push(destPath);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
351
446
|
}
|
|
352
|
-
postInstallSpinner.succeed("Post-install commands completed");
|
|
353
447
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
448
|
+
if (createdFiles.length === 0) {
|
|
449
|
+
logger_1.logger.warn("Skipping module post-install commands — no files were created by module patches to act upon.");
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
453
|
+
try {
|
|
454
|
+
for (const command of moduleMetadata.postInstall) {
|
|
455
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
456
|
+
}
|
|
457
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
357
463
|
}
|
|
358
464
|
}
|
|
359
465
|
if (Object.keys(mergedDeps).length > 0 && options?.install !== false) {
|
|
@@ -374,16 +480,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
374
480
|
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
|
|
375
481
|
}
|
|
376
482
|
}
|
|
377
|
-
if (moduleMetadata.envVars
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}));
|
|
382
|
-
if (!options?.dryRun) {
|
|
383
|
-
await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options?.force });
|
|
483
|
+
if (moduleMetadata.envVars) {
|
|
484
|
+
let envArray = [];
|
|
485
|
+
if (Array.isArray(moduleMetadata.envVars)) {
|
|
486
|
+
envArray = moduleMetadata.envVars;
|
|
384
487
|
}
|
|
385
|
-
else {
|
|
386
|
-
|
|
488
|
+
else if (typeof moduleMetadata.envVars === "object") {
|
|
489
|
+
envArray = Object.entries(moduleMetadata.envVars).map(([k, v]) => ({
|
|
490
|
+
key: k,
|
|
491
|
+
value: String(v),
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
if (envArray.length > 0) {
|
|
495
|
+
const processedEnvVars = envArray.map((envVar) => ({
|
|
496
|
+
key: envVar.key || "",
|
|
497
|
+
value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
|
|
498
|
+
required: false,
|
|
499
|
+
}));
|
|
500
|
+
if (!options?.dryRun) {
|
|
501
|
+
const envVarsToAdd = processedEnvVars.map((e) => ({
|
|
502
|
+
key: e.key,
|
|
503
|
+
value: e.value,
|
|
504
|
+
required: !!e.required,
|
|
505
|
+
}));
|
|
506
|
+
await (0, env_editor_1.addEnvVariables)(projectRoot, envVarsToAdd, { force: options?.force });
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
|
|
510
|
+
}
|
|
387
511
|
}
|
|
388
512
|
}
|
|
389
513
|
}
|
|
@@ -475,8 +599,11 @@ async function findModulePath(modulesDir, moduleName, provider) {
|
|
|
475
599
|
const metadataPath = path_1.default.join(modulePath, "module.json");
|
|
476
600
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
477
601
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
478
|
-
if (provider
|
|
479
|
-
|
|
602
|
+
if (provider) {
|
|
603
|
+
const baseProvider = String(provider).split("-")[0];
|
|
604
|
+
if (moduleDir === provider || moduleDir === baseProvider) {
|
|
605
|
+
return modulePath;
|
|
606
|
+
}
|
|
480
607
|
}
|
|
481
608
|
if (!provider && metadata.name === moduleName) {
|
|
482
609
|
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,47 +161,66 @@ 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
|
-
// If a prisma-* choice is selected above, `prismaProvider` will be derived from it,
|
|
164
|
-
// otherwise prompt for provider when `prisma` is selected directly.
|
|
165
208
|
{
|
|
166
209
|
type: "list",
|
|
167
210
|
name: "prismaProvider",
|
|
168
211
|
message: "Select database provider for Prisma:",
|
|
169
|
-
when: (answers) => answers.database === "prisma",
|
|
170
|
-
choices:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{ name: "SQLite", value: "sqlite" },
|
|
175
|
-
],
|
|
212
|
+
when: (answers) => answers.database === "prisma" && prismaProviders.length > 0,
|
|
213
|
+
choices: prismaProviders.map((p) => ({
|
|
214
|
+
name: p.charAt(0).toUpperCase() + p.slice(1),
|
|
215
|
+
value: p,
|
|
216
|
+
})),
|
|
176
217
|
},
|
|
177
218
|
{
|
|
178
219
|
type: "list",
|
|
179
220
|
name: "auth",
|
|
180
221
|
message: "Select authentication:",
|
|
181
222
|
when: (answers) => answers.database !== "none" || answers.framework === "react",
|
|
182
|
-
choices: (answers) => (0, module_discovery_1.getCompatibleAuthOptions)(discoveredModules.auth, answers.framework, answers.database || "none"),
|
|
223
|
+
choices: (answers) => (0, module_discovery_1.getCompatibleAuthOptions)(discoveredModules.auth, answers.framework, answers.database || "none", discoveredModules.frameworks),
|
|
183
224
|
},
|
|
184
225
|
{
|
|
185
226
|
type: "list",
|
|
@@ -204,7 +245,6 @@ async function getProjectConfig(projectName, options) {
|
|
|
204
245
|
default: "pnpm",
|
|
205
246
|
},
|
|
206
247
|
]));
|
|
207
|
-
// Normalize database answer (interactive flow): handle values like `prisma-postgresql`
|
|
208
248
|
let databaseAnswer = answers.framework === "react" ? "none" : answers.database;
|
|
209
249
|
let prismaProviderAnswer = answers.prismaProvider;
|
|
210
250
|
if (typeof databaseAnswer === "string" && databaseAnswer.startsWith("prisma-")) {
|