gitlo 1.0.1 ā 1.0.2
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/README.md +36 -19
- package/dist/backup.js +34 -5
- package/dist/cli.js +3 -3
- package/dist/cron.js +7 -4
- package/dist/index.js +3 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ GitHub accounts can be deleted, suspended, or compromised. This tool creates a l
|
|
|
34
34
|
|
|
35
35
|
## š¦ Installation
|
|
36
36
|
|
|
37
|
-
### Quick Install (npm
|
|
37
|
+
### Quick Install (npm)
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
npm install -g gitlo
|
|
@@ -61,15 +61,19 @@ yarn global add gitlo
|
|
|
61
61
|
# Clone and build
|
|
62
62
|
git clone https://github.com/dropocol/gitlo.git
|
|
63
63
|
cd gitlo
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
npm install
|
|
65
|
+
npm run build
|
|
66
66
|
|
|
67
67
|
# Link globally
|
|
68
|
-
|
|
68
|
+
npm link --global
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
## Getting a GitHub Token
|
|
72
72
|
|
|
73
|
+
gitlo supports two types of GitHub personal access tokens:
|
|
74
|
+
|
|
75
|
+
### Option 1: Classic Token (Recommended)
|
|
76
|
+
|
|
73
77
|
1. Go to [https://github.com/settings/tokens](https://github.com/settings/tokens)
|
|
74
78
|
2. Click "Generate new token (classic)"
|
|
75
79
|
3. Select these scopes:
|
|
@@ -77,6 +81,19 @@ pnpm link --global
|
|
|
77
81
|
- `read:user` - Read user profile data
|
|
78
82
|
4. Generate and copy the token
|
|
79
83
|
|
|
84
|
+
### Option 2: Fine-Grained Token
|
|
85
|
+
|
|
86
|
+
1. Go to [https://github.com/settings/tokens](https://github.com/settings/tokens)
|
|
87
|
+
2. Click "Generate new token" ā "Fine-grained token"
|
|
88
|
+
3. Configure permissions:
|
|
89
|
+
- **Repository permissions**:
|
|
90
|
+
- Contents: **Read and write** (required for cloning)
|
|
91
|
+
- Metadata: **Read-only** (required for repository access)
|
|
92
|
+
- **Account permissions**:
|
|
93
|
+
- User profile: **Read-only** (optional, for user info)
|
|
94
|
+
4. Select the repositories to access (or "All repositories")
|
|
95
|
+
5. Generate and copy the token
|
|
96
|
+
|
|
80
97
|
## š Quick Start
|
|
81
98
|
|
|
82
99
|
### 1. Get a GitHub Token
|
|
@@ -229,7 +246,7 @@ gitlo config remove output-dir
|
|
|
229
246
|
Schedule automatic backups with cron.
|
|
230
247
|
|
|
231
248
|
```bash
|
|
232
|
-
# Weekly on Sunday at 2 AM (default)
|
|
249
|
+
# Weekly on Sunday at 2 AM (default, updates existing repos)
|
|
233
250
|
gitlo schedule setup
|
|
234
251
|
|
|
235
252
|
# Daily at 3 AM
|
|
@@ -241,8 +258,8 @@ gitlo schedule setup --frequency weekly --day 1
|
|
|
241
258
|
# Monthly on the 1st at 2 AM
|
|
242
259
|
gitlo schedule setup --frequency monthly
|
|
243
260
|
|
|
244
|
-
#
|
|
245
|
-
gitlo schedule setup --
|
|
261
|
+
# Full backup (clone all repos, don't just update)
|
|
262
|
+
gitlo schedule setup --full
|
|
246
263
|
```
|
|
247
264
|
|
|
248
265
|
#### `gitlo schedule list`
|
|
@@ -332,19 +349,19 @@ If you want to contribute or modify the code:
|
|
|
332
349
|
|
|
333
350
|
```bash
|
|
334
351
|
# Install all dependencies (including dev)
|
|
335
|
-
|
|
352
|
+
npm install
|
|
336
353
|
|
|
337
354
|
# Build TypeScript to JavaScript
|
|
338
|
-
|
|
355
|
+
npm run build
|
|
339
356
|
|
|
340
357
|
# Run in development mode
|
|
341
|
-
|
|
358
|
+
npm run dev
|
|
342
359
|
|
|
343
360
|
# Clean build artifacts
|
|
344
|
-
|
|
361
|
+
npm run clean
|
|
345
362
|
|
|
346
363
|
# Test local changes globally
|
|
347
|
-
|
|
364
|
+
npm link --global
|
|
348
365
|
```
|
|
349
366
|
|
|
350
367
|
### Publishing
|
|
@@ -353,9 +370,6 @@ When you're ready to publish to npm:
|
|
|
353
370
|
|
|
354
371
|
```bash
|
|
355
372
|
# The prepublishOnly script automatically builds before publishing
|
|
356
|
-
pnpm publish
|
|
357
|
-
|
|
358
|
-
# Or publish to npm
|
|
359
373
|
npm publish
|
|
360
374
|
```
|
|
361
375
|
|
|
@@ -387,6 +401,7 @@ your-backup-directory/
|
|
|
387
401
|
|
|
388
402
|
```bash
|
|
389
403
|
# Setup weekly backups (default: Sundays at 2 AM)
|
|
404
|
+
# By default, updates existing repos (faster)
|
|
390
405
|
gitlo schedule setup
|
|
391
406
|
|
|
392
407
|
# Setup daily backups at 3 AM
|
|
@@ -395,8 +410,8 @@ gitlo schedule setup --frequency daily --time 03:00
|
|
|
395
410
|
# Setup weekly on Mondays at 2 AM
|
|
396
411
|
gitlo schedule setup --frequency weekly --day 1 --time 02:00
|
|
397
412
|
|
|
398
|
-
#
|
|
399
|
-
gitlo schedule setup --
|
|
413
|
+
# Full backup (clone all repos including new ones)
|
|
414
|
+
gitlo schedule setup --full
|
|
400
415
|
|
|
401
416
|
# View scheduled jobs
|
|
402
417
|
gitlo schedule list
|
|
@@ -410,9 +425,11 @@ gitlo schedule remove
|
|
|
410
425
|
- `-f, --frequency <freq>` - hourly, daily, weekly, monthly (default: weekly)
|
|
411
426
|
- `-t, --time <time>` - Time in HH:MM format (default: 02:00)
|
|
412
427
|
- `-d, --day <day>` - Day of week 0-6 for weekly (0=Sunday, default: 0)
|
|
413
|
-
-
|
|
428
|
+
- `--full` - Clone all repos instead of just updating existing (default: update)
|
|
414
429
|
- `-l, --log <path>` - Log file path (default: ~/.gitlo/backup.log)
|
|
415
430
|
|
|
431
|
+
**Note:** By default, scheduled backups update existing repositories for faster execution. Use `--full` to clone all repositories including new ones.
|
|
432
|
+
|
|
416
433
|
### Manual Cron Setup (Advanced)
|
|
417
434
|
|
|
418
435
|
If you prefer manual setup:
|
|
@@ -434,7 +451,7 @@ npm install -g gitlo && gitlo config set token YOUR_TOKEN && gitlo
|
|
|
434
451
|
### One-liner for Development Setup
|
|
435
452
|
|
|
436
453
|
```bash
|
|
437
|
-
cd ~ && git clone https://github.com/dropocol/gitlo.git && cd gitlo &&
|
|
454
|
+
cd ~ && git clone https://github.com/dropocol/gitlo.git && cd gitlo && npm install && npm run build && npm link --global && echo "Setup complete. Run: gitlo config set token YOUR_TOKEN"
|
|
438
455
|
```
|
|
439
456
|
|
|
440
457
|
## Security Notes
|
package/dist/backup.js
CHANGED
|
@@ -52,10 +52,29 @@ async function cloneOrUpdateRepo(repo, repoPath, options, progress, index, total
|
|
|
52
52
|
if (options.updateExisting) {
|
|
53
53
|
repoSpinner.text = `${progress} Updating ${repo.name}...`;
|
|
54
54
|
const git = (0, simple_git_1.default)(repoPath);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// For private repos with HTTPS, temporarily use authenticated URL
|
|
56
|
+
const originalUrlResult = await git.remote(['get-url', 'origin']);
|
|
57
|
+
const originalUrl = typeof originalUrlResult === 'string'
|
|
58
|
+
? originalUrlResult.trim().replace(/[\n\r\t]/g, '')
|
|
59
|
+
: '';
|
|
60
|
+
let needsRestore = false;
|
|
61
|
+
if (options.token && options.method === 'https' && originalUrl) {
|
|
62
|
+
const authenticatedUrl = repo.cloneUrl.replace('https://', `https://${options.token}@`);
|
|
63
|
+
await git.remote(['set-url', 'origin', authenticatedUrl.trim()]);
|
|
64
|
+
needsRestore = true;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await git.pull('origin', 'main').catch(async () => {
|
|
68
|
+
// Try master if main fails
|
|
69
|
+
await git.pull('origin', 'master');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
// Restore original URL without token
|
|
74
|
+
if (needsRestore && originalUrl) {
|
|
75
|
+
await git.remote(['set-url', 'origin', originalUrl]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
59
78
|
repoSpinner.succeed(`${progress} ${chalk_1.default.green('ā')} Updated ${chalk_1.default.white(repo.name)}`);
|
|
60
79
|
return { status: 'updated' };
|
|
61
80
|
}
|
|
@@ -66,9 +85,19 @@ async function cloneOrUpdateRepo(repo, repoPath, options, progress, index, total
|
|
|
66
85
|
}
|
|
67
86
|
else {
|
|
68
87
|
repoSpinner.text = `${progress} Cloning ${repo.name}...`;
|
|
69
|
-
|
|
88
|
+
let cloneUrl = options.method === 'ssh' ? repo.sshUrl : repo.cloneUrl;
|
|
89
|
+
// Inject token into HTTPS URL for private repos
|
|
90
|
+
if (options.token && options.method === 'https') {
|
|
91
|
+
cloneUrl = cloneUrl.replace('https://', `https://${options.token}@`);
|
|
92
|
+
}
|
|
70
93
|
const parentDir = path.dirname(repoPath);
|
|
71
94
|
await (0, simple_git_1.default)(parentDir).clone(cloneUrl, repoPath);
|
|
95
|
+
// Remove token from git config after clone for security
|
|
96
|
+
if (options.token && options.method === 'https') {
|
|
97
|
+
const git = (0, simple_git_1.default)(repoPath);
|
|
98
|
+
const cleanUrl = cloneUrl.replace(/https:\/\/[^@]+@/, 'https://').trim();
|
|
99
|
+
await git.remote(['set-url', 'origin', cleanUrl]);
|
|
100
|
+
}
|
|
72
101
|
repoSpinner.succeed(`${progress} ${chalk_1.default.green('ā')} Cloned ${chalk_1.default.white(repo.name)}`);
|
|
73
102
|
return { status: 'cloned' };
|
|
74
103
|
}
|
package/dist/cli.js
CHANGED
|
@@ -285,7 +285,7 @@ function createScheduleCommands(program) {
|
|
|
285
285
|
.option('-f, --frequency <freq>', 'Backup frequency: hourly, daily, weekly, monthly', 'weekly')
|
|
286
286
|
.option('-t, --time <time>', 'Time for daily/weekly/monthly (HH:MM format)', '02:00')
|
|
287
287
|
.option('-d, --day <day>', 'Day of week for weekly (0-6, 0=Sunday)', '0')
|
|
288
|
-
.option('
|
|
288
|
+
.option('--full', 'Clone all repos instead of updating existing (default: update existing)', false)
|
|
289
289
|
.option('-l, --log <path>', 'Log file path', '~/.gitlo/backup.log')
|
|
290
290
|
.action((cmdOptions) => {
|
|
291
291
|
const { addCronJob, getCronExpression, formatSchedule } = require('./cron.js');
|
|
@@ -293,12 +293,12 @@ function createScheduleCommands(program) {
|
|
|
293
293
|
frequency: cmdOptions.frequency,
|
|
294
294
|
time: cmdOptions.time,
|
|
295
295
|
dayOfWeek: parseInt(cmdOptions.day),
|
|
296
|
-
updateOnly: cmdOptions.
|
|
296
|
+
updateOnly: !cmdOptions.full,
|
|
297
297
|
};
|
|
298
298
|
const logFile = cmdOptions.log.replace(/^~/, require('os').homedir());
|
|
299
299
|
console.log(chalk_1.default.bold.blue('\nš
Schedule Automatic Backups\n'));
|
|
300
300
|
console.log(chalk_1.default.gray(`Frequency: ${formatSchedule(schedule)}`));
|
|
301
|
-
console.log(chalk_1.default.gray(`Update only: ${cmdOptions.
|
|
301
|
+
console.log(chalk_1.default.gray(`Update only: ${!cmdOptions.full ? 'Yes (default)' : 'No (full backup)'}`));
|
|
302
302
|
console.log(chalk_1.default.gray(`Log file: ${logFile}\n`));
|
|
303
303
|
if (addCronJob(schedule, logFile)) {
|
|
304
304
|
console.log(chalk_1.default.green('ā Automatic backup scheduled successfully!'));
|
package/dist/cron.js
CHANGED
|
@@ -48,7 +48,6 @@ const fs = __importStar(require("fs"));
|
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
49
|
const os = __importStar(require("os"));
|
|
50
50
|
const chalk_1 = __importDefault(require("chalk"));
|
|
51
|
-
const config_manager_js_1 = require("./config-manager.js");
|
|
52
51
|
const CRON_COMMENT = '# gitlo-auto-backup';
|
|
53
52
|
function getCronExpression(schedule) {
|
|
54
53
|
switch (schedule.frequency) {
|
|
@@ -71,9 +70,13 @@ function getCronExpression(schedule) {
|
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
function getCronCommand(updateOnly = false) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
// Use full path to node and the built JS file for cron compatibility
|
|
74
|
+
// Cron has limited PATH, so we can't rely on wrapper scripts
|
|
75
|
+
const nodePath = (0, child_process_1.execSync)('which node', { encoding: 'utf-8' }).trim();
|
|
76
|
+
// Get the path to dist/index.js from the current file location
|
|
77
|
+
// After compilation, this file is at dist/cron.js, so we need index.js in the same dir
|
|
78
|
+
const scriptPath = path.join(path.dirname(__filename), 'index.js');
|
|
79
|
+
let command = `${nodePath} ${scriptPath}`;
|
|
77
80
|
// Add update flag if specified
|
|
78
81
|
if (updateOnly) {
|
|
79
82
|
command += ' --update';
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const cli_js_1 = require("./cli.js");
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
7
|
+
const packageJson = require('../package.json');
|
|
6
8
|
const program = new commander_1.Command();
|
|
7
9
|
program
|
|
8
10
|
.name('gitlo')
|
|
9
11
|
.description('CLI tool to backup all your GitHub repositories locally')
|
|
10
|
-
.version(
|
|
12
|
+
.version(packageJson.version);
|
|
11
13
|
// Config subcommands
|
|
12
14
|
(0, cli_js_1.createConfigCommands)(program);
|
|
13
15
|
// Schedule subcommands
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitlo",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "CLI tool to backup all your GitHub repositories locally",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"start": "node dist/index.js",
|
|
17
17
|
"dev": "ts-node src/index.ts",
|
|
18
18
|
"clean": "rm -rf dist node_modules",
|
|
19
|
-
"prepublishOnly": "pnpm run build"
|
|
19
|
+
"prepublishOnly": "pnpm run build",
|
|
20
|
+
"link": "pnpm run build && pnpm link --global",
|
|
21
|
+
"unlink": "pnpm unlink --global && rm -f $(which gitlo 2>/dev/null || echo /dev/null)"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
|
22
24
|
"github",
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"bugs": {
|
|
35
37
|
"url": "https://github.com/dropocol/gitlo/issues"
|
|
36
38
|
},
|
|
37
|
-
"packageManager": "pnpm@
|
|
39
|
+
"packageManager": "pnpm@10.33.0",
|
|
38
40
|
"engines": {
|
|
39
41
|
"node": ">=18.0.0"
|
|
40
42
|
},
|