git-drive 0.1.5 → 0.1.6

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 (93) hide show
  1. package/dist/__tests__/commands/init.test.js +123 -0
  2. package/dist/__tests__/commands/list.test.js +91 -0
  3. package/dist/__tests__/commands/push.test.js +128 -0
  4. package/dist/__tests__/commands/restore.test.js +99 -0
  5. package/dist/__tests__/commands/status.test.js +151 -0
  6. package/dist/__tests__/config.test.js +150 -0
  7. package/dist/__tests__/e2e.test.js +107 -0
  8. package/dist/__tests__/errors.test.js +56 -0
  9. package/dist/__tests__/git.test.js +184 -0
  10. package/dist/__tests__/server.test.js +310 -0
  11. package/dist/commands/archive.js +32 -0
  12. package/dist/commands/init.js +55 -0
  13. package/dist/commands/link.js +175 -0
  14. package/dist/commands/list.js +83 -0
  15. package/dist/commands/push.js +112 -0
  16. package/dist/commands/restore.js +30 -0
  17. package/dist/commands/status.js +116 -0
  18. package/dist/config.js +62 -0
  19. package/dist/errors.js +30 -0
  20. package/dist/git.js +67 -0
  21. package/dist/index.js +108 -0
  22. package/dist/server.js +535 -0
  23. package/package.json +55 -20
  24. package/.github/workflows/ci.yml +0 -77
  25. package/.planning/codebase/ARCHITECTURE.md +0 -151
  26. package/.planning/codebase/CONCERNS.md +0 -191
  27. package/.planning/codebase/CONVENTIONS.md +0 -169
  28. package/.planning/codebase/INTEGRATIONS.md +0 -94
  29. package/.planning/codebase/STACK.md +0 -77
  30. package/.planning/codebase/STRUCTURE.md +0 -157
  31. package/.planning/codebase/TESTING.md +0 -156
  32. package/Dockerfile.cli +0 -30
  33. package/Dockerfile.server +0 -32
  34. package/README.md +0 -121
  35. package/docker-compose.yml +0 -48
  36. package/packages/cli/Dockerfile +0 -26
  37. package/packages/cli/jest.config.js +0 -26
  38. package/packages/cli/package.json +0 -65
  39. package/packages/cli/src/__tests__/commands/init.test.ts +0 -154
  40. package/packages/cli/src/__tests__/commands/list.test.ts +0 -118
  41. package/packages/cli/src/__tests__/commands/push.test.ts +0 -155
  42. package/packages/cli/src/__tests__/commands/restore.test.ts +0 -134
  43. package/packages/cli/src/__tests__/commands/status.test.ts +0 -195
  44. package/packages/cli/src/__tests__/config.test.ts +0 -198
  45. package/packages/cli/src/__tests__/e2e.test.ts +0 -125
  46. package/packages/cli/src/__tests__/errors.test.ts +0 -66
  47. package/packages/cli/src/__tests__/git.test.ts +0 -226
  48. package/packages/cli/src/__tests__/server.test.ts +0 -368
  49. package/packages/cli/src/commands/archive.ts +0 -39
  50. package/packages/cli/src/commands/init.ts +0 -64
  51. package/packages/cli/src/commands/link.ts +0 -151
  52. package/packages/cli/src/commands/list.ts +0 -94
  53. package/packages/cli/src/commands/push.ts +0 -77
  54. package/packages/cli/src/commands/restore.ts +0 -36
  55. package/packages/cli/src/commands/status.ts +0 -127
  56. package/packages/cli/src/config.ts +0 -73
  57. package/packages/cli/src/errors.ts +0 -23
  58. package/packages/cli/src/git.ts +0 -55
  59. package/packages/cli/src/index.ts +0 -122
  60. package/packages/cli/src/server.ts +0 -573
  61. package/packages/cli/tsconfig.json +0 -13
  62. package/packages/git-drive-docker/package.json +0 -15
  63. package/packages/server/package.json +0 -44
  64. package/packages/server/src/index.ts +0 -569
  65. package/packages/server/tsconfig.json +0 -9
  66. package/packages/ui/README.md +0 -73
  67. package/packages/ui/eslint.config.js +0 -23
  68. package/packages/ui/index.html +0 -13
  69. package/packages/ui/package.json +0 -52
  70. package/packages/ui/postcss.config.js +0 -6
  71. package/packages/ui/public/vite.svg +0 -1
  72. package/packages/ui/src/App.css +0 -23
  73. package/packages/ui/src/App.test.tsx +0 -242
  74. package/packages/ui/src/App.tsx +0 -755
  75. package/packages/ui/src/assets/react.svg +0 -8
  76. package/packages/ui/src/assets/vite.svg +0 -3
  77. package/packages/ui/src/index.css +0 -37
  78. package/packages/ui/src/main.tsx +0 -14
  79. package/packages/ui/src/test/setup.ts +0 -1
  80. package/packages/ui/tailwind.config.js +0 -11
  81. package/packages/ui/tsconfig.app.json +0 -28
  82. package/packages/ui/tsconfig.json +0 -26
  83. package/packages/ui/tsconfig.node.json +0 -12
  84. package/packages/ui/vite.config.ts +0 -7
  85. package/packages/ui/vitest.config.ts +0 -20
  86. package/pnpm-workspace.yaml +0 -4
  87. package/rewrite_app.js +0 -731
  88. package/tsconfig.json +0 -14
  89. /package/{packages/cli/ui → ui}/assets/index-Br8xQbJz.js +0 -0
  90. /package/{packages/cli/ui → ui}/assets/index-Cc2q1t5k.js +0 -0
  91. /package/{packages/cli/ui → ui}/assets/index-DrL7ojPA.css +0 -0
  92. /package/{packages/cli/ui → ui}/index.html +0 -0
  93. /package/{packages/cli/ui → ui}/vite.svg +0 -0
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const init_js_1 = require("../../commands/init.js");
7
+ const errors_js_1 = require("../../errors.js");
8
+ const memfs_1 = require("memfs");
9
+ // Mock fs and path modules
10
+ jest.mock('fs', () => {
11
+ const { fs } = require('memfs');
12
+ return fs;
13
+ });
14
+ // Mock prompts
15
+ jest.mock('prompts', () => ({
16
+ __esModule: true,
17
+ default: jest.fn(),
18
+ }));
19
+ // Mock config
20
+ jest.mock('../../config.js', () => ({
21
+ saveConfig: jest.fn(),
22
+ getDriveStorePath: jest.fn((drivePath) => `${drivePath}/.git-drive`),
23
+ }));
24
+ // Mock git
25
+ jest.mock('../../git.js', () => ({
26
+ listDrives: jest.fn(),
27
+ }));
28
+ const prompts_1 = __importDefault(require("prompts"));
29
+ const git_js_1 = require("../../git.js");
30
+ const config_js_1 = require("../../config.js");
31
+ const mockPrompts = prompts_1.default;
32
+ const mockListDrives = git_js_1.listDrives;
33
+ const mockSaveConfig = config_js_1.saveConfig;
34
+ const mockGetDriveStorePath = config_js_1.getDriveStorePath;
35
+ describe('init command', () => {
36
+ let consoleSpy;
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ memfs_1.vol.reset();
40
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
41
+ mockGetDriveStorePath.mockImplementation((drivePath) => `${drivePath}/.git-drive`);
42
+ });
43
+ afterEach(() => {
44
+ consoleSpy.mockRestore();
45
+ });
46
+ describe('with path argument', () => {
47
+ it('should initialize git-drive on specified path', async () => {
48
+ const drivePath = '/Volumes/TestDrive';
49
+ memfs_1.vol.fromJSON({
50
+ [drivePath]: '',
51
+ });
52
+ await (0, init_js_1.init)([drivePath]);
53
+ expect(mockSaveConfig).toHaveBeenCalledWith({ drivePath: expect.stringContaining('TestDrive') });
54
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Git Drive initialized'));
55
+ });
56
+ it('should throw error if path does not exist', async () => {
57
+ await expect((0, init_js_1.init)(['/Volumes/NonExistent'])).rejects.toThrow(errors_js_1.GitDriveError);
58
+ await expect((0, init_js_1.init)(['/Volumes/NonExistent'])).rejects.toThrow('Path not found');
59
+ });
60
+ it('should throw error if path is not a directory', async () => {
61
+ memfs_1.vol.fromJSON({
62
+ '/Volumes/SomeFile': 'file content',
63
+ });
64
+ await expect((0, init_js_1.init)(['/Volumes/SomeFile'])).rejects.toThrow('Path is not a directory');
65
+ });
66
+ it('should resolve relative paths', async () => {
67
+ memfs_1.vol.fromJSON({
68
+ '/current/dir/TestDrive': '',
69
+ });
70
+ // Mock cwd to return a specific directory
71
+ const originalCwd = process.cwd;
72
+ process.cwd = jest.fn(() => '/current/dir');
73
+ await (0, init_js_1.init)(['./TestDrive']);
74
+ expect(mockSaveConfig).toHaveBeenCalled();
75
+ process.cwd = originalCwd;
76
+ });
77
+ });
78
+ describe('without path argument (interactive)', () => {
79
+ it('should throw error when no drives found', async () => {
80
+ mockListDrives.mockResolvedValue([]);
81
+ await expect((0, init_js_1.init)([])).rejects.toThrow('No external drives found');
82
+ });
83
+ it('should prompt user to select a drive', async () => {
84
+ mockListDrives.mockResolvedValue([
85
+ { mounted: '/Volumes/Drive1', filesystem: 'Drive1', blocks: 1000000, available: 500000 },
86
+ { mounted: '/Volumes/Drive2', filesystem: 'Drive2', blocks: 2000000, available: 1000000 },
87
+ ]);
88
+ mockPrompts.mockResolvedValue({
89
+ selectedDrive: '/Volumes/Drive1',
90
+ });
91
+ memfs_1.vol.fromJSON({
92
+ '/Volumes/Drive1': '',
93
+ });
94
+ await (0, init_js_1.init)([]);
95
+ expect(mockPrompts).toHaveBeenCalledWith(expect.objectContaining({
96
+ type: 'select',
97
+ name: 'selectedDrive',
98
+ }));
99
+ });
100
+ it('should handle user cancellation', async () => {
101
+ mockListDrives.mockResolvedValue([
102
+ { mounted: '/Volumes/Drive1', filesystem: 'Drive1', blocks: 1000000, available: 500000 },
103
+ ]);
104
+ mockPrompts.mockResolvedValue({
105
+ selectedDrive: undefined,
106
+ });
107
+ await (0, init_js_1.init)([]);
108
+ expect(consoleSpy).toHaveBeenCalledWith('Operation cancelled.');
109
+ expect(mockSaveConfig).not.toHaveBeenCalled();
110
+ });
111
+ });
112
+ describe('store directory creation', () => {
113
+ it('should create .git-drive directory if it does not exist', async () => {
114
+ const drivePath = '/Volumes/TestDrive';
115
+ memfs_1.vol.fromJSON({
116
+ [drivePath]: '',
117
+ });
118
+ await (0, init_js_1.init)([drivePath]);
119
+ // The store path should have been requested
120
+ expect(mockGetDriveStorePath).toHaveBeenCalled();
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const memfs_1 = require("memfs");
4
+ // Mock fs
5
+ jest.mock('fs', () => {
6
+ const { fs } = require('memfs');
7
+ return fs;
8
+ });
9
+ // Mock node-disk-info
10
+ jest.mock('node-disk-info', () => ({
11
+ getDiskInfo: jest.fn(),
12
+ }));
13
+ // Mock server
14
+ jest.mock('../../server.js', () => ({
15
+ ensureServerRunning: jest.fn(),
16
+ }));
17
+ // Mock os
18
+ jest.mock('os', () => ({
19
+ homedir: () => '/home/testuser',
20
+ }));
21
+ const node_disk_info_1 = require("node-disk-info");
22
+ const list_js_1 = require("../../commands/list.js");
23
+ const mockGetDiskInfo = node_disk_info_1.getDiskInfo;
24
+ describe('list command', () => {
25
+ let consoleSpy;
26
+ const originalPlatform = process.platform;
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+ memfs_1.vol.reset();
30
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
31
+ Object.defineProperty(process, 'platform', { value: 'darwin', writable: true });
32
+ });
33
+ afterEach(() => {
34
+ consoleSpy.mockRestore();
35
+ Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true });
36
+ });
37
+ it('should display no drives message when no external drives found', async () => {
38
+ mockGetDiskInfo.mockResolvedValue([]);
39
+ memfs_1.vol.fromJSON({
40
+ '/home/testuser/.config/git-drive/links.json': '{}',
41
+ });
42
+ await (0, list_js_1.list)([]);
43
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No external drives detected'));
44
+ });
45
+ it('should list connected drives with git-drive status', async () => {
46
+ mockGetDiskInfo.mockResolvedValue([
47
+ { mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 32000000, available: 16000000 },
48
+ { mounted: '/Volumes/External', filesystem: 'External', blocks: 1000000000, available: 500000000 },
49
+ ]);
50
+ memfs_1.vol.fromJSON({
51
+ '/home/testuser/.config/git-drive/links.json': '{}',
52
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
53
+ '/Volumes/External': '',
54
+ });
55
+ await (0, list_js_1.list)([]);
56
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('/Volumes/MyUSB'));
57
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('/Volumes/External'));
58
+ });
59
+ it('should count repositories on initialized drives', async () => {
60
+ mockGetDiskInfo.mockResolvedValue([
61
+ { mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 32000000, available: 16000000 },
62
+ ]);
63
+ memfs_1.vol.fromJSON({
64
+ '/home/testuser/.config/git-drive/links.json': '{}',
65
+ '/Volumes/MyUSB/.git-drive/project1.git/HEAD': '',
66
+ '/Volumes/MyUSB/.git-drive/project2.git/HEAD': '',
67
+ '/Volumes/MyUSB/.git-drive/project3.git/HEAD': '',
68
+ });
69
+ await (0, list_js_1.list)([]);
70
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Repositories: 3'));
71
+ });
72
+ it('should display drive size in GB', async () => {
73
+ mockGetDiskInfo.mockResolvedValue([
74
+ { mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 97656250, available: 50000000 },
75
+ ]);
76
+ memfs_1.vol.fromJSON({
77
+ '/home/testuser/.config/git-drive/links.json': '{}',
78
+ '/Volumes/MyUSB': '',
79
+ });
80
+ await (0, list_js_1.list)([]);
81
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('GB'));
82
+ });
83
+ it('should handle errors when detecting drives', async () => {
84
+ mockGetDiskInfo.mockRejectedValue(new Error('Failed to detect drives'));
85
+ memfs_1.vol.fromJSON({
86
+ '/home/testuser/.config/git-drive/links.json': '{}',
87
+ });
88
+ await (0, list_js_1.list)([]);
89
+ expect(consoleSpy).toHaveBeenCalledWith('Error detecting drives:', expect.any(Error));
90
+ });
91
+ });
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const push_js_1 = require("../../commands/push.js");
7
+ const errors_js_1 = require("../../errors.js");
8
+ const memfs_1 = require("memfs");
9
+ // Mock fs
10
+ jest.mock('fs', () => {
11
+ const { fs } = require('memfs');
12
+ return fs;
13
+ });
14
+ // Mock prompts
15
+ jest.mock('prompts', () => ({
16
+ __esModule: true,
17
+ default: jest.fn(),
18
+ }));
19
+ // Mock git
20
+ jest.mock('../../git.js', () => ({
21
+ git: jest.fn(),
22
+ getRemoteUrl: jest.fn(),
23
+ isGitRepo: jest.fn(),
24
+ }));
25
+ // Mock server
26
+ jest.mock('../../server.js', () => ({
27
+ ensureServerRunning: jest.fn(),
28
+ }));
29
+ const prompts_1 = __importDefault(require("prompts"));
30
+ const git_js_1 = require("../../git.js");
31
+ const mockPrompts = prompts_1.default;
32
+ const mockGit = git_js_1.git;
33
+ const mockGetRemoteUrl = git_js_1.getRemoteUrl;
34
+ const mockIsGitRepo = git_js_1.isGitRepo;
35
+ describe('push command', () => {
36
+ let consoleSpy;
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ memfs_1.vol.reset();
40
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
41
+ mockIsGitRepo.mockReturnValue(true);
42
+ mockGetRemoteUrl.mockReturnValue('/Volumes/TestDrive/.git-drive/my-repo.git');
43
+ mockGit.mockImplementation((cmd) => {
44
+ if (cmd.includes('branch --show-current'))
45
+ return 'main';
46
+ return '';
47
+ });
48
+ });
49
+ afterEach(() => {
50
+ consoleSpy.mockRestore();
51
+ });
52
+ it('should throw error when not in a git repository', async () => {
53
+ mockIsGitRepo.mockReturnValue(false);
54
+ await expect((0, push_js_1.push)([])).rejects.toThrow(errors_js_1.GitDriveError);
55
+ await expect((0, push_js_1.push)([])).rejects.toThrow('Not in a git repository');
56
+ });
57
+ it('should throw error when no git-drive linked', async () => {
58
+ mockIsGitRepo.mockReturnValue(true);
59
+ mockGetRemoteUrl.mockReturnValue(null);
60
+ await expect((0, push_js_1.push)([])).rejects.toThrow("No git-drive linked for this project");
61
+ });
62
+ describe('push modes', () => {
63
+ beforeEach(() => {
64
+ memfs_1.vol.fromJSON({
65
+ '/Volumes/TestDrive/.git-drive/my-repo.git/HEAD': '',
66
+ });
67
+ });
68
+ it('should push current branch with --current flag', async () => {
69
+ await (0, push_js_1.push)(['--current']);
70
+ expect(mockGit).toHaveBeenCalledWith(expect.stringContaining('push gd main'));
71
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Successfully pushed'));
72
+ });
73
+ it('should push all branches and tags with --all flag', async () => {
74
+ await (0, push_js_1.push)(['--all']);
75
+ expect(mockGit).toHaveBeenCalledWith('push gd --all');
76
+ expect(mockGit).toHaveBeenCalledWith('push gd --tags');
77
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('all branches and tags'));
78
+ });
79
+ it('should prompt for push mode when no flags provided', async () => {
80
+ mockPrompts.mockResolvedValue({ pushMode: 'current' });
81
+ await (0, push_js_1.push)([]);
82
+ expect(mockPrompts).toHaveBeenCalledWith(expect.objectContaining({
83
+ type: 'select',
84
+ name: 'pushMode',
85
+ }));
86
+ });
87
+ it('should handle user cancellation in interactive mode', async () => {
88
+ mockPrompts.mockResolvedValue({ pushMode: undefined });
89
+ await (0, push_js_1.push)([]);
90
+ // Should not have pushed anything beyond the prompt
91
+ expect(mockGit).not.toHaveBeenCalledWith(expect.stringContaining('push gd'));
92
+ });
93
+ });
94
+ describe('pushlog', () => {
95
+ it('should write pushlog on successful push', async () => {
96
+ memfs_1.vol.fromJSON({
97
+ '/Volumes/TestDrive/.git-drive/my-repo.git/HEAD': '',
98
+ });
99
+ await (0, push_js_1.push)(['--current']);
100
+ // Check that the pushlog file was created
101
+ const files = memfs_1.vol.toJSON();
102
+ const pushlogPath = '/Volumes/TestDrive/.git-drive/my-repo.git/git-drive-pushlog.json';
103
+ // The pushlog should exist if the remote URL path exists
104
+ // This is tested indirectly through the code path
105
+ });
106
+ it('should handle pushlog write errors silently', async () => {
107
+ // Create a scenario where writing the pushlog would fail
108
+ memfs_1.vol.fromJSON({
109
+ '/Volumes/TestDrive/.git-drive/my-repo.git/HEAD': '',
110
+ });
111
+ // This should not throw even if pushlog writing fails
112
+ await expect((0, push_js_1.push)(['--current'])).resolves.not.toThrow();
113
+ });
114
+ });
115
+ describe('error handling', () => {
116
+ it('should throw GitDriveError when git push fails', async () => {
117
+ memfs_1.vol.fromJSON({
118
+ '/Volumes/TestDrive/.git-drive/my-repo.git/HEAD': '',
119
+ });
120
+ mockGit.mockImplementation((cmd) => {
121
+ if (cmd.includes('branch --show-current'))
122
+ return 'main';
123
+ throw new Error('fatal: failed to push');
124
+ });
125
+ await expect((0, push_js_1.push)(['--current'])).rejects.toThrow(errors_js_1.GitDriveError);
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const restore_js_1 = require("../../commands/restore.js");
4
+ const errors_js_1 = require("../../errors.js");
5
+ const memfs_1 = require("memfs");
6
+ // Mock fs
7
+ jest.mock('fs', () => {
8
+ const { fs } = require('memfs');
9
+ return fs;
10
+ });
11
+ // Mock config
12
+ jest.mock('../../config.js', () => ({
13
+ requireConfig: jest.fn(),
14
+ assertDriveMounted: jest.fn(),
15
+ getDriveStorePath: jest.fn((drivePath) => `${drivePath}/.git-drive`),
16
+ }));
17
+ // Mock git
18
+ jest.mock('../../git.js', () => ({
19
+ git: jest.fn(),
20
+ }));
21
+ // Mock server
22
+ jest.mock('../../server.js', () => ({
23
+ ensureServerRunning: jest.fn(),
24
+ }));
25
+ const git_js_1 = require("../../git.js");
26
+ const config_js_1 = require("../../config.js");
27
+ const mockGit = git_js_1.git;
28
+ const mockRequireConfig = config_js_1.requireConfig;
29
+ const mockAssertDriveMounted = config_js_1.assertDriveMounted;
30
+ describe('restore command', () => {
31
+ let consoleSpy;
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ memfs_1.vol.reset();
35
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
36
+ mockRequireConfig.mockReturnValue({ drivePath: '/Volumes/MyUSB' });
37
+ mockAssertDriveMounted.mockImplementation(() => { });
38
+ mockGit.mockImplementation(() => { });
39
+ });
40
+ afterEach(() => {
41
+ consoleSpy.mockRestore();
42
+ });
43
+ it('should throw error when no project name provided', () => {
44
+ expect(() => (0, restore_js_1.restore)([])).toThrow(errors_js_1.GitDriveError);
45
+ expect(() => (0, restore_js_1.restore)([])).toThrow('Usage: git drive restore <project-name> [target-dir]');
46
+ });
47
+ it('should throw error when project not found on drive', () => {
48
+ memfs_1.vol.fromJSON({
49
+ '/Volumes/MyUSB/.git-drive': '',
50
+ });
51
+ expect(() => (0, restore_js_1.restore)(['nonexistent-project'])).toThrow(errors_js_1.GitDriveError);
52
+ expect(() => (0, restore_js_1.restore)(['nonexistent-project'])).toThrow("Project 'nonexistent-project' not found on drive.");
53
+ });
54
+ it('should throw error when target directory already exists', () => {
55
+ memfs_1.vol.fromJSON({
56
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
57
+ '/home/user/my-project': '',
58
+ });
59
+ expect(() => (0, restore_js_1.restore)(['my-project', '/home/user/my-project'])).toThrow(errors_js_1.GitDriveError);
60
+ expect(() => (0, restore_js_1.restore)(['my-project', '/home/user/my-project'])).toThrow('Directory already exists');
61
+ });
62
+ it('should clone the bare repo to target directory', () => {
63
+ memfs_1.vol.fromJSON({
64
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
65
+ });
66
+ (0, restore_js_1.restore)(['my-project', '/home/user/restored-project']);
67
+ expect(mockGit).toHaveBeenCalledWith(expect.stringContaining('clone /Volumes/MyUSB/.git-drive/my-project.git'));
68
+ });
69
+ it('should rename origin remote to drive', () => {
70
+ memfs_1.vol.fromJSON({
71
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
72
+ });
73
+ (0, restore_js_1.restore)(['my-project', '/home/user/restored-project']);
74
+ expect(mockGit).toHaveBeenCalledWith(expect.stringContaining('remote rename origin drive'), expect.any(String));
75
+ });
76
+ it('should use project name as target directory if not specified', () => {
77
+ memfs_1.vol.fromJSON({
78
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
79
+ });
80
+ (0, restore_js_1.restore)(['my-project']);
81
+ // Should call git clone with the project name
82
+ expect(mockGit).toHaveBeenCalledWith(expect.stringMatching(/clone.*my-project\.git/), undefined);
83
+ });
84
+ it('should log success message', () => {
85
+ memfs_1.vol.fromJSON({
86
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
87
+ });
88
+ (0, restore_js_1.restore)(['my-project']);
89
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Restored'));
90
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('my-project'));
91
+ });
92
+ it('should work with custom target directory', () => {
93
+ memfs_1.vol.fromJSON({
94
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
95
+ });
96
+ (0, restore_js_1.restore)(['my-project', '/custom/target']);
97
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('/custom/target'));
98
+ });
99
+ });
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const memfs_1 = require("memfs");
4
+ // Mock fs
5
+ jest.mock('fs', () => {
6
+ const { fs } = require('memfs');
7
+ return fs;
8
+ });
9
+ // Mock node-disk-info
10
+ jest.mock('node-disk-info', () => ({
11
+ getDiskInfo: jest.fn(),
12
+ }));
13
+ // Mock git
14
+ jest.mock('../../git.js', () => ({
15
+ isGitRepo: jest.fn(),
16
+ getProjectName: jest.fn(),
17
+ getRemoteUrl: jest.fn(),
18
+ }));
19
+ // Mock server
20
+ jest.mock('../../server.js', () => ({
21
+ ensureServerRunning: jest.fn(),
22
+ }));
23
+ // Mock os
24
+ jest.mock('os', () => ({
25
+ homedir: () => '/home/testuser',
26
+ }));
27
+ const node_disk_info_1 = require("node-disk-info");
28
+ const git_js_1 = require("../../git.js");
29
+ const status_js_1 = require("../../commands/status.js");
30
+ const mockGetDiskInfo = node_disk_info_1.getDiskInfo;
31
+ const mockIsGitRepo = git_js_1.isGitRepo;
32
+ const mockGetProjectName = git_js_1.getProjectName;
33
+ const mockGetRemoteUrl = git_js_1.getRemoteUrl;
34
+ describe('status command', () => {
35
+ let consoleSpy;
36
+ const originalPlatform = process.platform;
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ memfs_1.vol.reset();
40
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
41
+ mockIsGitRepo.mockReturnValue(false);
42
+ Object.defineProperty(process, 'platform', { value: 'darwin', writable: true });
43
+ });
44
+ afterEach(() => {
45
+ consoleSpy.mockRestore();
46
+ Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true });
47
+ });
48
+ it('should display connected drives section', async () => {
49
+ mockGetDiskInfo.mockResolvedValue([
50
+ { mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 32000000, available: 16000000 },
51
+ ]);
52
+ memfs_1.vol.fromJSON({
53
+ '/home/testuser/.config/git-drive/links.json': '{}',
54
+ '/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
55
+ });
56
+ await (0, status_js_1.status)([]);
57
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Connected Drives'));
58
+ });
59
+ it('should display no external drives message when none found', async () => {
60
+ mockGetDiskInfo.mockResolvedValue([]);
61
+ memfs_1.vol.fromJSON({
62
+ '/home/testuser/.config/git-drive/links.json': '{}',
63
+ });
64
+ await (0, status_js_1.status)([]);
65
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No external drives connected'));
66
+ });
67
+ it('should display registered repositories section', async () => {
68
+ mockGetDiskInfo.mockResolvedValue([]);
69
+ memfs_1.vol.fromJSON({
70
+ '/home/testuser/.config/git-drive/links.json': JSON.stringify({
71
+ '/home/user/project1': {
72
+ mountpoint: '/Volumes/MyUSB',
73
+ repoName: 'project1.git',
74
+ linkedAt: '2024-01-01T00:00:00.000Z',
75
+ },
76
+ }),
77
+ '/home/user/project1': '',
78
+ '/Volumes/MyUSB': '',
79
+ });
80
+ await (0, status_js_1.status)([]);
81
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Registered Repositories'));
82
+ });
83
+ it('should show NOT CONNECTED status when drive is not mounted', async () => {
84
+ mockGetDiskInfo.mockResolvedValue([]);
85
+ memfs_1.vol.fromJSON({
86
+ '/home/testuser/.config/git-drive/links.json': JSON.stringify({
87
+ '/home/user/project1': {
88
+ mountpoint: '/Volumes/MyUSB',
89
+ repoName: 'project1.git',
90
+ linkedAt: '2024-01-01T00:00:00.000Z',
91
+ },
92
+ }),
93
+ '/home/user/project1': '',
94
+ });
95
+ await (0, status_js_1.status)([]);
96
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('NOT CONNECTED'));
97
+ });
98
+ it('should show NOT FOUND status when local directory does not exist', async () => {
99
+ mockGetDiskInfo.mockResolvedValue([]);
100
+ memfs_1.vol.fromJSON({
101
+ '/home/testuser/.config/git-drive/links.json': JSON.stringify({
102
+ '/home/user/nonexistent': {
103
+ mountpoint: '/Volumes/MyUSB',
104
+ repoName: 'project.git',
105
+ linkedAt: '2024-01-01T00:00:00.000Z',
106
+ },
107
+ }),
108
+ '/Volumes/MyUSB': '',
109
+ });
110
+ await (0, status_js_1.status)([]);
111
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('NOT FOUND'));
112
+ });
113
+ it('should display current repository section when in a git repo', async () => {
114
+ mockGetDiskInfo.mockResolvedValue([]);
115
+ mockIsGitRepo.mockReturnValue(true);
116
+ mockGetProjectName.mockReturnValue('my-project');
117
+ mockGetRemoteUrl.mockReturnValue('/Volumes/MyUSB/.git-drive/my-project.git');
118
+ memfs_1.vol.fromJSON({
119
+ '/home/testuser/.config/git-drive/links.json': '{}',
120
+ });
121
+ await (0, status_js_1.status)([]);
122
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Current Repository'));
123
+ });
124
+ it('should show no gd remote message when not linked', async () => {
125
+ mockGetDiskInfo.mockResolvedValue([]);
126
+ mockIsGitRepo.mockReturnValue(true);
127
+ mockGetProjectName.mockReturnValue('my-project');
128
+ mockGetRemoteUrl.mockReturnValue(null);
129
+ memfs_1.vol.fromJSON({
130
+ '/home/testuser/.config/git-drive/links.json': '{}',
131
+ });
132
+ await (0, status_js_1.status)([]);
133
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("No 'gd' remote configured"));
134
+ });
135
+ it('should display server status section', async () => {
136
+ mockGetDiskInfo.mockResolvedValue([]);
137
+ memfs_1.vol.fromJSON({
138
+ '/home/testuser/.config/git-drive/links.json': '{}',
139
+ });
140
+ await (0, status_js_1.status)([]);
141
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Server'));
142
+ });
143
+ it('should handle errors gracefully', async () => {
144
+ mockGetDiskInfo.mockRejectedValue(new Error('Failed to get disk info'));
145
+ memfs_1.vol.fromJSON({
146
+ '/home/testuser/.config/git-drive/links.json': '{}',
147
+ });
148
+ await (0, status_js_1.status)([]);
149
+ expect(consoleSpy).toHaveBeenCalledWith('Error detecting drives:', expect.any(Error));
150
+ });
151
+ });