ccmanager 2.8.0 → 2.9.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/dist/cli.test.js +13 -2
- package/dist/components/App.js +125 -50
- package/dist/components/App.test.js +270 -0
- package/dist/components/ConfigureShortcuts.js +82 -8
- package/dist/components/DeleteWorktree.js +39 -5
- package/dist/components/DeleteWorktree.test.d.ts +1 -0
- package/dist/components/DeleteWorktree.test.js +128 -0
- package/dist/components/LoadingSpinner.d.ts +8 -0
- package/dist/components/LoadingSpinner.js +37 -0
- package/dist/components/LoadingSpinner.test.d.ts +1 -0
- package/dist/components/LoadingSpinner.test.js +187 -0
- package/dist/components/Menu.js +64 -16
- package/dist/components/Menu.recent-projects.test.js +32 -11
- package/dist/components/Menu.test.js +136 -4
- package/dist/components/MergeWorktree.js +79 -18
- package/dist/components/MergeWorktree.test.d.ts +1 -0
- package/dist/components/MergeWorktree.test.js +227 -0
- package/dist/components/NewWorktree.js +88 -9
- package/dist/components/NewWorktree.test.d.ts +1 -0
- package/dist/components/NewWorktree.test.js +244 -0
- package/dist/components/ProjectList.js +44 -13
- package/dist/components/ProjectList.recent-projects.test.js +8 -3
- package/dist/components/ProjectList.test.js +105 -8
- package/dist/components/RemoteBranchSelector.test.js +3 -1
- package/dist/hooks/useGitStatus.d.ts +11 -0
- package/dist/hooks/useGitStatus.js +70 -12
- package/dist/hooks/useGitStatus.test.js +30 -23
- package/dist/services/configurationManager.d.ts +75 -0
- package/dist/services/configurationManager.effect.test.d.ts +1 -0
- package/dist/services/configurationManager.effect.test.js +407 -0
- package/dist/services/configurationManager.js +246 -0
- package/dist/services/globalSessionOrchestrator.test.js +0 -8
- package/dist/services/projectManager.d.ts +98 -2
- package/dist/services/projectManager.js +228 -59
- package/dist/services/projectManager.test.js +242 -2
- package/dist/services/sessionManager.d.ts +44 -2
- package/dist/services/sessionManager.effect.test.d.ts +1 -0
- package/dist/services/sessionManager.effect.test.js +321 -0
- package/dist/services/sessionManager.js +216 -65
- package/dist/services/sessionManager.statePersistence.test.js +18 -9
- package/dist/services/sessionManager.test.js +40 -36
- package/dist/services/worktreeService.d.ts +356 -26
- package/dist/services/worktreeService.js +793 -353
- package/dist/services/worktreeService.test.js +294 -313
- package/dist/types/errors.d.ts +74 -0
- package/dist/types/errors.js +31 -0
- package/dist/types/errors.test.d.ts +1 -0
- package/dist/types/errors.test.js +201 -0
- package/dist/types/index.d.ts +5 -17
- package/dist/utils/claudeDir.d.ts +58 -6
- package/dist/utils/claudeDir.js +103 -8
- package/dist/utils/claudeDir.test.d.ts +1 -0
- package/dist/utils/claudeDir.test.js +108 -0
- package/dist/utils/concurrencyLimit.d.ts +5 -0
- package/dist/utils/concurrencyLimit.js +11 -0
- package/dist/utils/concurrencyLimit.test.js +40 -1
- package/dist/utils/gitStatus.d.ts +36 -8
- package/dist/utils/gitStatus.js +170 -88
- package/dist/utils/gitStatus.test.js +12 -9
- package/dist/utils/hookExecutor.d.ts +41 -6
- package/dist/utils/hookExecutor.js +75 -32
- package/dist/utils/hookExecutor.test.js +73 -20
- package/dist/utils/terminalCapabilities.d.ts +18 -0
- package/dist/utils/terminalCapabilities.js +81 -0
- package/dist/utils/terminalCapabilities.test.d.ts +1 -0
- package/dist/utils/terminalCapabilities.test.js +104 -0
- package/dist/utils/testHelpers.d.ts +106 -0
- package/dist/utils/testHelpers.js +153 -0
- package/dist/utils/testHelpers.test.d.ts +1 -0
- package/dist/utils/testHelpers.test.js +114 -0
- package/dist/utils/worktreeConfig.d.ts +77 -2
- package/dist/utils/worktreeConfig.js +156 -16
- package/dist/utils/worktreeConfig.test.d.ts +1 -0
- package/dist/utils/worktreeConfig.test.js +39 -0
- package/package.json +4 -4
- package/dist/integration-tests/devcontainer.integration.test.js +0 -101
- /package/dist/{integration-tests/devcontainer.integration.test.d.ts → components/App.test.d.ts} +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { GitProject, IProjectManager, IWorktreeService, MenuMode, RecentProject } from '../types/index.js';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { FileSystemError, ConfigError } from '../types/errors.js';
|
|
2
4
|
export declare class ProjectManager implements IProjectManager {
|
|
3
5
|
currentMode: MenuMode;
|
|
4
6
|
currentProject?: GitProject;
|
|
@@ -15,7 +17,6 @@ export declare class ProjectManager implements IProjectManager {
|
|
|
15
17
|
setMode(mode: MenuMode): void;
|
|
16
18
|
selectProject(project: GitProject): void;
|
|
17
19
|
getWorktreeService(projectPath?: string): IWorktreeService;
|
|
18
|
-
refreshProjects(): Promise<void>;
|
|
19
20
|
isMultiProjectEnabled(): boolean;
|
|
20
21
|
getProjectsDir(): string | undefined;
|
|
21
22
|
getCurrentProjectPath(): string;
|
|
@@ -26,7 +27,6 @@ export declare class ProjectManager implements IProjectManager {
|
|
|
26
27
|
getRecentProjects(limit?: number): RecentProject[];
|
|
27
28
|
addRecentProject(project: GitProject): void;
|
|
28
29
|
clearRecentProjects(): void;
|
|
29
|
-
discoverProjects(projectsDir: string): Promise<GitProject[]>;
|
|
30
30
|
/**
|
|
31
31
|
* Fast directory discovery - similar to ghq's approach
|
|
32
32
|
*/
|
|
@@ -50,6 +50,102 @@ export declare class ProjectManager implements IProjectManager {
|
|
|
50
50
|
validateGitRepository(projectPath: string): Promise<boolean>;
|
|
51
51
|
getCachedProject(projectPath: string): GitProject | undefined;
|
|
52
52
|
refreshProject(projectPath: string): Promise<GitProject | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Discover Git projects in the specified directory using Effect
|
|
55
|
+
*
|
|
56
|
+
* Recursively scans the directory for Git repositories with parallel processing.
|
|
57
|
+
* Caches results for improved performance.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} projectsDir - Root directory to search for Git projects
|
|
60
|
+
* @returns {Effect.Effect<GitProject[], FileSystemError, never>} Effect containing discovered projects or FileSystemError
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import {Effect} from 'effect';
|
|
65
|
+
* import {projectManager} from './services/projectManager.js';
|
|
66
|
+
*
|
|
67
|
+
* // Discover projects with error handling
|
|
68
|
+
* const projects = await Effect.runPromise(
|
|
69
|
+
* Effect.catchAll(
|
|
70
|
+
* projectManager.instance.discoverProjectsEffect('/home/user/projects'),
|
|
71
|
+
* (error) => {
|
|
72
|
+
* console.error(`Discovery failed: ${error.cause}`);
|
|
73
|
+
* return Effect.succeed([]); // Return empty array on error
|
|
74
|
+
* }
|
|
75
|
+
* )
|
|
76
|
+
* );
|
|
77
|
+
*
|
|
78
|
+
* console.log(`Found ${projects.length} git repositories`);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
discoverProjectsEffect(projectsDir: string): Effect.Effect<GitProject[], FileSystemError, never>;
|
|
82
|
+
/**
|
|
83
|
+
* Load recent projects from cache using Effect
|
|
84
|
+
*
|
|
85
|
+
* Reads and parses the recent projects JSON file. Returns empty array if file doesn't exist.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Effect.Effect<RecentProject[], FileSystemError | ConfigError, never>} Effect containing recent projects or error
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* import {Effect} from 'effect';
|
|
92
|
+
* import {projectManager} from './services/projectManager.js';
|
|
93
|
+
*
|
|
94
|
+
* // Load recent projects with error handling
|
|
95
|
+
* const recent = await Effect.runPromise(
|
|
96
|
+
* Effect.match(
|
|
97
|
+
* projectManager.instance.loadRecentProjectsEffect(),
|
|
98
|
+
* {
|
|
99
|
+
* onFailure: (error) => {
|
|
100
|
+
* if (error._tag === 'ConfigError') {
|
|
101
|
+
* console.error(`Parse error: ${error.details}`);
|
|
102
|
+
* } else {
|
|
103
|
+
* console.error(`File error: ${error.cause}`);
|
|
104
|
+
* }
|
|
105
|
+
* return [];
|
|
106
|
+
* },
|
|
107
|
+
* onSuccess: (projects) => projects
|
|
108
|
+
* }
|
|
109
|
+
* )
|
|
110
|
+
* );
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
loadRecentProjectsEffect(): Effect.Effect<RecentProject[], FileSystemError | ConfigError, never>;
|
|
114
|
+
/**
|
|
115
|
+
* Save recent projects to cache using Effect
|
|
116
|
+
*
|
|
117
|
+
* Writes the recent projects array to JSON file.
|
|
118
|
+
*
|
|
119
|
+
* @param {RecentProject[]} projects - Recent projects to save
|
|
120
|
+
* @returns {Effect.Effect<void, FileSystemError, never>} Effect that succeeds or fails with FileSystemError
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* import {Effect} from 'effect';
|
|
125
|
+
* import {projectManager} from './services/projectManager.js';
|
|
126
|
+
*
|
|
127
|
+
* const recentProjects = [
|
|
128
|
+
* { path: '/home/user/project1', name: 'project1', lastAccessed: Date.now() }
|
|
129
|
+
* ];
|
|
130
|
+
*
|
|
131
|
+
* // Save with error recovery
|
|
132
|
+
* await Effect.runPromise(
|
|
133
|
+
* Effect.catchAll(
|
|
134
|
+
* projectManager.instance.saveRecentProjectsEffect(recentProjects),
|
|
135
|
+
* (error) => {
|
|
136
|
+
* console.error(`Failed to save: ${error.cause}`);
|
|
137
|
+
* return Effect.void; // Continue despite error
|
|
138
|
+
* }
|
|
139
|
+
* )
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
saveRecentProjectsEffect(projects: RecentProject[]): Effect.Effect<void, FileSystemError, never>;
|
|
144
|
+
/**
|
|
145
|
+
* Refresh projects list (Effect version)
|
|
146
|
+
* @returns Effect with void or FileSystemError
|
|
147
|
+
*/
|
|
148
|
+
refreshProjectsEffect(): Effect.Effect<void, FileSystemError, never>;
|
|
53
149
|
}
|
|
54
150
|
export declare const projectManager: {
|
|
55
151
|
readonly instance: ProjectManager;
|
|
@@ -4,6 +4,8 @@ import { promises as fs } from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { Effect } from 'effect';
|
|
8
|
+
import { FileSystemError, ConfigError } from '../types/errors.js';
|
|
7
9
|
export class ProjectManager {
|
|
8
10
|
constructor() {
|
|
9
11
|
Object.defineProperty(this, "currentMode", {
|
|
@@ -111,24 +113,6 @@ export class ProjectManager {
|
|
|
111
113
|
this.worktreeServiceCache.set(path, service);
|
|
112
114
|
return service;
|
|
113
115
|
}
|
|
114
|
-
async refreshProjects() {
|
|
115
|
-
if (!this.projectsDir) {
|
|
116
|
-
throw new Error('Projects directory not configured');
|
|
117
|
-
}
|
|
118
|
-
// Discover projects
|
|
119
|
-
this.projects = await this.discoverProjects(this.projectsDir);
|
|
120
|
-
// Update current project if it still exists
|
|
121
|
-
if (this.currentProject) {
|
|
122
|
-
const updatedProject = this.projects.find(p => p.path === this.currentProject.path);
|
|
123
|
-
if (updatedProject) {
|
|
124
|
-
this.currentProject = updatedProject;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
// Current project no longer exists
|
|
128
|
-
this.currentProject = undefined;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
116
|
// Helper methods
|
|
133
117
|
isMultiProjectEnabled() {
|
|
134
118
|
return !!process.env[ENV_VARS.MULTI_PROJECT_ROOT];
|
|
@@ -209,47 +193,6 @@ export class ProjectManager {
|
|
|
209
193
|
this.saveRecentProjects();
|
|
210
194
|
}
|
|
211
195
|
// Multi-project discovery methods
|
|
212
|
-
async discoverProjects(projectsDir) {
|
|
213
|
-
const projects = [];
|
|
214
|
-
const projectMap = new Map();
|
|
215
|
-
try {
|
|
216
|
-
// Verify the directory exists
|
|
217
|
-
await fs.access(projectsDir);
|
|
218
|
-
// Step 1: Fast concurrent directory discovery
|
|
219
|
-
const directories = await this.discoverDirectories(projectsDir);
|
|
220
|
-
// Step 2: Process directories in parallel to check if they're git repos
|
|
221
|
-
const results = await this.processDirectoriesInParallel(directories, projectsDir);
|
|
222
|
-
// Step 3: Create project objects (all results are valid git repos)
|
|
223
|
-
for (const result of results) {
|
|
224
|
-
// Handle name conflicts
|
|
225
|
-
let displayName = result.name;
|
|
226
|
-
if (projectMap.has(result.name)) {
|
|
227
|
-
displayName = result.relativePath.replace(/[\\/\\\\]/g, '/');
|
|
228
|
-
}
|
|
229
|
-
const project = {
|
|
230
|
-
name: displayName,
|
|
231
|
-
path: result.path,
|
|
232
|
-
relativePath: result.relativePath,
|
|
233
|
-
isValid: true,
|
|
234
|
-
error: result.error,
|
|
235
|
-
};
|
|
236
|
-
projectMap.set(displayName, project);
|
|
237
|
-
}
|
|
238
|
-
// Convert to array and sort
|
|
239
|
-
projects.push(...projectMap.values());
|
|
240
|
-
projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
241
|
-
// Cache results
|
|
242
|
-
this.projectCache.clear();
|
|
243
|
-
projects.forEach(p => this.projectCache.set(p.path, p));
|
|
244
|
-
return projects;
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
if (error.code === 'ENOENT') {
|
|
248
|
-
throw new Error(`Projects directory does not exist: ${projectsDir}`);
|
|
249
|
-
}
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
196
|
/**
|
|
254
197
|
* Fast directory discovery - similar to ghq's approach
|
|
255
198
|
*/
|
|
@@ -384,6 +327,232 @@ export class ProjectManager {
|
|
|
384
327
|
this.projectCache.set(projectPath, project);
|
|
385
328
|
return project;
|
|
386
329
|
}
|
|
330
|
+
// Effect-based API methods
|
|
331
|
+
/**
|
|
332
|
+
* Discover Git projects in the specified directory using Effect
|
|
333
|
+
*
|
|
334
|
+
* Recursively scans the directory for Git repositories with parallel processing.
|
|
335
|
+
* Caches results for improved performance.
|
|
336
|
+
*
|
|
337
|
+
* @param {string} projectsDir - Root directory to search for Git projects
|
|
338
|
+
* @returns {Effect.Effect<GitProject[], FileSystemError, never>} Effect containing discovered projects or FileSystemError
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* import {Effect} from 'effect';
|
|
343
|
+
* import {projectManager} from './services/projectManager.js';
|
|
344
|
+
*
|
|
345
|
+
* // Discover projects with error handling
|
|
346
|
+
* const projects = await Effect.runPromise(
|
|
347
|
+
* Effect.catchAll(
|
|
348
|
+
* projectManager.instance.discoverProjectsEffect('/home/user/projects'),
|
|
349
|
+
* (error) => {
|
|
350
|
+
* console.error(`Discovery failed: ${error.cause}`);
|
|
351
|
+
* return Effect.succeed([]); // Return empty array on error
|
|
352
|
+
* }
|
|
353
|
+
* )
|
|
354
|
+
* );
|
|
355
|
+
*
|
|
356
|
+
* console.log(`Found ${projects.length} git repositories`);
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
discoverProjectsEffect(projectsDir) {
|
|
360
|
+
return Effect.tryPromise({
|
|
361
|
+
try: async () => {
|
|
362
|
+
// Verify the directory exists
|
|
363
|
+
await fs.access(projectsDir);
|
|
364
|
+
// Step 1: Fast concurrent directory discovery
|
|
365
|
+
const directories = await this.discoverDirectories(projectsDir);
|
|
366
|
+
// Step 2: Process directories in parallel to check if they're git repos
|
|
367
|
+
const results = await this.processDirectoriesInParallel(directories, projectsDir);
|
|
368
|
+
// Step 3: Create project objects
|
|
369
|
+
const projects = [];
|
|
370
|
+
const projectMap = new Map();
|
|
371
|
+
for (const result of results) {
|
|
372
|
+
// Handle name conflicts
|
|
373
|
+
let displayName = result.name;
|
|
374
|
+
if (projectMap.has(result.name)) {
|
|
375
|
+
displayName = result.relativePath.replace(/[\\/\\\\]/g, '/');
|
|
376
|
+
}
|
|
377
|
+
const project = {
|
|
378
|
+
name: displayName,
|
|
379
|
+
path: result.path,
|
|
380
|
+
relativePath: result.relativePath,
|
|
381
|
+
isValid: true,
|
|
382
|
+
error: result.error,
|
|
383
|
+
};
|
|
384
|
+
projectMap.set(displayName, project);
|
|
385
|
+
}
|
|
386
|
+
// Convert to array and sort
|
|
387
|
+
projects.push(...projectMap.values());
|
|
388
|
+
projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
389
|
+
// Cache results
|
|
390
|
+
this.projectCache.clear();
|
|
391
|
+
projects.forEach(p => this.projectCache.set(p.path, p));
|
|
392
|
+
return projects;
|
|
393
|
+
},
|
|
394
|
+
catch: error => {
|
|
395
|
+
if (error instanceof FileSystemError) {
|
|
396
|
+
return error;
|
|
397
|
+
}
|
|
398
|
+
const nodeError = error;
|
|
399
|
+
const cause = nodeError.code === 'ENOENT'
|
|
400
|
+
? `Projects directory does not exist: ${projectsDir}`
|
|
401
|
+
: String(error);
|
|
402
|
+
return new FileSystemError({
|
|
403
|
+
operation: 'read',
|
|
404
|
+
path: projectsDir,
|
|
405
|
+
cause,
|
|
406
|
+
});
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Load recent projects from cache using Effect
|
|
412
|
+
*
|
|
413
|
+
* Reads and parses the recent projects JSON file. Returns empty array if file doesn't exist.
|
|
414
|
+
*
|
|
415
|
+
* @returns {Effect.Effect<RecentProject[], FileSystemError | ConfigError, never>} Effect containing recent projects or error
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```typescript
|
|
419
|
+
* import {Effect} from 'effect';
|
|
420
|
+
* import {projectManager} from './services/projectManager.js';
|
|
421
|
+
*
|
|
422
|
+
* // Load recent projects with error handling
|
|
423
|
+
* const recent = await Effect.runPromise(
|
|
424
|
+
* Effect.match(
|
|
425
|
+
* projectManager.instance.loadRecentProjectsEffect(),
|
|
426
|
+
* {
|
|
427
|
+
* onFailure: (error) => {
|
|
428
|
+
* if (error._tag === 'ConfigError') {
|
|
429
|
+
* console.error(`Parse error: ${error.details}`);
|
|
430
|
+
* } else {
|
|
431
|
+
* console.error(`File error: ${error.cause}`);
|
|
432
|
+
* }
|
|
433
|
+
* return [];
|
|
434
|
+
* },
|
|
435
|
+
* onSuccess: (projects) => projects
|
|
436
|
+
* }
|
|
437
|
+
* )
|
|
438
|
+
* );
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
loadRecentProjectsEffect() {
|
|
442
|
+
return Effect.try({
|
|
443
|
+
try: () => {
|
|
444
|
+
if (existsSync(this.dataPath)) {
|
|
445
|
+
const data = readFileSync(this.dataPath, 'utf-8');
|
|
446
|
+
try {
|
|
447
|
+
const parsed = JSON.parse(data);
|
|
448
|
+
return parsed || [];
|
|
449
|
+
}
|
|
450
|
+
catch (parseError) {
|
|
451
|
+
throw new ConfigError({
|
|
452
|
+
configPath: this.dataPath,
|
|
453
|
+
reason: 'parse',
|
|
454
|
+
details: String(parseError),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return [];
|
|
459
|
+
},
|
|
460
|
+
catch: error => {
|
|
461
|
+
if (error instanceof ConfigError) {
|
|
462
|
+
return error;
|
|
463
|
+
}
|
|
464
|
+
return new FileSystemError({
|
|
465
|
+
operation: 'read',
|
|
466
|
+
path: this.dataPath,
|
|
467
|
+
cause: String(error),
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Save recent projects to cache using Effect
|
|
474
|
+
*
|
|
475
|
+
* Writes the recent projects array to JSON file.
|
|
476
|
+
*
|
|
477
|
+
* @param {RecentProject[]} projects - Recent projects to save
|
|
478
|
+
* @returns {Effect.Effect<void, FileSystemError, never>} Effect that succeeds or fails with FileSystemError
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* import {Effect} from 'effect';
|
|
483
|
+
* import {projectManager} from './services/projectManager.js';
|
|
484
|
+
*
|
|
485
|
+
* const recentProjects = [
|
|
486
|
+
* { path: '/home/user/project1', name: 'project1', lastAccessed: Date.now() }
|
|
487
|
+
* ];
|
|
488
|
+
*
|
|
489
|
+
* // Save with error recovery
|
|
490
|
+
* await Effect.runPromise(
|
|
491
|
+
* Effect.catchAll(
|
|
492
|
+
* projectManager.instance.saveRecentProjectsEffect(recentProjects),
|
|
493
|
+
* (error) => {
|
|
494
|
+
* console.error(`Failed to save: ${error.cause}`);
|
|
495
|
+
* return Effect.void; // Continue despite error
|
|
496
|
+
* }
|
|
497
|
+
* )
|
|
498
|
+
* );
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
saveRecentProjectsEffect(projects) {
|
|
502
|
+
return Effect.try({
|
|
503
|
+
try: () => {
|
|
504
|
+
writeFileSync(this.dataPath, JSON.stringify(projects, null, 2));
|
|
505
|
+
},
|
|
506
|
+
catch: error => {
|
|
507
|
+
return new FileSystemError({
|
|
508
|
+
operation: 'write',
|
|
509
|
+
path: this.dataPath,
|
|
510
|
+
cause: String(error),
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Refresh projects list (Effect version)
|
|
517
|
+
* @returns Effect with void or FileSystemError
|
|
518
|
+
*/
|
|
519
|
+
refreshProjectsEffect() {
|
|
520
|
+
return Effect.flatMap(Effect.try({
|
|
521
|
+
try: () => {
|
|
522
|
+
if (!this.projectsDir) {
|
|
523
|
+
throw new FileSystemError({
|
|
524
|
+
operation: 'read',
|
|
525
|
+
path: '',
|
|
526
|
+
cause: 'Projects directory not configured',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return this.projectsDir;
|
|
530
|
+
},
|
|
531
|
+
catch: error => {
|
|
532
|
+
if (error instanceof FileSystemError) {
|
|
533
|
+
return error;
|
|
534
|
+
}
|
|
535
|
+
return new FileSystemError({
|
|
536
|
+
operation: 'read',
|
|
537
|
+
path: '',
|
|
538
|
+
cause: String(error),
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
}), projectsDir => Effect.flatMap(this.discoverProjectsEffect(projectsDir), projects => Effect.sync(() => {
|
|
542
|
+
this.projects = projects;
|
|
543
|
+
// Update current project if it still exists
|
|
544
|
+
if (this.currentProject) {
|
|
545
|
+
const updatedProject = this.projects.find(p => p.path === this.currentProject.path);
|
|
546
|
+
if (updatedProject) {
|
|
547
|
+
this.currentProject = updatedProject;
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
// Current project no longer exists
|
|
551
|
+
this.currentProject = undefined;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
})));
|
|
555
|
+
}
|
|
387
556
|
}
|
|
388
557
|
// Recent projects
|
|
389
558
|
Object.defineProperty(ProjectManager, "MAX_RECENT_PROJECTS", {
|