stackkit 0.2.1 → 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 +224 -54
- 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 +114 -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,10 +89,26 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
78
89
|
if (!moduleMetadata) {
|
|
79
90
|
throw new Error(`Auth provider "${provider}" not found`);
|
|
80
91
|
}
|
|
92
|
+
if (projectInfo) {
|
|
93
|
+
if (moduleMetadata.supportedFrameworks &&
|
|
94
|
+
!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
95
|
+
throw new Error(`${moduleMetadata.displayName} is not supported on ${projectInfo.framework}`);
|
|
96
|
+
}
|
|
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`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
81
108
|
return {
|
|
82
109
|
module: "auth",
|
|
83
110
|
provider,
|
|
84
|
-
displayName: moduleMetadata.displayName,
|
|
111
|
+
displayName: moduleMetadata.displayName || provider,
|
|
85
112
|
metadata: moduleMetadata,
|
|
86
113
|
};
|
|
87
114
|
}
|
|
@@ -89,22 +116,30 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
89
116
|
// Unknown module type
|
|
90
117
|
throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
|
|
91
118
|
}
|
|
92
|
-
async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
119
|
+
async function getInteractiveConfig(modulesDir, projectInfo, options) {
|
|
120
|
+
const projectRoot = process.cwd();
|
|
121
|
+
const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
|
|
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");
|
|
125
|
+
const categories = [
|
|
126
|
+
{ name: "Database", value: "database" },
|
|
127
|
+
];
|
|
128
|
+
// Offer Auth category when there's at least one compatible auth option
|
|
129
|
+
if (compatibleAuths.length > 0) {
|
|
130
|
+
categories.push({ name: "Auth", value: "auth" });
|
|
131
|
+
}
|
|
93
132
|
const answers = await inquirer_1.default.prompt([
|
|
94
133
|
{
|
|
95
134
|
type: "list",
|
|
96
135
|
name: "category",
|
|
97
136
|
message: "What would you like to add?",
|
|
98
|
-
choices:
|
|
99
|
-
{ name: "Database", value: "database" },
|
|
100
|
-
{ name: "Auth", value: "auth" },
|
|
101
|
-
],
|
|
137
|
+
choices: categories,
|
|
102
138
|
},
|
|
103
139
|
]);
|
|
104
140
|
const category = answers.category;
|
|
105
|
-
const discovered = await (0, module_discovery_1.discoverModules)(modulesDir);
|
|
106
141
|
if (category === "database") {
|
|
107
|
-
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework ||
|
|
142
|
+
const dbChoices = (0, module_discovery_1.getDatabaseChoices)(discovered.databases || [], projectInfo?.framework || defaultFramework);
|
|
108
143
|
const dbAnswers = await inquirer_1.default.prompt([
|
|
109
144
|
{
|
|
110
145
|
type: "list",
|
|
@@ -135,7 +170,57 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
135
170
|
};
|
|
136
171
|
}
|
|
137
172
|
else if (category === "auth") {
|
|
138
|
-
|
|
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
|
+
}
|
|
222
|
+
const dbString = projectInfo?.hasPrisma ? "prisma" : "none";
|
|
223
|
+
const authChoices = (0, module_discovery_1.getCompatibleAuthOptions)(discovered.auth || [], projectInfo?.framework || defaultFramework, dbString);
|
|
139
224
|
const authAnswers = await inquirer_1.default.prompt([
|
|
140
225
|
{
|
|
141
226
|
type: "list",
|
|
@@ -145,19 +230,30 @@ async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
|
145
230
|
},
|
|
146
231
|
]);
|
|
147
232
|
const selectedAuth = authAnswers.auth;
|
|
233
|
+
if (selectedAuth === "none") {
|
|
234
|
+
logger_1.logger.info("Cancelled");
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
148
237
|
const metadata = await loadModuleMetadata(modulesDir, selectedAuth, selectedAuth);
|
|
149
238
|
if (!metadata) {
|
|
150
239
|
throw new Error(`Auth provider "${selectedAuth}" not found`);
|
|
151
240
|
}
|
|
152
|
-
if (projectInfo &&
|
|
241
|
+
if (projectInfo &&
|
|
242
|
+
metadata.supportedFrameworks &&
|
|
243
|
+
!metadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
153
244
|
throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
|
|
154
245
|
}
|
|
155
|
-
|
|
246
|
+
const result = {
|
|
156
247
|
module: "auth",
|
|
157
248
|
provider: selectedAuth,
|
|
158
249
|
displayName: metadata.displayName || selectedAuth,
|
|
159
250
|
metadata,
|
|
160
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;
|
|
161
257
|
}
|
|
162
258
|
throw new Error("Invalid selection");
|
|
163
259
|
}
|
|
@@ -233,6 +329,32 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
233
329
|
}
|
|
234
330
|
}
|
|
235
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
|
+
}
|
|
236
358
|
if (config.module === "database" && config.provider) {
|
|
237
359
|
const parsed = (0, shared_1.parseDatabaseOption)(config.provider);
|
|
238
360
|
selectedModules.database = parsed.database;
|
|
@@ -244,19 +366,6 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
244
366
|
selectedModules.auth = config.provider;
|
|
245
367
|
}
|
|
246
368
|
const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
|
|
247
|
-
if (postInstall && postInstall.length > 0 && !options?.dryRun) {
|
|
248
|
-
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
249
|
-
try {
|
|
250
|
-
for (const command of postInstall) {
|
|
251
|
-
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
252
|
-
}
|
|
253
|
-
postInstallSpinner.succeed("Post-install commands completed");
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
postInstallSpinner.fail("Failed to run post-install commands");
|
|
257
|
-
throw error;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
369
|
if (!options?.dryRun && options?.install !== false) {
|
|
261
370
|
const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
|
|
262
371
|
try {
|
|
@@ -268,18 +377,40 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
268
377
|
throw err;
|
|
269
378
|
}
|
|
270
379
|
}
|
|
380
|
+
if (postInstall && postInstall.length > 0 && !options?.dryRun) {
|
|
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.");
|
|
384
|
+
}
|
|
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
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
271
399
|
return;
|
|
272
400
|
}
|
|
273
401
|
const mergedDeps = {};
|
|
274
402
|
const mergedDevDeps = {};
|
|
275
|
-
|
|
276
|
-
|
|
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);
|
|
277
410
|
}
|
|
278
|
-
|
|
279
|
-
|
|
411
|
+
catch {
|
|
412
|
+
// ignore malformed frameworkConfigs
|
|
280
413
|
}
|
|
281
|
-
// Adapter-specific dependencies are applied via generator metadata; frameworkConfigs still merge above.
|
|
282
|
-
// Do not mutate the loaded module metadata here; use mergedDeps/mergedDevDeps for installation.
|
|
283
414
|
const variables = {};
|
|
284
415
|
if (selectedProvider) {
|
|
285
416
|
variables.provider = selectedProvider;
|
|
@@ -315,16 +446,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
315
446
|
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
316
447
|
}
|
|
317
448
|
if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options?.dryRun) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
for (const
|
|
321
|
-
(
|
|
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
|
+
}
|
|
322
460
|
}
|
|
323
|
-
postInstallSpinner.succeed("Post-install commands completed");
|
|
324
461
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
477
|
}
|
|
329
478
|
}
|
|
330
479
|
if (Object.keys(mergedDeps).length > 0 && options?.install !== false) {
|
|
@@ -345,16 +494,34 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
345
494
|
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
|
|
346
495
|
}
|
|
347
496
|
}
|
|
348
|
-
if (moduleMetadata.envVars
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}));
|
|
353
|
-
if (!options?.dryRun) {
|
|
354
|
-
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;
|
|
355
501
|
}
|
|
356
|
-
else {
|
|
357
|
-
|
|
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
|
+
}
|
|
358
525
|
}
|
|
359
526
|
}
|
|
360
527
|
}
|
|
@@ -446,8 +613,11 @@ async function findModulePath(modulesDir, moduleName, provider) {
|
|
|
446
613
|
const metadataPath = path_1.default.join(modulePath, "module.json");
|
|
447
614
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
448
615
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
449
|
-
if (provider
|
|
450
|
-
|
|
616
|
+
if (provider) {
|
|
617
|
+
const baseProvider = String(provider).split("-")[0];
|
|
618
|
+
if (moduleDir === provider || moduleDir === baseProvider) {
|
|
619
|
+
return modulePath;
|
|
620
|
+
}
|
|
451
621
|
}
|
|
452
622
|
if (!provider && metadata.name === moduleName) {
|
|
453
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-")) {
|