git-drive 0.1.3 → 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.
@@ -0,0 +1,310 @@
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
+ });
@@ -54,17 +54,34 @@ async function link(args) {
54
54
  console.log("No initialized git-drives found. Please initialize a drive first.");
55
55
  return;
56
56
  }
57
- const { drive } = await (0, prompts_1.default)({
58
- type: "select",
59
- name: "drive",
60
- message: "Select a configured git-drive:",
61
- choices: configuredDrives.map((d) => ({
62
- title: `${d.filesystem} (${d.mounted})`,
63
- value: d,
64
- })),
65
- });
66
- if (!drive)
67
- return;
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
+ }
68
85
  const gitDrivePath = path.join(drive.mounted, ".git-drive");
69
86
  const existingRepos = fs.readdirSync(gitDrivePath).filter((entry) => {
70
87
  const entryPath = path.join(gitDrivePath, entry);
@@ -72,40 +89,59 @@ async function link(args) {
72
89
  (entry.endsWith(".git") || fs.existsSync(path.join(entryPath, "HEAD"))));
73
90
  });
74
91
  const CREATE_NEW = "__CREATE_NEW__";
75
- const { selectedRepo } = await (0, prompts_1.default)({
76
- type: "select",
77
- name: "selectedRepo",
78
- message: "Select an existing repository to link, or create a new one:",
79
- choices: [
80
- { title: "✨ Create new repository...", value: CREATE_NEW },
81
- ...existingRepos.map((repo) => ({
82
- title: `📁 ${repo.replace(/\.git$/, "")}`,
83
- value: repo,
84
- })),
85
- ],
86
- });
87
- if (!selectedRepo)
88
- return;
89
- let targetRepoName = selectedRepo;
90
- if (selectedRepo === CREATE_NEW) {
92
+ let targetRepoName = null;
93
+ if (createNew) {
94
+ // Non-interactive mode: create new repo with project name
91
95
  const defaultName = (0, git_js_1.getProjectName)();
92
- const { newRepoName } = await (0, prompts_1.default)({
93
- type: "text",
94
- name: "newRepoName",
95
- message: "Enter the new repository name:",
96
- initial: defaultName,
97
- });
98
- if (!newRepoName)
99
- return;
100
- targetRepoName = newRepoName.endsWith(".git") ? newRepoName : `${newRepoName}.git`;
96
+ targetRepoName = defaultName.endsWith(".git") ? defaultName : `${defaultName}.git`;
101
97
  const repoPath = path.join(gitDrivePath, targetRepoName);
102
98
  if (fs.existsSync(repoPath)) {
103
- console.log(`Repository ${targetRepoName} already exists in this drive.`);
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)
104
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}`);
105
141
  }
106
- (0, git_js_1.git)(`init --bare "${repoPath}"`);
107
- console.log(`Created new bare repository: ${targetRepoName}`);
108
142
  }
143
+ if (!targetRepoName)
144
+ return;
109
145
  const repoRoot = (0, git_js_1.getRepoRoot)();
110
146
  const finalRepoPath = path.join(gitDrivePath, targetRepoName);
111
147
  // Check if remote 'gd' already exists
@@ -43,7 +43,7 @@ const prompts_1 = __importDefault(require("prompts"));
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const os = __importStar(require("os"));
46
- async function push(_args) {
46
+ async function push(args) {
47
47
  if (!(0, git_js_1.isGitRepo)()) {
48
48
  throw new errors_js_1.GitDriveError("Not in a git repository.");
49
49
  }
@@ -53,15 +53,28 @@ async function push(_args) {
53
53
  }
54
54
  try {
55
55
  const currentBranch = (0, git_js_1.git)("branch --show-current") || "HEAD";
56
- const { pushMode } = await (0, prompts_1.default)({
57
- type: "select",
58
- name: "pushMode",
59
- message: `Pushing to ${existingUrl}\nSelect what to branch to push:`,
60
- choices: [
61
- { title: `Current branch only (${currentBranch})`, value: "current" },
62
- { title: "All branches & tags", value: "all" }
63
- ]
64
- });
56
+ // Check for --all or --current flags for non-interactive mode
57
+ const pushAll = args.includes("--all");
58
+ const pushCurrent = args.includes("--current");
59
+ let pushMode = null;
60
+ if (pushAll) {
61
+ pushMode = "all";
62
+ }
63
+ else if (pushCurrent) {
64
+ pushMode = "current";
65
+ }
66
+ else {
67
+ const result = await (0, prompts_1.default)({
68
+ type: "select",
69
+ name: "pushMode",
70
+ message: `Pushing to ${existingUrl}\nSelect what to branch to push:`,
71
+ choices: [
72
+ { title: `Current branch only (${currentBranch})`, value: "current" },
73
+ { title: "All branches & tags", value: "all" }
74
+ ]
75
+ });
76
+ pushMode = result.pushMode;
77
+ }
65
78
  if (!pushMode)
66
79
  return;
67
80
  if (pushMode === "current") {
package/dist/git.js CHANGED
@@ -24,6 +24,13 @@ async function listDrives() {
24
24
  return false;
25
25
  if (mp === "/" || mp === "100%")
26
26
  return false;
27
+ // Exclude temporary and system paths on all platforms
28
+ if (mp.startsWith("/var/") || mp.startsWith("/private/var/") || mp.startsWith("/tmp") || mp.startsWith("/private/tmp"))
29
+ return false;
30
+ if (mp.includes("TemporaryItems") || mp.includes("NSIRD_"))
31
+ return false;
32
+ if (mp.startsWith("/System/") || mp.startsWith("/Library/"))
33
+ return false;
27
34
  if (process.platform === "darwin") {
28
35
  return mp.startsWith("/Volumes/") && !mp.startsWith("/Volumes/Recovery");
29
36
  }
package/dist/server.js CHANGED
@@ -134,6 +134,13 @@ app.get('/api/drives', async (_req, res) => {
134
134
  return false;
135
135
  if (mp === "/" || mp === "100%")
136
136
  return false;
137
+ // Exclude temporary and system paths on all platforms
138
+ if (mp.startsWith("/var/") || mp.startsWith("/private/var/") || mp.startsWith("/tmp") || mp.startsWith("/private/tmp"))
139
+ return false;
140
+ if (mp.includes("TemporaryItems") || mp.includes("NSIRD_"))
141
+ return false;
142
+ if (mp.startsWith("/System/") || mp.startsWith("/Library/"))
143
+ return false;
137
144
  if (process.platform === "darwin") {
138
145
  return mp.startsWith("/Volumes/") && !mp.startsWith("/Volumes/Recovery");
139
146
  }
@@ -520,7 +527,9 @@ app.get('*', (_req, res) => {
520
527
  res.status(404).send('UI not built. The package may need to be rebuilt.');
521
528
  }
522
529
  });
523
- // Start server
524
- app.listen(port, () => {
525
- console.log(`\n 🚀 Git Drive is running at http://localhost:${port}\n`);
526
- });
530
+ // Start server only when run directly (not when imported)
531
+ if (require.main === module) {
532
+ app.listen(port, () => {
533
+ console.log(`\n 🚀 Git Drive is running at http://localhost:${port}\n`);
534
+ });
535
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-drive",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "Turn any external drive into a git remote backup for your code - CLI, server, and web UI",
5
5
  "keywords": [
6
6
  "git",
@@ -31,14 +31,6 @@
31
31
  "dist",
32
32
  "ui"
33
33
  ],
34
- "scripts": {
35
- "build": "tsc -p tsconfig.json",
36
- "start": "node dist/index.js",
37
- "start:server": "node dist/server.js",
38
- "docker:build": "docker build -t git-drive .",
39
- "docker:run": "docker run -it --rm -v /Volumes:/Volumes -p 4483:4483 git-drive",
40
- "prepublishOnly": "npm run build"
41
- },
42
34
  "dependencies": {
43
35
  "express": "^4.19.2",
44
36
  "node-disk-info": "^1.3.0",
@@ -48,9 +40,25 @@
48
40
  "@types/express": "^4.17.21",
49
41
  "@types/node": "^22.0.0",
50
42
  "@types/prompts": "^2.4.9",
43
+ "@types/jest": "^29.5.14",
44
+ "@types/supertest": "^6.0.2",
45
+ "jest": "^29.7.0",
46
+ "ts-jest": "^29.2.5",
47
+ "supertest": "^6.3.4",
48
+ "memfs": "^4.14.0",
51
49
  "typescript": "^5.7.0"
52
50
  },
53
51
  "engines": {
54
52
  "node": ">=18"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.json",
56
+ "start": "node dist/index.js",
57
+ "start:server": "node dist/server.js",
58
+ "docker:build": "docker build -t git-drive .",
59
+ "docker:run": "docker run -it --rm -v /Volumes:/Volumes -p 4483:4483 git-drive",
60
+ "test": "jest",
61
+ "test:watch": "jest --watch",
62
+ "test:coverage": "jest --coverage"
55
63
  }
56
- }
64
+ }