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.
@@ -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
+ }