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