git-drive 0.1.6 ā 0.1.7
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/.github/workflows/ci.yml +77 -0
- package/.planning/codebase/ARCHITECTURE.md +151 -0
- package/.planning/codebase/CONCERNS.md +191 -0
- package/.planning/codebase/CONVENTIONS.md +169 -0
- package/.planning/codebase/INTEGRATIONS.md +94 -0
- package/.planning/codebase/STACK.md +77 -0
- package/.planning/codebase/STRUCTURE.md +157 -0
- package/.planning/codebase/TESTING.md +156 -0
- package/Dockerfile.cli +30 -0
- package/Dockerfile.server +32 -0
- package/README.md +157 -0
- package/docker-compose.yml +48 -0
- package/package.json +20 -55
- package/packages/cli/Dockerfile +26 -0
- package/packages/cli/jest.config.js +26 -0
- package/packages/cli/package.json +65 -0
- package/packages/cli/src/__tests__/commands/companion.test.ts +152 -0
- package/packages/cli/src/__tests__/commands/init.test.ts +154 -0
- package/packages/cli/src/__tests__/commands/list.test.ts +122 -0
- package/packages/cli/src/__tests__/commands/push.test.ts +155 -0
- package/packages/cli/src/__tests__/commands/restore.test.ts +135 -0
- package/packages/cli/src/__tests__/commands/status.test.ts +199 -0
- package/packages/cli/src/__tests__/config.test.ts +198 -0
- package/packages/cli/src/__tests__/e2e.test.ts +125 -0
- package/packages/cli/src/__tests__/errors.test.ts +66 -0
- package/packages/cli/src/__tests__/git.test.ts +250 -0
- package/packages/cli/src/__tests__/server.test.ts +371 -0
- package/packages/cli/src/commands/archive.ts +39 -0
- package/packages/cli/src/commands/companion.ts +205 -0
- package/packages/cli/src/commands/init.ts +130 -0
- package/packages/cli/src/commands/link.ts +151 -0
- package/packages/cli/src/commands/list.ts +94 -0
- package/packages/cli/src/commands/push.ts +77 -0
- package/packages/cli/src/commands/restore.ts +36 -0
- package/packages/cli/src/commands/status.ts +127 -0
- package/packages/cli/src/config.ts +73 -0
- package/packages/cli/src/errors.ts +23 -0
- package/packages/cli/src/git.ts +60 -0
- package/packages/cli/src/index.ts +129 -0
- package/packages/cli/src/server.ts +700 -0
- package/packages/cli/tsconfig.json +13 -0
- package/packages/git-drive-docker/package.json +15 -0
- package/packages/server/package.json +44 -0
- package/packages/server/src/index.ts +569 -0
- package/packages/server/tsconfig.json +9 -0
- package/packages/ui/README.md +73 -0
- package/packages/ui/eslint.config.js +23 -0
- package/packages/ui/index.html +13 -0
- package/packages/ui/package.json +52 -0
- package/packages/ui/postcss.config.js +6 -0
- package/packages/ui/public/vite.svg +1 -0
- package/packages/ui/src/App.css +23 -0
- package/packages/ui/src/App.test.tsx +248 -0
- package/packages/ui/src/App.tsx +803 -0
- package/packages/ui/src/assets/react.svg +8 -0
- package/packages/ui/src/assets/vite.svg +3 -0
- package/packages/ui/src/index.css +37 -0
- package/packages/ui/src/main.tsx +14 -0
- package/packages/ui/src/test/setup.ts +1 -0
- package/packages/ui/tailwind.config.js +11 -0
- package/packages/ui/tsconfig.app.json +28 -0
- package/packages/ui/tsconfig.json +26 -0
- package/packages/ui/tsconfig.node.json +12 -0
- package/packages/ui/vite.config.ts +7 -0
- package/packages/ui/vitest.config.ts +20 -0
- package/pnpm-workspace.yaml +4 -0
- package/rewrite_app.js +731 -0
- package/tsconfig.json +14 -0
- package/dist/__tests__/commands/init.test.js +0 -123
- package/dist/__tests__/commands/list.test.js +0 -91
- package/dist/__tests__/commands/push.test.js +0 -128
- package/dist/__tests__/commands/restore.test.js +0 -99
- package/dist/__tests__/commands/status.test.js +0 -151
- package/dist/__tests__/config.test.js +0 -150
- package/dist/__tests__/e2e.test.js +0 -107
- package/dist/__tests__/errors.test.js +0 -56
- package/dist/__tests__/git.test.js +0 -184
- package/dist/__tests__/server.test.js +0 -310
- package/dist/commands/archive.js +0 -32
- package/dist/commands/init.js +0 -55
- package/dist/commands/link.js +0 -175
- package/dist/commands/list.js +0 -83
- package/dist/commands/push.js +0 -112
- package/dist/commands/restore.js +0 -30
- package/dist/commands/status.js +0 -116
- package/dist/config.js +0 -62
- package/dist/errors.js +0 -30
- package/dist/git.js +0 -67
- package/dist/index.js +0 -108
- package/dist/server.js +0 -535
- /package/{ui ā packages/cli/ui}/assets/index-Br8xQbJz.js +0 -0
- /package/{ui ā packages/cli/ui}/assets/index-Cc2q1t5k.js +0 -0
- /package/{ui ā packages/cli/ui}/assets/index-DrL7ojPA.css +0 -0
- /package/{ui ā packages/cli/ui}/index.html +0 -0
- /package/{ui ā packages/cli/ui}/vite.svg +0 -0
|
@@ -1,310 +0,0 @@
|
|
|
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 supertest_1 = __importDefault(require("supertest"));
|
|
7
|
-
const express_1 = __importDefault(require("express"));
|
|
8
|
-
const memfs_1 = require("memfs");
|
|
9
|
-
// Mock fs
|
|
10
|
-
jest.mock('fs', () => {
|
|
11
|
-
const { fs } = require('memfs');
|
|
12
|
-
return fs;
|
|
13
|
-
});
|
|
14
|
-
// Mock child_process
|
|
15
|
-
jest.mock('child_process', () => ({
|
|
16
|
-
execSync: jest.fn(),
|
|
17
|
-
}));
|
|
18
|
-
// Mock node-disk-info
|
|
19
|
-
jest.mock('node-disk-info', () => ({
|
|
20
|
-
getDiskInfo: jest.fn(),
|
|
21
|
-
}));
|
|
22
|
-
const child_process_1 = require("child_process");
|
|
23
|
-
const node_disk_info_1 = require("node-disk-info");
|
|
24
|
-
const mockExecSync = child_process_1.execSync;
|
|
25
|
-
const mockGetDiskInfo = node_disk_info_1.getDiskInfo;
|
|
26
|
-
// Create a test app with the same routes as server.ts
|
|
27
|
-
function createTestApp() {
|
|
28
|
-
const app = (0, express_1.default)();
|
|
29
|
-
app.use(express_1.default.json());
|
|
30
|
-
// Health check endpoint
|
|
31
|
-
app.get('/api/health', (_req, res) => {
|
|
32
|
-
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
33
|
-
});
|
|
34
|
-
// List all connected drives
|
|
35
|
-
app.get('/api/drives', async (_req, res) => {
|
|
36
|
-
try {
|
|
37
|
-
const drives = await mockGetDiskInfo();
|
|
38
|
-
const result = drives
|
|
39
|
-
.filter((d) => {
|
|
40
|
-
const mp = d.mounted;
|
|
41
|
-
if (!mp)
|
|
42
|
-
return false;
|
|
43
|
-
if (mp === '/' || mp === '100%')
|
|
44
|
-
return false;
|
|
45
|
-
if (process.platform === 'darwin') {
|
|
46
|
-
return mp.startsWith('/Volumes/') && !mp.startsWith('/Volumes/Recovery');
|
|
47
|
-
}
|
|
48
|
-
if (mp.startsWith('/sys') || mp.startsWith('/proc') || mp.startsWith('/run') || mp.startsWith('/snap') || mp.startsWith('/boot'))
|
|
49
|
-
return false;
|
|
50
|
-
if (d.filesystem === 'tmpfs' || d.filesystem === 'devtmpfs' || d.filesystem === 'udev' || d.filesystem === 'overlay')
|
|
51
|
-
return false;
|
|
52
|
-
return true;
|
|
53
|
-
})
|
|
54
|
-
.map((d) => ({
|
|
55
|
-
device: d.filesystem,
|
|
56
|
-
description: d.mounted,
|
|
57
|
-
size: d.blocks ? parseInt(d.blocks) * 1024 : 0,
|
|
58
|
-
isRemovable: true,
|
|
59
|
-
isSystem: d.mounted === '/',
|
|
60
|
-
mountpoints: [d.mounted],
|
|
61
|
-
hasGitDrive: memfs_1.vol.existsSync(`${d.mounted}/.git-drive`),
|
|
62
|
-
}));
|
|
63
|
-
res.json(result);
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
res.status(500).json({ error: 'Failed to list drives' });
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
// List repos on a specific drive
|
|
70
|
-
app.get('/api/drives/:mountpoint/repos', (req, res) => {
|
|
71
|
-
try {
|
|
72
|
-
const mountpoint = decodeURIComponent(req.params.mountpoint);
|
|
73
|
-
const gitDrivePath = `${mountpoint}/.git-drive`;
|
|
74
|
-
if (!memfs_1.vol.existsSync(mountpoint)) {
|
|
75
|
-
res.status(404).json({ error: 'Drive not found or not mounted' });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const entries = memfs_1.vol.existsSync(gitDrivePath) ? memfs_1.vol.readdirSync(gitDrivePath) : [];
|
|
79
|
-
const repos = entries
|
|
80
|
-
.filter((entry) => {
|
|
81
|
-
const entryPath = `${gitDrivePath}/${entry}`;
|
|
82
|
-
const stat = memfs_1.vol.statSync(entryPath);
|
|
83
|
-
const isDir = stat.isDirectory();
|
|
84
|
-
return isDir && (entry.endsWith('.git') || memfs_1.vol.existsSync(`${entryPath}/HEAD`));
|
|
85
|
-
})
|
|
86
|
-
.map((entry) => {
|
|
87
|
-
const entryPath = `${gitDrivePath}/${entry}`;
|
|
88
|
-
const stat = memfs_1.vol.statSync(entryPath);
|
|
89
|
-
return {
|
|
90
|
-
name: entry.replace(/\.git$/, ''),
|
|
91
|
-
path: entryPath,
|
|
92
|
-
lastModified: stat.mtime.toISOString(),
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
res.json({
|
|
96
|
-
mountpoint,
|
|
97
|
-
gitDrivePath,
|
|
98
|
-
initialized: memfs_1.vol.existsSync(gitDrivePath),
|
|
99
|
-
repos,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
res.status(500).json({ error: 'Failed to list repos' });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
// Initialize git-drive on a drive
|
|
107
|
-
app.post('/api/drives/:mountpoint/init', (req, res) => {
|
|
108
|
-
try {
|
|
109
|
-
const mountpoint = decodeURIComponent(req.params.mountpoint);
|
|
110
|
-
if (!memfs_1.vol.existsSync(mountpoint)) {
|
|
111
|
-
res.status(404).json({ error: 'Drive not found or not mounted' });
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const gitDrivePath = `${mountpoint}/.git-drive`;
|
|
115
|
-
memfs_1.vol.mkdirSync(gitDrivePath, { recursive: true });
|
|
116
|
-
res.json({
|
|
117
|
-
mountpoint,
|
|
118
|
-
gitDrivePath,
|
|
119
|
-
message: 'Git Drive initialized on this drive',
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
res.status(500).json({ error: err.message || 'Failed to initialize drive' });
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
// Create a new bare repo on a drive
|
|
127
|
-
app.post('/api/drives/:mountpoint/repos', (req, res) => {
|
|
128
|
-
try {
|
|
129
|
-
const mountpoint = decodeURIComponent(req.params.mountpoint);
|
|
130
|
-
const { name } = req.body;
|
|
131
|
-
if (!name || typeof name !== 'string') {
|
|
132
|
-
res.status(400).json({ error: 'Repo name is required' });
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const safeName = name.replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
136
|
-
if (!memfs_1.vol.existsSync(mountpoint)) {
|
|
137
|
-
res.status(404).json({ error: 'Drive not found or not mounted' });
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const gitDrivePath = `${mountpoint}/.git-drive`;
|
|
141
|
-
if (!memfs_1.vol.existsSync(gitDrivePath)) {
|
|
142
|
-
memfs_1.vol.mkdirSync(gitDrivePath, { recursive: true });
|
|
143
|
-
}
|
|
144
|
-
const repoName = safeName.endsWith('.git') ? safeName : `${safeName}.git`;
|
|
145
|
-
const repoPath = `${gitDrivePath}/${repoName}`;
|
|
146
|
-
if (memfs_1.vol.existsSync(repoPath)) {
|
|
147
|
-
res.status(409).json({ error: 'Repository already exists' });
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
// Mock git init --bare
|
|
151
|
-
memfs_1.vol.mkdirSync(repoPath, { recursive: true });
|
|
152
|
-
memfs_1.vol.writeFileSync(`${repoPath}/HEAD`, 'ref: refs/heads/main');
|
|
153
|
-
res.status(201).json({
|
|
154
|
-
name: safeName.replace(/\.git$/, ''),
|
|
155
|
-
path: repoPath,
|
|
156
|
-
message: `Bare repository created: ${repoName}`,
|
|
157
|
-
remoteUrl: repoPath,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
res.status(500).json({ error: 'Failed to create repository' });
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
// Delete a repo from a drive
|
|
165
|
-
app.delete('/api/drives/:mountpoint/repos/:repoName', (req, res) => {
|
|
166
|
-
try {
|
|
167
|
-
const mountpoint = decodeURIComponent(req.params.mountpoint);
|
|
168
|
-
const repoName = decodeURIComponent(req.params.repoName);
|
|
169
|
-
const gitDrivePath = `${mountpoint}/.git-drive`;
|
|
170
|
-
const bareRepoName = repoName.endsWith('.git') ? repoName : `${repoName}.git`;
|
|
171
|
-
const repoPath = `${gitDrivePath}/${bareRepoName}`;
|
|
172
|
-
if (!memfs_1.vol.existsSync(repoPath)) {
|
|
173
|
-
res.status(404).json({ error: 'Repository not found' });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
// Delete the repo directory
|
|
177
|
-
memfs_1.vol.rmSync(repoPath, { recursive: true, force: true });
|
|
178
|
-
res.json({ message: `Repository '${repoName}' deleted` });
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
res.status(500).json({ error: 'Failed to delete repository' });
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
return app;
|
|
185
|
-
}
|
|
186
|
-
describe('Server API', () => {
|
|
187
|
-
let app;
|
|
188
|
-
beforeEach(() => {
|
|
189
|
-
jest.clearAllMocks();
|
|
190
|
-
memfs_1.vol.reset();
|
|
191
|
-
app = createTestApp();
|
|
192
|
-
});
|
|
193
|
-
describe('GET /api/health', () => {
|
|
194
|
-
it('should return health status', async () => {
|
|
195
|
-
const response = await (0, supertest_1.default)(app).get('/api/health');
|
|
196
|
-
expect(response.status).toBe(200);
|
|
197
|
-
expect(response.body).toHaveProperty('status', 'ok');
|
|
198
|
-
expect(response.body).toHaveProperty('timestamp');
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
describe('GET /api/drives', () => {
|
|
202
|
-
it('should return list of drives', async () => {
|
|
203
|
-
mockGetDiskInfo.mockResolvedValue([
|
|
204
|
-
{ mounted: '/Volumes/MyUSB', filesystem: 'MyUSB', blocks: 32000000, available: 16000000 },
|
|
205
|
-
{ mounted: '/Volumes/External', filesystem: 'External', blocks: 1000000000, available: 500000000 },
|
|
206
|
-
]);
|
|
207
|
-
memfs_1.vol.fromJSON({
|
|
208
|
-
'/Volumes/MyUSB/.git-drive': '',
|
|
209
|
-
'/Volumes/External': '',
|
|
210
|
-
});
|
|
211
|
-
const response = await (0, supertest_1.default)(app).get('/api/drives');
|
|
212
|
-
expect(response.status).toBe(200);
|
|
213
|
-
expect(response.body).toHaveLength(2);
|
|
214
|
-
expect(response.body[0]).toHaveProperty('device');
|
|
215
|
-
expect(response.body[0]).toHaveProperty('mountpoints');
|
|
216
|
-
expect(response.body[0]).toHaveProperty('hasGitDrive');
|
|
217
|
-
});
|
|
218
|
-
it('should handle errors gracefully', async () => {
|
|
219
|
-
mockGetDiskInfo.mockRejectedValue(new Error('Failed to get drives'));
|
|
220
|
-
const response = await (0, supertest_1.default)(app).get('/api/drives');
|
|
221
|
-
expect(response.status).toBe(500);
|
|
222
|
-
expect(response.body).toHaveProperty('error', 'Failed to list drives');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
describe('GET /api/drives/:mountpoint/repos', () => {
|
|
226
|
-
it('should return repos on a drive', async () => {
|
|
227
|
-
memfs_1.vol.fromJSON({
|
|
228
|
-
'/Volumes/MyUSB/.git-drive/my-project.git/HEAD': 'ref: refs/heads/main',
|
|
229
|
-
'/Volumes/MyUSB/.git-drive/another-repo.git/HEAD': 'ref: refs/heads/main',
|
|
230
|
-
});
|
|
231
|
-
const response = await (0, supertest_1.default)(app).get('/api/drives/%2FVolumes%2FMyUSB/repos');
|
|
232
|
-
expect(response.status).toBe(200);
|
|
233
|
-
expect(response.body).toHaveProperty('mountpoint', '/Volumes/MyUSB');
|
|
234
|
-
expect(response.body).toHaveProperty('initialized', true);
|
|
235
|
-
expect(response.body.repos).toHaveLength(2);
|
|
236
|
-
});
|
|
237
|
-
it('should return 404 for non-existent drive', async () => {
|
|
238
|
-
const response = await (0, supertest_1.default)(app).get('/api/drives/%2FVolumes%2FNonExistent/repos');
|
|
239
|
-
expect(response.status).toBe(404);
|
|
240
|
-
expect(response.body).toHaveProperty('error', 'Drive not found or not mounted');
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
describe('POST /api/drives/:mountpoint/init', () => {
|
|
244
|
-
it('should initialize git-drive on a drive', async () => {
|
|
245
|
-
memfs_1.vol.fromJSON({
|
|
246
|
-
'/Volumes/MyUSB': '',
|
|
247
|
-
});
|
|
248
|
-
const response = await (0, supertest_1.default)(app).post('/api/drives/%2FVolumes%2FMyUSB/init');
|
|
249
|
-
expect(response.status).toBe(200);
|
|
250
|
-
expect(response.body).toHaveProperty('message', 'Git Drive initialized on this drive');
|
|
251
|
-
});
|
|
252
|
-
it('should return 404 for non-existent drive', async () => {
|
|
253
|
-
const response = await (0, supertest_1.default)(app).post('/api/drives/%2FVolumes%2FNonExistent/init');
|
|
254
|
-
expect(response.status).toBe(404);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
describe('POST /api/drives/:mountpoint/repos', () => {
|
|
258
|
-
it('should create a new repository', async () => {
|
|
259
|
-
memfs_1.vol.fromJSON({
|
|
260
|
-
'/Volumes/MyUSB/.git-drive': '',
|
|
261
|
-
});
|
|
262
|
-
const response = await (0, supertest_1.default)(app)
|
|
263
|
-
.post('/api/drives/%2FVolumes%2FMyUSB/repos')
|
|
264
|
-
.send({ name: 'new-project' });
|
|
265
|
-
expect(response.status).toBe(201);
|
|
266
|
-
expect(response.body).toHaveProperty('name', 'new-project');
|
|
267
|
-
});
|
|
268
|
-
it('should sanitize repository name', async () => {
|
|
269
|
-
memfs_1.vol.fromJSON({
|
|
270
|
-
'/Volumes/MyUSB/.git-drive': '',
|
|
271
|
-
});
|
|
272
|
-
const response = await (0, supertest_1.default)(app)
|
|
273
|
-
.post('/api/drives/%2FVolumes%2FMyUSB/repos')
|
|
274
|
-
.send({ name: 'my project with spaces!' });
|
|
275
|
-
expect(response.status).toBe(201);
|
|
276
|
-
expect(response.body.name).toBe('my-project-with-spaces-');
|
|
277
|
-
});
|
|
278
|
-
it('should return 400 if name is missing', async () => {
|
|
279
|
-
const response = await (0, supertest_1.default)(app)
|
|
280
|
-
.post('/api/drives/%2FVolumes%2FMyUSB/repos')
|
|
281
|
-
.send({});
|
|
282
|
-
expect(response.status).toBe(400);
|
|
283
|
-
});
|
|
284
|
-
it('should return 409 if repository already exists', async () => {
|
|
285
|
-
memfs_1.vol.fromJSON({
|
|
286
|
-
'/Volumes/MyUSB/.git-drive/existing-project.git/HEAD': '',
|
|
287
|
-
});
|
|
288
|
-
const response = await (0, supertest_1.default)(app)
|
|
289
|
-
.post('/api/drives/%2FVolumes%2FMyUSB/repos')
|
|
290
|
-
.send({ name: 'existing-project' });
|
|
291
|
-
expect(response.status).toBe(409);
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
describe('DELETE /api/drives/:mountpoint/repos/:repoName', () => {
|
|
295
|
-
it('should delete a repository', async () => {
|
|
296
|
-
memfs_1.vol.fromJSON({
|
|
297
|
-
'/Volumes/MyUSB/.git-drive/my-project.git/HEAD': '',
|
|
298
|
-
});
|
|
299
|
-
const response = await (0, supertest_1.default)(app).delete('/api/drives/%2FVolumes%2FMyUSB/repos/my-project');
|
|
300
|
-
expect(response.status).toBe(200);
|
|
301
|
-
});
|
|
302
|
-
it('should return 404 for non-existent repository', async () => {
|
|
303
|
-
memfs_1.vol.fromJSON({
|
|
304
|
-
'/Volumes/MyUSB/.git-drive': '',
|
|
305
|
-
});
|
|
306
|
-
const response = await (0, supertest_1.default)(app).delete('/api/drives/%2FVolumes%2FMyUSB/repos/nonexistent');
|
|
307
|
-
expect(response.status).toBe(404);
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
package/dist/commands/archive.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.archive = archive;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const config_js_1 = require("../config.js");
|
|
6
|
-
const git_js_1 = require("../git.js");
|
|
7
|
-
const push_js_1 = require("./push.js");
|
|
8
|
-
const errors_js_1 = require("../errors.js");
|
|
9
|
-
function archive(args) {
|
|
10
|
-
if (!(0, git_js_1.isGitRepo)()) {
|
|
11
|
-
throw new errors_js_1.GitDriveError("Not in a git repository.");
|
|
12
|
-
}
|
|
13
|
-
const force = args.includes("--force");
|
|
14
|
-
// Check for uncommitted changes
|
|
15
|
-
if (!force) {
|
|
16
|
-
const status = (0, git_js_1.git)("status --porcelain");
|
|
17
|
-
if (status) {
|
|
18
|
-
throw new errors_js_1.GitDriveError("Working tree has uncommitted changes.\nCommit first or use --force to archive anyway.");
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
const config = (0, config_js_1.requireConfig)();
|
|
22
|
-
(0, config_js_1.assertDriveMounted)(config.drivePath);
|
|
23
|
-
const projectName = (0, git_js_1.getProjectName)();
|
|
24
|
-
const repoRoot = (0, git_js_1.getRepoRoot)();
|
|
25
|
-
// Push first
|
|
26
|
-
(0, push_js_1.push)([]);
|
|
27
|
-
// Remove local copy
|
|
28
|
-
process.chdir("..");
|
|
29
|
-
(0, fs_1.rmSync)(repoRoot, { recursive: true, force: true });
|
|
30
|
-
console.log(`Archived: ${projectName}`);
|
|
31
|
-
console.log(`Restore with: git drive restore ${projectName}`);
|
|
32
|
-
}
|
package/dist/commands/init.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
exports.init = init;
|
|
7
|
-
const fs_1 = require("fs");
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
const prompts_1 = __importDefault(require("prompts"));
|
|
10
|
-
const config_js_1 = require("../config.js");
|
|
11
|
-
const git_js_1 = require("../git.js");
|
|
12
|
-
const errors_js_1 = require("../errors.js");
|
|
13
|
-
async function init(args) {
|
|
14
|
-
let drivePath;
|
|
15
|
-
const rawPath = args[0];
|
|
16
|
-
if (!rawPath) {
|
|
17
|
-
// No argument provided - prompt user to select a drive
|
|
18
|
-
const drives = await (0, git_js_1.listDrives)();
|
|
19
|
-
if (drives.length === 0) {
|
|
20
|
-
throw new errors_js_1.GitDriveError("No external drives found. Please connect a drive and try again.");
|
|
21
|
-
}
|
|
22
|
-
const { selectedDrive } = await (0, prompts_1.default)({
|
|
23
|
-
type: "select",
|
|
24
|
-
name: "selectedDrive",
|
|
25
|
-
message: "Select a drive to initialize git-drive:",
|
|
26
|
-
choices: drives.map((d) => ({
|
|
27
|
-
title: `${d.filesystem} (${d.mounted}) - ${Math.round((d.available / d.blocks) * 100)}% free`,
|
|
28
|
-
value: d.mounted,
|
|
29
|
-
})),
|
|
30
|
-
});
|
|
31
|
-
if (!selectedDrive) {
|
|
32
|
-
console.log("Operation cancelled.");
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
drivePath = (0, path_1.resolve)(selectedDrive);
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
drivePath = (0, path_1.resolve)(rawPath);
|
|
39
|
-
}
|
|
40
|
-
if (!(0, fs_1.existsSync)(drivePath)) {
|
|
41
|
-
throw new errors_js_1.GitDriveError(`Path not found: ${drivePath}\nIs the drive mounted?`);
|
|
42
|
-
}
|
|
43
|
-
const stat = (0, fs_1.statSync)(drivePath);
|
|
44
|
-
if (!stat.isDirectory()) {
|
|
45
|
-
throw new errors_js_1.GitDriveError(`Path is not a directory: ${drivePath}`);
|
|
46
|
-
}
|
|
47
|
-
const storePath = (0, config_js_1.getDriveStorePath)(drivePath);
|
|
48
|
-
if (!(0, fs_1.existsSync)(storePath)) {
|
|
49
|
-
(0, fs_1.mkdirSync)(storePath, { recursive: true });
|
|
50
|
-
}
|
|
51
|
-
(0, config_js_1.saveConfig)({ drivePath });
|
|
52
|
-
console.log(`\nā
Git Drive initialized!`);
|
|
53
|
-
console.log(` Drive: ${drivePath}`);
|
|
54
|
-
console.log(` Store: ${storePath}`);
|
|
55
|
-
}
|
package/dist/commands/link.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.link = link;
|
|
40
|
-
const errors_js_1 = require("../errors.js");
|
|
41
|
-
const config_js_1 = require("../config.js");
|
|
42
|
-
const git_js_1 = require("../git.js");
|
|
43
|
-
const prompts_1 = __importDefault(require("prompts"));
|
|
44
|
-
const fs = __importStar(require("fs"));
|
|
45
|
-
const path = __importStar(require("path"));
|
|
46
|
-
async function link(args) {
|
|
47
|
-
try {
|
|
48
|
-
const drives = await (0, git_js_1.listDrives)();
|
|
49
|
-
// Only fetch drives that actually have .git-drive configured
|
|
50
|
-
const configuredDrives = drives
|
|
51
|
-
.filter((drive) => drive.mounted && drive.mounted !== "/")
|
|
52
|
-
.filter((drive) => fs.existsSync(path.join(drive.mounted, ".git-drive")));
|
|
53
|
-
if (configuredDrives.length === 0) {
|
|
54
|
-
console.log("No initialized git-drives found. Please initialize a drive first.");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
// Check for --drive flag for non-interactive mode
|
|
58
|
-
const driveFlagIndex = args.findIndex((a) => a === "--drive" || a === "-d");
|
|
59
|
-
const drivePath = driveFlagIndex !== -1 ? args[driveFlagIndex + 1] : null;
|
|
60
|
-
const createNew = args.includes("--create") || args.includes("-c");
|
|
61
|
-
let drive;
|
|
62
|
-
if (drivePath) {
|
|
63
|
-
// Non-interactive mode: find the drive by path
|
|
64
|
-
drive = configuredDrives.find((d) => d.mounted === drivePath);
|
|
65
|
-
if (!drive) {
|
|
66
|
-
console.log(`Drive not found at: ${drivePath}`);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Interactive mode
|
|
72
|
-
const result = await (0, prompts_1.default)({
|
|
73
|
-
type: "select",
|
|
74
|
-
name: "drive",
|
|
75
|
-
message: "Select a configured git-drive:",
|
|
76
|
-
choices: configuredDrives.map((d) => ({
|
|
77
|
-
title: `${d.filesystem} (${d.mounted})`,
|
|
78
|
-
value: d,
|
|
79
|
-
})),
|
|
80
|
-
});
|
|
81
|
-
if (!result.drive)
|
|
82
|
-
return;
|
|
83
|
-
drive = result.drive;
|
|
84
|
-
}
|
|
85
|
-
const gitDrivePath = path.join(drive.mounted, ".git-drive");
|
|
86
|
-
const existingRepos = fs.readdirSync(gitDrivePath).filter((entry) => {
|
|
87
|
-
const entryPath = path.join(gitDrivePath, entry);
|
|
88
|
-
return (fs.statSync(entryPath).isDirectory() &&
|
|
89
|
-
(entry.endsWith(".git") || fs.existsSync(path.join(entryPath, "HEAD"))));
|
|
90
|
-
});
|
|
91
|
-
const CREATE_NEW = "__CREATE_NEW__";
|
|
92
|
-
let targetRepoName = null;
|
|
93
|
-
if (createNew) {
|
|
94
|
-
// Non-interactive mode: create new repo with project name
|
|
95
|
-
const defaultName = (0, git_js_1.getProjectName)();
|
|
96
|
-
targetRepoName = defaultName.endsWith(".git") ? defaultName : `${defaultName}.git`;
|
|
97
|
-
const repoPath = path.join(gitDrivePath, targetRepoName);
|
|
98
|
-
if (fs.existsSync(repoPath)) {
|
|
99
|
-
console.log(`Repository ${targetRepoName} already exists in this drive. Linking to existing.`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
(0, git_js_1.git)(`init --bare "${repoPath}"`);
|
|
103
|
-
console.log(`Created new bare repository: ${targetRepoName}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
// Interactive mode
|
|
108
|
-
const { selectedRepo } = await (0, prompts_1.default)({
|
|
109
|
-
type: "select",
|
|
110
|
-
name: "selectedRepo",
|
|
111
|
-
message: "Select an existing repository to link, or create a new one:",
|
|
112
|
-
choices: [
|
|
113
|
-
{ title: "⨠Create new repository...", value: CREATE_NEW },
|
|
114
|
-
...existingRepos.map((repo) => ({
|
|
115
|
-
title: `š ${repo.replace(/\.git$/, "")}`,
|
|
116
|
-
value: repo,
|
|
117
|
-
})),
|
|
118
|
-
],
|
|
119
|
-
});
|
|
120
|
-
if (!selectedRepo)
|
|
121
|
-
return;
|
|
122
|
-
targetRepoName = selectedRepo;
|
|
123
|
-
if (selectedRepo === CREATE_NEW) {
|
|
124
|
-
const defaultName = (0, git_js_1.getProjectName)();
|
|
125
|
-
const { newRepoName } = await (0, prompts_1.default)({
|
|
126
|
-
type: "text",
|
|
127
|
-
name: "newRepoName",
|
|
128
|
-
message: "Enter the new repository name:",
|
|
129
|
-
initial: defaultName,
|
|
130
|
-
});
|
|
131
|
-
if (!newRepoName)
|
|
132
|
-
return;
|
|
133
|
-
targetRepoName = newRepoName.endsWith(".git") ? newRepoName : `${newRepoName}.git`;
|
|
134
|
-
const repoPath = path.join(gitDrivePath, targetRepoName);
|
|
135
|
-
if (fs.existsSync(repoPath)) {
|
|
136
|
-
console.log(`Repository ${targetRepoName} already exists in this drive.`);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
(0, git_js_1.git)(`init --bare "${repoPath}"`);
|
|
140
|
-
console.log(`Created new bare repository: ${targetRepoName}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (!targetRepoName)
|
|
144
|
-
return;
|
|
145
|
-
const repoRoot = (0, git_js_1.getRepoRoot)();
|
|
146
|
-
const finalRepoPath = path.join(gitDrivePath, targetRepoName);
|
|
147
|
-
// Check if remote 'gd' already exists
|
|
148
|
-
let gdExists = false;
|
|
149
|
-
try {
|
|
150
|
-
(0, git_js_1.git)(`remote get-url gd`, repoRoot);
|
|
151
|
-
gdExists = true;
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
// Remote does not exist
|
|
155
|
-
}
|
|
156
|
-
if (gdExists) {
|
|
157
|
-
console.log("Remote 'gd' already exists. Updating it to point to the new drive.");
|
|
158
|
-
(0, git_js_1.git)(`remote set-url gd "${finalRepoPath}"`, repoRoot);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
(0, git_js_1.git)(`remote add gd "${finalRepoPath}"`, repoRoot);
|
|
162
|
-
}
|
|
163
|
-
// Persist to global git-drive registry for the Web UI
|
|
164
|
-
(0, config_js_1.saveLink)(repoRoot, drive.mounted, targetRepoName);
|
|
165
|
-
console.log(`\nā
Successfully linked!`);
|
|
166
|
-
console.log(`Repository: ${targetRepoName.replace(/\.git$/, "")}`);
|
|
167
|
-
console.log(`Drive: ${drive.mounted}`);
|
|
168
|
-
console.log(`\nYou can now push to this remote using:`);
|
|
169
|
-
console.log(` git push gd main`);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
(0, errors_js_1.handleError)(err);
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
}
|
package/dist/commands/list.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.list = list;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const os_1 = require("os");
|
|
7
|
-
const node_disk_info_1 = require("node-disk-info");
|
|
8
|
-
function loadLinks() {
|
|
9
|
-
const linksFile = (0, path_1.join)((0, os_1.homedir)(), ".config", "git-drive", "links.json");
|
|
10
|
-
if (!(0, fs_1.existsSync)(linksFile))
|
|
11
|
-
return {};
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse((0, fs_1.readFileSync)(linksFile, "utf-8"));
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function getGitDrivePath(mountpoint) {
|
|
20
|
-
return (0, path_1.join)(mountpoint, ".git-drive");
|
|
21
|
-
}
|
|
22
|
-
async function list(_args) {
|
|
23
|
-
console.log("Git Drive - Connected Drives\n");
|
|
24
|
-
// Get all connected drives
|
|
25
|
-
let drives = [];
|
|
26
|
-
try {
|
|
27
|
-
drives = await (0, node_disk_info_1.getDiskInfo)();
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
console.error("Error detecting drives:", err);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
// Filter to external/removable drives
|
|
34
|
-
const externalDrives = drives.filter((d) => {
|
|
35
|
-
const mp = d.mounted;
|
|
36
|
-
if (!mp)
|
|
37
|
-
return false;
|
|
38
|
-
if (mp === "/" || mp === "100%")
|
|
39
|
-
return false;
|
|
40
|
-
if (process.platform === "darwin") {
|
|
41
|
-
return mp.startsWith("/Volumes/") && !mp.startsWith("/Volumes/Recovery");
|
|
42
|
-
}
|
|
43
|
-
if (mp.startsWith("/sys") || mp.startsWith("/proc") || mp.startsWith("/run") || mp.startsWith("/snap") || mp.startsWith("/boot"))
|
|
44
|
-
return false;
|
|
45
|
-
if (d.filesystem === "tmpfs" || d.filesystem === "devtmpfs" || d.filesystem === "udev" || d.filesystem === "overlay")
|
|
46
|
-
return false;
|
|
47
|
-
return true;
|
|
48
|
-
});
|
|
49
|
-
if (externalDrives.length === 0) {
|
|
50
|
-
console.log("No external drives detected.");
|
|
51
|
-
console.log("\nConnect an external drive and try again.");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
// Load links to show which drives are registered
|
|
55
|
-
const links = loadLinks();
|
|
56
|
-
const registeredMountpoints = new Set(Object.values(links).map(l => l.mountpoint));
|
|
57
|
-
for (const drive of externalDrives) {
|
|
58
|
-
const mp = drive.mounted;
|
|
59
|
-
const gitDrivePath = getGitDrivePath(mp);
|
|
60
|
-
const hasGitDrive = (0, fs_1.existsSync)(gitDrivePath);
|
|
61
|
-
const isRegistered = registeredMountpoints.has(mp);
|
|
62
|
-
// Count repos on this drive
|
|
63
|
-
let repoCount = 0;
|
|
64
|
-
if (hasGitDrive) {
|
|
65
|
-
try {
|
|
66
|
-
const entries = (0, fs_1.readdirSync)(gitDrivePath).filter(n => n.endsWith(".git") || (0, fs_1.existsSync)((0, path_1.join)(gitDrivePath, n, "HEAD")));
|
|
67
|
-
repoCount = entries.length;
|
|
68
|
-
}
|
|
69
|
-
catch { }
|
|
70
|
-
}
|
|
71
|
-
// Format size
|
|
72
|
-
const sizeGB = drive.blocks ? ((parseInt(drive.blocks) * 1024) / (1024 * 1024 * 1024)).toFixed(1) : "?";
|
|
73
|
-
// Status indicator
|
|
74
|
-
const status = hasGitDrive ? "ā registered" : "ā not registered";
|
|
75
|
-
console.log(` ${mp}`);
|
|
76
|
-
console.log(` Size: ${sizeGB} GB`);
|
|
77
|
-
console.log(` Status: ${status}`);
|
|
78
|
-
console.log(` Repositories: ${repoCount}`);
|
|
79
|
-
console.log();
|
|
80
|
-
}
|
|
81
|
-
console.log(`\n${externalDrives.length} drive${externalDrives.length === 1 ? "" : "s"} detected.`);
|
|
82
|
-
console.log("\nRun 'git-drive link' to link a repo to a drive.");
|
|
83
|
-
}
|