stackkit 0.1.4 → 0.1.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/README.md +1 -1
- package/dist/cli/add.js +146 -105
- package/dist/cli/create.d.ts +2 -2
- package/dist/cli/create.js +94 -27
- package/dist/cli/doctor.js +42 -17
- package/dist/cli/list.js +14 -2
- package/dist/index.js +16 -16
- package/dist/lib/conversion/js-conversion.js +1 -1
- package/dist/lib/discovery/module-discovery.js +35 -35
- package/dist/lib/env/env-editor.js +1 -1
- package/dist/lib/framework/framework-utils.d.ts +1 -1
- package/dist/lib/framework/framework-utils.js +3 -3
- package/dist/lib/generation/code-generator.d.ts +5 -5
- package/dist/lib/generation/code-generator.js +166 -132
- package/dist/lib/generation/generator-utils.d.ts +2 -2
- package/dist/lib/generation/generator-utils.js +28 -15
- package/dist/lib/utils/package-root.js +2 -2
- package/modules/auth/authjs/generator.json +1 -2
- package/modules/auth/better-auth/files/lib/auth.ts +37 -30
- package/modules/auth/better-auth/files/prisma/schema.prisma +3 -3
- package/modules/auth/better-auth/generator.json +46 -16
- package/modules/database/mongoose/generator.json +23 -4
- package/modules/database/prisma/files/lib/prisma.ts +26 -21
- package/modules/database/prisma/files/prisma.config.ts +17 -0
- package/modules/database/prisma/generator.json +50 -11
- package/package.json +3 -1
- package/templates/express/src/server.ts +9 -3
- package/templates/express/tsconfig.json +2 -2
- package/templates/nextjs/lib/env.ts +1 -1
package/README.md
CHANGED
package/dist/cli/add.js
CHANGED
|
@@ -17,7 +17,6 @@ const logger_1 = require("../lib/ui/logger");
|
|
|
17
17
|
const package_manager_1 = require("../lib/pm/package-manager");
|
|
18
18
|
const package_root_1 = require("../lib/utils/package-root");
|
|
19
19
|
const framework_utils_1 = require("../lib/framework/framework-utils");
|
|
20
|
-
const generator_utils_1 = require("../lib/generation/generator-utils");
|
|
21
20
|
async function addCommand(module, options) {
|
|
22
21
|
try {
|
|
23
22
|
const projectRoot = process.cwd();
|
|
@@ -29,10 +28,6 @@ async function addCommand(module, options) {
|
|
|
29
28
|
logger_1.logger.newLine();
|
|
30
29
|
logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
|
|
31
30
|
logger_1.logger.newLine();
|
|
32
|
-
if (config.metadata.envVars && config.metadata.envVars.length > 0) {
|
|
33
|
-
logger_1.logger.log("Next: Fill in environment variables in .env");
|
|
34
|
-
}
|
|
35
|
-
logger_1.logger.newLine();
|
|
36
31
|
}
|
|
37
32
|
catch (error) {
|
|
38
33
|
logger_1.logger.error(`Failed to add module: ${error.message}`);
|
|
@@ -43,87 +38,60 @@ async function addCommand(module, options) {
|
|
|
43
38
|
}
|
|
44
39
|
}
|
|
45
40
|
async function getAddConfig(module, options, projectInfo) {
|
|
46
|
-
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (module === "database" || module === "auth") {
|
|
41
|
+
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
|
|
42
|
+
// If no module provided, go interactive
|
|
43
|
+
if (!module) {
|
|
44
|
+
return await getInteractiveConfig(modulesDir, projectInfo);
|
|
45
|
+
}
|
|
46
|
+
// Only allow: no-arg interactive, or explicit category + --provider.
|
|
47
|
+
// Disallow positional provider names like `npx stackkit add better-auth` or
|
|
48
|
+
// `npx stackkit add auth prisma-postgresql` — require `--provider` flag.
|
|
49
|
+
if (module === "database" || module === "auth") {
|
|
50
|
+
if (!options?.provider) {
|
|
57
51
|
if (module === "database") {
|
|
58
|
-
|
|
59
|
-
throw new Error("Provider is required for database. Use --provider <provider>");
|
|
60
|
-
}
|
|
61
|
-
let baseProvider = options.provider;
|
|
62
|
-
let adapterProvider = options.provider;
|
|
63
|
-
if (options.provider.includes('-')) {
|
|
64
|
-
const parts = options.provider.split('-');
|
|
65
|
-
baseProvider = parts[0]; // e.g., "prisma"
|
|
66
|
-
adapterProvider = options.provider; // e.g., "prisma-postgresql"
|
|
67
|
-
}
|
|
68
|
-
const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
|
|
69
|
-
if (!moduleMetadata) {
|
|
70
|
-
throw new Error(`Database provider "${baseProvider}" not found`);
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
module: "database",
|
|
74
|
-
provider: adapterProvider,
|
|
75
|
-
displayName: `${moduleMetadata.displayName} (${adapterProvider.split('-')[1] || adapterProvider})`,
|
|
76
|
-
metadata: moduleMetadata,
|
|
77
|
-
};
|
|
52
|
+
throw new Error('Provider is required for database. Use: `npx stackkit add database --provider <provider>`');
|
|
78
53
|
}
|
|
79
|
-
else
|
|
80
|
-
|
|
81
|
-
const moduleMetadata = await loadModuleMetadata(modulesDir, provider, provider);
|
|
82
|
-
if (!moduleMetadata) {
|
|
83
|
-
throw new Error(`Auth provider "${provider}" not found`);
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
module: "auth",
|
|
87
|
-
provider,
|
|
88
|
-
displayName: moduleMetadata.displayName,
|
|
89
|
-
metadata: moduleMetadata,
|
|
90
|
-
};
|
|
54
|
+
else {
|
|
55
|
+
throw new Error('Provider is required for auth. Use: `npx stackkit add auth --provider <provider>`');
|
|
91
56
|
}
|
|
92
57
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
if (providers.length > 0) {
|
|
105
|
-
const { provider } = await inquirer_1.default.prompt([
|
|
106
|
-
{
|
|
107
|
-
type: "list",
|
|
108
|
-
name: "provider",
|
|
109
|
-
message: "Select database provider:",
|
|
110
|
-
choices: providers.map((p) => ({ name: p, value: p })),
|
|
111
|
-
},
|
|
112
|
-
]);
|
|
113
|
-
selectedProvider = provider;
|
|
114
|
-
}
|
|
58
|
+
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
|
+
}
|
|
66
|
+
const moduleMetadata = await loadModuleMetadata(modulesDir, baseProvider, baseProvider);
|
|
67
|
+
if (!moduleMetadata) {
|
|
68
|
+
throw new Error(`Database provider "${baseProvider}" not found`);
|
|
115
69
|
}
|
|
70
|
+
return {
|
|
71
|
+
module: "database",
|
|
72
|
+
provider: adapterProvider,
|
|
73
|
+
displayName: `${moduleMetadata.displayName} (${adapterProvider.split("-")[1] || adapterProvider})`,
|
|
74
|
+
metadata: moduleMetadata,
|
|
75
|
+
};
|
|
116
76
|
}
|
|
117
|
-
if (
|
|
118
|
-
|
|
77
|
+
else if (module === "auth") {
|
|
78
|
+
const provider = options.provider;
|
|
79
|
+
const moduleMetadata = await loadModuleMetadata(modulesDir, provider, provider);
|
|
80
|
+
if (!moduleMetadata) {
|
|
81
|
+
throw new Error(`Auth provider "${provider}" not found`);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
module: "auth",
|
|
85
|
+
provider,
|
|
86
|
+
displayName: moduleMetadata.displayName,
|
|
87
|
+
metadata: moduleMetadata,
|
|
88
|
+
};
|
|
119
89
|
}
|
|
120
|
-
return {
|
|
121
|
-
module,
|
|
122
|
-
provider: selectedProvider,
|
|
123
|
-
displayName: moduleMetadata.displayName,
|
|
124
|
-
metadata: moduleMetadata,
|
|
125
|
-
};
|
|
126
90
|
}
|
|
91
|
+
// Unknown module type
|
|
92
|
+
throw new Error(`Unknown module type "${module}". Use "database" or "auth", or specify a provider directly.`);
|
|
93
|
+
}
|
|
94
|
+
async function getInteractiveConfig(modulesDir, projectInfo) {
|
|
127
95
|
const answers = await inquirer_1.default.prompt([
|
|
128
96
|
{
|
|
129
97
|
type: "list",
|
|
@@ -131,7 +99,7 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
131
99
|
message: "What would you like to add?",
|
|
132
100
|
choices: [
|
|
133
101
|
{ name: "Database", value: "database" },
|
|
134
|
-
{ name: "
|
|
102
|
+
{ name: "Auth", value: "auth" },
|
|
135
103
|
],
|
|
136
104
|
},
|
|
137
105
|
]);
|
|
@@ -165,9 +133,9 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
165
133
|
]);
|
|
166
134
|
return {
|
|
167
135
|
module: "database",
|
|
168
|
-
provider:
|
|
136
|
+
provider: `prisma-${providerAnswers.provider}`,
|
|
169
137
|
displayName: `Prisma (${providerAnswers.provider})`,
|
|
170
|
-
metadata: await loadModuleMetadata(modulesDir, "prisma", "prisma"),
|
|
138
|
+
metadata: (await loadModuleMetadata(modulesDir, "prisma", "prisma")),
|
|
171
139
|
};
|
|
172
140
|
}
|
|
173
141
|
else {
|
|
@@ -175,7 +143,7 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
175
143
|
module: "database",
|
|
176
144
|
provider: "mongoose",
|
|
177
145
|
displayName: "Mongoose",
|
|
178
|
-
metadata: await loadModuleMetadata(modulesDir, "mongoose", "mongoose"),
|
|
146
|
+
metadata: (await loadModuleMetadata(modulesDir, "mongoose", "mongoose")),
|
|
179
147
|
};
|
|
180
148
|
}
|
|
181
149
|
}
|
|
@@ -208,6 +176,55 @@ async function getAddConfig(module, options, projectInfo) {
|
|
|
208
176
|
}
|
|
209
177
|
throw new Error("Invalid selection");
|
|
210
178
|
}
|
|
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
|
+
}
|
|
211
228
|
async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
212
229
|
const moduleMetadata = config.metadata;
|
|
213
230
|
const selectedProvider = config.provider;
|
|
@@ -226,24 +243,45 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
226
243
|
return;
|
|
227
244
|
}
|
|
228
245
|
}
|
|
246
|
+
if (config.module === "database" && projectInfo.hasDatabase && !options?.force) {
|
|
247
|
+
logger_1.logger.warn("Database library already detected in this project");
|
|
248
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
249
|
+
{
|
|
250
|
+
type: "confirm",
|
|
251
|
+
name: "proceed",
|
|
252
|
+
message: "Continue anyway? (use --force to skip this prompt)",
|
|
253
|
+
default: false,
|
|
254
|
+
},
|
|
255
|
+
]);
|
|
256
|
+
if (!proceed) {
|
|
257
|
+
logger_1.logger.info("Cancelled");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
229
261
|
if (options?.dryRun) {
|
|
230
262
|
logger_1.logger.warn("Dry run mode - no changes will be made");
|
|
231
263
|
logger_1.logger.newLine();
|
|
232
264
|
}
|
|
233
|
-
const moduleBasePath = await findModulePath(path_1.default.join((0, package_root_1.getPackageRoot)(),
|
|
265
|
+
const moduleBasePath = await findModulePath(path_1.default.join((0, package_root_1.getPackageRoot)(), "modules"), config.module, config.provider);
|
|
234
266
|
if (moduleBasePath) {
|
|
235
|
-
const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(projectInfo.framework, path_1.default.join((0, package_root_1.getPackageRoot)(),
|
|
267
|
+
const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(projectInfo.framework, path_1.default.join((0, package_root_1.getPackageRoot)(), "templates"));
|
|
236
268
|
const gen = new code_generator_1.AdvancedCodeGenerator(frameworkConfig);
|
|
237
|
-
await gen.loadGenerators(path_1.default.join((0, package_root_1.getPackageRoot)(),
|
|
269
|
+
await gen.loadGenerators(path_1.default.join((0, package_root_1.getPackageRoot)(), "modules"));
|
|
238
270
|
const moduleName = path_1.default.basename(moduleBasePath);
|
|
239
271
|
const available = gen.getAvailableGenerators();
|
|
240
|
-
const alreadyRegistered = (config.module ===
|
|
272
|
+
const alreadyRegistered = (config.module === "database" && available.databases.includes(moduleName)) ||
|
|
273
|
+
(config.module === "auth" && available.auths.includes(moduleName));
|
|
241
274
|
if (!alreadyRegistered) {
|
|
242
275
|
const ops = [];
|
|
243
276
|
if (Array.isArray(moduleMetadata.patches)) {
|
|
244
277
|
for (const p of moduleMetadata.patches) {
|
|
245
|
-
if (p.type ===
|
|
246
|
-
ops.push({
|
|
278
|
+
if (p.type === "create-file") {
|
|
279
|
+
ops.push({
|
|
280
|
+
type: "create-file",
|
|
281
|
+
source: p.source,
|
|
282
|
+
destination: p.destination,
|
|
283
|
+
condition: p.condition,
|
|
284
|
+
});
|
|
247
285
|
}
|
|
248
286
|
}
|
|
249
287
|
}
|
|
@@ -258,40 +296,40 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
258
296
|
}
|
|
259
297
|
}
|
|
260
298
|
const selectedModules = { framework: projectInfo.framework };
|
|
261
|
-
if (config.module ===
|
|
262
|
-
if (config.provider.startsWith(
|
|
263
|
-
selectedModules.database =
|
|
264
|
-
selectedModules.prismaProvider = config.provider.split(
|
|
299
|
+
if (config.module === "database" && config.provider) {
|
|
300
|
+
if (config.provider.startsWith("prisma-")) {
|
|
301
|
+
selectedModules.database = "prisma";
|
|
302
|
+
selectedModules.prismaProvider = config.provider.split("-")[1];
|
|
265
303
|
}
|
|
266
304
|
else {
|
|
267
305
|
selectedModules.database = config.provider;
|
|
268
306
|
}
|
|
269
307
|
}
|
|
270
|
-
if (config.module ===
|
|
308
|
+
if (config.module === "auth" && config.provider) {
|
|
271
309
|
selectedModules.auth = config.provider;
|
|
272
310
|
}
|
|
273
311
|
const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
|
|
274
312
|
if (postInstall && postInstall.length > 0 && !options?.dryRun) {
|
|
275
|
-
const postInstallSpinner = logger_1.logger.startSpinner(
|
|
313
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
276
314
|
try {
|
|
277
315
|
for (const command of postInstall) {
|
|
278
|
-
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio:
|
|
316
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
279
317
|
}
|
|
280
|
-
postInstallSpinner.succeed(
|
|
318
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
281
319
|
}
|
|
282
320
|
catch (error) {
|
|
283
|
-
postInstallSpinner.fail(
|
|
321
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
284
322
|
throw error;
|
|
285
323
|
}
|
|
286
324
|
}
|
|
287
325
|
if (!options?.dryRun && options?.install !== false) {
|
|
288
|
-
const installSpinner = logger_1.logger.startSpinner(
|
|
326
|
+
const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
|
|
289
327
|
try {
|
|
290
328
|
await (0, package_manager_1.installDependencies)(projectRoot, projectInfo.packageManager);
|
|
291
|
-
installSpinner.succeed(
|
|
329
|
+
installSpinner.succeed("Dependencies installed");
|
|
292
330
|
}
|
|
293
331
|
catch (err) {
|
|
294
|
-
installSpinner.fail(
|
|
332
|
+
installSpinner.fail("Failed to install dependencies");
|
|
295
333
|
throw err;
|
|
296
334
|
}
|
|
297
335
|
}
|
|
@@ -314,15 +352,18 @@ async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
|
314
352
|
if (moduleMetadata.envVars) {
|
|
315
353
|
const envArray = Array.isArray(moduleMetadata.envVars)
|
|
316
354
|
? moduleMetadata.envVars
|
|
317
|
-
: Object.entries(moduleMetadata.envVars).map(([k, v]) => ({
|
|
355
|
+
: Object.entries(moduleMetadata.envVars).map(([k, v]) => ({
|
|
356
|
+
key: k,
|
|
357
|
+
value: String(v),
|
|
358
|
+
}));
|
|
318
359
|
for (const ev of envArray) {
|
|
319
|
-
if (ev.key && typeof ev.value ===
|
|
360
|
+
if (ev.key && typeof ev.value === "string")
|
|
320
361
|
variables[ev.key] = ev.value;
|
|
321
362
|
}
|
|
322
363
|
for (let pass = 0; pass < 5; pass++) {
|
|
323
364
|
let changed = false;
|
|
324
365
|
for (const ev of envArray) {
|
|
325
|
-
if (!ev.key || typeof ev.value !==
|
|
366
|
+
if (!ev.key || typeof ev.value !== "string")
|
|
326
367
|
continue;
|
|
327
368
|
const resolved = ev.value.replace(/\{\{(\w+)\}\}/g, (_m, k) => variables[k] ?? _m);
|
|
328
369
|
if (variables[ev.key] !== resolved) {
|
|
@@ -402,10 +443,10 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
402
443
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
403
444
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
404
445
|
if (provider && moduleDir === provider) {
|
|
405
|
-
return
|
|
446
|
+
return metadata;
|
|
406
447
|
}
|
|
407
448
|
if (!provider && (metadata.category === moduleName || moduleDir === moduleName)) {
|
|
408
|
-
return
|
|
449
|
+
return metadata;
|
|
409
450
|
}
|
|
410
451
|
}
|
|
411
452
|
}
|
package/dist/cli/create.d.ts
CHANGED
|
@@ -10,10 +10,10 @@ interface CliOptions {
|
|
|
10
10
|
packageManager?: string;
|
|
11
11
|
p?: string;
|
|
12
12
|
install?: boolean;
|
|
13
|
-
|
|
13
|
+
"skip-install"?: boolean;
|
|
14
14
|
skipInstall?: boolean;
|
|
15
15
|
git?: boolean;
|
|
16
|
-
|
|
16
|
+
"no-git"?: boolean;
|
|
17
17
|
noGit?: boolean;
|
|
18
18
|
yes?: boolean;
|
|
19
19
|
y?: boolean;
|
package/dist/cli/create.js
CHANGED
|
@@ -18,6 +18,7 @@ const framework_utils_1 = require("../lib/framework/framework-utils");
|
|
|
18
18
|
const module_discovery_1 = require("../lib/discovery/module-discovery");
|
|
19
19
|
const code_generator_1 = require("../lib/generation/code-generator");
|
|
20
20
|
const package_root_1 = require("../lib/utils/package-root");
|
|
21
|
+
const env_editor_1 = require("../lib/env/env-editor");
|
|
21
22
|
async function createProject(projectName, options) {
|
|
22
23
|
logger_1.logger.newLine();
|
|
23
24
|
logger_1.logger.log(chalk_1.default.bold.cyan("📦 Create StackKit App"));
|
|
@@ -30,18 +31,19 @@ async function createProject(projectName, options) {
|
|
|
30
31
|
process.exit(1);
|
|
31
32
|
}
|
|
32
33
|
await generateProject(config, targetDir, options);
|
|
34
|
+
await processGeneratorEnvVars(config, targetDir);
|
|
33
35
|
showNextSteps(config);
|
|
34
36
|
}
|
|
35
37
|
async function getProjectConfig(projectName, options) {
|
|
36
|
-
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(),
|
|
38
|
+
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
|
|
37
39
|
const discoveredModules = await (0, module_discovery_1.discoverModules)(modulesDir);
|
|
38
40
|
const argv = process.argv.slice(2);
|
|
39
|
-
const createIndex = argv.indexOf(
|
|
41
|
+
const createIndex = argv.indexOf("create");
|
|
40
42
|
const argsAfterCreate = createIndex >= 0 ? argv.slice(createIndex + 1) : [];
|
|
41
|
-
const flagsProvided = argsAfterCreate.some(arg => arg.startsWith(
|
|
43
|
+
const flagsProvided = argsAfterCreate.some((arg) => arg.startsWith("-"));
|
|
42
44
|
const optionsProvided = flagsProvided || !!(options && (options.yes || options.y));
|
|
43
45
|
if (optionsProvided) {
|
|
44
|
-
if (
|
|
46
|
+
if (options && (options.yes || options.y) && !flagsProvided) {
|
|
45
47
|
return {
|
|
46
48
|
projectName: projectName || "my-app",
|
|
47
49
|
framework: "nextjs",
|
|
@@ -54,37 +56,37 @@ async function getProjectConfig(projectName, options) {
|
|
|
54
56
|
}
|
|
55
57
|
const framework = (options && (options.framework || options.f)) || undefined;
|
|
56
58
|
if (discoveredModules.frameworks && discoveredModules.frameworks.length > 0) {
|
|
57
|
-
const validFrameworks = discoveredModules.frameworks.map(f => f.name);
|
|
59
|
+
const validFrameworks = discoveredModules.frameworks.map((f) => f.name);
|
|
58
60
|
if (framework && !validFrameworks.includes(framework)) {
|
|
59
|
-
throw new Error(`Invalid framework: ${framework}. Valid options: ${validFrameworks.join(
|
|
61
|
+
throw new Error(`Invalid framework: ${framework}. Valid options: ${validFrameworks.join(", ")}`);
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
const db = (options && (options.database || options.d)) || undefined;
|
|
63
65
|
let allValidDatabases = [];
|
|
64
66
|
if (discoveredModules.databases && discoveredModules.databases.length > 0) {
|
|
65
67
|
const validDatabases = (0, module_discovery_1.getValidDatabaseOptions)(discoveredModules.databases);
|
|
66
|
-
const validBaseDatabases = discoveredModules.databases.map(db => db.name);
|
|
68
|
+
const validBaseDatabases = discoveredModules.databases.map((db) => db.name);
|
|
67
69
|
allValidDatabases = [...validDatabases, ...validBaseDatabases];
|
|
68
70
|
if (db && !allValidDatabases.includes(db)) {
|
|
69
|
-
throw new Error(`Invalid database: ${db}. Valid options: ${allValidDatabases.filter((v, i, arr) => arr.indexOf(v) === i).join(
|
|
71
|
+
throw new Error(`Invalid database: ${db}. Valid options: ${allValidDatabases.filter((v, i, arr) => arr.indexOf(v) === i).join(", ")}`);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
const authOpt = (options && (options.auth || options.a)) || undefined;
|
|
73
75
|
if (discoveredModules.auth && discoveredModules.auth.length > 0) {
|
|
74
76
|
const validAuth = (0, module_discovery_1.getValidAuthOptions)(discoveredModules.auth);
|
|
75
77
|
if (authOpt && !validAuth.includes(authOpt)) {
|
|
76
|
-
throw new Error(`Invalid auth: ${authOpt}. Valid options: ${validAuth.join(
|
|
78
|
+
throw new Error(`Invalid auth: ${authOpt}. Valid options: ${validAuth.join(", ")}`);
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
|
-
const validLanguages = [
|
|
81
|
+
const validLanguages = ["typescript", "javascript"];
|
|
80
82
|
const language = (options && (options.language || options.l)) || undefined;
|
|
81
83
|
if (language && !validLanguages.includes(language)) {
|
|
82
|
-
throw new Error(`Invalid language: ${language}. Valid options: ${validLanguages.join(
|
|
84
|
+
throw new Error(`Invalid language: ${language}. Valid options: ${validLanguages.join(", ")}`);
|
|
83
85
|
}
|
|
84
|
-
const validPackageManagers = [
|
|
86
|
+
const validPackageManagers = ["pnpm", "npm", "yarn", "bun"];
|
|
85
87
|
const pm = (options && (options.packageManager || options.p)) || undefined;
|
|
86
88
|
if (pm && !validPackageManagers.includes(pm)) {
|
|
87
|
-
throw new Error(`Invalid package manager: ${pm}. Valid options: ${validPackageManagers.join(
|
|
89
|
+
throw new Error(`Invalid package manager: ${pm}. Valid options: ${validPackageManagers.join(", ")}`);
|
|
88
90
|
}
|
|
89
91
|
let database = "none";
|
|
90
92
|
let prismaProvider;
|
|
@@ -136,12 +138,12 @@ async function getProjectConfig(projectName, options) {
|
|
|
136
138
|
type: "list",
|
|
137
139
|
name: "framework",
|
|
138
140
|
message: "Select framework:",
|
|
139
|
-
choices:
|
|
140
|
-
? discoveredModules.frameworks.map(f => ({ name: f.displayName, value: f.name }))
|
|
141
|
+
choices: discoveredModules.frameworks && discoveredModules.frameworks.length > 0
|
|
142
|
+
? discoveredModules.frameworks.map((f) => ({ name: f.displayName, value: f.name }))
|
|
141
143
|
: [
|
|
142
|
-
{ name:
|
|
143
|
-
{ name:
|
|
144
|
-
{ name:
|
|
144
|
+
{ name: "Next.js", value: "nextjs" },
|
|
145
|
+
{ name: "Express.js", value: "express" },
|
|
146
|
+
{ name: "React (Vite)", value: "react" },
|
|
145
147
|
],
|
|
146
148
|
},
|
|
147
149
|
{
|
|
@@ -171,7 +173,7 @@ async function getProjectConfig(projectName, options) {
|
|
|
171
173
|
type: "list",
|
|
172
174
|
name: "auth",
|
|
173
175
|
message: "Select authentication:",
|
|
174
|
-
when: (answers) =>
|
|
176
|
+
when: (answers) => answers.database !== "none" || answers.framework === "react",
|
|
175
177
|
choices: (answers) => (0, module_discovery_1.getCompatibleAuthOptions)(discoveredModules.auth, answers.framework, answers.database || "none"),
|
|
176
178
|
},
|
|
177
179
|
{
|
|
@@ -204,7 +206,7 @@ async function getProjectConfig(projectName, options) {
|
|
|
204
206
|
? "none"
|
|
205
207
|
: answers.database),
|
|
206
208
|
prismaProvider: answers.prismaProvider,
|
|
207
|
-
auth:
|
|
209
|
+
auth: answers.auth || "none",
|
|
208
210
|
language: answers.language,
|
|
209
211
|
packageManager: answers.packageManager,
|
|
210
212
|
};
|
|
@@ -220,7 +222,7 @@ async function generateProject(config, targetDir, options) {
|
|
|
220
222
|
copySpinner.fail("Failed to create project files");
|
|
221
223
|
throw error;
|
|
222
224
|
}
|
|
223
|
-
if (options?.install !== false && !(options?.[
|
|
225
|
+
if (options?.install !== false && !(options?.["skip-install"] || options?.skipInstall)) {
|
|
224
226
|
const installSpinner = logger_1.logger.startSpinner("Installing dependencies...");
|
|
225
227
|
try {
|
|
226
228
|
await (0, package_manager_1.installDependencies)(targetDir, config.packageManager);
|
|
@@ -231,7 +233,9 @@ async function generateProject(config, targetDir, options) {
|
|
|
231
233
|
throw error;
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
|
-
if (postInstallCommands.length > 0 &&
|
|
236
|
+
if (postInstallCommands.length > 0 &&
|
|
237
|
+
options?.install !== false &&
|
|
238
|
+
!(options?.["skip-install"] || options?.skipInstall)) {
|
|
235
239
|
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
236
240
|
try {
|
|
237
241
|
for (const command of postInstallCommands) {
|
|
@@ -244,7 +248,7 @@ async function generateProject(config, targetDir, options) {
|
|
|
244
248
|
throw error;
|
|
245
249
|
}
|
|
246
250
|
}
|
|
247
|
-
if (options?.git !== false && !(options?.[
|
|
251
|
+
if (options?.git !== false && !(options?.["no-git"] || options?.noGit)) {
|
|
248
252
|
const gitSpinner = logger_1.logger.startSpinner("Initializing git repository...");
|
|
249
253
|
try {
|
|
250
254
|
await (0, git_utils_1.initGit)(targetDir);
|
|
@@ -257,8 +261,8 @@ async function generateProject(config, targetDir, options) {
|
|
|
257
261
|
}
|
|
258
262
|
async function composeTemplate(config, targetDir) {
|
|
259
263
|
const packageRoot = (0, package_root_1.getPackageRoot)();
|
|
260
|
-
const templatesDir = path_1.default.join(packageRoot,
|
|
261
|
-
const modulesDirForGenerator = path_1.default.join(packageRoot,
|
|
264
|
+
const templatesDir = path_1.default.join(packageRoot, "templates");
|
|
265
|
+
const modulesDirForGenerator = path_1.default.join(packageRoot, "modules");
|
|
262
266
|
await fs_extra_1.default.ensureDir(targetDir);
|
|
263
267
|
const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(config.framework, templatesDir);
|
|
264
268
|
const generator = new code_generator_1.AdvancedCodeGenerator(frameworkConfig);
|
|
@@ -266,8 +270,8 @@ async function composeTemplate(config, targetDir) {
|
|
|
266
270
|
const features = [];
|
|
267
271
|
const postInstallCommands = await generator.generate({
|
|
268
272
|
framework: config.framework,
|
|
269
|
-
database: config.database ===
|
|
270
|
-
auth: config.auth ===
|
|
273
|
+
database: config.database === "none" ? undefined : config.database,
|
|
274
|
+
auth: config.auth === "none" ? undefined : config.auth,
|
|
271
275
|
prismaProvider: config.prismaProvider,
|
|
272
276
|
}, features, targetDir);
|
|
273
277
|
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
@@ -285,12 +289,75 @@ async function composeTemplate(config, targetDir) {
|
|
|
285
289
|
}
|
|
286
290
|
}
|
|
287
291
|
catch {
|
|
292
|
+
// env copy failed.
|
|
288
293
|
}
|
|
289
294
|
if (config.language === "javascript") {
|
|
290
295
|
await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
|
|
291
296
|
}
|
|
292
297
|
return postInstallCommands;
|
|
293
298
|
}
|
|
299
|
+
async function processGeneratorEnvVars(config, targetDir) {
|
|
300
|
+
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), "modules");
|
|
301
|
+
const envVars = [];
|
|
302
|
+
// Process database generator env vars
|
|
303
|
+
if (config.database && config.database !== "none") {
|
|
304
|
+
const dbGeneratorPath = path_1.default.join(modulesDir, "database", config.database, "generator.json");
|
|
305
|
+
if (await fs_extra_1.default.pathExists(dbGeneratorPath)) {
|
|
306
|
+
const generator = await fs_extra_1.default.readJson(dbGeneratorPath);
|
|
307
|
+
if (generator.operations) {
|
|
308
|
+
for (const operation of generator.operations) {
|
|
309
|
+
if (operation.type === "add-env" && (!operation.condition || checkCondition(operation.condition, config))) {
|
|
310
|
+
for (const [key, value] of Object.entries(operation.envVars)) {
|
|
311
|
+
envVars.push({
|
|
312
|
+
key,
|
|
313
|
+
value: value,
|
|
314
|
+
required: true,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Process auth generator env vars
|
|
323
|
+
if (config.auth && config.auth !== "none") {
|
|
324
|
+
const authGeneratorPath = path_1.default.join(modulesDir, "auth", config.auth, "generator.json");
|
|
325
|
+
if (await fs_extra_1.default.pathExists(authGeneratorPath)) {
|
|
326
|
+
const generator = await fs_extra_1.default.readJson(authGeneratorPath);
|
|
327
|
+
if (generator.operations) {
|
|
328
|
+
for (const operation of generator.operations) {
|
|
329
|
+
if (operation.type === "add-env" && (!operation.condition || checkCondition(operation.condition, config))) {
|
|
330
|
+
for (const [key, value] of Object.entries(operation.envVars)) {
|
|
331
|
+
envVars.push({
|
|
332
|
+
key,
|
|
333
|
+
value: value,
|
|
334
|
+
required: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (envVars.length > 0) {
|
|
343
|
+
await (0, env_editor_1.addEnvVariables)(targetDir, envVars, { force: true });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function checkCondition(condition, config) {
|
|
347
|
+
for (const [key, value] of Object.entries(condition)) {
|
|
348
|
+
if (Array.isArray(value)) {
|
|
349
|
+
if (!value.includes(config[key])) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
if (config[key] !== value) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
294
361
|
function showNextSteps(config) {
|
|
295
362
|
logger_1.logger.newLine();
|
|
296
363
|
logger_1.logger.success(`Created ${config.projectName}`);
|