projxo 1.0.2 → 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,273 @@
1
+ /**
2
+ * Projects storage management
3
+ * CRUD operations for project tracking
4
+ */
5
+
6
+ const { randomUUID } = require('crypto');
7
+ const fs = require('fs');
8
+ const { readProjects, writeProjects } = require('./database');
9
+
10
+ /**
11
+ * Add a new project to tracking
12
+ * @param {Object} projectData - Project information
13
+ * @param {string} projectData.name - Project name
14
+ * @param {string} projectData.path - Full path to project
15
+ * @param {string} projectData.type - Project type (react-vite, nextjs, etc.)
16
+ * @param {string} [projectData.ide] - IDE used to open
17
+ * @returns {Object} Created project object with ID
18
+ */
19
+ function addProject({ name, path, type, ide = null }) {
20
+ const data = readProjects();
21
+
22
+ // Check if project already exists
23
+ const existing = data.projects.find(p => p.path === path);
24
+ if (existing) {
25
+ // Update existing project
26
+ existing.lastAccessed = new Date().toISOString();
27
+ existing.type = type;
28
+ if (ide) existing.ide = ide;
29
+ writeProjects(data);
30
+ return existing;
31
+ }
32
+
33
+ // Create new project entry
34
+ const project = {
35
+ id: randomUUID(),
36
+ name,
37
+ path,
38
+ type,
39
+ createdAt: new Date().toISOString(),
40
+ lastAccessed: new Date().toISOString(),
41
+ ide: ide || null,
42
+ bookmarked: false,
43
+ tags: []
44
+ };
45
+
46
+ data.projects.push(project);
47
+ writeProjects(data);
48
+
49
+ return project;
50
+ }
51
+
52
+ /**
53
+ * Get all projects
54
+ * @param {Object} options - Filter options
55
+ * @param {boolean} [options.bookmarkedOnly] - Return only bookmarked projects
56
+ * @param {string} [options.type] - Filter by project type
57
+ * @returns {Array} Array of project objects
58
+ */
59
+ function getAllProjects({ bookmarkedOnly = false, type = null } = {}) {
60
+ const data = readProjects();
61
+ let projects = data.projects;
62
+
63
+ // Filter bookmarked
64
+ if (bookmarkedOnly) {
65
+ projects = projects.filter(p => p.bookmarked);
66
+ }
67
+
68
+ // Filter by type
69
+ if (type) {
70
+ projects = projects.filter(p => p.type === type);
71
+ }
72
+
73
+ // Sort by last accessed (most recent first)
74
+ projects.sort((a, b) =>
75
+ new Date(b.lastAccessed) - new Date(a.lastAccessed)
76
+ );
77
+
78
+ return projects;
79
+ }
80
+
81
+ /**
82
+ * Get project by name (fuzzy match)
83
+ * @param {string} name - Project name to search for
84
+ * @returns {Object|null} Project object or null if not found
85
+ */
86
+ function getProjectByName(name) {
87
+ const data = readProjects();
88
+
89
+ // Try exact match first
90
+ let project = data.projects.find(p =>
91
+ p.name.toLowerCase() === name.toLowerCase()
92
+ );
93
+
94
+ if (project) return project;
95
+
96
+ // Try partial match
97
+ project = data.projects.find(p =>
98
+ p.name.toLowerCase().includes(name.toLowerCase())
99
+ );
100
+
101
+ return project || null;
102
+ }
103
+
104
+ /**
105
+ * Get project by ID
106
+ * @param {string} id - Project ID
107
+ * @returns {Object|null} Project object or null if not found
108
+ */
109
+ function getProjectById(id) {
110
+ const data = readProjects();
111
+ return data.projects.find(p => p.id === id) || null;
112
+ }
113
+
114
+ /**
115
+ * Get project by path
116
+ * @param {string} path - Project path
117
+ * @returns {Object|null} Project object or null if not found
118
+ */
119
+ function getProjectByPath(path) {
120
+ const data = readProjects();
121
+ return data.projects.find(p => p.path === path) || null;
122
+ }
123
+
124
+ /**
125
+ * Update project
126
+ * @param {string} id - Project ID
127
+ * @param {Object} updates - Fields to update
128
+ * @returns {Object|null} Updated project or null if not found
129
+ */
130
+ function updateProject(id, updates) {
131
+ const data = readProjects();
132
+ const project = data.projects.find(p => p.id === id);
133
+
134
+ if (!project) return null;
135
+
136
+ // Update fields
137
+ Object.assign(project, updates);
138
+ project.lastAccessed = new Date().toISOString();
139
+
140
+ writeProjects(data);
141
+ return project;
142
+ }
143
+
144
+ /**
145
+ * Update project's last accessed time
146
+ * @param {string} id - Project ID
147
+ */
148
+ function touchProject(id) {
149
+ updateProject(id, { lastAccessed: new Date().toISOString() });
150
+ }
151
+
152
+ /**
153
+ * Delete project from tracking
154
+ * @param {string} id - Project ID
155
+ * @returns {boolean} True if deleted, false if not found
156
+ */
157
+ function deleteProject(id) {
158
+ const data = readProjects();
159
+ const index = data.projects.findIndex(p => p.id === id);
160
+
161
+ if (index === -1) return false;
162
+
163
+ data.projects.splice(index, 1);
164
+ writeProjects(data);
165
+
166
+ return true;
167
+ }
168
+
169
+ /**
170
+ * Get recently accessed projects
171
+ * @param {number} limit - Maximum number of projects to return
172
+ * @returns {Array} Array of recent project objects
173
+ */
174
+ function getRecentProjects(limit = 10) {
175
+ const data = readProjects();
176
+
177
+ return data.projects
178
+ .sort((a, b) => new Date(b.lastAccessed) - new Date(a.lastAccessed))
179
+ .slice(0, limit);
180
+ }
181
+
182
+ /**
183
+ * Search projects by query
184
+ * @param {string} query - Search query
185
+ * @returns {Array} Array of matching project objects
186
+ */
187
+ function searchProjects(query) {
188
+ const data = readProjects();
189
+ const lowerQuery = query.toLowerCase();
190
+
191
+ return data.projects.filter(p =>
192
+ p.name.toLowerCase().includes(lowerQuery) ||
193
+ p.type.toLowerCase().includes(lowerQuery) ||
194
+ p.path.toLowerCase().includes(lowerQuery) ||
195
+ (p.tags && p.tags.some(tag => tag.toLowerCase().includes(lowerQuery)))
196
+ );
197
+ }
198
+
199
+ /**
200
+ * Clean projects that no longer exist on disk
201
+ * @returns {Array} Array of removed project paths
202
+ */
203
+ function cleanProjects() {
204
+ const data = readProjects();
205
+ const removed = [];
206
+
207
+ data.projects = data.projects.filter(p => {
208
+ if (!fs.existsSync(p.path)) {
209
+ removed.push(p.path);
210
+ return false;
211
+ }
212
+ return true;
213
+ });
214
+
215
+ if (removed.length > 0) {
216
+ writeProjects(data);
217
+ }
218
+
219
+ return removed;
220
+ }
221
+
222
+ /**
223
+ * Get project statistics
224
+ * @returns {Object} Statistics object
225
+ */
226
+ function getProjectStats() {
227
+ const data = readProjects();
228
+
229
+ // Count by type
230
+ const byType = {};
231
+ data.projects.forEach(p => {
232
+ byType[p.type] = (byType[p.type] || 0) + 1;
233
+ });
234
+
235
+ // Most used IDE
236
+ const ideCount = {};
237
+ data.projects.forEach(p => {
238
+ if (p.ide) {
239
+ ideCount[p.ide] = (ideCount[p.ide] || 0) + 1;
240
+ }
241
+ });
242
+
243
+ const mostUsedIDE = Object.entries(ideCount)
244
+ .sort((a, b) => b[1] - a[1])[0]?.[0] || 'None';
245
+
246
+ return {
247
+ total: data.projects.length,
248
+ bookmarked: data.projects.filter(p => p.bookmarked).length,
249
+ byType,
250
+ mostUsedIDE,
251
+ oldest: data.projects.sort((a, b) =>
252
+ new Date(a.createdAt) - new Date(b.createdAt)
253
+ )[0],
254
+ newest: data.projects.sort((a, b) =>
255
+ new Date(b.createdAt) - new Date(a.createdAt)
256
+ )[0]
257
+ };
258
+ }
259
+
260
+ module.exports = {
261
+ addProject,
262
+ getAllProjects,
263
+ getProjectByName,
264
+ getProjectById,
265
+ getProjectByPath,
266
+ updateProject,
267
+ touchProject,
268
+ deleteProject,
269
+ getRecentProjects,
270
+ searchProjects,
271
+ cleanProjects,
272
+ getProjectStats
273
+ };