initkit 1.1.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/LICENSE +21 -0
- package/README.md +844 -0
- package/bin/index.js +8 -0
- package/package.json +79 -0
- package/src/cli.js +264 -0
- package/src/commands/create.js +264 -0
- package/src/index.js +9 -0
- package/src/prompts/questions.js +358 -0
- package/src/templates/express.js +915 -0
- package/src/templates/fullstack.js +1236 -0
- package/src/templates/nextjs.js +620 -0
- package/src/templates/react.js +586 -0
- package/src/templates/vue.js +545 -0
- package/src/utils/errorHandler.js +275 -0
- package/src/utils/git.js +69 -0
- package/src/utils/packageManager.js +90 -0
- package/src/utils/templateGenerator.js +365 -0
- package/src/utils/validation.js +186 -0
- package/src/utils/versionFetcher.js +128 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Custom error class for CLI operations
|
|
8
|
+
*/
|
|
9
|
+
export class CLIError extends Error {
|
|
10
|
+
constructor(message, code, details = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'CLIError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Error codes for different types of failures
|
|
21
|
+
*/
|
|
22
|
+
export const ERROR_CODES = {
|
|
23
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
24
|
+
DIRECTORY_EXISTS: 'DIRECTORY_EXISTS',
|
|
25
|
+
CREATION_FAILED: 'CREATION_FAILED',
|
|
26
|
+
INSTALL_FAILED: 'INSTALL_FAILED',
|
|
27
|
+
GIT_INIT_FAILED: 'GIT_INIT_FAILED',
|
|
28
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
29
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
30
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format and display error messages with helpful context
|
|
35
|
+
* @param {Error} error - The error object
|
|
36
|
+
* @param {Object} context - Additional context about the error
|
|
37
|
+
*/
|
|
38
|
+
export function displayError(error, context = {}) {
|
|
39
|
+
console.log('\n');
|
|
40
|
+
console.log(chalk.red('╔════════════════════════════════════════════════════════════╗'));
|
|
41
|
+
console.log(chalk.red('║') + chalk.red.bold(' ❌ Error ') + chalk.red('║'));
|
|
42
|
+
console.log(chalk.red('╚════════════════════════════════════════════════════════════╝'));
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
if (error instanceof CLIError) {
|
|
46
|
+
console.error(chalk.red.bold(' ' + error.message));
|
|
47
|
+
|
|
48
|
+
// Display error code
|
|
49
|
+
if (error.code) {
|
|
50
|
+
console.error(chalk.gray(`\n Error code: ${error.code}`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Display additional details
|
|
54
|
+
if (Object.keys(error.details).length > 0) {
|
|
55
|
+
console.log(chalk.yellow('\n 📋 Details:'));
|
|
56
|
+
Object.entries(error.details).forEach(([key, value]) => {
|
|
57
|
+
console.log(chalk.yellow(` • ${key}: ${value}`));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Display helpful suggestions based on error type
|
|
62
|
+
console.log('');
|
|
63
|
+
displayErrorSuggestions(error.code, context);
|
|
64
|
+
} else {
|
|
65
|
+
console.error(chalk.red.bold(' Unexpected error: ') + chalk.red(error.message));
|
|
66
|
+
|
|
67
|
+
if (process.env.DEBUG) {
|
|
68
|
+
console.error(chalk.gray('\n Stack trace:'));
|
|
69
|
+
console.error(chalk.gray(error.stack));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Display helpful suggestions based on error type
|
|
78
|
+
* @param {string} errorCode - The error code
|
|
79
|
+
* @param {Object} context - Additional context
|
|
80
|
+
*/
|
|
81
|
+
function displayErrorSuggestions(errorCode, context) {
|
|
82
|
+
const suggestions = {
|
|
83
|
+
[ERROR_CODES.VALIDATION_ERROR]: [
|
|
84
|
+
'💡 Tip: Project names should be lowercase with hyphens (e.g., my-awesome-app)',
|
|
85
|
+
'💡 Valid characters: letters, numbers, hyphens (-) and underscores (_)',
|
|
86
|
+
'💡 Spaces are not allowed - use hyphens instead!',
|
|
87
|
+
],
|
|
88
|
+
[ERROR_CODES.DIRECTORY_EXISTS]: [
|
|
89
|
+
`🤔 Oops! That folder already exists here.`,
|
|
90
|
+
`💡 Try a different name, or remove the existing one first`,
|
|
91
|
+
` Command: rm -rf ${context.projectName || 'project-name'}`,
|
|
92
|
+
],
|
|
93
|
+
[ERROR_CODES.CREATION_FAILED]: [
|
|
94
|
+
'😕 Hmm, couldn\'t create the project folder.',
|
|
95
|
+
'💡 Check if you have permission to write here',
|
|
96
|
+
'💡 Make sure you have enough disk space',
|
|
97
|
+
'💡 Try running in a different directory',
|
|
98
|
+
],
|
|
99
|
+
[ERROR_CODES.INSTALL_FAILED]: [
|
|
100
|
+
'📦 Package installation hit a snag!',
|
|
101
|
+
'💡 Check your internet connection and try again',
|
|
102
|
+
'💡 You can install packages later with: npm install',
|
|
103
|
+
'💡 Or use --no-install flag to skip installation',
|
|
104
|
+
],
|
|
105
|
+
[ERROR_CODES.GIT_INIT_FAILED]: [
|
|
106
|
+
'🔧 Git initialization failed.',
|
|
107
|
+
'💡 Make sure Git is installed: git --version',
|
|
108
|
+
'💡 Or skip Git setup with: --no-git flag',
|
|
109
|
+
],
|
|
110
|
+
[ERROR_CODES.PERMISSION_DENIED]: [
|
|
111
|
+
'You may need elevated permissions to write to this directory',
|
|
112
|
+
'Try running in a different directory where you have write access',
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const errorSuggestions = suggestions[errorCode];
|
|
117
|
+
if (errorSuggestions && errorSuggestions.length > 0) {
|
|
118
|
+
console.log(chalk.cyan('\n Suggestions:'));
|
|
119
|
+
errorSuggestions.forEach((suggestion) => {
|
|
120
|
+
console.log(chalk.cyan(` • ${suggestion}`));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Rollback project creation on failure
|
|
127
|
+
* @param {string} projectPath - Path to the project directory
|
|
128
|
+
* @param {Object} options - Rollback options
|
|
129
|
+
*/
|
|
130
|
+
export async function rollbackProject(projectPath, options = {}) {
|
|
131
|
+
const { verbose = false } = options;
|
|
132
|
+
|
|
133
|
+
if (!projectPath || !fs.existsSync(projectPath)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const spinner = ora({
|
|
138
|
+
text: 'Cleaning up...',
|
|
139
|
+
color: 'yellow',
|
|
140
|
+
}).start();
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Remove the project directory
|
|
144
|
+
await fs.remove(projectPath);
|
|
145
|
+
|
|
146
|
+
spinner.succeed(chalk.yellow('Cleaned up incomplete project'));
|
|
147
|
+
|
|
148
|
+
if (verbose) {
|
|
149
|
+
console.log(chalk.gray(` Removed: ${projectPath}`));
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
spinner.fail(chalk.red('Failed to clean up'));
|
|
153
|
+
|
|
154
|
+
if (verbose) {
|
|
155
|
+
console.error(chalk.gray(` Error: ${error.message}`));
|
|
156
|
+
console.log(chalk.yellow(`\n Please manually remove: ${projectPath}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Wrap an async operation with error handling and rollback
|
|
163
|
+
* @param {Function} operation - The async operation to execute
|
|
164
|
+
* @param {Object} options - Options for error handling
|
|
165
|
+
* @returns {Promise<any>}
|
|
166
|
+
*/
|
|
167
|
+
export async function withErrorHandling(operation, options = {}) {
|
|
168
|
+
const {
|
|
169
|
+
projectPath = null,
|
|
170
|
+
rollback = true,
|
|
171
|
+
errorCode = ERROR_CODES.UNKNOWN_ERROR,
|
|
172
|
+
context = {},
|
|
173
|
+
} = options;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
return await operation();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
// Perform rollback if requested
|
|
179
|
+
if (rollback && projectPath) {
|
|
180
|
+
await rollbackProject(projectPath, { verbose: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Throw a CLI error with proper formatting
|
|
184
|
+
if (error instanceof CLIError) {
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new CLIError(
|
|
189
|
+
error.message || 'An unexpected error occurred',
|
|
190
|
+
errorCode,
|
|
191
|
+
{ originalError: error.name, ...context }
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Handle process termination signals gracefully
|
|
198
|
+
* @param {string} projectPath - Path to clean up on termination
|
|
199
|
+
*/
|
|
200
|
+
export function setupGracefulShutdown(projectPath) {
|
|
201
|
+
const cleanup = async (signal) => {
|
|
202
|
+
console.log(chalk.yellow(`\n\nReceived ${signal}. Cleaning up...`));
|
|
203
|
+
|
|
204
|
+
if (projectPath && fs.existsSync(projectPath)) {
|
|
205
|
+
await rollbackProject(projectPath, { verbose: false });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
process.exit(0);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Handle SIGINT (Ctrl+C)
|
|
212
|
+
process.on('SIGINT', () => cleanup('SIGINT'));
|
|
213
|
+
|
|
214
|
+
// Handle SIGTERM
|
|
215
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate and handle errors for a specific operation
|
|
220
|
+
* @param {Function} validator - Validation function that returns { valid, errors }
|
|
221
|
+
* @param {string} errorMessage - Custom error message
|
|
222
|
+
*/
|
|
223
|
+
export function handleValidationError(validator, errorMessage) {
|
|
224
|
+
const result = validator();
|
|
225
|
+
|
|
226
|
+
if (!result.valid) {
|
|
227
|
+
const errorDetails = {};
|
|
228
|
+
Object.entries(result.errors).forEach(([key, errors]) => {
|
|
229
|
+
errorDetails[key] = errors.join(', ');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
throw new CLIError(
|
|
233
|
+
errorMessage,
|
|
234
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
235
|
+
errorDetails
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Create a safe wrapper for file operations
|
|
242
|
+
* @param {Function} operation - File operation to wrap
|
|
243
|
+
* @param {string} operationName - Name of the operation for error messages
|
|
244
|
+
*/
|
|
245
|
+
export async function safeFileOperation(operation, operationName = 'File operation') {
|
|
246
|
+
try {
|
|
247
|
+
return await operation();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error.code === 'EACCES') {
|
|
250
|
+
throw new CLIError(
|
|
251
|
+
`Permission denied: ${operationName}`,
|
|
252
|
+
ERROR_CODES.PERMISSION_DENIED,
|
|
253
|
+
{ systemError: error.code }
|
|
254
|
+
);
|
|
255
|
+
} else if (error.code === 'ENOENT') {
|
|
256
|
+
throw new CLIError(
|
|
257
|
+
`Path not found: ${operationName}`,
|
|
258
|
+
ERROR_CODES.CREATION_FAILED,
|
|
259
|
+
{ systemError: error.code }
|
|
260
|
+
);
|
|
261
|
+
} else if (error.code === 'ENOSPC') {
|
|
262
|
+
throw new CLIError(
|
|
263
|
+
'Not enough disk space',
|
|
264
|
+
ERROR_CODES.CREATION_FAILED,
|
|
265
|
+
{ systemError: error.code }
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
throw new CLIError(
|
|
270
|
+
`${operationName} failed: ${error.message}`,
|
|
271
|
+
ERROR_CODES.CREATION_FAILED,
|
|
272
|
+
{ systemError: error.code || 'UNKNOWN' }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/utils/git.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initialize a Git repository and create first commit
|
|
10
|
+
*
|
|
11
|
+
* Performs the following operations:
|
|
12
|
+
* 1. Runs `git init` to create a new repository
|
|
13
|
+
* 2. Creates .gitignore file with common patterns (if not exists)
|
|
14
|
+
* 3. Stages all files with `git add .`
|
|
15
|
+
* 4. Creates initial commit with message "Initial commit"
|
|
16
|
+
*
|
|
17
|
+
* Note: Catches and warns if Git is not installed or initialization fails.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} projectPath - Absolute path to the project directory
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* await initGit('/path/to/my-project');
|
|
24
|
+
* // Creates .git directory and initial commit
|
|
25
|
+
*
|
|
26
|
+
* @throws {Error} Logs warning but does not throw - Git init is optional
|
|
27
|
+
*/
|
|
28
|
+
async function initGit(projectPath) {
|
|
29
|
+
try {
|
|
30
|
+
// Initialize git repo
|
|
31
|
+
await execAsync('git init', { cwd: projectPath });
|
|
32
|
+
|
|
33
|
+
// Create .gitignore if it doesn't exist
|
|
34
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
35
|
+
if (!(await fs.pathExists(gitignorePath))) {
|
|
36
|
+
const gitignoreContent = `# Dependencies
|
|
37
|
+
node_modules/
|
|
38
|
+
|
|
39
|
+
# Environment variables
|
|
40
|
+
.env
|
|
41
|
+
.env.local
|
|
42
|
+
|
|
43
|
+
# Build outputs
|
|
44
|
+
dist/
|
|
45
|
+
build/
|
|
46
|
+
|
|
47
|
+
# Logs
|
|
48
|
+
*.log
|
|
49
|
+
|
|
50
|
+
# OS
|
|
51
|
+
.DS_Store
|
|
52
|
+
Thumbs.db
|
|
53
|
+
|
|
54
|
+
# IDE
|
|
55
|
+
.vscode/
|
|
56
|
+
.idea/
|
|
57
|
+
`;
|
|
58
|
+
await fs.writeFile(gitignorePath, gitignoreContent);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Initial commit
|
|
62
|
+
await execAsync('git add .', { cwd: projectPath });
|
|
63
|
+
await execAsync('git commit -m "Initial commit"', { cwd: projectPath });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn('Warning: Could not initialize Git repository');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { initGit };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Install project dependencies using the specified package manager
|
|
11
|
+
*
|
|
12
|
+
* Executes the package manager's install command in the project directory.
|
|
13
|
+
* Shows a spinner during installation and provides helpful error messages
|
|
14
|
+
* with manual installation instructions if the operation fails.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} projectPath - Absolute path to the project directory
|
|
17
|
+
* @param {string} [packageManager='npm'] - Package manager to use ('npm'|'yarn'|'pnpm'|'bun')
|
|
18
|
+
* @param {Object} [options={}] - Installation options
|
|
19
|
+
* @param {boolean} [options.verbose=false] - Show detailed command output
|
|
20
|
+
*
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
* @throws {Error} If dependency installation fails
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Basic usage with npm
|
|
26
|
+
* await installDependencies('/path/to/project');
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // With specific package manager and verbose output
|
|
30
|
+
* await installDependencies('/path/to/project', 'yarn', { verbose: true });
|
|
31
|
+
*/
|
|
32
|
+
async function installDependencies(projectPath, packageManager = 'npm', options = {}) {
|
|
33
|
+
const { verbose = false } = options;
|
|
34
|
+
const spinner = ora(`Installing dependencies with ${packageManager}...`).start();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const installCommand = getInstallCommand(packageManager);
|
|
38
|
+
|
|
39
|
+
if (verbose) {
|
|
40
|
+
spinner.info(chalk.gray(`Running: ${installCommand}`));
|
|
41
|
+
spinner.start('Installing...');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await execAsync(installCommand, {
|
|
45
|
+
cwd: projectPath,
|
|
46
|
+
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
spinner.succeed(chalk.green('Dependencies installed successfully'));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
spinner.fail(chalk.red('Failed to install dependencies'));
|
|
52
|
+
console.log(chalk.yellow('\nYou can install them manually by running:'));
|
|
53
|
+
console.log(chalk.cyan(` cd ${path.basename(projectPath)}`));
|
|
54
|
+
console.log(chalk.cyan(` ${packageManager} install\n`));
|
|
55
|
+
|
|
56
|
+
if (verbose) {
|
|
57
|
+
console.log(chalk.gray(`Error details: ${error.message}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the install command for the specified package manager
|
|
66
|
+
*
|
|
67
|
+
* Returns the correct install command syntax for different package managers.
|
|
68
|
+
* Defaults to 'npm install' if an unknown package manager is specified.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} packageManager - Package manager name ('npm'|'yarn'|'pnpm'|'bun')
|
|
71
|
+
* @returns {string} The complete install command to execute
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* getInstallCommand('npm'); // Returns: 'npm install'
|
|
75
|
+
* getInstallCommand('yarn'); // Returns: 'yarn install'
|
|
76
|
+
* getInstallCommand('pnpm'); // Returns: 'pnpm install'
|
|
77
|
+
* getInstallCommand('unknown'); // Returns: 'npm install' (fallback)
|
|
78
|
+
*/
|
|
79
|
+
function getInstallCommand(packageManager) {
|
|
80
|
+
const commands = {
|
|
81
|
+
npm: 'npm install',
|
|
82
|
+
yarn: 'yarn install',
|
|
83
|
+
pnpm: 'pnpm install',
|
|
84
|
+
bun: 'bun install',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return commands[packageManager] || commands.npm;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { installDependencies, getInstallCommand };
|