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.
Files changed (77) hide show
  1. package/dist/cli.test.js +13 -2
  2. package/dist/components/App.js +125 -50
  3. package/dist/components/App.test.js +270 -0
  4. package/dist/components/ConfigureShortcuts.js +82 -8
  5. package/dist/components/DeleteWorktree.js +39 -5
  6. package/dist/components/DeleteWorktree.test.d.ts +1 -0
  7. package/dist/components/DeleteWorktree.test.js +128 -0
  8. package/dist/components/LoadingSpinner.d.ts +8 -0
  9. package/dist/components/LoadingSpinner.js +37 -0
  10. package/dist/components/LoadingSpinner.test.d.ts +1 -0
  11. package/dist/components/LoadingSpinner.test.js +187 -0
  12. package/dist/components/Menu.js +64 -16
  13. package/dist/components/Menu.recent-projects.test.js +32 -11
  14. package/dist/components/Menu.test.js +136 -4
  15. package/dist/components/MergeWorktree.js +79 -18
  16. package/dist/components/MergeWorktree.test.d.ts +1 -0
  17. package/dist/components/MergeWorktree.test.js +227 -0
  18. package/dist/components/NewWorktree.js +88 -9
  19. package/dist/components/NewWorktree.test.d.ts +1 -0
  20. package/dist/components/NewWorktree.test.js +244 -0
  21. package/dist/components/ProjectList.js +44 -13
  22. package/dist/components/ProjectList.recent-projects.test.js +8 -3
  23. package/dist/components/ProjectList.test.js +105 -8
  24. package/dist/components/RemoteBranchSelector.test.js +3 -1
  25. package/dist/hooks/useGitStatus.d.ts +11 -0
  26. package/dist/hooks/useGitStatus.js +70 -12
  27. package/dist/hooks/useGitStatus.test.js +30 -23
  28. package/dist/services/configurationManager.d.ts +75 -0
  29. package/dist/services/configurationManager.effect.test.d.ts +1 -0
  30. package/dist/services/configurationManager.effect.test.js +407 -0
  31. package/dist/services/configurationManager.js +246 -0
  32. package/dist/services/globalSessionOrchestrator.test.js +0 -8
  33. package/dist/services/projectManager.d.ts +98 -2
  34. package/dist/services/projectManager.js +228 -59
  35. package/dist/services/projectManager.test.js +242 -2
  36. package/dist/services/sessionManager.d.ts +44 -2
  37. package/dist/services/sessionManager.effect.test.d.ts +1 -0
  38. package/dist/services/sessionManager.effect.test.js +321 -0
  39. package/dist/services/sessionManager.js +216 -65
  40. package/dist/services/sessionManager.statePersistence.test.js +18 -9
  41. package/dist/services/sessionManager.test.js +40 -36
  42. package/dist/services/worktreeService.d.ts +356 -26
  43. package/dist/services/worktreeService.js +793 -353
  44. package/dist/services/worktreeService.test.js +294 -313
  45. package/dist/types/errors.d.ts +74 -0
  46. package/dist/types/errors.js +31 -0
  47. package/dist/types/errors.test.d.ts +1 -0
  48. package/dist/types/errors.test.js +201 -0
  49. package/dist/types/index.d.ts +5 -17
  50. package/dist/utils/claudeDir.d.ts +58 -6
  51. package/dist/utils/claudeDir.js +103 -8
  52. package/dist/utils/claudeDir.test.d.ts +1 -0
  53. package/dist/utils/claudeDir.test.js +108 -0
  54. package/dist/utils/concurrencyLimit.d.ts +5 -0
  55. package/dist/utils/concurrencyLimit.js +11 -0
  56. package/dist/utils/concurrencyLimit.test.js +40 -1
  57. package/dist/utils/gitStatus.d.ts +36 -8
  58. package/dist/utils/gitStatus.js +170 -88
  59. package/dist/utils/gitStatus.test.js +12 -9
  60. package/dist/utils/hookExecutor.d.ts +41 -6
  61. package/dist/utils/hookExecutor.js +75 -32
  62. package/dist/utils/hookExecutor.test.js +73 -20
  63. package/dist/utils/terminalCapabilities.d.ts +18 -0
  64. package/dist/utils/terminalCapabilities.js +81 -0
  65. package/dist/utils/terminalCapabilities.test.d.ts +1 -0
  66. package/dist/utils/terminalCapabilities.test.js +104 -0
  67. package/dist/utils/testHelpers.d.ts +106 -0
  68. package/dist/utils/testHelpers.js +153 -0
  69. package/dist/utils/testHelpers.test.d.ts +1 -0
  70. package/dist/utils/testHelpers.test.js +114 -0
  71. package/dist/utils/worktreeConfig.d.ts +77 -2
  72. package/dist/utils/worktreeConfig.js +156 -16
  73. package/dist/utils/worktreeConfig.test.d.ts +1 -0
  74. package/dist/utils/worktreeConfig.test.js +39 -0
  75. package/package.json +4 -4
  76. package/dist/integration-tests/devcontainer.integration.test.js +0 -101
  77. /package/dist/{integration-tests/devcontainer.integration.test.d.ts → components/App.test.d.ts} +0 -0
@@ -1,12 +1,16 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { Effect } from 'effect';
2
3
  import { spawn } from 'node-pty';
3
4
  import { EventEmitter } from 'events';
4
5
  import { exec } from 'child_process';
5
6
  // Mock node-pty
6
- vi.mock('node-pty');
7
+ vi.mock('node-pty', () => ({
8
+ spawn: vi.fn(),
9
+ }));
7
10
  // Mock child_process
8
11
  vi.mock('child_process', () => ({
9
12
  exec: vi.fn(),
13
+ execFile: vi.fn(),
10
14
  }));
11
15
  // Mock configuration manager
12
16
  vi.mock('./configurationManager.js', () => ({
@@ -93,7 +97,7 @@ describe('SessionManager', () => {
93
97
  afterEach(() => {
94
98
  sessionManager.destroy();
95
99
  });
96
- describe('createSessionWithPreset', () => {
100
+ describe('createSessionWithPresetEffect', () => {
97
101
  it('should use default preset when no preset ID specified', async () => {
98
102
  // Setup mock preset
99
103
  vi.mocked(configurationManager.getDefaultPreset).mockReturnValue({
@@ -105,7 +109,7 @@ describe('SessionManager', () => {
105
109
  // Setup spawn mock
106
110
  vi.mocked(spawn).mockReturnValue(mockPty);
107
111
  // Create session with preset
108
- await sessionManager.createSessionWithPreset('/test/worktree');
112
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
109
113
  // Verify spawn was called with preset config
110
114
  expect(spawn).toHaveBeenCalledWith('claude', ['--preset-arg'], {
111
115
  name: 'xterm-256color',
@@ -127,7 +131,7 @@ describe('SessionManager', () => {
127
131
  // Setup spawn mock
128
132
  vi.mocked(spawn).mockReturnValue(mockPty);
129
133
  // Create session with specific preset
130
- await sessionManager.createSessionWithPreset('/test/worktree', '2');
134
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree', '2'));
131
135
  // Verify getPresetById was called with correct ID
132
136
  expect(configurationManager.getPresetById).toHaveBeenCalledWith('2');
133
137
  // Verify spawn was called with preset config
@@ -150,7 +154,7 @@ describe('SessionManager', () => {
150
154
  // Setup spawn mock
151
155
  vi.mocked(spawn).mockReturnValue(mockPty);
152
156
  // Create session with non-existent preset
153
- await sessionManager.createSessionWithPreset('/test/worktree', 'invalid');
157
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree', 'invalid'));
154
158
  // Verify fallback to default preset
155
159
  expect(configurationManager.getDefaultPreset).toHaveBeenCalled();
156
160
  expect(spawn).toHaveBeenCalledWith('claude', [], expect.any(Object));
@@ -168,8 +172,8 @@ describe('SessionManager', () => {
168
172
  vi.mocked(spawn).mockImplementation(() => {
169
173
  throw new Error('Command failed');
170
174
  });
171
- // Expect createSessionWithPreset to throw
172
- await expect(sessionManager.createSessionWithPreset('/test/worktree')).rejects.toThrow('Command failed');
175
+ // Expect createSessionWithPresetEffect to throw
176
+ await expect(Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'))).rejects.toThrow('Command failed');
173
177
  // Verify only one spawn attempt was made
174
178
  expect(spawn).toHaveBeenCalledTimes(1);
175
179
  expect(spawn).toHaveBeenCalledWith('claude', ['--bad-flag'], expect.any(Object));
@@ -184,8 +188,8 @@ describe('SessionManager', () => {
184
188
  // Setup spawn mock
185
189
  vi.mocked(spawn).mockReturnValue(mockPty);
186
190
  // Create session twice
187
- const session1 = await sessionManager.createSessionWithPreset('/test/worktree');
188
- const session2 = await sessionManager.createSessionWithPreset('/test/worktree');
191
+ const session1 = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
192
+ const session2 = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
189
193
  // Should return the same session
190
194
  expect(session1).toBe(session2);
191
195
  // Spawn should only be called once
@@ -204,8 +208,8 @@ describe('SessionManager', () => {
204
208
  vi.mocked(spawn).mockImplementation(() => {
205
209
  throw new Error('Command not found');
206
210
  });
207
- // Expect createSessionWithPreset to throw the original error
208
- await expect(sessionManager.createSessionWithPreset('/test/worktree')).rejects.toThrow('Command not found');
211
+ // Expect createSessionWithPresetEffect to throw the original error
212
+ await expect(Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'))).rejects.toThrow('Command not found');
209
213
  });
210
214
  it('should use fallback args when main command exits with code 1', async () => {
211
215
  // Setup mock preset with fallback
@@ -224,7 +228,7 @@ describe('SessionManager', () => {
224
228
  .mockReturnValueOnce(firstMockPty)
225
229
  .mockReturnValueOnce(secondMockPty);
226
230
  // Create session
227
- const session = await sessionManager.createSessionWithPreset('/test/worktree');
231
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
228
232
  // Verify initial spawn
229
233
  expect(spawn).toHaveBeenCalledTimes(1);
230
234
  expect(spawn).toHaveBeenCalledWith('claude', ['--invalid-flag'], expect.objectContaining({ cwd: '/test/worktree' }));
@@ -251,7 +255,7 @@ describe('SessionManager', () => {
251
255
  // Setup spawn mock - process doesn't exit early
252
256
  vi.mocked(spawn).mockReturnValue(mockPty);
253
257
  // Create session
254
- await sessionManager.createSessionWithPreset('/test/worktree');
258
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
255
259
  // Wait a bit to ensure no early exit
256
260
  await new Promise(resolve => setTimeout(resolve, 600));
257
261
  // Verify only one spawn attempt
@@ -275,7 +279,7 @@ describe('SessionManager', () => {
275
279
  .mockReturnValueOnce(firstMockPty)
276
280
  .mockReturnValueOnce(secondMockPty);
277
281
  // Create session
278
- const session = await sessionManager.createSessionWithPreset('/test/worktree');
282
+ const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
279
283
  // Verify initial spawn
280
284
  expect(spawn).toHaveBeenCalledTimes(1);
281
285
  expect(spawn).toHaveBeenCalledWith('claude', ['--invalid-flag'], expect.objectContaining({ cwd: '/test/worktree' }));
@@ -302,7 +306,7 @@ describe('SessionManager', () => {
302
306
  // Setup spawn mock
303
307
  vi.mocked(spawn).mockReturnValue(mockPty);
304
308
  // Create session
305
- await sessionManager.createSessionWithPreset('/test/worktree');
309
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
306
310
  // Verify spawn was called with custom command
307
311
  expect(spawn).toHaveBeenCalledWith('my-custom-claude', ['--config', '/path/to/config'], expect.objectContaining({
308
312
  cwd: '/test/worktree',
@@ -321,7 +325,7 @@ describe('SessionManager', () => {
321
325
  throw new Error('spawn failed');
322
326
  });
323
327
  // Expect createSessionWithPreset to throw
324
- await expect(sessionManager.createSessionWithPreset('/test/worktree')).rejects.toThrow('spawn failed');
328
+ await expect(Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'))).rejects.toThrow('spawn failed');
325
329
  });
326
330
  });
327
331
  describe('session lifecycle', () => {
@@ -334,7 +338,7 @@ describe('SessionManager', () => {
334
338
  });
335
339
  vi.mocked(spawn).mockReturnValue(mockPty);
336
340
  // Create and destroy session
337
- await sessionManager.createSessionWithPreset('/test/worktree');
341
+ await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
338
342
  sessionManager.destroySession('/test/worktree');
339
343
  // Verify cleanup
340
344
  expect(mockPty.kill).toHaveBeenCalled();
@@ -354,7 +358,7 @@ describe('SessionManager', () => {
354
358
  exitedSession = session;
355
359
  });
356
360
  // Create session
357
- const createdSession = await sessionManager.createSessionWithPreset('/test/worktree');
361
+ const createdSession = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/worktree'));
358
362
  // Simulate process exit after successful creation
359
363
  setTimeout(() => {
360
364
  mockPty.emit('exit', { exitCode: 0 });
@@ -365,7 +369,7 @@ describe('SessionManager', () => {
365
369
  expect(sessionManager.getSession('/test/worktree')).toBeUndefined();
366
370
  });
367
371
  });
368
- describe('createSessionWithDevcontainer', () => {
372
+ describe('createSessionWithDevcontainerEffect', () => {
369
373
  beforeEach(() => {
370
374
  // Reset shouldFail flag
371
375
  const mockExec = vi.mocked(exec);
@@ -401,7 +405,7 @@ describe('SessionManager', () => {
401
405
  upCommand: 'devcontainer up --workspace-folder .',
402
406
  execCommand: 'devcontainer exec --workspace-folder .',
403
407
  };
404
- await sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig);
408
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig));
405
409
  // Verify spawn was called correctly which proves devcontainer up succeeded
406
410
  // Verify spawn was called with devcontainer exec
407
411
  expect(spawn).toHaveBeenCalledWith('devcontainer', ['exec', '--workspace-folder', '.', '--', 'claude', '--resume'], expect.objectContaining({ cwd: '/test/worktree' }));
@@ -421,7 +425,7 @@ describe('SessionManager', () => {
421
425
  upCommand: 'devcontainer up',
422
426
  execCommand: 'devcontainer exec',
423
427
  };
424
- await sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig, '2');
428
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig, '2'));
425
429
  // Verify correct preset was used
426
430
  expect(configurationManager.getPresetById).toHaveBeenCalledWith('2');
427
431
  expect(spawn).toHaveBeenCalledWith('devcontainer', ['exec', '--', 'claude', '--resume', '--dev'], expect.any(Object));
@@ -435,7 +439,7 @@ describe('SessionManager', () => {
435
439
  upCommand: 'devcontainer up',
436
440
  execCommand: 'devcontainer exec',
437
441
  };
438
- await expect(sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig)).rejects.toThrow('Failed to start devcontainer: Container startup failed');
442
+ await expect(Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig))).rejects.toThrow('Failed to start devcontainer: Container startup failed');
439
443
  });
440
444
  it('should return existing session if already created', async () => {
441
445
  // Setup mock preset
@@ -451,8 +455,8 @@ describe('SessionManager', () => {
451
455
  execCommand: 'devcontainer exec',
452
456
  };
453
457
  // Create session twice
454
- const session1 = await sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig);
455
- const session2 = await sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig);
458
+ const session1 = await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig));
459
+ const session2 = await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig));
456
460
  // Should return the same session
457
461
  expect(session1).toBe(session2);
458
462
  // spawn should only be called once
@@ -473,7 +477,7 @@ describe('SessionManager', () => {
473
477
  upCommand: 'devcontainer up --workspace-folder . --log-level debug',
474
478
  execCommand: 'devcontainer exec --workspace-folder . --container-name mycontainer',
475
479
  };
476
- await sessionManager.createSessionWithDevcontainer('/test/worktree', devcontainerConfig);
480
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', devcontainerConfig));
477
481
  // Verify spawn was called with properly parsed exec command
478
482
  expect(spawn).toHaveBeenCalledWith('devcontainer', [
479
483
  'exec',
@@ -510,10 +514,10 @@ describe('SessionManager', () => {
510
514
  }
511
515
  return {};
512
516
  });
513
- await sessionManager.createSessionWithDevcontainer('/test/worktree2', {
517
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree2', {
514
518
  upCommand: 'devcontainer up --workspace-folder .',
515
519
  execCommand: 'devcontainer exec --workspace-folder .',
516
- });
520
+ }));
517
521
  // Should spawn with devcontainer exec command
518
522
  expect(spawn).toHaveBeenCalledWith('devcontainer', ['exec', '--workspace-folder', '.', '--', 'claude'], expect.objectContaining({
519
523
  cwd: '/test/worktree2',
@@ -531,10 +535,10 @@ describe('SessionManager', () => {
531
535
  }
532
536
  return {};
533
537
  });
534
- await sessionManager.createSessionWithDevcontainer('/test/worktree', {
538
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', {
535
539
  upCommand: 'devcontainer up --workspace-folder .',
536
540
  execCommand: 'devcontainer exec --workspace-folder .',
537
- }, 'custom-preset');
541
+ }, 'custom-preset'));
538
542
  // Should call createSessionWithPreset internally
539
543
  const session = sessionManager.getSession('/test/worktree');
540
544
  expect(session).toBeDefined();
@@ -559,7 +563,7 @@ describe('SessionManager', () => {
559
563
  upCommand: 'devcontainer up --workspace-folder /path/to/project',
560
564
  execCommand: 'devcontainer exec --workspace-folder /path/to/project --user vscode',
561
565
  };
562
- await sessionManager.createSessionWithDevcontainer('/test/worktree', config);
566
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', config));
563
567
  expect(spawn).toHaveBeenCalledWith('devcontainer', [
564
568
  'exec',
565
569
  '--workspace-folder',
@@ -588,10 +592,10 @@ describe('SessionManager', () => {
588
592
  command: 'claude',
589
593
  args: ['-m', 'claude-3-opus'],
590
594
  });
591
- await sessionManager.createSessionWithDevcontainer('/test/worktree', {
595
+ await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', {
592
596
  upCommand: 'devcontainer up --workspace-folder .',
593
597
  execCommand: 'devcontainer exec --workspace-folder .',
594
- }, 'claude-with-args');
598
+ }, 'claude-with-args'));
595
599
  expect(spawn).toHaveBeenCalledWith('devcontainer', [
596
600
  'exec',
597
601
  '--workspace-folder',
@@ -629,10 +633,10 @@ describe('SessionManager', () => {
629
633
  vi.mocked(spawn)
630
634
  .mockReturnValueOnce(firstMockPty)
631
635
  .mockReturnValueOnce(secondMockPty);
632
- const session = await sessionManager.createSessionWithDevcontainer('/test/worktree', {
636
+ const session = await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', {
633
637
  upCommand: 'devcontainer up --workspace-folder .',
634
638
  execCommand: 'devcontainer exec --workspace-folder .',
635
- });
639
+ }));
636
640
  // Verify initial spawn
637
641
  expect(spawn).toHaveBeenCalledTimes(1);
638
642
  expect(spawn).toHaveBeenCalledWith('devcontainer', ['exec', '--workspace-folder', '.', '--', 'claude', '--invalid-flag'], expect.objectContaining({ cwd: '/test/worktree' }));
@@ -675,10 +679,10 @@ describe('SessionManager', () => {
675
679
  vi.mocked(spawn)
676
680
  .mockReturnValueOnce(firstMockPty)
677
681
  .mockReturnValueOnce(secondMockPty);
678
- const session = await sessionManager.createSessionWithDevcontainer('/test/worktree', {
682
+ const session = await Effect.runPromise(sessionManager.createSessionWithDevcontainerEffect('/test/worktree', {
679
683
  upCommand: 'devcontainer up --workspace-folder .',
680
684
  execCommand: 'devcontainer exec --workspace-folder .',
681
- });
685
+ }));
682
686
  // Verify initial spawn
683
687
  expect(spawn).toHaveBeenCalledTimes(1);
684
688
  expect(spawn).toHaveBeenCalledWith('devcontainer', ['exec', '--workspace-folder', '.', '--', 'claude', '--bad-flag'], expect.objectContaining({ cwd: '/test/worktree' }));