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
|
@@ -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,151 @@
|
|
|
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
|
+
// Add project to tracking database
|
|
63
|
+
const { addProject } = require('../storage/projects');
|
|
64
|
+
try {
|
|
65
|
+
addProject({
|
|
66
|
+
name: projectName,
|
|
67
|
+
path: fullPath,
|
|
68
|
+
type: projectType,
|
|
69
|
+
ide: null // Will be set when opened
|
|
70
|
+
});
|
|
71
|
+
logger.success('Project added to ProjXO tracking');
|
|
72
|
+
} catch (dbError) {
|
|
73
|
+
// Don't fail project creation if database save fails
|
|
74
|
+
logger.error(`Failed to add project to tracking database: ${dbError.message}`);
|
|
75
|
+
if (process.env.DEBUG) {
|
|
76
|
+
console.error(dbError);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return fullPath;
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Provide helpful error message
|
|
84
|
+
throw new Error(`Failed to create project: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Display project creation success message with next steps
|
|
90
|
+
* @param {string} projectPath - Full path to the created project
|
|
91
|
+
* @param {string} projectType - Project type key
|
|
92
|
+
*/
|
|
93
|
+
function displaySuccessMessage(projectPath, projectType) {
|
|
94
|
+
logger.section('✓ Project created successfully!');
|
|
95
|
+
|
|
96
|
+
logger.log('Project location:', 'bright');
|
|
97
|
+
logger.log(` ${projectPath}`, 'cyan');
|
|
98
|
+
|
|
99
|
+
logger.newLine();
|
|
100
|
+
logger.log('Next steps:', 'bright');
|
|
101
|
+
|
|
102
|
+
const steps = getNextSteps(projectType, projectPath);
|
|
103
|
+
steps.forEach(step => {
|
|
104
|
+
logger.log(` ${step}`, 'green');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
logger.newLine();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Validate project creation parameters
|
|
112
|
+
* @param {Object} params - Parameters to validate
|
|
113
|
+
* @param {string} params.projectType - Project type key
|
|
114
|
+
* @param {string} params.projectName - Project name
|
|
115
|
+
* @param {string} params.directory - Directory path
|
|
116
|
+
* @returns {Object} { valid: boolean, error?: string }
|
|
117
|
+
*/
|
|
118
|
+
function validateProjectParams({ projectType, projectName, directory }) {
|
|
119
|
+
// Validate project type
|
|
120
|
+
const config = getProjectType(projectType);
|
|
121
|
+
if (!config) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
error: `Invalid project type: ${projectType}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Validate project name
|
|
129
|
+
if (!projectName || projectName.trim() === '') {
|
|
130
|
+
return {
|
|
131
|
+
valid: false,
|
|
132
|
+
error: 'Project name is required'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Validate directory
|
|
137
|
+
if (!directory || directory.trim() === '') {
|
|
138
|
+
return {
|
|
139
|
+
valid: false,
|
|
140
|
+
error: 'Directory is required'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { valid: true };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
createProject,
|
|
149
|
+
displaySuccessMessage,
|
|
150
|
+
validateProjectParams
|
|
151
|
+
};
|
|
@@ -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,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database utility for ProjXO
|
|
3
|
+
* Handles low-level file operations for project tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Storage implementation using JSON files.
|
|
8
|
+
*
|
|
9
|
+
* Future consideration: Migrate to SQLite for better performance
|
|
10
|
+
* at scale (1000+ projects). Node.js includes sqlite module
|
|
11
|
+
* since v22.5.0, making it dependency-free.
|
|
12
|
+
*
|
|
13
|
+
* Reasons for current JSON approach:
|
|
14
|
+
* - Human-readable and editable
|
|
15
|
+
* - Zero dependencies
|
|
16
|
+
* - Sufficient performance for typical use
|
|
17
|
+
* - Easier debugging and backup
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
|
|
24
|
+
// Storage directory in user's home
|
|
25
|
+
const STORAGE_DIR = path.join(os.homedir(), '.projxo');
|
|
26
|
+
const PROJECTS_FILE = path.join(STORAGE_DIR, 'projects.json');
|
|
27
|
+
const CONFIG_FILE = path.join(STORAGE_DIR, 'config.json');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize storage directory and files
|
|
31
|
+
* Creates directory and empty files if they don't exist
|
|
32
|
+
*/
|
|
33
|
+
function initializeStorage() {
|
|
34
|
+
// Create storage directory if it doesn't exist
|
|
35
|
+
if (!fs.existsSync(STORAGE_DIR)) {
|
|
36
|
+
fs.mkdirSync(STORAGE_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create projects file if it doesn't exist
|
|
40
|
+
if (!fs.existsSync(PROJECTS_FILE)) {
|
|
41
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify({ projects: [] }, null, 2), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create config file if it doesn't exist
|
|
45
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
46
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify({
|
|
47
|
+
version: '1.0.0',
|
|
48
|
+
defaultIDE: null,
|
|
49
|
+
lastSync: null
|
|
50
|
+
}, null, 2), 'utf8');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read projects database
|
|
56
|
+
* @returns {Object} Projects data object
|
|
57
|
+
*/
|
|
58
|
+
function readProjects() {
|
|
59
|
+
try {
|
|
60
|
+
initializeStorage();
|
|
61
|
+
const data = fs.readFileSync(PROJECTS_FILE, 'utf8');
|
|
62
|
+
return JSON.parse(data);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// If file is corrupted, return empty structure
|
|
65
|
+
console.warn('Failed to read projects database, initializing new one');
|
|
66
|
+
return { projects: [] };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Write projects database
|
|
72
|
+
* @param {Object} data - Projects data object
|
|
73
|
+
*/
|
|
74
|
+
function writeProjects(data) {
|
|
75
|
+
try {
|
|
76
|
+
initializeStorage();
|
|
77
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`Failed to write projects database: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read config
|
|
85
|
+
* @returns {Object} Config data object
|
|
86
|
+
*/
|
|
87
|
+
function readConfig() {
|
|
88
|
+
try {
|
|
89
|
+
initializeStorage();
|
|
90
|
+
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
91
|
+
return JSON.parse(data);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn('Failed to read config, initializing new one');
|
|
94
|
+
return { version: '1.0.0', defaultIDE: null };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Write config
|
|
100
|
+
* @param {Object} data - Config data object
|
|
101
|
+
*/
|
|
102
|
+
function writeConfig(data) {
|
|
103
|
+
try {
|
|
104
|
+
initializeStorage();
|
|
105
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new Error(`Failed to write config: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get storage directory path
|
|
113
|
+
* @returns {string} Full path to storage directory
|
|
114
|
+
*/
|
|
115
|
+
function getStorageDir() {
|
|
116
|
+
return STORAGE_DIR;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Backup database files
|
|
121
|
+
* Creates timestamped backups
|
|
122
|
+
* @returns {string} Backup directory path
|
|
123
|
+
*/
|
|
124
|
+
function backupDatabase() {
|
|
125
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
126
|
+
const backupDir = path.join(STORAGE_DIR, 'backups', timestamp);
|
|
127
|
+
|
|
128
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
129
|
+
|
|
130
|
+
if (fs.existsSync(PROJECTS_FILE)) {
|
|
131
|
+
fs.copyFileSync(PROJECTS_FILE, path.join(backupDir, 'projects.json'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
135
|
+
fs.copyFileSync(CONFIG_FILE, path.join(backupDir, 'config.json'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return backupDir;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
initializeStorage,
|
|
143
|
+
readProjects,
|
|
144
|
+
writeProjects,
|
|
145
|
+
readConfig,
|
|
146
|
+
writeConfig,
|
|
147
|
+
getStorageDir,
|
|
148
|
+
backupDatabase
|
|
149
|
+
};
|