initkit 1.1.0 โ 1.2.1
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 +109 -117
- package/package.json +2 -5
- package/src/cli.js +77 -34
- package/src/commands/create.js +173 -84
- package/src/prompts/questions.js +95 -39
- package/src/utils/addonInstaller.js +402 -0
- package/src/utils/cliRunner.js +146 -0
- package/src/utils/frameworkBootstrap.js +304 -0
- package/src/utils/templateGenerator.js +191 -171
- package/src/templates/express.js +0 -915
- package/src/templates/fullstack.js +0 -1236
- package/src/templates/nextjs.js +0 -620
- package/src/templates/react.js +0 -586
- package/src/templates/vue.js +0 -545
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { execCommand } from './cliRunner.js';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Install add-ons/libraries using their official installation methods
|
|
6
|
+
*
|
|
7
|
+
* This module installs additional libraries and tools using their
|
|
8
|
+
* official CLIs and installation commands, ensuring proper setup
|
|
9
|
+
* and configuration.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} projectPath - Project directory
|
|
12
|
+
* @param {Object} config - User configuration with selected add-ons
|
|
13
|
+
* @returns {Promise<void>}
|
|
14
|
+
*/
|
|
15
|
+
export async function installAddons(projectPath, config) {
|
|
16
|
+
const {
|
|
17
|
+
stateManagement,
|
|
18
|
+
uiLibrary,
|
|
19
|
+
database,
|
|
20
|
+
orm,
|
|
21
|
+
authentication,
|
|
22
|
+
testing = [],
|
|
23
|
+
styling,
|
|
24
|
+
additionalLibraries = [],
|
|
25
|
+
} = config;
|
|
26
|
+
|
|
27
|
+
// State Management
|
|
28
|
+
if (stateManagement && stateManagement !== 'none') {
|
|
29
|
+
console.log(chalk.cyan('\n๐ฆ Installing state management...'));
|
|
30
|
+
await installStateManagement(projectPath, stateManagement, config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// UI Library
|
|
34
|
+
if (uiLibrary && uiLibrary !== 'none') {
|
|
35
|
+
console.log(chalk.cyan('\n๐จ Installing UI library...'));
|
|
36
|
+
await installUILibrary(projectPath, uiLibrary, config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Database & ORM
|
|
40
|
+
if (orm && orm !== 'none') {
|
|
41
|
+
console.log(chalk.cyan('\n๐๏ธ Installing ORM...'));
|
|
42
|
+
await installORM(projectPath, orm, database, config);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Authentication
|
|
46
|
+
if (authentication && authentication !== 'none') {
|
|
47
|
+
console.log(chalk.cyan('\n๐ Installing authentication...'));
|
|
48
|
+
await installAuthentication(projectPath, authentication, config);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Testing
|
|
52
|
+
if (testing && testing.length > 0) {
|
|
53
|
+
console.log(chalk.cyan('\n๐งช Installing testing frameworks...'));
|
|
54
|
+
await installTesting(projectPath, testing, config);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Additional libraries
|
|
58
|
+
if (additionalLibraries && additionalLibraries.length > 0) {
|
|
59
|
+
console.log(chalk.cyan('\n๐ฆ Installing additional libraries...'));
|
|
60
|
+
for (const lib of additionalLibraries) {
|
|
61
|
+
await installAdditionalLibrary(projectPath, lib, config);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Install state management library
|
|
68
|
+
*/
|
|
69
|
+
async function installStateManagement(projectPath, library, config) {
|
|
70
|
+
const { packageManager } = config;
|
|
71
|
+
const installCmd = getInstallCommand(packageManager);
|
|
72
|
+
|
|
73
|
+
switch (library) {
|
|
74
|
+
case 'redux-toolkit':
|
|
75
|
+
console.log(chalk.dim(' Installing Redux Toolkit and React Redux...'));
|
|
76
|
+
await execCommand(`${installCmd} @reduxjs/toolkit react-redux`, {
|
|
77
|
+
cwd: projectPath,
|
|
78
|
+
});
|
|
79
|
+
// Store setup will be done in template generator
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'zustand':
|
|
83
|
+
console.log(chalk.dim(' Installing Zustand...'));
|
|
84
|
+
await execCommand(`${installCmd} zustand`, { cwd: projectPath });
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case 'jotai':
|
|
88
|
+
console.log(chalk.dim(' Installing Jotai...'));
|
|
89
|
+
await execCommand(`${installCmd} jotai`, { cwd: projectPath });
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case 'recoil':
|
|
93
|
+
console.log(chalk.dim(' Installing Recoil...'));
|
|
94
|
+
await execCommand(`${installCmd} recoil`, { cwd: projectPath });
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'pinia':
|
|
98
|
+
// Pinia is usually included in Vue setup
|
|
99
|
+
console.log(chalk.dim(' Pinia will be configured in Vue setup...'));
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
console.log(chalk.yellow(` Unknown state management: ${library}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Install UI library (some have their own CLIs!)
|
|
109
|
+
*/
|
|
110
|
+
async function installUILibrary(projectPath, library, config) {
|
|
111
|
+
const { packageManager, language } = config;
|
|
112
|
+
const installCmd = getInstallCommand(packageManager);
|
|
113
|
+
|
|
114
|
+
switch (library) {
|
|
115
|
+
case 'shadcn':
|
|
116
|
+
case 'shadcn-ui':
|
|
117
|
+
console.log(chalk.dim(' Running shadcn-ui init...'));
|
|
118
|
+
// shadcn has its own CLI!
|
|
119
|
+
await execCommand(`npx shadcn@latest init -y`, { cwd: projectPath });
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'mui':
|
|
123
|
+
case 'material-ui':
|
|
124
|
+
console.log(chalk.dim(' Installing Material-UI...'));
|
|
125
|
+
await execCommand(
|
|
126
|
+
`${installCmd} @mui/material @emotion/react @emotion/styled @mui/icons-material`,
|
|
127
|
+
{ cwd: projectPath }
|
|
128
|
+
);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'antd':
|
|
132
|
+
case 'ant-design':
|
|
133
|
+
console.log(chalk.dim(' Installing Ant Design...'));
|
|
134
|
+
await execCommand(`${installCmd} antd`, { cwd: projectPath });
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case 'chakra':
|
|
138
|
+
case 'chakra-ui':
|
|
139
|
+
console.log(chalk.dim(' Installing Chakra UI...'));
|
|
140
|
+
await execCommand(
|
|
141
|
+
`${installCmd} @chakra-ui/react @emotion/react @emotion/styled framer-motion`,
|
|
142
|
+
{ cwd: projectPath }
|
|
143
|
+
);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'mantine':
|
|
147
|
+
console.log(chalk.dim(' Installing Mantine...'));
|
|
148
|
+
await execCommand(
|
|
149
|
+
`${installCmd} @mantine/core @mantine/hooks @mantine/form @mantine/notifications`,
|
|
150
|
+
{ cwd: projectPath }
|
|
151
|
+
);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'daisyui':
|
|
155
|
+
console.log(chalk.dim(' Installing DaisyUI...'));
|
|
156
|
+
await execCommand(`${installCmd} -D daisyui`, { cwd: projectPath });
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
default:
|
|
160
|
+
console.log(chalk.yellow(` Unknown UI library: ${library}`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Install ORM (Prisma has its own CLI!)
|
|
166
|
+
*/
|
|
167
|
+
async function installORM(projectPath, orm, database, config) {
|
|
168
|
+
const { packageManager, language } = config;
|
|
169
|
+
const installCmd = getInstallCommand(packageManager);
|
|
170
|
+
|
|
171
|
+
switch (orm) {
|
|
172
|
+
case 'prisma': {
|
|
173
|
+
console.log(chalk.dim(' Installing Prisma...'));
|
|
174
|
+
// Prisma has its own init CLI!
|
|
175
|
+
await execCommand(`${installCmd} -D prisma`, { cwd: projectPath });
|
|
176
|
+
await execCommand(`${installCmd} @prisma/client`, { cwd: projectPath });
|
|
177
|
+
|
|
178
|
+
// Initialize Prisma with database
|
|
179
|
+
const datasource = getDatasourceForPrisma(database);
|
|
180
|
+
console.log(chalk.dim(` Initializing Prisma with ${datasource}...`));
|
|
181
|
+
await execCommand(`npx prisma init --datasource-provider ${datasource}`, {
|
|
182
|
+
cwd: projectPath,
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case 'drizzle': {
|
|
188
|
+
console.log(chalk.dim(' Installing Drizzle ORM...'));
|
|
189
|
+
await execCommand(`${installCmd} drizzle-orm`, { cwd: projectPath });
|
|
190
|
+
await execCommand(`${installCmd} -D drizzle-kit`, { cwd: projectPath });
|
|
191
|
+
|
|
192
|
+
// Add database driver
|
|
193
|
+
const driver = getDriverForDrizzle(database);
|
|
194
|
+
console.log(chalk.dim(` Installing database driver: ${driver}...`));
|
|
195
|
+
await execCommand(`${installCmd} ${driver}`, { cwd: projectPath });
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case 'typeorm': {
|
|
200
|
+
console.log(chalk.dim(' Installing TypeORM...'));
|
|
201
|
+
await execCommand(`${installCmd} typeorm reflect-metadata`, {
|
|
202
|
+
cwd: projectPath,
|
|
203
|
+
});
|
|
204
|
+
if (language === 'typescript') {
|
|
205
|
+
await execCommand(`${installCmd} -D @types/node`, { cwd: projectPath });
|
|
206
|
+
}
|
|
207
|
+
// Add database driver
|
|
208
|
+
const typeormDriver = getDriverForTypeORM(database);
|
|
209
|
+
if (typeormDriver) {
|
|
210
|
+
console.log(chalk.dim(` Installing database driver: ${typeormDriver}...`));
|
|
211
|
+
await execCommand(`${installCmd} ${typeormDriver}`, {
|
|
212
|
+
cwd: projectPath,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
case 'mongoose':
|
|
219
|
+
console.log(chalk.dim(' Installing Mongoose...'));
|
|
220
|
+
await execCommand(`${installCmd} mongoose`, { cwd: projectPath });
|
|
221
|
+
if (language === 'typescript') {
|
|
222
|
+
await execCommand(`${installCmd} -D @types/mongoose`, {
|
|
223
|
+
cwd: projectPath,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
default:
|
|
229
|
+
console.log(chalk.yellow(` Unknown ORM: ${orm}`));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Install authentication library
|
|
235
|
+
*/
|
|
236
|
+
async function installAuthentication(projectPath, auth, config) {
|
|
237
|
+
const { packageManager } = config;
|
|
238
|
+
const installCmd = getInstallCommand(packageManager);
|
|
239
|
+
|
|
240
|
+
switch (auth) {
|
|
241
|
+
case 'nextauth':
|
|
242
|
+
case 'next-auth':
|
|
243
|
+
console.log(chalk.dim(' Installing NextAuth.js...'));
|
|
244
|
+
await execCommand(`${installCmd} next-auth`, { cwd: projectPath });
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 'clerk':
|
|
248
|
+
console.log(chalk.dim(' Installing Clerk...'));
|
|
249
|
+
await execCommand(`${installCmd} @clerk/nextjs`, { cwd: projectPath });
|
|
250
|
+
break;
|
|
251
|
+
|
|
252
|
+
case 'supabase':
|
|
253
|
+
console.log(chalk.dim(' Installing Supabase client...'));
|
|
254
|
+
await execCommand(`${installCmd} @supabase/supabase-js`, {
|
|
255
|
+
cwd: projectPath,
|
|
256
|
+
});
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case 'auth0':
|
|
260
|
+
console.log(chalk.dim(' Installing Auth0...'));
|
|
261
|
+
await execCommand(`${installCmd} @auth0/nextjs-auth0`, {
|
|
262
|
+
cwd: projectPath,
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case 'lucia':
|
|
267
|
+
console.log(chalk.dim(' Installing Lucia...'));
|
|
268
|
+
await execCommand(`${installCmd} lucia`, { cwd: projectPath });
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
default:
|
|
272
|
+
console.log(chalk.yellow(` Unknown authentication library: ${auth}`));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Install testing frameworks (Playwright has its own CLI!)
|
|
278
|
+
*/
|
|
279
|
+
async function installTesting(projectPath, testingLibs, config) {
|
|
280
|
+
const { packageManager } = config;
|
|
281
|
+
const installCmd = getInstallCommand(packageManager);
|
|
282
|
+
|
|
283
|
+
for (const lib of testingLibs) {
|
|
284
|
+
switch (lib) {
|
|
285
|
+
case 'vitest':
|
|
286
|
+
console.log(chalk.dim(' Installing Vitest...'));
|
|
287
|
+
await execCommand(`${installCmd} -D vitest`, { cwd: projectPath });
|
|
288
|
+
await execCommand(`${installCmd} -D @vitest/ui`, { cwd: projectPath });
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case 'jest':
|
|
292
|
+
console.log(chalk.dim(' Installing Jest...'));
|
|
293
|
+
await execCommand(`${installCmd} -D jest @types/jest`, {
|
|
294
|
+
cwd: projectPath,
|
|
295
|
+
});
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case 'playwright':
|
|
299
|
+
console.log(chalk.dim(' Installing Playwright...'));
|
|
300
|
+
// Playwright has its own init CLI!
|
|
301
|
+
await execCommand(`npm init playwright@latest`, { cwd: projectPath });
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'cypress':
|
|
305
|
+
console.log(chalk.dim(' Installing Cypress...'));
|
|
306
|
+
await execCommand(`${installCmd} -D cypress`, { cwd: projectPath });
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'testing-library':
|
|
310
|
+
case '@testing-library':
|
|
311
|
+
console.log(chalk.dim(' Installing React Testing Library...'));
|
|
312
|
+
await execCommand(
|
|
313
|
+
`${installCmd} -D @testing-library/react @testing-library/jest-dom @testing-library/user-event`,
|
|
314
|
+
{ cwd: projectPath }
|
|
315
|
+
);
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
default:
|
|
319
|
+
console.log(chalk.yellow(` Unknown testing library: ${lib}`));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Install additional library
|
|
326
|
+
*/
|
|
327
|
+
async function installAdditionalLibrary(projectPath, lib, config) {
|
|
328
|
+
const { packageManager } = config;
|
|
329
|
+
const installCmd = getInstallCommand(packageManager);
|
|
330
|
+
|
|
331
|
+
console.log(chalk.dim(` Installing ${lib}...`));
|
|
332
|
+
await execCommand(`${installCmd} ${lib}`, { cwd: projectPath });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get install command for package manager
|
|
337
|
+
*/
|
|
338
|
+
function getInstallCommand(packageManager) {
|
|
339
|
+
const commands = {
|
|
340
|
+
npm: 'npm install',
|
|
341
|
+
yarn: 'yarn add',
|
|
342
|
+
pnpm: 'pnpm add',
|
|
343
|
+
bun: 'bun add',
|
|
344
|
+
};
|
|
345
|
+
return commands[packageManager] || 'npm install';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Helper functions for database mappings
|
|
350
|
+
*/
|
|
351
|
+
function getDatasourceForPrisma(database) {
|
|
352
|
+
if (!database || database === 'none') return 'postgresql';
|
|
353
|
+
|
|
354
|
+
const mapping = {
|
|
355
|
+
postgresql: 'postgresql',
|
|
356
|
+
postgres: 'postgresql',
|
|
357
|
+
mysql: 'mysql',
|
|
358
|
+
sqlite: 'sqlite',
|
|
359
|
+
mongodb: 'mongodb',
|
|
360
|
+
sqlserver: 'sqlserver',
|
|
361
|
+
};
|
|
362
|
+
return mapping[database.toLowerCase()] || 'postgresql';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function getDriverForDrizzle(database) {
|
|
366
|
+
if (!database || database === 'none') return 'pg';
|
|
367
|
+
|
|
368
|
+
const drivers = {
|
|
369
|
+
postgresql: 'pg',
|
|
370
|
+
postgres: 'pg',
|
|
371
|
+
mysql: 'mysql2',
|
|
372
|
+
sqlite: 'better-sqlite3',
|
|
373
|
+
};
|
|
374
|
+
return drivers[database.toLowerCase()] || 'pg';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getDriverForTypeORM(database) {
|
|
378
|
+
if (!database || database === 'none') return null;
|
|
379
|
+
|
|
380
|
+
const drivers = {
|
|
381
|
+
postgresql: 'pg',
|
|
382
|
+
postgres: 'pg',
|
|
383
|
+
mysql: 'mysql2',
|
|
384
|
+
sqlite: 'sqlite3',
|
|
385
|
+
mongodb: 'mongodb',
|
|
386
|
+
};
|
|
387
|
+
return drivers[database.toLowerCase()] || null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Check if user selected any add-ons
|
|
392
|
+
*/
|
|
393
|
+
export function hasAddons(config) {
|
|
394
|
+
return !!(
|
|
395
|
+
(config.stateManagement && config.stateManagement !== 'none') ||
|
|
396
|
+
(config.uiLibrary && config.uiLibrary !== 'none') ||
|
|
397
|
+
(config.orm && config.orm !== 'none') ||
|
|
398
|
+
(config.authentication && config.authentication !== 'none') ||
|
|
399
|
+
(config.testing && config.testing.length > 0) ||
|
|
400
|
+
(config.additionalLibraries && config.additionalLibraries.length > 0)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Execute a CLI command with proper error handling and output streaming
|
|
6
|
+
*
|
|
7
|
+
* This utility function spawns a child process to run external CLI commands
|
|
8
|
+
* (like create-next-app, create-vite, etc.) while maintaining proper error
|
|
9
|
+
* handling and allowing the user to see the output in real-time.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} command - The full command to execute (e.g., "npx create-next-app my-app")
|
|
12
|
+
* @param {Object} [options={}] - Execution options
|
|
13
|
+
* @param {string} [options.cwd=process.cwd()] - Working directory for the command
|
|
14
|
+
* @param {string|Array} [options.stdio='inherit'] - How to handle stdin/stdout/stderr
|
|
15
|
+
* @param {boolean} [options.shell=true] - Whether to run in a shell
|
|
16
|
+
* @param {Object} [options.env] - Environment variables
|
|
17
|
+
*
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
* @throws {Error} If command execution fails
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Bootstrap a Next.js project
|
|
23
|
+
* await execCommand('npx create-next-app@latest my-app --typescript', {
|
|
24
|
+
* cwd: '/path/to/parent/dir'
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Install dependencies
|
|
29
|
+
* await execCommand('npm install @reduxjs/toolkit react-redux', {
|
|
30
|
+
* cwd: '/path/to/project'
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
export async function execCommand(command, options = {}) {
|
|
34
|
+
const {
|
|
35
|
+
cwd = process.cwd(),
|
|
36
|
+
stdio = 'inherit',
|
|
37
|
+
shell = true,
|
|
38
|
+
env = { ...process.env, FORCE_COLOR: '1', CI: 'true' },
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
// Parse command into executable and arguments
|
|
43
|
+
const args = command.split(' ');
|
|
44
|
+
const cmd = args.shift();
|
|
45
|
+
|
|
46
|
+
const child = spawn(cmd, args, {
|
|
47
|
+
cwd,
|
|
48
|
+
stdio,
|
|
49
|
+
shell,
|
|
50
|
+
env,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
child.on('close', (code) => {
|
|
54
|
+
if (code === 0) {
|
|
55
|
+
resolve();
|
|
56
|
+
} else {
|
|
57
|
+
reject(new Error(`Command failed with exit code ${code}: ${chalk.red(command)}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
child.on('error', (error) => {
|
|
62
|
+
reject(
|
|
63
|
+
new Error(`Failed to execute command: ${chalk.red(error.message)}\nCommand: ${command}`)
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Execute a command and capture its output
|
|
71
|
+
*
|
|
72
|
+
* Unlike execCommand which streams output to the console, this function
|
|
73
|
+
* captures stdout and stderr and returns them as strings. Useful for
|
|
74
|
+
* commands where you need to parse the output.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} command - The command to execute
|
|
77
|
+
* @param {Object} [options={}] - Execution options
|
|
78
|
+
* @param {string} [options.cwd=process.cwd()] - Working directory
|
|
79
|
+
*
|
|
80
|
+
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
81
|
+
* @throws {Error} If command execution fails
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Get npm version
|
|
85
|
+
* const { stdout } = await execCommandWithOutput('npm --version');
|
|
86
|
+
* console.log('npm version:', stdout.trim());
|
|
87
|
+
*/
|
|
88
|
+
export async function execCommandWithOutput(command, options = {}) {
|
|
89
|
+
const { cwd = process.cwd() } = options;
|
|
90
|
+
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const args = command.split(' ');
|
|
93
|
+
const cmd = args.shift();
|
|
94
|
+
|
|
95
|
+
let stdout = '';
|
|
96
|
+
let stderr = '';
|
|
97
|
+
|
|
98
|
+
const child = spawn(cmd, args, {
|
|
99
|
+
cwd,
|
|
100
|
+
shell: true,
|
|
101
|
+
env: { ...process.env },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
child.stdout?.on('data', (data) => {
|
|
105
|
+
stdout += data.toString();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
child.stderr?.on('data', (data) => {
|
|
109
|
+
stderr += data.toString();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
child.on('close', (code) => {
|
|
113
|
+
if (code === 0) {
|
|
114
|
+
resolve({ stdout, stderr });
|
|
115
|
+
} else {
|
|
116
|
+
reject(new Error(`Command failed with exit code ${code}\nStderr: ${stderr}`));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
child.on('error', (error) => {
|
|
121
|
+
reject(new Error(`Failed to execute command: ${error.message}`));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if a command exists in the system
|
|
128
|
+
*
|
|
129
|
+
* @param {string} command - The command to check (e.g., 'npm', 'git')
|
|
130
|
+
* @returns {Promise<boolean>}
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* if (await commandExists('pnpm')) {
|
|
134
|
+
* console.log('pnpm is installed');
|
|
135
|
+
* }
|
|
136
|
+
*/
|
|
137
|
+
export async function commandExists(command) {
|
|
138
|
+
const checkCmd = process.platform === 'win32' ? `where ${command}` : `which ${command}`;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await execCommandWithOutput(checkCmd);
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|