stackkit 0.1.3 → 0.1.4
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 +4 -2
- package/dist/cli/add.d.ts +2 -1
- package/dist/cli/add.js +317 -105
- package/dist/cli/create.js +2 -3
- package/dist/cli/doctor.js +0 -4
- package/dist/index.js +23 -4
- package/dist/lib/conversion/js-conversion.js +1 -1
- package/dist/lib/discovery/module-discovery.d.ts +0 -1
- package/dist/lib/discovery/module-discovery.js +3 -3
- package/dist/lib/generation/code-generator.d.ts +14 -0
- package/dist/lib/generation/code-generator.js +55 -24
- package/dist/lib/generation/generator-utils.d.ts +11 -0
- package/dist/lib/generation/generator-utils.js +116 -0
- package/dist/lib/git-utils.js +20 -0
- package/dist/lib/project/detect.js +2 -4
- package/dist/types/index.d.ts +0 -10
- package/modules/auth/authjs/generator.json +10 -0
- package/modules/auth/authjs/module.json +0 -9
- package/modules/auth/better-auth/files/lib/auth.ts +7 -7
- package/modules/auth/better-auth/generator.json +12 -12
- package/modules/auth/better-auth/module.json +0 -24
- package/modules/database/mongoose/files/models/health.ts +34 -0
- package/modules/database/mongoose/generator.json +4 -4
- package/modules/database/mongoose/module.json +1 -2
- package/modules/database/prisma/files/lib/prisma.ts +2 -1
- package/modules/database/prisma/generator.json +33 -8
- package/modules/database/prisma/module.json +1 -4
- package/package.json +1 -1
- package/templates/express/tsconfig.json +2 -23
- package/dist/lib/database/database-config.d.ts +0 -6
- package/dist/lib/database/database-config.js +0 -9
- package/modules/database/mongoose/files/models/User.ts +0 -34
- /package/modules/database/mongoose/files/lib/{db.ts → mongoose.ts} +0 -0
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ npx stackkit create my-app
|
|
|
19
19
|
## Add Features to Existing Project
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
+
npx stackkit add
|
|
23
|
+
# or non-interactive
|
|
22
24
|
npx stackkit add auth
|
|
23
25
|
npx stackkit add database
|
|
24
26
|
```
|
|
@@ -32,10 +34,10 @@ npx stackkit doctor
|
|
|
32
34
|
## Supported Technologies
|
|
33
35
|
|
|
34
36
|
- **Frameworks**: Next.js, React, Express
|
|
35
|
-
- **Databases**: Prisma (PostgreSQL, MySQL, SQLite), Mongoose (MongoDB)
|
|
37
|
+
- **Databases**: Prisma (PostgreSQL, MongoDB, MySQL, SQLite), Mongoose (MongoDB)
|
|
36
38
|
- **Auth**: Better Auth, Auth.js
|
|
37
39
|
|
|
38
40
|
## Documentation
|
|
39
41
|
|
|
40
|
-
- [StackKit Docs](https://
|
|
42
|
+
- [StackKit Docs](https://stackkit.tariqul.dev)
|
|
41
43
|
- [GitHub Repository](https://github.com/tariqul420/stackkit)
|
package/dist/cli/add.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ interface AddOptions {
|
|
|
3
3
|
force?: boolean;
|
|
4
4
|
dryRun?: boolean;
|
|
5
5
|
install?: boolean;
|
|
6
|
+
yes?: boolean;
|
|
6
7
|
}
|
|
7
|
-
export declare function addCommand(module
|
|
8
|
+
export declare function addCommand(module?: string, options?: AddOptions): Promise<void>;
|
|
8
9
|
export {};
|
package/dist/cli/add.js
CHANGED
|
@@ -9,25 +9,95 @@ const child_process_1 = require("child_process");
|
|
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
10
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const code_generator_1 = require("../lib/generation/code-generator");
|
|
12
13
|
const detect_1 = require("../lib/project/detect");
|
|
13
14
|
const env_editor_1 = require("../lib/env/env-editor");
|
|
14
15
|
const files_1 = require("../lib/fs/files");
|
|
15
16
|
const logger_1 = require("../lib/ui/logger");
|
|
16
17
|
const package_manager_1 = require("../lib/pm/package-manager");
|
|
17
|
-
const database_config_1 = require("../lib/database/database-config");
|
|
18
18
|
const package_root_1 = require("../lib/utils/package-root");
|
|
19
|
+
const framework_utils_1 = require("../lib/framework/framework-utils");
|
|
20
|
+
const generator_utils_1 = require("../lib/generation/generator-utils");
|
|
19
21
|
async function addCommand(module, options) {
|
|
20
22
|
try {
|
|
21
23
|
const projectRoot = process.cwd();
|
|
22
24
|
const spinner = logger_1.logger.startSpinner("Detecting project...");
|
|
23
25
|
const projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
24
26
|
spinner.succeed(`Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`);
|
|
25
|
-
const
|
|
27
|
+
const config = await getAddConfig(module, options, projectInfo);
|
|
28
|
+
await addModuleToProject(projectRoot, projectInfo, config, options);
|
|
29
|
+
logger_1.logger.newLine();
|
|
30
|
+
logger_1.logger.success(`Added ${chalk_1.default.bold(config.displayName)}`);
|
|
31
|
+
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
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
logger_1.logger.error(`Failed to add module: ${error.message}`);
|
|
39
|
+
if (error instanceof Error && error.stack) {
|
|
40
|
+
logger_1.logger.log(chalk_1.default.gray(error.stack));
|
|
41
|
+
}
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function getAddConfig(module, options, projectInfo) {
|
|
46
|
+
const modulesDir = path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules');
|
|
47
|
+
const argv = process.argv.slice(2);
|
|
48
|
+
const addIndex = argv.indexOf('add');
|
|
49
|
+
const argsAfterAdd = addIndex >= 0 ? argv.slice(addIndex + 1) : [];
|
|
50
|
+
const flagsProvided = argsAfterAdd.some(arg => arg.startsWith('-'));
|
|
51
|
+
const optionsProvided = flagsProvided || !!(options && (options.yes || options.provider || options.force || options.dryRun));
|
|
52
|
+
if (optionsProvided) {
|
|
53
|
+
if (!module) {
|
|
54
|
+
throw new Error("Module name is required when using flags");
|
|
55
|
+
}
|
|
56
|
+
if (module === "database" || module === "auth") {
|
|
57
|
+
if (module === "database") {
|
|
58
|
+
if (!options?.provider) {
|
|
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
|
+
};
|
|
78
|
+
}
|
|
79
|
+
else if (module === "auth") {
|
|
80
|
+
const provider = options?.provider || "better-auth";
|
|
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
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const moduleMetadata = await loadModuleMetadata(modulesDir, module, options?.provider);
|
|
26
94
|
if (!moduleMetadata) {
|
|
27
|
-
|
|
28
|
-
|
|
95
|
+
throw new Error(`Module "${module}" not found`);
|
|
96
|
+
}
|
|
97
|
+
let selectedProvider = options?.provider;
|
|
98
|
+
if (!selectedProvider && moduleMetadata.category !== module) {
|
|
99
|
+
selectedProvider = module;
|
|
29
100
|
}
|
|
30
|
-
let selectedProvider = options.provider;
|
|
31
101
|
if (moduleMetadata.category === "database" && !selectedProvider) {
|
|
32
102
|
if (typeof moduleMetadata.dependencies === "object" && "providers" in moduleMetadata.dependencies) {
|
|
33
103
|
const providers = Object.keys(moduleMetadata.dependencies.providers || {});
|
|
@@ -44,111 +114,272 @@ async function addCommand(module, options) {
|
|
|
44
114
|
}
|
|
45
115
|
}
|
|
46
116
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
|
|
50
|
-
Object.assign(mergedDeps, moduleMetadata.frameworkConfigs.shared.dependencies);
|
|
117
|
+
if (projectInfo && !moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
118
|
+
throw new Error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(", ")}`);
|
|
51
119
|
}
|
|
52
|
-
|
|
53
|
-
|
|
120
|
+
return {
|
|
121
|
+
module,
|
|
122
|
+
provider: selectedProvider,
|
|
123
|
+
displayName: moduleMetadata.displayName,
|
|
124
|
+
metadata: moduleMetadata,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const answers = await inquirer_1.default.prompt([
|
|
128
|
+
{
|
|
129
|
+
type: "list",
|
|
130
|
+
name: "category",
|
|
131
|
+
message: "What would you like to add?",
|
|
132
|
+
choices: [
|
|
133
|
+
{ name: "Database", value: "database" },
|
|
134
|
+
{ name: "Authentication", value: "auth" },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
const category = answers.category;
|
|
139
|
+
if (category === "database") {
|
|
140
|
+
const dbAnswers = await inquirer_1.default.prompt([
|
|
141
|
+
{
|
|
142
|
+
type: "list",
|
|
143
|
+
name: "database",
|
|
144
|
+
message: "Select database:",
|
|
145
|
+
choices: [
|
|
146
|
+
{ name: "Prisma", value: "prisma" },
|
|
147
|
+
{ name: "Mongoose", value: "mongoose" },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
const selectedDb = dbAnswers.database;
|
|
152
|
+
if (selectedDb === "prisma") {
|
|
153
|
+
const providerAnswers = await inquirer_1.default.prompt([
|
|
154
|
+
{
|
|
155
|
+
type: "list",
|
|
156
|
+
name: "provider",
|
|
157
|
+
message: "Select Prisma provider:",
|
|
158
|
+
choices: [
|
|
159
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
160
|
+
{ name: "MongoDB", value: "mongodb" },
|
|
161
|
+
{ name: "MySQL", value: "mysql" },
|
|
162
|
+
{ name: "SQLite", value: "sqlite" },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
return {
|
|
167
|
+
module: "database",
|
|
168
|
+
provider: "prisma",
|
|
169
|
+
displayName: `Prisma (${providerAnswers.provider})`,
|
|
170
|
+
metadata: await loadModuleMetadata(modulesDir, "prisma", "prisma"),
|
|
171
|
+
};
|
|
54
172
|
}
|
|
55
|
-
|
|
56
|
-
|
|
173
|
+
else {
|
|
174
|
+
return {
|
|
175
|
+
module: "database",
|
|
176
|
+
provider: "mongoose",
|
|
177
|
+
displayName: "Mongoose",
|
|
178
|
+
metadata: await loadModuleMetadata(modulesDir, "mongoose", "mongoose"),
|
|
179
|
+
};
|
|
57
180
|
}
|
|
58
|
-
|
|
59
|
-
|
|
181
|
+
}
|
|
182
|
+
else if (category === "auth") {
|
|
183
|
+
const authAnswers = await inquirer_1.default.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: "list",
|
|
186
|
+
name: "auth",
|
|
187
|
+
message: "Select authentication:",
|
|
188
|
+
choices: [
|
|
189
|
+
{ name: "Better Auth", value: "better-auth" },
|
|
190
|
+
{ name: "Auth.js", value: "authjs" },
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
const selectedAuth = authAnswers.auth;
|
|
195
|
+
const metadata = await loadModuleMetadata(modulesDir, selectedAuth, selectedAuth);
|
|
196
|
+
if (!metadata) {
|
|
197
|
+
throw new Error(`Auth provider "${selectedAuth}" not found`);
|
|
60
198
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const variables = {};
|
|
64
|
-
if (selectedProvider) {
|
|
65
|
-
variables.provider = selectedProvider;
|
|
66
|
-
variables.connectionString = database_config_1.DATABASE_CONNECTION_STRINGS[selectedProvider] || "";
|
|
199
|
+
if (projectInfo && !metadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
200
|
+
throw new Error(`Auth provider "${selectedAuth}" does not support ${projectInfo.framework}`);
|
|
67
201
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
202
|
+
return {
|
|
203
|
+
module: "auth",
|
|
204
|
+
provider: selectedAuth,
|
|
205
|
+
displayName: selectedAuth === "better-auth" ? "Better Auth" : "Auth.js",
|
|
206
|
+
metadata,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
throw new Error("Invalid selection");
|
|
210
|
+
}
|
|
211
|
+
async function addModuleToProject(projectRoot, projectInfo, config, options) {
|
|
212
|
+
const moduleMetadata = config.metadata;
|
|
213
|
+
const selectedProvider = config.provider;
|
|
214
|
+
if (config.module === "auth" && projectInfo.hasAuth && !options?.force) {
|
|
215
|
+
logger_1.logger.warn("Auth library already detected in this project");
|
|
216
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
217
|
+
{
|
|
218
|
+
type: "confirm",
|
|
219
|
+
name: "proceed",
|
|
220
|
+
message: "Continue anyway? (use --force to skip this prompt)",
|
|
221
|
+
default: false,
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
if (!proceed) {
|
|
225
|
+
logger_1.logger.info("Cancelled");
|
|
226
|
+
return;
|
|
71
227
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
228
|
+
}
|
|
229
|
+
if (options?.dryRun) {
|
|
230
|
+
logger_1.logger.warn("Dry run mode - no changes will be made");
|
|
231
|
+
logger_1.logger.newLine();
|
|
232
|
+
}
|
|
233
|
+
const moduleBasePath = await findModulePath(path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules'), config.module, config.provider);
|
|
234
|
+
if (moduleBasePath) {
|
|
235
|
+
const frameworkConfig = await framework_utils_1.FrameworkUtils.loadFrameworkConfig(projectInfo.framework, path_1.default.join((0, package_root_1.getPackageRoot)(), 'templates'));
|
|
236
|
+
const gen = new code_generator_1.AdvancedCodeGenerator(frameworkConfig);
|
|
237
|
+
await gen.loadGenerators(path_1.default.join((0, package_root_1.getPackageRoot)(), 'modules'));
|
|
238
|
+
const moduleName = path_1.default.basename(moduleBasePath);
|
|
239
|
+
const available = gen.getAvailableGenerators();
|
|
240
|
+
const alreadyRegistered = (config.module === 'database' && available.databases.includes(moduleName)) || (config.module === 'auth' && available.auths.includes(moduleName));
|
|
241
|
+
if (!alreadyRegistered) {
|
|
242
|
+
const ops = [];
|
|
243
|
+
if (Array.isArray(moduleMetadata.patches)) {
|
|
244
|
+
for (const p of moduleMetadata.patches) {
|
|
245
|
+
if (p.type === 'create-file') {
|
|
246
|
+
ops.push({ type: 'create-file', source: p.source, destination: p.destination, condition: p.condition });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (ops.length > 0) {
|
|
251
|
+
gen.registerGenerator(config.module, moduleName, {
|
|
252
|
+
name: moduleName,
|
|
253
|
+
type: config.module,
|
|
254
|
+
priority: 0,
|
|
255
|
+
operations: ops,
|
|
256
|
+
dependencies: {},
|
|
257
|
+
});
|
|
85
258
|
}
|
|
86
259
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
260
|
+
const selectedModules = { framework: projectInfo.framework };
|
|
261
|
+
if (config.module === 'database' && config.provider) {
|
|
262
|
+
if (config.provider.startsWith('prisma-')) {
|
|
263
|
+
selectedModules.database = 'prisma';
|
|
264
|
+
selectedModules.prismaProvider = config.provider.split('-')[1];
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
selectedModules.database = config.provider;
|
|
268
|
+
}
|
|
90
269
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
270
|
+
if (config.module === 'auth' && config.provider) {
|
|
271
|
+
selectedModules.auth = config.provider;
|
|
94
272
|
}
|
|
95
|
-
|
|
96
|
-
|
|
273
|
+
const postInstall = await gen.applyToProject(selectedModules, [], projectRoot);
|
|
274
|
+
if (postInstall && postInstall.length > 0 && !options?.dryRun) {
|
|
275
|
+
const postInstallSpinner = logger_1.logger.startSpinner('Running post-install commands...');
|
|
97
276
|
try {
|
|
98
|
-
for (const command of
|
|
99
|
-
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio:
|
|
277
|
+
for (const command of postInstall) {
|
|
278
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: 'pipe' });
|
|
100
279
|
}
|
|
101
|
-
postInstallSpinner.succeed(
|
|
280
|
+
postInstallSpinner.succeed('Post-install commands completed');
|
|
102
281
|
}
|
|
103
282
|
catch (error) {
|
|
104
|
-
postInstallSpinner.fail(
|
|
283
|
+
postInstallSpinner.fail('Failed to run post-install commands');
|
|
105
284
|
throw error;
|
|
106
285
|
}
|
|
107
286
|
}
|
|
108
|
-
if (
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
await (0, package_manager_1.
|
|
287
|
+
if (!options?.dryRun && options?.install !== false) {
|
|
288
|
+
const installSpinner = logger_1.logger.startSpinner('Installing dependencies...');
|
|
289
|
+
try {
|
|
290
|
+
await (0, package_manager_1.installDependencies)(projectRoot, projectInfo.packageManager);
|
|
291
|
+
installSpinner.succeed('Dependencies installed');
|
|
112
292
|
}
|
|
113
|
-
|
|
114
|
-
|
|
293
|
+
catch (err) {
|
|
294
|
+
installSpinner.fail('Failed to install dependencies');
|
|
295
|
+
throw err;
|
|
115
296
|
}
|
|
116
297
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const mergedDeps = {};
|
|
301
|
+
const mergedDevDeps = {};
|
|
302
|
+
if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
|
|
303
|
+
Object.assign(mergedDeps, moduleMetadata.frameworkConfigs.shared.dependencies);
|
|
304
|
+
}
|
|
305
|
+
if (moduleMetadata.frameworkConfigs?.shared?.devDependencies) {
|
|
306
|
+
Object.assign(mergedDevDeps, moduleMetadata.frameworkConfigs.shared.devDependencies);
|
|
307
|
+
}
|
|
308
|
+
// Adapter-specific dependencies are applied via generator metadata; frameworkConfigs still merge above.
|
|
309
|
+
// Do not mutate the loaded module metadata here; use mergedDeps/mergedDevDeps for installation.
|
|
310
|
+
const variables = {};
|
|
311
|
+
if (selectedProvider) {
|
|
312
|
+
variables.provider = selectedProvider;
|
|
313
|
+
}
|
|
314
|
+
if (moduleMetadata.envVars) {
|
|
315
|
+
const envArray = Array.isArray(moduleMetadata.envVars)
|
|
316
|
+
? moduleMetadata.envVars
|
|
317
|
+
: Object.entries(moduleMetadata.envVars).map(([k, v]) => ({ key: k, value: String(v) }));
|
|
318
|
+
for (const ev of envArray) {
|
|
319
|
+
if (ev.key && typeof ev.value === 'string')
|
|
320
|
+
variables[ev.key] = ev.value;
|
|
125
321
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
322
|
+
for (let pass = 0; pass < 5; pass++) {
|
|
323
|
+
let changed = false;
|
|
324
|
+
for (const ev of envArray) {
|
|
325
|
+
if (!ev.key || typeof ev.value !== 'string')
|
|
326
|
+
continue;
|
|
327
|
+
const resolved = ev.value.replace(/\{\{(\w+)\}\}/g, (_m, k) => variables[k] ?? _m);
|
|
328
|
+
if (variables[ev.key] !== resolved) {
|
|
329
|
+
variables[ev.key] = resolved;
|
|
330
|
+
changed = true;
|
|
331
|
+
}
|
|
133
332
|
}
|
|
134
|
-
|
|
135
|
-
|
|
333
|
+
if (!changed)
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
await applyModulePatches(projectRoot, projectInfo, moduleMetadata, path_1.default.join((0, package_root_1.getPackageRoot)(), "modules"), config.module, options || {});
|
|
338
|
+
if (moduleMetadata.frameworkPatches && !options?.dryRun) {
|
|
339
|
+
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
340
|
+
}
|
|
341
|
+
if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options?.dryRun) {
|
|
342
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
343
|
+
try {
|
|
344
|
+
for (const command of moduleMetadata.postInstall) {
|
|
345
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
136
346
|
}
|
|
347
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
137
348
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (moduleMetadata.envVars && moduleMetadata.envVars.some((v) => v.required)) {
|
|
142
|
-
logger_1.logger.log("Next: Fill in environment variables in .env");
|
|
349
|
+
catch (error) {
|
|
350
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
351
|
+
throw error;
|
|
143
352
|
}
|
|
144
|
-
logger_1.logger.newLine();
|
|
145
353
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
354
|
+
if (Object.keys(mergedDeps).length > 0 && options?.install !== false) {
|
|
355
|
+
const deps = Object.entries(mergedDeps).map(([name, version]) => `${name}@${version}`);
|
|
356
|
+
if (!options?.dryRun) {
|
|
357
|
+
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, deps, false);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
logger_1.logger.info(`Would add dependencies: ${deps.join(", ")}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (Object.keys(mergedDevDeps).length > 0 && options?.install !== false) {
|
|
364
|
+
const devDeps = Object.entries(mergedDevDeps).map(([name, version]) => `${name}@${version}`);
|
|
365
|
+
if (!options?.dryRun) {
|
|
366
|
+
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, devDeps, true);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (moduleMetadata.envVars && moduleMetadata.envVars.length > 0) {
|
|
373
|
+
const processedEnvVars = moduleMetadata.envVars.map((envVar) => ({
|
|
374
|
+
...envVar,
|
|
375
|
+
value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
|
|
376
|
+
}));
|
|
377
|
+
if (!options?.dryRun) {
|
|
378
|
+
await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options?.force });
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
|
|
150
382
|
}
|
|
151
|
-
process.exit(1);
|
|
152
383
|
}
|
|
153
384
|
}
|
|
154
385
|
async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
@@ -171,35 +402,16 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
171
402
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
172
403
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
173
404
|
if (provider && moduleDir === provider) {
|
|
174
|
-
return await
|
|
405
|
+
return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
|
|
175
406
|
}
|
|
176
|
-
if (!provider && metadata.category === moduleName) {
|
|
177
|
-
return await
|
|
407
|
+
if (!provider && (metadata.category === moduleName || moduleDir === moduleName)) {
|
|
408
|
+
return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
|
|
178
409
|
}
|
|
179
410
|
}
|
|
180
411
|
}
|
|
181
412
|
}
|
|
182
413
|
return null;
|
|
183
414
|
}
|
|
184
|
-
async function loadGeneratorAndMerge(metadata, modulePath) {
|
|
185
|
-
const generatorPath = path_1.default.join(modulePath, "generator.json");
|
|
186
|
-
if (await fs_extra_1.default.pathExists(generatorPath)) {
|
|
187
|
-
const generator = await fs_extra_1.default.readJSON(generatorPath);
|
|
188
|
-
if (generator.envVars) {
|
|
189
|
-
metadata.envVars = metadata.envVars || [];
|
|
190
|
-
for (const [key, value] of Object.entries(generator.envVars)) {
|
|
191
|
-
metadata.envVars.push({ key, value: value, description: `Environment variable for ${key}`, required: true });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (generator.dependencies) {
|
|
195
|
-
metadata.dependencies = { ...metadata.dependencies, ...generator.dependencies };
|
|
196
|
-
}
|
|
197
|
-
if (generator.devDependencies) {
|
|
198
|
-
metadata.devDependencies = { ...metadata.devDependencies, ...generator.devDependencies };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return metadata;
|
|
202
|
-
}
|
|
203
415
|
async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, moduleName, options) {
|
|
204
416
|
if (!moduleMetadata.patches || !Array.isArray(moduleMetadata.patches)) {
|
|
205
417
|
return;
|
package/dist/cli/create.js
CHANGED
|
@@ -250,8 +250,8 @@ async function generateProject(config, targetDir, options) {
|
|
|
250
250
|
await (0, git_utils_1.initGit)(targetDir);
|
|
251
251
|
gitSpinner.succeed("Git repository initialized");
|
|
252
252
|
}
|
|
253
|
-
catch {
|
|
254
|
-
gitSpinner.warn(
|
|
253
|
+
catch (error) {
|
|
254
|
+
gitSpinner.warn(`Failed to initialize git repository: ${error.message}`);
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
}
|
|
@@ -285,7 +285,6 @@ async function composeTemplate(config, targetDir) {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
catch {
|
|
288
|
-
// Non-fatal: .env creation from .env.example is optional
|
|
289
288
|
}
|
|
290
289
|
if (config.language === "javascript") {
|
|
291
290
|
await (0, js_conversion_1.convertToJavaScript)(targetDir, config.framework);
|
package/dist/cli/doctor.js
CHANGED
|
@@ -336,19 +336,16 @@ function checkConflicts(authModules, databaseModules) {
|
|
|
336
336
|
}
|
|
337
337
|
async function checkConfigFiles(projectRoot, projectType, packageJson) {
|
|
338
338
|
const checks = [];
|
|
339
|
-
// Check tsconfig.json
|
|
340
339
|
const tsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(projectRoot, "tsconfig.json"));
|
|
341
340
|
checks.push({
|
|
342
341
|
status: tsconfigExists ? "success" : "error",
|
|
343
342
|
message: tsconfigExists ? MESSAGES.TSCONFIG_FOUND : MESSAGES.TSCONFIG_MISSING,
|
|
344
343
|
});
|
|
345
|
-
// Check ESLint config
|
|
346
344
|
const eslintExists = await checkEslintConfigExists(projectRoot);
|
|
347
345
|
checks.push({
|
|
348
346
|
status: eslintExists ? "success" : "warning",
|
|
349
347
|
message: eslintExists ? MESSAGES.ESLINT_CONFIG_FOUND : MESSAGES.ESLINT_CONFIG_MISSING,
|
|
350
348
|
});
|
|
351
|
-
// Check build script
|
|
352
349
|
const hasBuildScript = packageJson.scripts && typeof packageJson.scripts === "object" && "build" in packageJson.scripts;
|
|
353
350
|
checks.push({
|
|
354
351
|
status: hasBuildScript ? "success" : "warning",
|
|
@@ -363,7 +360,6 @@ async function checkDependencies(packageJson) {
|
|
|
363
360
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
364
361
|
for (const [name, version] of Object.entries(deps || {})) {
|
|
365
362
|
if (typeof version === "string" && (version.startsWith("^") || version.startsWith("~"))) {
|
|
366
|
-
// Assume up to date
|
|
367
363
|
}
|
|
368
364
|
else {
|
|
369
365
|
outdated.push(name);
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,28 @@ const program = new commander_1.Command();
|
|
|
11
11
|
program
|
|
12
12
|
.name("stackkit")
|
|
13
13
|
.description("CLI for creating and managing StackKit projects")
|
|
14
|
-
.version("0.1.
|
|
14
|
+
.version("0.1.4")
|
|
15
|
+
.configureHelp({
|
|
16
|
+
subcommandTerm: (cmd) => {
|
|
17
|
+
const name = cmd.name();
|
|
18
|
+
if (name === 'create')
|
|
19
|
+
return 'create [project-name] [options]';
|
|
20
|
+
if (name === 'add')
|
|
21
|
+
return 'add <module> [options]';
|
|
22
|
+
if (name === 'doctor')
|
|
23
|
+
return 'doctor [options]';
|
|
24
|
+
if (name === 'list')
|
|
25
|
+
return 'list [options]';
|
|
26
|
+
if (name === 'help')
|
|
27
|
+
return 'help [command]';
|
|
28
|
+
return name + ' [options]';
|
|
29
|
+
}
|
|
30
|
+
});
|
|
15
31
|
// Create command
|
|
16
32
|
program
|
|
17
|
-
.command(
|
|
33
|
+
.command('create [project-name]')
|
|
18
34
|
.description("Create a new StackKit project")
|
|
35
|
+
.usage('[project-name] [options]')
|
|
19
36
|
.option("-f, --framework <framework>", "Framework: nextjs, express, react")
|
|
20
37
|
.option("-d, --database <database>", "Database: prisma, mongoose, none")
|
|
21
38
|
.option("--prisma-provider <provider>", "Prisma provider: postgresql, mongodb, mysql, sqlite")
|
|
@@ -36,12 +53,14 @@ program
|
|
|
36
53
|
});
|
|
37
54
|
// Add command
|
|
38
55
|
program
|
|
39
|
-
.command("add
|
|
40
|
-
.description("Add a module to your existing project")
|
|
56
|
+
.command("add [module]")
|
|
57
|
+
.description("Add a module or category to your existing project")
|
|
58
|
+
.usage('[module] [options]')
|
|
41
59
|
.option("--provider <provider>", "Specific provider/variant to use")
|
|
42
60
|
.option("--force", "Overwrite existing files")
|
|
43
61
|
.option("--dry-run", "Show what would be changed without making changes")
|
|
44
62
|
.option("--no-install", "Skip installing dependencies")
|
|
63
|
+
.option("-y, --yes", "Use default options")
|
|
45
64
|
.action(async (module, options) => {
|
|
46
65
|
try {
|
|
47
66
|
await (0, add_1.addCommand)(module, options);
|
|
@@ -5,7 +5,6 @@ export interface ModuleMetadata {
|
|
|
5
5
|
category: string;
|
|
6
6
|
provider?: string;
|
|
7
7
|
supportedFrameworks?: string[];
|
|
8
|
-
databaseAdapters?: Record<string, unknown>;
|
|
9
8
|
frameworkConfigs?: Record<string, unknown>;
|
|
10
9
|
dependencies?: Record<string, unknown>;
|
|
11
10
|
devDependencies?: Record<string, unknown>;
|