stackkit 0.1.2 → 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 +318 -110
- package/dist/cli/create.js +8 -45
- package/dist/cli/doctor.js +0 -10
- package/dist/cli/list.js +3 -5
- package/dist/index.js +23 -4
- package/dist/lib/conversion/js-conversion.js +3 -2
- package/dist/lib/discovery/module-discovery.d.ts +0 -1
- package/dist/lib/discovery/module-discovery.js +27 -5
- package/dist/lib/generation/code-generator.d.ts +14 -0
- package/dist/lib/generation/code-generator.js +69 -65
- 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/lib/utils/package-root.d.ts +1 -0
- package/dist/lib/utils/package-root.js +45 -0
- 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 -2
- 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/templates/react-vite/README.md +0 -56
- /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,24 +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
|
|
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");
|
|
18
21
|
async function addCommand(module, options) {
|
|
19
22
|
try {
|
|
20
23
|
const projectRoot = process.cwd();
|
|
21
24
|
const spinner = logger_1.logger.startSpinner("Detecting project...");
|
|
22
25
|
const projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
23
26
|
spinner.succeed(`Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`);
|
|
24
|
-
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);
|
|
25
94
|
if (!moduleMetadata) {
|
|
26
|
-
|
|
27
|
-
|
|
95
|
+
throw new Error(`Module "${module}" not found`);
|
|
96
|
+
}
|
|
97
|
+
let selectedProvider = options?.provider;
|
|
98
|
+
if (!selectedProvider && moduleMetadata.category !== module) {
|
|
99
|
+
selectedProvider = module;
|
|
28
100
|
}
|
|
29
|
-
let selectedProvider = options.provider;
|
|
30
101
|
if (moduleMetadata.category === "database" && !selectedProvider) {
|
|
31
102
|
if (typeof moduleMetadata.dependencies === "object" && "providers" in moduleMetadata.dependencies) {
|
|
32
103
|
const providers = Object.keys(moduleMetadata.dependencies.providers || {});
|
|
@@ -43,111 +114,272 @@ async function addCommand(module, options) {
|
|
|
43
114
|
}
|
|
44
115
|
}
|
|
45
116
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (moduleMetadata.frameworkConfigs?.shared?.dependencies) {
|
|
49
|
-
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(", ")}`);
|
|
50
119
|
}
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
};
|
|
53
172
|
}
|
|
54
|
-
|
|
55
|
-
|
|
173
|
+
else {
|
|
174
|
+
return {
|
|
175
|
+
module: "database",
|
|
176
|
+
provider: "mongoose",
|
|
177
|
+
displayName: "Mongoose",
|
|
178
|
+
metadata: await loadModuleMetadata(modulesDir, "mongoose", "mongoose"),
|
|
179
|
+
};
|
|
56
180
|
}
|
|
57
|
-
|
|
58
|
-
|
|
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`);
|
|
59
198
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const variables = {};
|
|
63
|
-
if (selectedProvider) {
|
|
64
|
-
variables.provider = selectedProvider;
|
|
65
|
-
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}`);
|
|
66
201
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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;
|
|
70
227
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
});
|
|
84
258
|
}
|
|
85
259
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
269
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
270
|
+
if (config.module === 'auth' && config.provider) {
|
|
271
|
+
selectedModules.auth = config.provider;
|
|
93
272
|
}
|
|
94
|
-
|
|
95
|
-
|
|
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...');
|
|
96
276
|
try {
|
|
97
|
-
for (const command of
|
|
98
|
-
(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' });
|
|
99
279
|
}
|
|
100
|
-
postInstallSpinner.succeed(
|
|
280
|
+
postInstallSpinner.succeed('Post-install commands completed');
|
|
101
281
|
}
|
|
102
282
|
catch (error) {
|
|
103
|
-
postInstallSpinner.fail(
|
|
283
|
+
postInstallSpinner.fail('Failed to run post-install commands');
|
|
104
284
|
throw error;
|
|
105
285
|
}
|
|
106
286
|
}
|
|
107
|
-
if (
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
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');
|
|
111
292
|
}
|
|
112
|
-
|
|
113
|
-
|
|
293
|
+
catch (err) {
|
|
294
|
+
installSpinner.fail('Failed to install dependencies');
|
|
295
|
+
throw err;
|
|
114
296
|
}
|
|
115
297
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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;
|
|
124
321
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
132
332
|
}
|
|
133
|
-
|
|
134
|
-
|
|
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" });
|
|
135
346
|
}
|
|
347
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
136
348
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (moduleMetadata.envVars && moduleMetadata.envVars.some((v) => v.required)) {
|
|
141
|
-
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;
|
|
142
352
|
}
|
|
143
|
-
logger_1.logger.newLine();
|
|
144
353
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
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`);
|
|
149
382
|
}
|
|
150
|
-
process.exit(1);
|
|
151
383
|
}
|
|
152
384
|
}
|
|
153
385
|
async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
@@ -170,40 +402,16 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
170
402
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
171
403
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
172
404
|
if (provider && moduleDir === provider) {
|
|
173
|
-
return await
|
|
405
|
+
return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
|
|
174
406
|
}
|
|
175
|
-
if (!provider && metadata.category === moduleName) {
|
|
176
|
-
return await
|
|
407
|
+
if (!provider && (metadata.category === moduleName || moduleDir === moduleName)) {
|
|
408
|
+
return await (0, generator_utils_1.mergeGeneratorIntoModuleMetadata)(metadata, modulePath);
|
|
177
409
|
}
|
|
178
410
|
}
|
|
179
411
|
}
|
|
180
412
|
}
|
|
181
413
|
return null;
|
|
182
414
|
}
|
|
183
|
-
async function loadGeneratorAndMerge(metadata, modulePath) {
|
|
184
|
-
const generatorPath = path_1.default.join(modulePath, "generator.json");
|
|
185
|
-
if (await fs_extra_1.default.pathExists(generatorPath)) {
|
|
186
|
-
const generator = await fs_extra_1.default.readJSON(generatorPath);
|
|
187
|
-
// Merge envVars, dependencies, etc.
|
|
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
|
-
if (generator.scripts) {
|
|
201
|
-
// Perhaps add to metadata, but currently not used
|
|
202
|
-
}
|
|
203
|
-
// For operations, perhaps add to patches or something, but for now, keep manual
|
|
204
|
-
}
|
|
205
|
-
return metadata;
|
|
206
|
-
}
|
|
207
415
|
async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, moduleName, options) {
|
|
208
416
|
if (!moduleMetadata.patches || !Array.isArray(moduleMetadata.patches)) {
|
|
209
417
|
return;
|