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