initkit 1.1.0 ā 1.2.0
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/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,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
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { execCommand } from './cliRunner.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bootstrap a project using official framework CLIs
|
|
7
|
+
*
|
|
8
|
+
* This function delegates project creation to official framework CLIs
|
|
9
|
+
* instead of manually creating files. This ensures we always use the
|
|
10
|
+
* latest best practices and configurations from framework maintainers.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} projectPath - Absolute path to the project directory
|
|
13
|
+
* @param {Object} config - User configuration object
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
export async function bootstrapWithOfficialCLI(projectPath, config) {
|
|
17
|
+
const { projectType, frontend, backend } = config;
|
|
18
|
+
|
|
19
|
+
switch (projectType) {
|
|
20
|
+
case 'frontend':
|
|
21
|
+
return bootstrapFrontend(projectPath, config);
|
|
22
|
+
|
|
23
|
+
case 'backend':
|
|
24
|
+
return bootstrapBackend(projectPath, config);
|
|
25
|
+
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported project type: ${projectType}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Bootstrap a frontend project
|
|
33
|
+
*/
|
|
34
|
+
async function bootstrapFrontend(projectPath, config) {
|
|
35
|
+
const { frontend } = config;
|
|
36
|
+
|
|
37
|
+
switch (frontend) {
|
|
38
|
+
case 'nextjs':
|
|
39
|
+
return bootstrapNextjs(projectPath, config);
|
|
40
|
+
|
|
41
|
+
case 'react':
|
|
42
|
+
return bootstrapReact(projectPath, config);
|
|
43
|
+
|
|
44
|
+
case 'vue':
|
|
45
|
+
return bootstrapVue(projectPath, config);
|
|
46
|
+
|
|
47
|
+
case 'nuxt':
|
|
48
|
+
return bootstrapNuxt(projectPath, config);
|
|
49
|
+
|
|
50
|
+
case 'svelte':
|
|
51
|
+
return bootstrapSvelte(projectPath, config);
|
|
52
|
+
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Unsupported frontend framework: ${frontend}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Bootstrap a backend project
|
|
60
|
+
*/
|
|
61
|
+
async function bootstrapBackend(projectPath, config) {
|
|
62
|
+
const { backend } = config;
|
|
63
|
+
|
|
64
|
+
switch (backend) {
|
|
65
|
+
case 'express':
|
|
66
|
+
return bootstrapExpress(projectPath, config);
|
|
67
|
+
|
|
68
|
+
case 'nestjs':
|
|
69
|
+
return bootstrapNestJS(projectPath, config);
|
|
70
|
+
|
|
71
|
+
case 'fastify':
|
|
72
|
+
return bootstrapFastify(projectPath, config);
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Unsupported backend framework: ${backend}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// Frontend Framework Bootstrappers
|
|
81
|
+
// ============================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Bootstrap Next.js project using create-next-app
|
|
85
|
+
*
|
|
86
|
+
* @param {string} projectPath - Absolute path to project
|
|
87
|
+
* @param {Object} config - Configuration object
|
|
88
|
+
*/
|
|
89
|
+
export async function bootstrapNextjs(projectPath, config) {
|
|
90
|
+
const { language, styling, packageManager = 'npm' } = config;
|
|
91
|
+
const projectName = path.basename(projectPath);
|
|
92
|
+
const parentDir = path.dirname(projectPath);
|
|
93
|
+
|
|
94
|
+
console.log(chalk.cyan(`\nš¦ Creating Next.js project...`));
|
|
95
|
+
console.log(
|
|
96
|
+
chalk.gray(` Language: ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}`)
|
|
97
|
+
);
|
|
98
|
+
console.log(chalk.gray(` Styling: ${styling || 'CSS'}`));
|
|
99
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
100
|
+
|
|
101
|
+
const args = ['create-next-app@latest', projectName];
|
|
102
|
+
|
|
103
|
+
// Language
|
|
104
|
+
if (language === 'typescript') {
|
|
105
|
+
args.push('--typescript');
|
|
106
|
+
} else {
|
|
107
|
+
args.push('--javascript');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Styling - check if user wants Tailwind
|
|
111
|
+
if (styling === 'tailwind' || styling === 'tailwindcss') {
|
|
112
|
+
args.push('--tailwind');
|
|
113
|
+
} else {
|
|
114
|
+
args.push('--no-tailwind');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Default options
|
|
118
|
+
args.push('--eslint'); // Always include ESLint
|
|
119
|
+
args.push('--app'); // Use App Router
|
|
120
|
+
args.push('--src-dir'); // Use src directory
|
|
121
|
+
args.push('--import-alias', '@/*'); // Set import alias
|
|
122
|
+
args.push('--no-git'); // We handle git separately
|
|
123
|
+
|
|
124
|
+
// Package manager
|
|
125
|
+
args.push(`--use-${packageManager}`);
|
|
126
|
+
|
|
127
|
+
// Skip installation - we'll install add-ons after
|
|
128
|
+
args.push('--skip-install');
|
|
129
|
+
|
|
130
|
+
console.log(chalk.dim(` Command: npx ${args.join(' ')}\n`));
|
|
131
|
+
|
|
132
|
+
await execCommand(`npx ${args.join(' ')}`, {
|
|
133
|
+
cwd: parentDir,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Bootstrap React project using Vite
|
|
139
|
+
*
|
|
140
|
+
* @param {string} projectPath - Absolute path to project
|
|
141
|
+
* @param {Object} config - Configuration object
|
|
142
|
+
*/
|
|
143
|
+
export async function bootstrapReact(projectPath, config) {
|
|
144
|
+
const { language = 'typescript', packageManager = 'npm' } = config;
|
|
145
|
+
const projectName = path.basename(projectPath);
|
|
146
|
+
const parentDir = path.dirname(projectPath);
|
|
147
|
+
const template = language === 'typescript' ? 'react-ts' : 'react';
|
|
148
|
+
|
|
149
|
+
console.log(chalk.cyan(`\nš¦ Creating React project with Vite...`));
|
|
150
|
+
console.log(
|
|
151
|
+
chalk.gray(` Language: ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}`)
|
|
152
|
+
);
|
|
153
|
+
console.log(chalk.gray(` Template: ${template}`));
|
|
154
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
155
|
+
|
|
156
|
+
// Use create-vite 5.1.0 which is more stable and doesn't auto-start dev server
|
|
157
|
+
// The --template flag should make it non-interactive
|
|
158
|
+
const command = `npx create-vite@5.1.0 ${projectName} --template ${template}`;
|
|
159
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
160
|
+
|
|
161
|
+
await execCommand(command, {
|
|
162
|
+
cwd: parentDir,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Bootstrap Vue project
|
|
168
|
+
*
|
|
169
|
+
* @param {string} projectPath - Absolute path to project
|
|
170
|
+
* @param {Object} config - Configuration object
|
|
171
|
+
*/
|
|
172
|
+
export async function bootstrapVue(projectPath, config) {
|
|
173
|
+
const { language = 'typescript', packageManager = 'npm' } = config;
|
|
174
|
+
const projectName = path.basename(projectPath);
|
|
175
|
+
const parentDir = path.dirname(projectPath);
|
|
176
|
+
|
|
177
|
+
console.log(chalk.cyan(`\nš¦ Creating Vue project...`));
|
|
178
|
+
console.log(
|
|
179
|
+
chalk.gray(` Language: ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}`)
|
|
180
|
+
);
|
|
181
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
182
|
+
|
|
183
|
+
// Use --typescript and --default flags to create non-interactive TypeScript project
|
|
184
|
+
const flags = language === 'typescript' ? '--typescript --default' : '--default';
|
|
185
|
+
const command = `npm create vue@latest ${projectName} -- ${flags}`;
|
|
186
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
187
|
+
|
|
188
|
+
await execCommand(command, { cwd: parentDir });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Bootstrap Nuxt project
|
|
193
|
+
*
|
|
194
|
+
* @param {string} projectPath - Absolute path to project
|
|
195
|
+
* @param {Object} config - Configuration object
|
|
196
|
+
*/
|
|
197
|
+
export async function bootstrapNuxt(projectPath, config) {
|
|
198
|
+
const { packageManager = 'npm' } = config;
|
|
199
|
+
const projectName = path.basename(projectPath);
|
|
200
|
+
const parentDir = path.dirname(projectPath);
|
|
201
|
+
|
|
202
|
+
console.log(chalk.cyan(`\nš¦ Creating Nuxt project...`));
|
|
203
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
204
|
+
|
|
205
|
+
const command = `npx nuxi@latest init ${projectName} --packageManager ${packageManager}`;
|
|
206
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
207
|
+
|
|
208
|
+
await execCommand(command, { cwd: parentDir });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Bootstrap Svelte project
|
|
213
|
+
*
|
|
214
|
+
* @param {string} projectPath - Absolute path to project
|
|
215
|
+
* @param {Object} config - Configuration object
|
|
216
|
+
*/
|
|
217
|
+
export async function bootstrapSvelte(projectPath, config) {
|
|
218
|
+
const { packageManager = 'npm' } = config;
|
|
219
|
+
const projectName = path.basename(projectPath);
|
|
220
|
+
const parentDir = path.dirname(projectPath);
|
|
221
|
+
|
|
222
|
+
console.log(chalk.cyan(`\nš¦ Creating Svelte project...`));
|
|
223
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
224
|
+
|
|
225
|
+
const command = `npm create svelte@latest ${projectName} -y`;
|
|
226
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
227
|
+
|
|
228
|
+
await execCommand(command, { cwd: parentDir });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ============================================
|
|
232
|
+
// Backend Framework Bootstrappers
|
|
233
|
+
// ============================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Bootstrap Express project
|
|
237
|
+
*
|
|
238
|
+
* @param {string} projectPath - Absolute path to project
|
|
239
|
+
* @param {Object} config - Configuration object
|
|
240
|
+
*/
|
|
241
|
+
export async function bootstrapExpress(projectPath, config) {
|
|
242
|
+
const { language, packageManager = 'npm' } = config;
|
|
243
|
+
const projectName = path.basename(projectPath);
|
|
244
|
+
const parentDir = path.dirname(projectPath);
|
|
245
|
+
|
|
246
|
+
console.log(chalk.cyan(`\nš¦ Creating Express project...`));
|
|
247
|
+
console.log(
|
|
248
|
+
chalk.gray(` Language: ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}\n`)
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Express doesn't have a modern CLI, so we'll use express-generator
|
|
252
|
+
const command = `npx express-generator --no-view --git ${projectName}`;
|
|
253
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
254
|
+
|
|
255
|
+
await execCommand(command, { cwd: parentDir });
|
|
256
|
+
|
|
257
|
+
// If TypeScript, we'll need to convert it later in the template generator
|
|
258
|
+
if (language === 'typescript') {
|
|
259
|
+
console.log(chalk.yellow(' TypeScript setup will be added in the next step...\n'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Bootstrap NestJS project
|
|
265
|
+
*
|
|
266
|
+
* @param {string} projectPath - Absolute path to project
|
|
267
|
+
* @param {Object} config - Configuration object
|
|
268
|
+
*/
|
|
269
|
+
export async function bootstrapNestJS(projectPath, config) {
|
|
270
|
+
const { packageManager = 'npm' } = config;
|
|
271
|
+
const projectName = path.basename(projectPath);
|
|
272
|
+
const parentDir = path.dirname(projectPath);
|
|
273
|
+
|
|
274
|
+
console.log(chalk.cyan(`\nš¦ Creating NestJS project...`));
|
|
275
|
+
console.log(chalk.gray(` Language: TypeScript (NestJS default)`));
|
|
276
|
+
console.log(chalk.gray(` Package Manager: ${packageManager}\n`));
|
|
277
|
+
|
|
278
|
+
const command = `npx @nestjs/cli new ${projectName} --package-manager ${packageManager} --skip-git --skip-install`;
|
|
279
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
280
|
+
|
|
281
|
+
await execCommand(command, { cwd: parentDir });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Bootstrap Fastify project
|
|
286
|
+
*
|
|
287
|
+
* @param {string} projectPath - Absolute path to project
|
|
288
|
+
* @param {Object} config - Configuration object
|
|
289
|
+
*/
|
|
290
|
+
export async function bootstrapFastify(projectPath, config) {
|
|
291
|
+
const { language = 'javascript' } = config;
|
|
292
|
+
const projectName = path.basename(projectPath);
|
|
293
|
+
const parentDir = path.dirname(projectPath);
|
|
294
|
+
|
|
295
|
+
console.log(chalk.cyan(`\nš¦ Creating Fastify project...`));
|
|
296
|
+
console.log(
|
|
297
|
+
chalk.gray(` Language: ${language === 'typescript' ? 'TypeScript' : 'JavaScript'}\n`)
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const command = `npx fastify-cli generate ${projectName}`;
|
|
301
|
+
console.log(chalk.dim(` Command: ${command}\n`));
|
|
302
|
+
|
|
303
|
+
await execCommand(command, { cwd: parentDir });
|
|
304
|
+
}
|