projxo 1.0.0 → 1.0.2
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/index.js +2 -2
- package/package.json +2 -1
- package/src/cli.js +173 -0
- package/src/config/ides.js +129 -0
- package/src/config/projectTypes.js +145 -0
- package/src/handlers/ideOpener.js +73 -0
- package/src/handlers/projectCreator.js +133 -0
- package/src/prompts/questions.js +105 -0
- package/src/utils/command.js +64 -0
- package/src/utils/fileSystem.js +147 -0
- package/src/utils/logger.js +110 -0
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projxo",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Cross-platform CLI tool to quickly create React, Next.js, Angular, and React Native projects with automatic IDE integration",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"homepage": "https://github.com/sasangachathumal/ProjXO#readme",
|
|
45
45
|
"files": [
|
|
46
46
|
"index.js",
|
|
47
|
+
"src/**/*",
|
|
47
48
|
"README.md",
|
|
48
49
|
"LICENSE"
|
|
49
50
|
],
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main CLI orchestration
|
|
3
|
+
* Coordinates the project creation workflow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const logger = require('./utils/logger');
|
|
8
|
+
const { pathExists } = require('./utils/fileSystem');
|
|
9
|
+
const { getProjectType } = require('./config/projectTypes');
|
|
10
|
+
const {
|
|
11
|
+
getProjectTypePrompt,
|
|
12
|
+
getProjectNamePrompt,
|
|
13
|
+
getDirectoryPrompt,
|
|
14
|
+
getIDEPrompt,
|
|
15
|
+
getOverwritePrompt
|
|
16
|
+
} = require('./prompts/questions');
|
|
17
|
+
const {
|
|
18
|
+
createProject,
|
|
19
|
+
displaySuccessMessage,
|
|
20
|
+
validateProjectParams
|
|
21
|
+
} = require('./handlers/projectCreator');
|
|
22
|
+
const { openInIDE } = require('./handlers/ideOpener');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Main CLI function
|
|
26
|
+
* Orchestrates the entire project creation flow
|
|
27
|
+
*/
|
|
28
|
+
async function run() {
|
|
29
|
+
try {
|
|
30
|
+
// Display welcome banner
|
|
31
|
+
displayBanner();
|
|
32
|
+
|
|
33
|
+
// Get user inputs through prompts
|
|
34
|
+
const answers = await getUserInputs();
|
|
35
|
+
|
|
36
|
+
// Validate inputs
|
|
37
|
+
const validation = validateProjectParams(answers);
|
|
38
|
+
if (!validation.valid) {
|
|
39
|
+
logger.error(validation.error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if project exists and handle overwrite
|
|
44
|
+
const shouldProceed = await handleExistingProject(answers);
|
|
45
|
+
if (!shouldProceed) {
|
|
46
|
+
logger.warning('Operation cancelled');
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create the project
|
|
51
|
+
const projectPath = await createProject({
|
|
52
|
+
projectType: answers.projectType,
|
|
53
|
+
projectName: answers.projectName,
|
|
54
|
+
directory: answers.directory
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Display success message
|
|
58
|
+
displaySuccessMessage(projectPath, answers.projectType);
|
|
59
|
+
|
|
60
|
+
// Open in IDE if selected
|
|
61
|
+
if (answers.selectedIDE !== 'skip') {
|
|
62
|
+
await openInIDE(projectPath, answers.selectedIDE);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
handleError(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Display welcome banner
|
|
72
|
+
*/
|
|
73
|
+
function displayBanner() {
|
|
74
|
+
logger.newLine();
|
|
75
|
+
logger.log('═══════════════════════════════════════════════════', 'bright');
|
|
76
|
+
logger.log(' ProjXO - Quick Project Setup', 'brightCyan');
|
|
77
|
+
logger.log('═══════════════════════════════════════════════════', 'bright');
|
|
78
|
+
logger.newLine();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get user inputs through interactive prompts
|
|
83
|
+
* @returns {Promise<Object>} User answers
|
|
84
|
+
*/
|
|
85
|
+
async function getUserInputs() {
|
|
86
|
+
const defaultDirectory = process.cwd();
|
|
87
|
+
|
|
88
|
+
// Get project type
|
|
89
|
+
const { projectType } = await inquirer.prompt([getProjectTypePrompt()]);
|
|
90
|
+
|
|
91
|
+
const config = getProjectType(projectType);
|
|
92
|
+
logger.success(`Selected: ${config.name}`);
|
|
93
|
+
logger.newLine();
|
|
94
|
+
|
|
95
|
+
// Get project name
|
|
96
|
+
const { projectName } = await inquirer.prompt([getProjectNamePrompt()]);
|
|
97
|
+
|
|
98
|
+
// Get directory
|
|
99
|
+
const { directory } = await inquirer.prompt([getDirectoryPrompt(defaultDirectory)]);
|
|
100
|
+
|
|
101
|
+
// Get IDE choice
|
|
102
|
+
const { selectedIDE } = await inquirer.prompt([getIDEPrompt()]);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
projectType,
|
|
106
|
+
projectName,
|
|
107
|
+
directory,
|
|
108
|
+
selectedIDE
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle existing project scenario
|
|
114
|
+
* @param {Object} answers - User answers containing projectName and directory
|
|
115
|
+
* @returns {Promise<boolean>} True if should proceed, false if cancelled
|
|
116
|
+
*/
|
|
117
|
+
async function handleExistingProject({ projectName, directory }) {
|
|
118
|
+
const { expandHomePath, getProjectPath } = require('./utils/fileSystem');
|
|
119
|
+
const expandedDir = expandHomePath(directory);
|
|
120
|
+
const fullPath = getProjectPath(expandedDir, projectName);
|
|
121
|
+
|
|
122
|
+
if (pathExists(fullPath)) {
|
|
123
|
+
logger.warning(`Project "${projectName}" already exists at: ${fullPath}`);
|
|
124
|
+
|
|
125
|
+
const { overwrite } = await inquirer.prompt([getOverwritePrompt(projectName)]);
|
|
126
|
+
|
|
127
|
+
if (!overwrite) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logger.info('Proceeding with overwrite...');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle errors gracefully
|
|
139
|
+
* @param {Error} error - Error object
|
|
140
|
+
*/
|
|
141
|
+
function handleError(error) {
|
|
142
|
+
logger.newLine();
|
|
143
|
+
|
|
144
|
+
if (error.isTtyError) {
|
|
145
|
+
logger.error('Prompt could not be rendered in this environment');
|
|
146
|
+
logger.info('Please ensure you are running in an interactive terminal');
|
|
147
|
+
} else if (error.message === 'PROJECT_EXISTS') {
|
|
148
|
+
// This shouldn't happen as we handle it, but just in case
|
|
149
|
+
logger.error('Project already exists');
|
|
150
|
+
} else {
|
|
151
|
+
logger.error(`An error occurred: ${error.message}`);
|
|
152
|
+
|
|
153
|
+
// Show stack trace in debug mode
|
|
154
|
+
if (process.env.DEBUG) {
|
|
155
|
+
console.error(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
logger.newLine();
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Handle graceful shutdown on SIGINT (Ctrl+C)
|
|
165
|
+
*/
|
|
166
|
+
process.on('SIGINT', () => {
|
|
167
|
+
logger.newLine();
|
|
168
|
+
logger.warning('Operation cancelled by user');
|
|
169
|
+
logger.newLine();
|
|
170
|
+
process.exit(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
module.exports = { run };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE/Editor configurations
|
|
3
|
+
* Defines supported IDEs and their command-line commands
|
|
4
|
+
*
|
|
5
|
+
* To add a new IDE:
|
|
6
|
+
* 1. Add a new key to IDES object
|
|
7
|
+
* 2. Specify name and command
|
|
8
|
+
* 3. Optionally add installation instructions in getIDEInstallInstructions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* IDE configuration object
|
|
13
|
+
* @typedef {Object} IDEConfig
|
|
14
|
+
* @property {string} name - Display name for the IDE
|
|
15
|
+
* @property {string|null} command - Command-line command to open IDE (null for 'skip')
|
|
16
|
+
* @property {string} [description] - Optional description
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const IDES = {
|
|
20
|
+
'vscode': {
|
|
21
|
+
name: 'VS Code',
|
|
22
|
+
command: 'code',
|
|
23
|
+
description: 'Visual Studio Code'
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
'cursor': {
|
|
27
|
+
name: 'Cursor',
|
|
28
|
+
command: 'cursor',
|
|
29
|
+
description: 'Cursor AI Editor'
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
'webstorm': {
|
|
33
|
+
name: 'WebStorm',
|
|
34
|
+
command: 'webstorm',
|
|
35
|
+
description: 'JetBrains WebStorm IDE'
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
'idea': {
|
|
39
|
+
name: 'IntelliJ IDEA',
|
|
40
|
+
command: 'idea',
|
|
41
|
+
description: 'JetBrains IntelliJ IDEA'
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
'sublime': {
|
|
45
|
+
name: 'Sublime Text',
|
|
46
|
+
command: 'subl',
|
|
47
|
+
description: 'Sublime Text Editor'
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
'atom': {
|
|
51
|
+
name: 'Atom',
|
|
52
|
+
command: 'atom',
|
|
53
|
+
description: 'GitHub Atom Editor'
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
'skip': {
|
|
57
|
+
name: 'Skip (open manually)',
|
|
58
|
+
command: null,
|
|
59
|
+
description: 'Do not open in any IDE'
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get IDE configuration
|
|
65
|
+
* @param {string} ideKey - IDE key
|
|
66
|
+
* @returns {IDEConfig|null} Configuration object or null if not found
|
|
67
|
+
*/
|
|
68
|
+
function getIDE(ideKey) {
|
|
69
|
+
return IDES[ideKey] || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all IDEs as an array
|
|
74
|
+
* @returns {Array<{key: string, config: IDEConfig}>}
|
|
75
|
+
*/
|
|
76
|
+
function getAllIDEs() {
|
|
77
|
+
return Object.entries(IDES).map(([key, config]) => ({
|
|
78
|
+
key,
|
|
79
|
+
config
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get formatted choices for inquirer prompts
|
|
85
|
+
* @returns {Array<{name: string, value: string}>}
|
|
86
|
+
*/
|
|
87
|
+
function getIDEChoices() {
|
|
88
|
+
return Object.entries(IDES).map(([key, config]) => ({
|
|
89
|
+
name: config.name,
|
|
90
|
+
value: key,
|
|
91
|
+
short: config.name
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if an IDE key is valid
|
|
97
|
+
* @param {string} ideKey - IDE key to check
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function isValidIDE(ideKey) {
|
|
101
|
+
return ideKey in IDES;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get installation instructions for an IDE command
|
|
106
|
+
* @param {string} ideKey - IDE key
|
|
107
|
+
* @returns {string} Installation instructions
|
|
108
|
+
*/
|
|
109
|
+
function getIDEInstallInstructions(ideKey) {
|
|
110
|
+
const instructions = {
|
|
111
|
+
'vscode': 'Install "Shell Command: Install \'code\' command in PATH" from Command Palette (Cmd/Ctrl+Shift+P)',
|
|
112
|
+
'cursor': 'Cursor command is usually available after installation',
|
|
113
|
+
'webstorm': 'Enable in WebStorm: Tools → Create Command-line Launcher',
|
|
114
|
+
'idea': 'Enable in IntelliJ IDEA: Tools → Create Command-line Launcher',
|
|
115
|
+
'sublime': 'Create symlink: ln -s "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" /usr/local/bin/subl',
|
|
116
|
+
'atom': 'Install shell commands from Atom: Atom → Install Shell Commands'
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return instructions[ideKey] || 'Please refer to your IDE\'s documentation for command-line setup';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
IDES,
|
|
124
|
+
getIDE,
|
|
125
|
+
getAllIDEs,
|
|
126
|
+
getIDEChoices,
|
|
127
|
+
isValidIDE,
|
|
128
|
+
getIDEInstallInstructions
|
|
129
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project type configurations
|
|
3
|
+
* Defines all supported project types and their creation commands
|
|
4
|
+
*
|
|
5
|
+
* To add a new project type:
|
|
6
|
+
* 1. Add a new key to PROJECT_TYPES object
|
|
7
|
+
* 2. Specify name, command, getArgs function, and postInstall flag
|
|
8
|
+
* 3. Add corresponding help text to getNextSteps function
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Project type configuration object
|
|
13
|
+
* @typedef {Object} ProjectTypeConfig
|
|
14
|
+
* @property {string} name - Display name for the project type
|
|
15
|
+
* @property {string} command - Command to execute (npm, npx, etc.)
|
|
16
|
+
* @property {Function} getArgs - Function that returns command arguments
|
|
17
|
+
* @property {boolean} postInstall - Whether to run npm install after creation
|
|
18
|
+
* @property {string} [description] - Optional description
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const PROJECT_TYPES = {
|
|
22
|
+
'react-vite': {
|
|
23
|
+
name: 'React + Vite',
|
|
24
|
+
description: 'React with Vite bundler (JavaScript)',
|
|
25
|
+
command: 'npm',
|
|
26
|
+
getArgs: (name) => ['create', 'vite@latest', name, '--', '--template', 'react'],
|
|
27
|
+
postInstall: true
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
'react-vite-ts': {
|
|
31
|
+
name: 'React + Vite (TypeScript)',
|
|
32
|
+
description: 'React with Vite bundler and TypeScript',
|
|
33
|
+
command: 'npm',
|
|
34
|
+
getArgs: (name) => ['create', 'vite@latest', name, '--', '--template', 'react-ts'],
|
|
35
|
+
postInstall: true
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
'nextjs': {
|
|
39
|
+
name: 'Next.js',
|
|
40
|
+
description: 'React framework for production',
|
|
41
|
+
command: 'npx',
|
|
42
|
+
getArgs: (name) => ['create-next-app@latest', name],
|
|
43
|
+
postInstall: false // create-next-app already installs dependencies
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
'angular': {
|
|
47
|
+
name: 'Angular',
|
|
48
|
+
description: 'Platform for building web applications',
|
|
49
|
+
command: 'npx',
|
|
50
|
+
getArgs: (name) => ['@angular/cli@latest', 'new', name],
|
|
51
|
+
postInstall: false // Angular CLI already installs dependencies
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
'react-native': {
|
|
55
|
+
name: 'React Native (Expo)',
|
|
56
|
+
description: 'Build native mobile apps with React',
|
|
57
|
+
command: 'npx',
|
|
58
|
+
getArgs: (name) => ['create-expo-app', name],
|
|
59
|
+
postInstall: false // Expo already installs dependencies
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get project type configuration
|
|
65
|
+
* @param {string} typeKey - Project type key
|
|
66
|
+
* @returns {ProjectTypeConfig|null} Configuration object or null if not found
|
|
67
|
+
*/
|
|
68
|
+
function getProjectType(typeKey) {
|
|
69
|
+
return PROJECT_TYPES[typeKey] || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all project types as an array
|
|
74
|
+
* @returns {Array<{key: string, config: ProjectTypeConfig}>}
|
|
75
|
+
*/
|
|
76
|
+
function getAllProjectTypes() {
|
|
77
|
+
return Object.entries(PROJECT_TYPES).map(([key, config]) => ({
|
|
78
|
+
key,
|
|
79
|
+
config
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get formatted choices for inquirer prompts
|
|
85
|
+
* @returns {Array<{name: string, value: string}>}
|
|
86
|
+
*/
|
|
87
|
+
function getProjectTypeChoices() {
|
|
88
|
+
return Object.entries(PROJECT_TYPES).map(([key, config]) => ({
|
|
89
|
+
name: `${config.name} (${key})`,
|
|
90
|
+
value: key,
|
|
91
|
+
short: config.name
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get next steps instructions based on project type
|
|
97
|
+
* @param {string} typeKey - Project type key
|
|
98
|
+
* @param {string} projectPath - Full path to the project
|
|
99
|
+
* @returns {string[]} Array of instruction strings
|
|
100
|
+
*/
|
|
101
|
+
function getNextSteps(typeKey, projectPath) {
|
|
102
|
+
const steps = [`cd ${projectPath}`];
|
|
103
|
+
|
|
104
|
+
switch (typeKey) {
|
|
105
|
+
case 'react-vite':
|
|
106
|
+
case 'react-vite-ts':
|
|
107
|
+
steps.push('npm run dev');
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'nextjs':
|
|
111
|
+
steps.push('npm run dev');
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'angular':
|
|
115
|
+
steps.push('ng serve');
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case 'react-native':
|
|
119
|
+
steps.push('npx expo start');
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
steps.push('npm start');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return steps;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if a project type exists
|
|
131
|
+
* @param {string} typeKey - Project type key to check
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function isValidProjectType(typeKey) {
|
|
135
|
+
return typeKey in PROJECT_TYPES;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
PROJECT_TYPES,
|
|
140
|
+
getProjectType,
|
|
141
|
+
getAllProjectTypes,
|
|
142
|
+
getProjectTypeChoices,
|
|
143
|
+
getNextSteps,
|
|
144
|
+
isValidProjectType
|
|
145
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE opener handler
|
|
3
|
+
* Handles opening projects in various IDEs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { runCommand } = require('../utils/command');
|
|
7
|
+
const { getIDE, getIDEInstallInstructions } = require('../config/ides');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Open a project in the specified IDE
|
|
12
|
+
* @param {string} projectPath - Full path to the project
|
|
13
|
+
* @param {string} ideKey - IDE key (e.g., 'vscode', 'cursor')
|
|
14
|
+
* @returns {Promise<boolean>} True if opened successfully, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
async function openInIDE(projectPath, ideKey) {
|
|
17
|
+
// Get IDE configuration
|
|
18
|
+
const ide = getIDE(ideKey);
|
|
19
|
+
|
|
20
|
+
// Skip if no IDE selected or invalid IDE
|
|
21
|
+
if (!ide || !ide.command) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
logger.info(`Opening project in ${ide.name}...`);
|
|
27
|
+
|
|
28
|
+
// Execute IDE command with project path
|
|
29
|
+
await runCommand(ide.command, [projectPath]);
|
|
30
|
+
|
|
31
|
+
logger.success(`Project opened in ${ide.name}`);
|
|
32
|
+
return true;
|
|
33
|
+
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Handle IDE command failure
|
|
36
|
+
logger.error(`Could not open ${ide.name}`);
|
|
37
|
+
logger.warning(`Please ensure ${ide.name} is installed and command-line tools are enabled`);
|
|
38
|
+
|
|
39
|
+
// Provide installation instructions
|
|
40
|
+
const instructions = getIDEInstallInstructions(ideKey);
|
|
41
|
+
logger.log(`\n Setup: ${instructions}`, 'dim');
|
|
42
|
+
|
|
43
|
+
// Provide fallback option
|
|
44
|
+
logger.log(` Or manually open: ${projectPath}`, 'yellow');
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate if IDE command is available
|
|
52
|
+
* @param {string} ideKey - IDE key to validate
|
|
53
|
+
* @returns {Promise<boolean>} True if command is available
|
|
54
|
+
*/
|
|
55
|
+
async function validateIDECommand(ideKey) {
|
|
56
|
+
const ide = getIDE(ideKey);
|
|
57
|
+
|
|
58
|
+
if (!ide || !ide.command) {
|
|
59
|
+
return true; // Skip validation for 'skip' option
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const { commandExists } = require('../utils/command');
|
|
64
|
+
return await commandExists(ide.command);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
openInIDE,
|
|
72
|
+
validateIDECommand
|
|
73
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project creator handler
|
|
3
|
+
* Handles the project creation process
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { runCommand } = require('../utils/command');
|
|
7
|
+
const {
|
|
8
|
+
expandHomePath,
|
|
9
|
+
ensureDirectory,
|
|
10
|
+
pathExists,
|
|
11
|
+
getProjectPath
|
|
12
|
+
} = require('../utils/fileSystem');
|
|
13
|
+
const { getProjectType, getNextSteps } = require('../config/projectTypes');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a new project
|
|
18
|
+
* @param {Object} options - Project creation options
|
|
19
|
+
* @param {string} options.projectType - Project type key
|
|
20
|
+
* @param {string} options.projectName - Name of the project
|
|
21
|
+
* @param {string} options.directory - Directory path where project will be created
|
|
22
|
+
* @returns {Promise<string>} Full path to the created project
|
|
23
|
+
* @throws {Error} If project creation fails
|
|
24
|
+
*/
|
|
25
|
+
async function createProject({ projectType, projectName, directory }) {
|
|
26
|
+
// Get project type configuration
|
|
27
|
+
const config = getProjectType(projectType);
|
|
28
|
+
|
|
29
|
+
if (!config) {
|
|
30
|
+
throw new Error(`Invalid project type: ${projectType}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Expand home directory and ensure directory exists
|
|
34
|
+
const expandedDir = expandHomePath(directory);
|
|
35
|
+
ensureDirectory(expandedDir);
|
|
36
|
+
|
|
37
|
+
// Get full project path
|
|
38
|
+
const fullPath = getProjectPath(expandedDir, projectName);
|
|
39
|
+
|
|
40
|
+
// Check if project already exists
|
|
41
|
+
if (pathExists(fullPath)) {
|
|
42
|
+
throw new Error('PROJECT_EXISTS');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Display creation start message
|
|
46
|
+
logger.section('Starting project creation...');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Execute project creation command in the target directory
|
|
50
|
+
await runCommand(
|
|
51
|
+
config.command,
|
|
52
|
+
config.getArgs(projectName),
|
|
53
|
+
expandedDir
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Run post-install if needed
|
|
57
|
+
if (config.postInstall) {
|
|
58
|
+
logger.info('Installing dependencies...');
|
|
59
|
+
await runCommand('npm', ['install'], fullPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return fullPath;
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Provide helpful error message
|
|
66
|
+
throw new Error(`Failed to create project: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Display project creation success message with next steps
|
|
72
|
+
* @param {string} projectPath - Full path to the created project
|
|
73
|
+
* @param {string} projectType - Project type key
|
|
74
|
+
*/
|
|
75
|
+
function displaySuccessMessage(projectPath, projectType) {
|
|
76
|
+
logger.section('✓ Project created successfully!');
|
|
77
|
+
|
|
78
|
+
logger.log('Project location:', 'bright');
|
|
79
|
+
logger.log(` ${projectPath}`, 'cyan');
|
|
80
|
+
|
|
81
|
+
logger.newLine();
|
|
82
|
+
logger.log('Next steps:', 'bright');
|
|
83
|
+
|
|
84
|
+
const steps = getNextSteps(projectType, projectPath);
|
|
85
|
+
steps.forEach(step => {
|
|
86
|
+
logger.log(` ${step}`, 'green');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
logger.newLine();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validate project creation parameters
|
|
94
|
+
* @param {Object} params - Parameters to validate
|
|
95
|
+
* @param {string} params.projectType - Project type key
|
|
96
|
+
* @param {string} params.projectName - Project name
|
|
97
|
+
* @param {string} params.directory - Directory path
|
|
98
|
+
* @returns {Object} { valid: boolean, error?: string }
|
|
99
|
+
*/
|
|
100
|
+
function validateProjectParams({ projectType, projectName, directory }) {
|
|
101
|
+
// Validate project type
|
|
102
|
+
const config = getProjectType(projectType);
|
|
103
|
+
if (!config) {
|
|
104
|
+
return {
|
|
105
|
+
valid: false,
|
|
106
|
+
error: `Invalid project type: ${projectType}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate project name
|
|
111
|
+
if (!projectName || projectName.trim() === '') {
|
|
112
|
+
return {
|
|
113
|
+
valid: false,
|
|
114
|
+
error: 'Project name is required'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Validate directory
|
|
119
|
+
if (!directory || directory.trim() === '') {
|
|
120
|
+
return {
|
|
121
|
+
valid: false,
|
|
122
|
+
error: 'Directory is required'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { valid: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
createProject,
|
|
131
|
+
displaySuccessMessage,
|
|
132
|
+
validateProjectParams
|
|
133
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inquirer prompt definitions
|
|
3
|
+
* Centralizes all user prompts for consistency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getProjectTypeChoices } = require('../config/projectTypes');
|
|
7
|
+
const { getIDEChoices } = require('../config/ides');
|
|
8
|
+
const { validateProjectName } = require('../utils/fileSystem');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get project type selection prompt
|
|
12
|
+
* @returns {Object} Inquirer prompt configuration
|
|
13
|
+
*/
|
|
14
|
+
function getProjectTypePrompt() {
|
|
15
|
+
return {
|
|
16
|
+
type: 'list',
|
|
17
|
+
name: 'projectType',
|
|
18
|
+
message: 'Select project type:',
|
|
19
|
+
choices: getProjectTypeChoices(),
|
|
20
|
+
pageSize: 10
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get project name input prompt
|
|
26
|
+
* @returns {Object} Inquirer prompt configuration
|
|
27
|
+
*/
|
|
28
|
+
function getProjectNamePrompt() {
|
|
29
|
+
return {
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'projectName',
|
|
32
|
+
message: 'Enter project name:',
|
|
33
|
+
validate: (input) => {
|
|
34
|
+
const result = validateProjectName(input);
|
|
35
|
+
return result.valid ? true : result.error;
|
|
36
|
+
},
|
|
37
|
+
filter: (input) => input.trim() // Remove leading/trailing whitespace
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get directory path input prompt
|
|
43
|
+
* @param {string} defaultPath - Default directory path
|
|
44
|
+
* @returns {Object} Inquirer prompt configuration
|
|
45
|
+
*/
|
|
46
|
+
function getDirectoryPrompt(defaultPath) {
|
|
47
|
+
return {
|
|
48
|
+
type: 'input',
|
|
49
|
+
name: 'directory',
|
|
50
|
+
message: 'Enter directory path:',
|
|
51
|
+
default: defaultPath,
|
|
52
|
+
filter: (input) => input.trim()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get IDE selection prompt
|
|
58
|
+
* @returns {Object} Inquirer prompt configuration
|
|
59
|
+
*/
|
|
60
|
+
function getIDEPrompt() {
|
|
61
|
+
return {
|
|
62
|
+
type: 'list',
|
|
63
|
+
name: 'selectedIDE',
|
|
64
|
+
message: 'Select IDE to open:',
|
|
65
|
+
choices: getIDEChoices(),
|
|
66
|
+
pageSize: 10
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get overwrite confirmation prompt
|
|
72
|
+
* @param {string} projectName - Name of the project that already exists
|
|
73
|
+
* @returns {Object} Inquirer prompt configuration
|
|
74
|
+
*/
|
|
75
|
+
function getOverwritePrompt(projectName) {
|
|
76
|
+
return {
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'overwrite',
|
|
79
|
+
message: `Project "${projectName}" already exists. Overwrite?`,
|
|
80
|
+
default: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get all prompts for project creation flow
|
|
86
|
+
* @param {string} defaultDirectory - Default directory path
|
|
87
|
+
* @returns {Array<Object>} Array of prompt configurations
|
|
88
|
+
*/
|
|
89
|
+
function getProjectCreationPrompts(defaultDirectory) {
|
|
90
|
+
return [
|
|
91
|
+
getProjectTypePrompt(),
|
|
92
|
+
getProjectNamePrompt(),
|
|
93
|
+
getDirectoryPrompt(defaultDirectory),
|
|
94
|
+
getIDEPrompt()
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
getProjectTypePrompt,
|
|
100
|
+
getProjectNamePrompt,
|
|
101
|
+
getDirectoryPrompt,
|
|
102
|
+
getIDEPrompt,
|
|
103
|
+
getOverwritePrompt,
|
|
104
|
+
getProjectCreationPrompts
|
|
105
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command execution utility
|
|
3
|
+
* Handles spawning child processes and command execution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const logger = require('./logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute a shell command
|
|
11
|
+
* @param {string} command - Command to execute (e.g., 'npm', 'npx')
|
|
12
|
+
* @param {string[]} args - Array of command arguments
|
|
13
|
+
* @param {string} [cwd] - Working directory for command execution
|
|
14
|
+
* @returns {Promise<void>} Resolves when command completes successfully
|
|
15
|
+
* @throws {Error} If command exits with non-zero code
|
|
16
|
+
*/
|
|
17
|
+
function runCommand(command, args, cwd) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
// Log the command being executed for debugging
|
|
20
|
+
logger.log(`\nExecuting: ${command} ${args.join(' ')}`, 'cyan');
|
|
21
|
+
|
|
22
|
+
// Spawn the process
|
|
23
|
+
const proc = spawn(command, args, {
|
|
24
|
+
stdio: 'inherit', // Pipe stdin, stdout, stderr to parent process
|
|
25
|
+
shell: true, // Use shell for command execution
|
|
26
|
+
cwd: cwd || process.cwd() // Use provided cwd or current directory
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Handle process completion
|
|
30
|
+
proc.on('close', (code) => {
|
|
31
|
+
if (code !== 0) {
|
|
32
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
33
|
+
} else {
|
|
34
|
+
resolve();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Handle process errors (e.g., command not found)
|
|
39
|
+
proc.on('error', (err) => {
|
|
40
|
+
reject(new Error(`Failed to execute command: ${err.message}`));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a command exists in the system PATH
|
|
47
|
+
* @param {string} command - Command to check
|
|
48
|
+
* @returns {Promise<boolean>} True if command exists
|
|
49
|
+
*/
|
|
50
|
+
async function commandExists(command) {
|
|
51
|
+
const checkCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await runCommand(checkCmd, [command], process.cwd());
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
runCommand,
|
|
63
|
+
commandExists
|
|
64
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system utility functions
|
|
3
|
+
* Handles all file and directory operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const logger = require('./logger');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Expand tilde (~) in path to home directory
|
|
13
|
+
* @param {string} filePath - Path that may contain ~
|
|
14
|
+
* @returns {string} Expanded absolute path
|
|
15
|
+
* @example
|
|
16
|
+
* expandHomePath('~/Documents') // -> '/Users/username/Documents'
|
|
17
|
+
*/
|
|
18
|
+
function expandHomePath(filePath) {
|
|
19
|
+
if (filePath.startsWith('~')) {
|
|
20
|
+
return filePath.replace('~', os.homedir());
|
|
21
|
+
}
|
|
22
|
+
return filePath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Ensure a directory exists, create if it doesn't
|
|
27
|
+
* @param {string} dirPath - Directory path to ensure
|
|
28
|
+
* @returns {boolean} True if directory was created, false if already existed
|
|
29
|
+
*/
|
|
30
|
+
function ensureDirectory(dirPath) {
|
|
31
|
+
const expandedPath = expandHomePath(dirPath);
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(expandedPath)) {
|
|
34
|
+
fs.mkdirSync(expandedPath, { recursive: true });
|
|
35
|
+
logger.success(`Created directory: ${expandedPath}`);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a path exists
|
|
44
|
+
* @param {string} filePath - Path to check
|
|
45
|
+
* @returns {boolean} True if path exists
|
|
46
|
+
*/
|
|
47
|
+
function pathExists(filePath) {
|
|
48
|
+
return fs.existsSync(expandHomePath(filePath));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a path is a directory
|
|
53
|
+
* @param {string} dirPath - Path to check
|
|
54
|
+
* @returns {boolean} True if path exists and is a directory
|
|
55
|
+
*/
|
|
56
|
+
function isDirectory(dirPath) {
|
|
57
|
+
try {
|
|
58
|
+
const stats = fs.statSync(expandHomePath(dirPath));
|
|
59
|
+
return stats.isDirectory();
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the full absolute path
|
|
67
|
+
* @param {string} dirPath - Directory path
|
|
68
|
+
* @param {string} projectName - Project name
|
|
69
|
+
* @returns {string} Full project path
|
|
70
|
+
*/
|
|
71
|
+
function getProjectPath(dirPath, projectName) {
|
|
72
|
+
const expandedDir = expandHomePath(dirPath);
|
|
73
|
+
return path.join(expandedDir, projectName);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate directory path
|
|
78
|
+
* @param {string} dirPath - Directory path to validate
|
|
79
|
+
* @returns {Object} { valid: boolean, error?: string }
|
|
80
|
+
*/
|
|
81
|
+
function validateDirectoryPath(dirPath) {
|
|
82
|
+
const expandedPath = expandHomePath(dirPath);
|
|
83
|
+
|
|
84
|
+
// Check if path contains invalid characters
|
|
85
|
+
const invalidChars = /[<>:"|?*]/;
|
|
86
|
+
if (invalidChars.test(expandedPath)) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
error: 'Path contains invalid characters'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if parent directory exists (if path doesn't exist)
|
|
94
|
+
if (!pathExists(expandedPath)) {
|
|
95
|
+
const parentDir = path.dirname(expandedPath);
|
|
96
|
+
if (!pathExists(parentDir)) {
|
|
97
|
+
return {
|
|
98
|
+
valid: false,
|
|
99
|
+
error: 'Parent directory does not exist'
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { valid: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validate project name
|
|
109
|
+
* @param {string} projectName - Project name to validate
|
|
110
|
+
* @returns {Object} { valid: boolean, error?: string }
|
|
111
|
+
*/
|
|
112
|
+
function validateProjectName(projectName) {
|
|
113
|
+
if (!projectName || !projectName.trim()) {
|
|
114
|
+
return {
|
|
115
|
+
valid: false,
|
|
116
|
+
error: 'Project name cannot be empty'
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Allow letters, numbers, hyphens, underscores, dots
|
|
121
|
+
if (!/^[a-zA-Z0-9-_.]+$/.test(projectName)) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
error: 'Project name can only contain letters, numbers, hyphens, underscores, and dots'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Don't allow names starting with dots or hyphens
|
|
129
|
+
if (/^[.-]/.test(projectName)) {
|
|
130
|
+
return {
|
|
131
|
+
valid: false,
|
|
132
|
+
error: 'Project name cannot start with a dot or hyphen'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { valid: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
expandHomePath,
|
|
141
|
+
ensureDirectory,
|
|
142
|
+
pathExists,
|
|
143
|
+
isDirectory,
|
|
144
|
+
getProjectPath,
|
|
145
|
+
validateDirectoryPath,
|
|
146
|
+
validateProjectName
|
|
147
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for colored terminal output
|
|
3
|
+
* Provides consistent formatting across the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ANSI color codes for terminal styling
|
|
7
|
+
const COLORS = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bright: '\x1b[1m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
|
|
12
|
+
// Standard colors
|
|
13
|
+
black: '\x1b[30m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
blue: '\x1b[34m',
|
|
18
|
+
magenta: '\x1b[35m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
white: '\x1b[37m',
|
|
21
|
+
|
|
22
|
+
// Bright colors
|
|
23
|
+
brightRed: '\x1b[91m',
|
|
24
|
+
brightGreen: '\x1b[92m',
|
|
25
|
+
brightYellow: '\x1b[93m',
|
|
26
|
+
brightBlue: '\x1b[94m',
|
|
27
|
+
brightCyan: '\x1b[96m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Main logging function
|
|
32
|
+
* @param {string} message - Message to log
|
|
33
|
+
* @param {string} color - Color key from COLORS object
|
|
34
|
+
*/
|
|
35
|
+
function log(message, color = 'reset') {
|
|
36
|
+
const colorCode = COLORS[color] || COLORS.reset;
|
|
37
|
+
console.log(`${colorCode}${message}${COLORS.reset}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Log success message with green checkmark
|
|
42
|
+
* @param {string} message - Success message
|
|
43
|
+
*/
|
|
44
|
+
function success(message) {
|
|
45
|
+
log(`✓ ${message}`, 'green');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Log error message with red X
|
|
50
|
+
* @param {string} message - Error message
|
|
51
|
+
*/
|
|
52
|
+
function error(message) {
|
|
53
|
+
log(`✗ ${message}`, 'red');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Log warning message with yellow exclamation
|
|
58
|
+
* @param {string} message - Warning message
|
|
59
|
+
*/
|
|
60
|
+
function warning(message) {
|
|
61
|
+
log(`⚠ ${message}`, 'yellow');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Log info message with blue icon
|
|
66
|
+
* @param {string} message - Info message
|
|
67
|
+
*/
|
|
68
|
+
function info(message) {
|
|
69
|
+
log(`ℹ ${message}`, 'blue');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Log a horizontal separator line
|
|
74
|
+
* @param {number} length - Length of separator (default: 50)
|
|
75
|
+
* @param {string} color - Color of separator
|
|
76
|
+
*/
|
|
77
|
+
function separator(length = 50, color = 'bright') {
|
|
78
|
+
log('='.repeat(length), color);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Log a section header with separators
|
|
83
|
+
* @param {string} title - Section title
|
|
84
|
+
*/
|
|
85
|
+
function section(title) {
|
|
86
|
+
console.log(); // Empty line
|
|
87
|
+
separator();
|
|
88
|
+
log(title, 'bright');
|
|
89
|
+
separator();
|
|
90
|
+
console.log(); // Empty line
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Log an empty line
|
|
95
|
+
*/
|
|
96
|
+
function newLine() {
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
log,
|
|
102
|
+
success,
|
|
103
|
+
error,
|
|
104
|
+
warning,
|
|
105
|
+
info,
|
|
106
|
+
separator,
|
|
107
|
+
section,
|
|
108
|
+
newLine,
|
|
109
|
+
COLORS
|
|
110
|
+
};
|