projxo 1.0.1 → 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/Readme.md +187 -402
- package/index.js +48 -11
- package/package.json +4 -2
- package/src/cli.js +184 -0
- package/src/commands/list.js +251 -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 +151 -0
- package/src/prompts/questions.js +105 -0
- package/src/storage/database.js +149 -0
- package/src/storage/debug-storage.js +99 -0
- package/src/storage/projects.js +273 -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
|
@@ -1,20 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* ProjXO - Quick Project Setup CLI
|
|
4
|
+
* ProjXO - Quick Project Setup & Management CLI
|
|
5
5
|
*
|
|
6
6
|
* Entry point for the CLI application
|
|
7
|
-
*
|
|
7
|
+
* Handles command routing and argument parsing
|
|
8
8
|
*
|
|
9
|
-
* @
|
|
10
|
-
* @version 1.0.1
|
|
9
|
+
* @version 1.1.0
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
const { run } = require('./src/cli');
|
|
12
|
+
const { program } = require('commander');
|
|
13
|
+
const { run: createProject } = require('./src/cli');
|
|
14
|
+
const { listCommand } = require('./src/commands/list');
|
|
15
|
+
const logger = require('./src/utils/logger');
|
|
15
16
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
// Package info
|
|
18
|
+
const packageJson = require('./package.json');
|
|
19
|
+
|
|
20
|
+
// Configure CLI
|
|
21
|
+
program
|
|
22
|
+
.name('pxo')
|
|
23
|
+
.description('Quick project setup and management for modern web frameworks')
|
|
24
|
+
.version(packageJson.version);
|
|
25
|
+
|
|
26
|
+
// Default command (no arguments) - Create new project
|
|
27
|
+
program
|
|
28
|
+
.action(() => {
|
|
29
|
+
createProject();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// List all projects
|
|
33
|
+
program
|
|
34
|
+
.command('list')
|
|
35
|
+
.alias('ls')
|
|
36
|
+
.description('List all tracked projects')
|
|
37
|
+
.action(() => {
|
|
38
|
+
listCommand();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Handle errors
|
|
42
|
+
program.exitOverride();
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
program.parse(process.argv);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code === 'commander.help') {
|
|
48
|
+
// Help was displayed, exit normally
|
|
49
|
+
process.exit(0);
|
|
50
|
+
} else if (error.code === 'commander.version') {
|
|
51
|
+
// Version was displayed, exit normally
|
|
52
|
+
process.exit(0);
|
|
53
|
+
} else {
|
|
54
|
+
// logger.error(`Error: ${error.message}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projxo",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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
|
],
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"win32"
|
|
55
56
|
],
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"inquirer": "^8.2.6"
|
|
58
|
+
"inquirer": "^8.2.6",
|
|
59
|
+
"commander": "^11.1.0"
|
|
58
60
|
}
|
|
59
61
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
// Update project with IDE preference
|
|
65
|
+
const { getProjectByPath, updateProject } = require('./storage/projects');
|
|
66
|
+
const project = getProjectByPath(projectPath);
|
|
67
|
+
if (project) {
|
|
68
|
+
updateProject(project.id, { ide: answers.selectedIDE });
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// Show quick access info
|
|
72
|
+
logger.log('\nQuick access:', 'dim');
|
|
73
|
+
logger.log(` pxo list`, 'cyan');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
handleError(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Display welcome banner
|
|
83
|
+
*/
|
|
84
|
+
function displayBanner() {
|
|
85
|
+
logger.newLine();
|
|
86
|
+
logger.log('═══════════════════════════════════════════════════', 'bright');
|
|
87
|
+
logger.log(' ProjXO - Quick Project Setup', 'brightCyan');
|
|
88
|
+
logger.log('═══════════════════════════════════════════════════', 'bright');
|
|
89
|
+
logger.newLine();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get user inputs through interactive prompts
|
|
94
|
+
* @returns {Promise<Object>} User answers
|
|
95
|
+
*/
|
|
96
|
+
async function getUserInputs() {
|
|
97
|
+
const defaultDirectory = process.cwd();
|
|
98
|
+
|
|
99
|
+
// Get project type
|
|
100
|
+
const { projectType } = await inquirer.prompt([getProjectTypePrompt()]);
|
|
101
|
+
|
|
102
|
+
const config = getProjectType(projectType);
|
|
103
|
+
logger.success(`Selected: ${config.name}`);
|
|
104
|
+
logger.newLine();
|
|
105
|
+
|
|
106
|
+
// Get project name
|
|
107
|
+
const { projectName } = await inquirer.prompt([getProjectNamePrompt()]);
|
|
108
|
+
|
|
109
|
+
// Get directory
|
|
110
|
+
const { directory } = await inquirer.prompt([getDirectoryPrompt(defaultDirectory)]);
|
|
111
|
+
|
|
112
|
+
// Get IDE choice
|
|
113
|
+
const { selectedIDE } = await inquirer.prompt([getIDEPrompt()]);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
projectType,
|
|
117
|
+
projectName,
|
|
118
|
+
directory,
|
|
119
|
+
selectedIDE
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle existing project scenario
|
|
125
|
+
* @param {Object} answers - User answers containing projectName and directory
|
|
126
|
+
* @returns {Promise<boolean>} True if should proceed, false if cancelled
|
|
127
|
+
*/
|
|
128
|
+
async function handleExistingProject({ projectName, directory }) {
|
|
129
|
+
const { expandHomePath, getProjectPath } = require('./utils/fileSystem');
|
|
130
|
+
const expandedDir = expandHomePath(directory);
|
|
131
|
+
const fullPath = getProjectPath(expandedDir, projectName);
|
|
132
|
+
|
|
133
|
+
if (pathExists(fullPath)) {
|
|
134
|
+
logger.warning(`Project "${projectName}" already exists at: ${fullPath}`);
|
|
135
|
+
|
|
136
|
+
const { overwrite } = await inquirer.prompt([getOverwritePrompt(projectName)]);
|
|
137
|
+
|
|
138
|
+
if (!overwrite) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
logger.info('Proceeding with overwrite...');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Handle errors gracefully
|
|
150
|
+
* @param {Error} error - Error object
|
|
151
|
+
*/
|
|
152
|
+
function handleError(error) {
|
|
153
|
+
logger.newLine();
|
|
154
|
+
|
|
155
|
+
if (error.isTtyError) {
|
|
156
|
+
logger.error('Prompt could not be rendered in this environment');
|
|
157
|
+
logger.info('Please ensure you are running in an interactive terminal');
|
|
158
|
+
} else if (error.message === 'PROJECT_EXISTS') {
|
|
159
|
+
// This shouldn't happen as we handle it, but just in case
|
|
160
|
+
logger.error('Project already exists');
|
|
161
|
+
} else {
|
|
162
|
+
logger.error(`An error occurred: ${error.message}`);
|
|
163
|
+
|
|
164
|
+
// Show stack trace in debug mode
|
|
165
|
+
if (process.env.DEBUG) {
|
|
166
|
+
console.error(error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
logger.newLine();
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle graceful shutdown on SIGINT (Ctrl+C)
|
|
176
|
+
*/
|
|
177
|
+
process.on('SIGINT', () => {
|
|
178
|
+
logger.newLine();
|
|
179
|
+
logger.warning('Operation cancelled by user');
|
|
180
|
+
logger.newLine();
|
|
181
|
+
process.exit(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
module.exports = { run };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List command - Show all tracked projects
|
|
3
|
+
* Usage: pxo list
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { getAllProjects, touchProject, deleteProject } = require('../storage/projects');
|
|
9
|
+
const { openInIDE } = require('../handlers/ideOpener');
|
|
10
|
+
const { getIDE } = require('../config/ides');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format relative time
|
|
15
|
+
* @param {string} isoDate - ISO date string
|
|
16
|
+
* @returns {string} Human-readable relative time
|
|
17
|
+
*/
|
|
18
|
+
function formatRelativeTime(isoDate) {
|
|
19
|
+
const date = new Date(isoDate);
|
|
20
|
+
const now = new Date();
|
|
21
|
+
const diffMs = now - date;
|
|
22
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
23
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
24
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
25
|
+
|
|
26
|
+
if (diffMins < 1) return 'just now';
|
|
27
|
+
if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
|
|
28
|
+
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
29
|
+
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
30
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)} week${Math.floor(diffDays / 7) > 1 ? 's' : ''} ago`;
|
|
31
|
+
return `${Math.floor(diffDays / 30)} month${Math.floor(diffDays / 30) > 1 ? 's' : ''} ago`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get project type display name
|
|
36
|
+
* @param {string} type - Project type key
|
|
37
|
+
* @returns {string} Formatted display name
|
|
38
|
+
*/
|
|
39
|
+
function getTypeDisplay(type) {
|
|
40
|
+
const typeMap = {
|
|
41
|
+
'react-vite': 'React+Vite',
|
|
42
|
+
'react-vite-ts': 'React+Vite(TS)',
|
|
43
|
+
'nextjs': 'Next.js',
|
|
44
|
+
'angular': 'Angular',
|
|
45
|
+
'react-native': 'React Native'
|
|
46
|
+
};
|
|
47
|
+
return typeMap[type] || type;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute list command
|
|
52
|
+
*/
|
|
53
|
+
async function listCommand() {
|
|
54
|
+
try {
|
|
55
|
+
const projects = getAllProjects();
|
|
56
|
+
|
|
57
|
+
if (projects.length === 0) {
|
|
58
|
+
logger.info('No projects found');
|
|
59
|
+
logger.log('\nCreate your first project with:', 'dim');
|
|
60
|
+
logger.log(' pxo', 'cyan');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
logger.newLine();
|
|
65
|
+
logger.log(`📦 Your Projects (${projects.length})`, 'bright');
|
|
66
|
+
logger.newLine();
|
|
67
|
+
|
|
68
|
+
// Create choices for inquirer
|
|
69
|
+
const choices = projects.map(project => {
|
|
70
|
+
const typeDisplay = getTypeDisplay(project.type);
|
|
71
|
+
const timeAgo = formatRelativeTime(project.lastAccessed);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name: `${project.name} │ ${typeDisplay} │ ${timeAgo}`,
|
|
75
|
+
value: project.id,
|
|
76
|
+
short: project.name
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Add separator and action options
|
|
81
|
+
choices.push(
|
|
82
|
+
new inquirer.Separator(),
|
|
83
|
+
{ name: '← Back', value: 'back' }
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const { selectedId } = await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'selectedId',
|
|
90
|
+
message: 'Select a project:',
|
|
91
|
+
choices,
|
|
92
|
+
pageSize: 15
|
|
93
|
+
}
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
if (selectedId === 'back') {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Show actions for selected project
|
|
101
|
+
await showProjectActions(selectedId);
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.isTtyError) {
|
|
105
|
+
logger.error('This command requires an interactive terminal');
|
|
106
|
+
} else {
|
|
107
|
+
logger.error(`Failed to list projects: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Show actions for a selected project
|
|
114
|
+
* @param {string} projectId - Project ID
|
|
115
|
+
*/
|
|
116
|
+
async function showProjectActions(projectId) {
|
|
117
|
+
const { getProjectById } = require('../storage/projects');
|
|
118
|
+
const project = getProjectById(projectId);
|
|
119
|
+
|
|
120
|
+
if (!project) {
|
|
121
|
+
logger.error('Project not found');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { action } = await inquirer.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'list',
|
|
128
|
+
name: 'action',
|
|
129
|
+
message: `Actions for "${project.name}":`,
|
|
130
|
+
choices: [
|
|
131
|
+
{ name: '📂 Open in IDE', value: 'open' },
|
|
132
|
+
{ name: '📋 Copy path', value: 'copy' },
|
|
133
|
+
{ name: '🗑️ Remove from tracking', value: 'delete' },
|
|
134
|
+
{ name: 'ℹ️ Show details', value: 'details' },
|
|
135
|
+
new inquirer.Separator(),
|
|
136
|
+
{ name: '← Back to list', value: 'back' }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
switch (action) {
|
|
142
|
+
case 'open':
|
|
143
|
+
await handleOpenProject(project);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'copy':
|
|
147
|
+
handleCopyPath(project);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case 'delete':
|
|
151
|
+
await handleDeleteProject(project);
|
|
152
|
+
await listCommand(); // Refresh list
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case 'details':
|
|
156
|
+
showProjectDetails(project);
|
|
157
|
+
await showProjectActions(projectId); // Show actions again
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'back':
|
|
161
|
+
await listCommand(); // Go back to list
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Handle opening project in IDE
|
|
168
|
+
* @param {Object} project - Project object
|
|
169
|
+
*/
|
|
170
|
+
async function handleOpenProject(project) {
|
|
171
|
+
// Update last accessed time
|
|
172
|
+
touchProject(project.id);
|
|
173
|
+
|
|
174
|
+
// Use project's preferred IDE or prompt
|
|
175
|
+
let ideKey = project.ide;
|
|
176
|
+
|
|
177
|
+
if (!ideKey || ideKey === 'skip') {
|
|
178
|
+
const { getIDEChoices } = require('../config/ides');
|
|
179
|
+
const { selectedIDE } = await inquirer.prompt([
|
|
180
|
+
{
|
|
181
|
+
type: 'list',
|
|
182
|
+
name: 'selectedIDE',
|
|
183
|
+
message: 'Select IDE:',
|
|
184
|
+
choices: getIDEChoices()
|
|
185
|
+
}
|
|
186
|
+
]);
|
|
187
|
+
ideKey = selectedIDE;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (ideKey !== 'skip') {
|
|
191
|
+
const success = await openInIDE(project.path, ideKey);
|
|
192
|
+
if (success) {
|
|
193
|
+
logger.success(`Opened ${project.name}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Handle copying project path
|
|
200
|
+
* @param {Object} project - Project object
|
|
201
|
+
*/
|
|
202
|
+
function handleCopyPath(project) {
|
|
203
|
+
// For now, just display the path
|
|
204
|
+
// In future, could use clipboard library
|
|
205
|
+
logger.info('Project path:');
|
|
206
|
+
logger.log(` ${project.path}`, 'cyan');
|
|
207
|
+
logger.log('\n(Copy from above)', 'dim');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Handle deleting project from tracking
|
|
212
|
+
* @param {Object} project - Project object
|
|
213
|
+
*/
|
|
214
|
+
async function handleDeleteProject(project) {
|
|
215
|
+
const { confirm } = await inquirer.prompt([
|
|
216
|
+
{
|
|
217
|
+
type: 'confirm',
|
|
218
|
+
name: 'confirm',
|
|
219
|
+
message: `Remove "${project.name}" from tracking? (Files won't be deleted)`,
|
|
220
|
+
default: false
|
|
221
|
+
}
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
if (confirm) {
|
|
225
|
+
deleteProject(project.id);
|
|
226
|
+
logger.success(`Removed ${project.name} from tracking`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Show detailed project information
|
|
232
|
+
* @param {Object} project - Project object
|
|
233
|
+
*/
|
|
234
|
+
function showProjectDetails(project) {
|
|
235
|
+
logger.newLine();
|
|
236
|
+
logger.log('━'.repeat(50), 'dim');
|
|
237
|
+
logger.log(` ${project.name}`, 'bright');
|
|
238
|
+
logger.log('━'.repeat(50), 'dim');
|
|
239
|
+
logger.log(` Type: ${getTypeDisplay(project.type)}`, 'cyan');
|
|
240
|
+
logger.log(` Path: ${project.path}`, 'dim');
|
|
241
|
+
logger.log(` Created: ${new Date(project.createdAt).toLocaleString()}`, 'dim');
|
|
242
|
+
logger.log(` Last accessed: ${formatRelativeTime(project.lastAccessed)}`, 'dim');
|
|
243
|
+
if (project.ide) {
|
|
244
|
+
const ide = getIDE(project.ide);
|
|
245
|
+
logger.log(` Default IDE: ${ide?.name || project.ide}`, 'dim');
|
|
246
|
+
}
|
|
247
|
+
logger.log('━'.repeat(50), 'dim');
|
|
248
|
+
logger.newLine();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = { listCommand };
|
|
@@ -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
|
+
};
|