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