gitlo 1.0.0
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/LICENSE +21 -0
- package/README.md +496 -0
- package/dist/backup.js +108 -0
- package/dist/cli.js +346 -0
- package/dist/config-manager.js +79 -0
- package/dist/cron.js +177 -0
- package/dist/github.js +88 -0
- package/dist/index.js +30 -0
- package/dist/types.js +2 -0
- package/package.json +55 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
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.parseOptions = parseOptions;
|
|
40
|
+
exports.runBackup = runBackup;
|
|
41
|
+
exports.createConfigCommands = createConfigCommands;
|
|
42
|
+
exports.createScheduleCommands = createScheduleCommands;
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const rest_1 = require("@octokit/rest");
|
|
45
|
+
const ora_1 = __importDefault(require("ora"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const config_manager_js_1 = require("./config-manager.js");
|
|
48
|
+
const github_js_1 = require("./github.js");
|
|
49
|
+
const backup_js_1 = require("./backup.js");
|
|
50
|
+
function parseOptions(cmdOptions) {
|
|
51
|
+
const config = (0, config_manager_js_1.readConfig)();
|
|
52
|
+
// Resolve token priority: CLI arg > env var > config file
|
|
53
|
+
const token = cmdOptions.token || process.env.GITHUB_TOKEN || config.githubToken;
|
|
54
|
+
// Resolve output dir priority: CLI arg > config file > undefined (will use username)
|
|
55
|
+
let outputDir = cmdOptions.outputDir || config.outputDir;
|
|
56
|
+
if (outputDir) {
|
|
57
|
+
outputDir = (0, config_manager_js_1.expandPath)(outputDir);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
token,
|
|
61
|
+
outputDir,
|
|
62
|
+
method: cmdOptions.method,
|
|
63
|
+
includePrivate: !cmdOptions.excludePrivate,
|
|
64
|
+
includeForks: cmdOptions.includeForks,
|
|
65
|
+
dryRun: cmdOptions.dryRun,
|
|
66
|
+
updateExisting: cmdOptions.update,
|
|
67
|
+
verbose: cmdOptions.verbose,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function runBackup(options) {
|
|
71
|
+
console.log(chalk_1.default.bold.blue('\n🗄️ gitlo - GitHub Repository Backup Tool\n'));
|
|
72
|
+
// Validate token
|
|
73
|
+
if (!options.token) {
|
|
74
|
+
console.error(chalk_1.default.red('❌ Error: GitHub token is required'));
|
|
75
|
+
console.log(chalk_1.default.yellow('\nGet a token at: https://github.com/settings/tokens'));
|
|
76
|
+
console.log(chalk_1.default.yellow('Required scopes: repo (for private repos), read:user\n'));
|
|
77
|
+
console.log(chalk_1.default.gray('Setup options:'));
|
|
78
|
+
console.log(chalk_1.default.gray(' 1. Set via config: gitlo config set token YOUR_TOKEN'));
|
|
79
|
+
console.log(chalk_1.default.gray(' 2. Environment var: export GITHUB_TOKEN=YOUR_TOKEN'));
|
|
80
|
+
console.log(chalk_1.default.gray(' 3. Pass directly: gitlo -t YOUR_TOKEN\n'));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// Initialize Octokit
|
|
84
|
+
const octokit = new rest_1.Octokit({ auth: options.token });
|
|
85
|
+
try {
|
|
86
|
+
// Get authenticated user
|
|
87
|
+
const spinner = (0, ora_1.default)('Fetching user profile...').start();
|
|
88
|
+
const username = await (0, github_js_1.getAuthenticatedUser)(octokit);
|
|
89
|
+
spinner.succeed(`Authenticated as ${chalk_1.default.green(username)}`);
|
|
90
|
+
// Set output directory
|
|
91
|
+
const backupDir = options.outputDir || username;
|
|
92
|
+
const backupPath = path.resolve(backupDir);
|
|
93
|
+
console.log(chalk_1.default.gray(`Backup location: ${backupPath}`));
|
|
94
|
+
console.log(chalk_1.default.gray(`Clone method: ${options.method}`));
|
|
95
|
+
console.log(chalk_1.default.gray(`Include private: ${options.includePrivate}`));
|
|
96
|
+
console.log(chalk_1.default.gray(`Include forks: ${options.includeForks}\n`));
|
|
97
|
+
// Get expected repo count from user profile
|
|
98
|
+
const { data: user } = await octokit.rest.users.getAuthenticated();
|
|
99
|
+
const expectedPublic = user.public_repos || 0;
|
|
100
|
+
const expectedPrivate = user.total_private_repos || 0;
|
|
101
|
+
const expectedTotal = expectedPublic + expectedPrivate;
|
|
102
|
+
// Fetch repositories
|
|
103
|
+
const reposSpinner = (0, ora_1.default)('Fetching repositories...').start();
|
|
104
|
+
const { repos, totalFetched, forksFiltered, privateFiltered, pagesFetched } = await (0, github_js_1.fetchAllRepos)(octokit, options);
|
|
105
|
+
reposSpinner.succeed(`Found ${chalk_1.default.green(repos.length)} repositories to backup`);
|
|
106
|
+
// Show filtering details
|
|
107
|
+
console.log(chalk_1.default.gray(` Pages fetched: ${pagesFetched}`));
|
|
108
|
+
console.log(chalk_1.default.gray(` Total from API: ${totalFetched} (GitHub says you have: ${expectedTotal})`));
|
|
109
|
+
// Warn if we're not getting all repos
|
|
110
|
+
if (totalFetched < expectedTotal) {
|
|
111
|
+
console.log(chalk_1.default.yellow(` ⚠️ Warning: Only fetched ${totalFetched} of ${expectedTotal} expected repos`));
|
|
112
|
+
console.log(chalk_1.default.yellow(` Your token might not have 'repo' scope for private repos`));
|
|
113
|
+
console.log(chalk_1.default.yellow(` Or some repos might be in organizations that need additional access`));
|
|
114
|
+
}
|
|
115
|
+
if (forksFiltered > 0) {
|
|
116
|
+
console.log(chalk_1.default.yellow(` Skipped forks: ${forksFiltered} (use --include-forks to backup)`));
|
|
117
|
+
}
|
|
118
|
+
if (privateFiltered > 0) {
|
|
119
|
+
console.log(chalk_1.default.yellow(` Skipped private: ${privateFiltered} (use --include-private to backup)`));
|
|
120
|
+
}
|
|
121
|
+
console.log();
|
|
122
|
+
if (repos.length === 0) {
|
|
123
|
+
console.log(chalk_1.default.yellow('\nNo repositories found matching your criteria.'));
|
|
124
|
+
if (forksFiltered > 0 && !options.includeForks) {
|
|
125
|
+
console.log(chalk_1.default.yellow(`Tip: You have ${forksFiltered} forked repo(s). Use --include-forks to backup forks too.`));
|
|
126
|
+
}
|
|
127
|
+
if (privateFiltered > 0 && !options.includePrivate) {
|
|
128
|
+
console.log(chalk_1.default.yellow(`Tip: You have ${privateFiltered} private repo(s). Use --include-private to backup private repos.`));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Display repository list
|
|
133
|
+
(0, backup_js_1.displayRepoList)(repos);
|
|
134
|
+
if (options.dryRun) {
|
|
135
|
+
console.log(chalk_1.default.yellow('\n🏃 Dry run mode - no repositories were cloned.\n'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Create backup directory
|
|
139
|
+
(0, backup_js_1.ensureDirectory)(backupPath);
|
|
140
|
+
console.log(chalk_1.default.green(`\n📁 Backup directory: ${backupDir}`));
|
|
141
|
+
// Clone/update repositories
|
|
142
|
+
console.log(chalk_1.default.bold('\n⬇️ Backing up repositories...\n'));
|
|
143
|
+
const stats = {
|
|
144
|
+
successCount: 0,
|
|
145
|
+
errorCount: 0,
|
|
146
|
+
updateCount: 0,
|
|
147
|
+
};
|
|
148
|
+
for (let i = 0; i < repos.length; i++) {
|
|
149
|
+
const repo = repos[i];
|
|
150
|
+
const repoPath = path.join(backupPath, repo.name);
|
|
151
|
+
const progress = chalk_1.default.gray(`[${i + 1}/${repos.length}]`);
|
|
152
|
+
const result = await (0, backup_js_1.cloneOrUpdateRepo)(repo, repoPath, options, progress, i, repos.length);
|
|
153
|
+
switch (result.status) {
|
|
154
|
+
case 'cloned':
|
|
155
|
+
stats.successCount++;
|
|
156
|
+
break;
|
|
157
|
+
case 'updated':
|
|
158
|
+
stats.updateCount++;
|
|
159
|
+
break;
|
|
160
|
+
case 'error':
|
|
161
|
+
stats.errorCount++;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Summary
|
|
166
|
+
(0, backup_js_1.displaySummary)(stats, options.updateExisting, backupPath);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof Error) {
|
|
170
|
+
if (error.message.includes('Bad credentials')) {
|
|
171
|
+
console.error(chalk_1.default.red('\n❌ Invalid GitHub token. Please check your token and try again.\n'));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.error(chalk_1.default.red(`\n❌ Error: ${error.message}\n`));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function createConfigCommands(program) {
|
|
181
|
+
const configCmd = program
|
|
182
|
+
.command('config')
|
|
183
|
+
.description('Manage gitlo configuration (token and default output directory)');
|
|
184
|
+
configCmd
|
|
185
|
+
.command('set')
|
|
186
|
+
.description('Set a configuration value')
|
|
187
|
+
.argument('<key>', 'Configuration key (token or output-dir)')
|
|
188
|
+
.argument('<value>', 'Value to set')
|
|
189
|
+
.action((key, value) => {
|
|
190
|
+
if (key === 'token') {
|
|
191
|
+
(0, config_manager_js_1.setConfig)('githubToken', value);
|
|
192
|
+
console.log(chalk_1.default.green('✓ GitHub token saved'));
|
|
193
|
+
console.log(chalk_1.default.gray(`Config file: ~/.gitlo/config.json`));
|
|
194
|
+
}
|
|
195
|
+
else if (key === 'output-dir' || key === 'outputDir') {
|
|
196
|
+
const expandedPath = (0, config_manager_js_1.expandPath)(value);
|
|
197
|
+
(0, config_manager_js_1.setConfig)('outputDir', expandedPath);
|
|
198
|
+
console.log(chalk_1.default.green('✓ Default output directory saved'));
|
|
199
|
+
console.log(chalk_1.default.gray(`Path: ${expandedPath}`));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.error(chalk_1.default.red(`❌ Unknown config key: ${key}`));
|
|
203
|
+
console.log(chalk_1.default.gray('Valid keys: token, output-dir'));
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
configCmd
|
|
208
|
+
.command('get')
|
|
209
|
+
.description('Get a configuration value')
|
|
210
|
+
.argument('<key>', 'Configuration key (token or output-dir)')
|
|
211
|
+
.action((key) => {
|
|
212
|
+
let value;
|
|
213
|
+
if (key === 'token') {
|
|
214
|
+
value = (0, config_manager_js_1.getConfig)('githubToken');
|
|
215
|
+
}
|
|
216
|
+
else if (key === 'output-dir' || key === 'outputDir') {
|
|
217
|
+
value = (0, config_manager_js_1.getConfig)('outputDir');
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.error(chalk_1.default.red(`❌ Unknown config key: ${key}`));
|
|
221
|
+
console.log(chalk_1.default.gray('Valid keys: token, output-dir'));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
if (value) {
|
|
225
|
+
if (key === 'token') {
|
|
226
|
+
// Mask token for security
|
|
227
|
+
console.log(chalk_1.default.green(`${key}: ${value.substring(0, 4)}****${value.substring(value.length - 4)}`));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(chalk_1.default.green(`${key}: ${value}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.log(chalk_1.default.yellow(`${key}: not set`));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
configCmd
|
|
238
|
+
.command('remove')
|
|
239
|
+
.description('Remove a configuration value')
|
|
240
|
+
.argument('<key>', 'Configuration key (token or output-dir)')
|
|
241
|
+
.action((key) => {
|
|
242
|
+
if (key === 'token') {
|
|
243
|
+
(0, config_manager_js_1.removeConfig)('githubToken');
|
|
244
|
+
console.log(chalk_1.default.green('✓ GitHub token removed'));
|
|
245
|
+
}
|
|
246
|
+
else if (key === 'output-dir' || key === 'outputDir') {
|
|
247
|
+
(0, config_manager_js_1.removeConfig)('outputDir');
|
|
248
|
+
console.log(chalk_1.default.green('✓ Default output directory removed'));
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.error(chalk_1.default.red(`❌ Unknown config key: ${key}`));
|
|
252
|
+
console.log(chalk_1.default.gray('Valid keys: token, output-dir'));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
configCmd
|
|
257
|
+
.command('list')
|
|
258
|
+
.description('List all configuration values')
|
|
259
|
+
.action(() => {
|
|
260
|
+
const config = (0, config_manager_js_1.readConfig)();
|
|
261
|
+
console.log(chalk_1.default.bold('\n📋 gitlo Configuration\n'));
|
|
262
|
+
if (config.githubToken) {
|
|
263
|
+
const masked = `${config.githubToken.substring(0, 4)}****${config.githubToken.substring(config.githubToken.length - 4)}`;
|
|
264
|
+
console.log(` ${chalk_1.default.cyan('token')}: ${masked}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.log(` ${chalk_1.default.cyan('token')}: ${chalk_1.default.gray('not set')}`);
|
|
268
|
+
}
|
|
269
|
+
if (config.outputDir) {
|
|
270
|
+
console.log(` ${chalk_1.default.cyan('output-dir')}: ${config.outputDir}`);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log(` ${chalk_1.default.cyan('output-dir')}: ${chalk_1.default.gray('not set')}`);
|
|
274
|
+
}
|
|
275
|
+
console.log(chalk_1.default.gray(`\nConfig file: ~/.gitlo/config.json\n`));
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function createScheduleCommands(program) {
|
|
279
|
+
const scheduleCmd = program
|
|
280
|
+
.command('schedule')
|
|
281
|
+
.description('Schedule automatic backups with cron');
|
|
282
|
+
scheduleCmd
|
|
283
|
+
.command('setup')
|
|
284
|
+
.description('Set up automatic backups')
|
|
285
|
+
.option('-f, --frequency <freq>', 'Backup frequency: hourly, daily, weekly, monthly', 'weekly')
|
|
286
|
+
.option('-t, --time <time>', 'Time for daily/weekly/monthly (HH:MM format)', '02:00')
|
|
287
|
+
.option('-d, --day <day>', 'Day of week for weekly (0-6, 0=Sunday)', '0')
|
|
288
|
+
.option('-u, --update', 'Only update existing repos (faster)', false)
|
|
289
|
+
.option('-l, --log <path>', 'Log file path', '~/.gitlo/backup.log')
|
|
290
|
+
.action((cmdOptions) => {
|
|
291
|
+
const { addCronJob, getCronExpression, formatSchedule } = require('./cron.js');
|
|
292
|
+
const schedule = {
|
|
293
|
+
frequency: cmdOptions.frequency,
|
|
294
|
+
time: cmdOptions.time,
|
|
295
|
+
dayOfWeek: parseInt(cmdOptions.day),
|
|
296
|
+
updateOnly: cmdOptions.update,
|
|
297
|
+
};
|
|
298
|
+
const logFile = cmdOptions.log.replace(/^~/, require('os').homedir());
|
|
299
|
+
console.log(chalk_1.default.bold.blue('\n📅 Schedule Automatic Backups\n'));
|
|
300
|
+
console.log(chalk_1.default.gray(`Frequency: ${formatSchedule(schedule)}`));
|
|
301
|
+
console.log(chalk_1.default.gray(`Update only: ${cmdOptions.update ? 'Yes' : 'No (full backup)'}`));
|
|
302
|
+
console.log(chalk_1.default.gray(`Log file: ${logFile}\n`));
|
|
303
|
+
if (addCronJob(schedule, logFile)) {
|
|
304
|
+
console.log(chalk_1.default.green('✓ Automatic backup scheduled successfully!'));
|
|
305
|
+
console.log(chalk_1.default.gray(`Cron expression: ${getCronExpression(schedule)}`));
|
|
306
|
+
console.log(chalk_1.default.gray(`\nTo view scheduled jobs: gitlo schedule list`));
|
|
307
|
+
console.log(chalk_1.default.gray(`To remove: gitlo schedule remove\n`));
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.error(chalk_1.default.red('❌ Failed to schedule backup'));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
scheduleCmd
|
|
315
|
+
.command('list')
|
|
316
|
+
.description('List scheduled backup jobs')
|
|
317
|
+
.action(() => {
|
|
318
|
+
const { listCronJobs } = require('./cron.js');
|
|
319
|
+
console.log(chalk_1.default.bold('\n📋 Scheduled Backup Jobs\n'));
|
|
320
|
+
const jobs = listCronJobs();
|
|
321
|
+
if (jobs.length === 0) {
|
|
322
|
+
console.log(chalk_1.default.yellow('No scheduled backups found.'));
|
|
323
|
+
console.log(chalk_1.default.gray('Run: gitlo schedule setup'));
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
jobs.forEach((job, i) => {
|
|
327
|
+
console.log(` ${i + 1}. ${chalk_1.default.cyan(job)}`);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
console.log();
|
|
331
|
+
});
|
|
332
|
+
scheduleCmd
|
|
333
|
+
.command('remove')
|
|
334
|
+
.description('Remove scheduled backup')
|
|
335
|
+
.action(() => {
|
|
336
|
+
const { removeCronJob } = require('./cron.js');
|
|
337
|
+
console.log(chalk_1.default.bold('\n🗑️ Remove Scheduled Backup\n'));
|
|
338
|
+
if (removeCronJob()) {
|
|
339
|
+
console.log(chalk_1.default.green('✓ Scheduled backup removed successfully!\n'));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.error(chalk_1.default.red('❌ Failed to remove scheduled backup\n'));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readConfig = readConfig;
|
|
37
|
+
exports.writeConfig = writeConfig;
|
|
38
|
+
exports.getConfig = getConfig;
|
|
39
|
+
exports.setConfig = setConfig;
|
|
40
|
+
exports.removeConfig = removeConfig;
|
|
41
|
+
exports.expandPath = expandPath;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const CONFIG_DIR = path.join(os.homedir(), '.gitlo');
|
|
46
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
47
|
+
function readConfig() {
|
|
48
|
+
try {
|
|
49
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
50
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Ignore errors
|
|
55
|
+
}
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
function writeConfig(config) {
|
|
59
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
60
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
63
|
+
}
|
|
64
|
+
function getConfig(key) {
|
|
65
|
+
return readConfig()[key];
|
|
66
|
+
}
|
|
67
|
+
function setConfig(key, value) {
|
|
68
|
+
const config = readConfig();
|
|
69
|
+
config[key] = value;
|
|
70
|
+
writeConfig(config);
|
|
71
|
+
}
|
|
72
|
+
function removeConfig(key) {
|
|
73
|
+
const config = readConfig();
|
|
74
|
+
delete config[key];
|
|
75
|
+
writeConfig(config);
|
|
76
|
+
}
|
|
77
|
+
function expandPath(filePath) {
|
|
78
|
+
return filePath.replace(/^~/, os.homedir());
|
|
79
|
+
}
|
package/dist/cron.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
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.getCronExpression = getCronExpression;
|
|
40
|
+
exports.getCronCommand = getCronCommand;
|
|
41
|
+
exports.listCronJobs = listCronJobs;
|
|
42
|
+
exports.getAllCronLines = getAllCronLines;
|
|
43
|
+
exports.addCronJob = addCronJob;
|
|
44
|
+
exports.removeCronJob = removeCronJob;
|
|
45
|
+
exports.formatSchedule = formatSchedule;
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
51
|
+
const config_manager_js_1 = require("./config-manager.js");
|
|
52
|
+
const CRON_COMMENT = '# gitlo-auto-backup';
|
|
53
|
+
function getCronExpression(schedule) {
|
|
54
|
+
switch (schedule.frequency) {
|
|
55
|
+
case 'hourly':
|
|
56
|
+
return '0 * * * *';
|
|
57
|
+
case 'daily':
|
|
58
|
+
const [hour, minute] = (schedule.time || '02:00').split(':');
|
|
59
|
+
return `${minute} ${hour} * * *`;
|
|
60
|
+
case 'weekly':
|
|
61
|
+
const [weekHour, weekMinute] = (schedule.time || '02:00').split(':');
|
|
62
|
+
const day = schedule.dayOfWeek ?? 0;
|
|
63
|
+
return `${weekMinute} ${weekHour} * * ${day}`;
|
|
64
|
+
case 'monthly':
|
|
65
|
+
const [monthHour, monthMinute] = (schedule.time || '02:00').split(':');
|
|
66
|
+
return `${monthMinute} ${monthHour} 1 * *`;
|
|
67
|
+
case 'custom':
|
|
68
|
+
return schedule.customExpression || '0 2 * * 0';
|
|
69
|
+
default:
|
|
70
|
+
return '0 2 * * 0'; // Default: Sundays at 2 AM
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getCronCommand(updateOnly = false) {
|
|
74
|
+
const config = (0, config_manager_js_1.readConfig)();
|
|
75
|
+
const gitloPath = (0, child_process_1.execSync)('which gitlo', { encoding: 'utf-8' }).trim() || 'gitlo';
|
|
76
|
+
let command = `${gitloPath}`;
|
|
77
|
+
// Add update flag if specified
|
|
78
|
+
if (updateOnly) {
|
|
79
|
+
command += ' --update';
|
|
80
|
+
}
|
|
81
|
+
return command;
|
|
82
|
+
}
|
|
83
|
+
function listCronJobs() {
|
|
84
|
+
try {
|
|
85
|
+
const currentCrontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
86
|
+
return currentCrontab.split('\n').filter(line => line.includes(CRON_COMMENT));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function getAllCronLines() {
|
|
93
|
+
try {
|
|
94
|
+
const currentCrontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
95
|
+
return currentCrontab.split('\n');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function addCronJob(schedule, logFile) {
|
|
102
|
+
try {
|
|
103
|
+
const cronExpression = getCronExpression(schedule);
|
|
104
|
+
const command = getCronCommand(schedule.updateOnly);
|
|
105
|
+
let fullCommand = command;
|
|
106
|
+
if (logFile) {
|
|
107
|
+
fullCommand += ` >> ${logFile} 2>&1`;
|
|
108
|
+
}
|
|
109
|
+
const cronLine = `${cronExpression} ${fullCommand} ${CRON_COMMENT}`;
|
|
110
|
+
// Get existing crontab
|
|
111
|
+
let currentLines = [];
|
|
112
|
+
try {
|
|
113
|
+
const currentCrontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf-8' });
|
|
114
|
+
currentLines = currentCrontab.split('\n').filter(line => line.trim());
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// No existing crontab, that's fine
|
|
118
|
+
}
|
|
119
|
+
// Remove any existing gitlo jobs
|
|
120
|
+
const filteredLines = currentLines.filter(line => !line.includes(CRON_COMMENT));
|
|
121
|
+
// Add new job
|
|
122
|
+
filteredLines.push(cronLine);
|
|
123
|
+
// Write new crontab
|
|
124
|
+
const newCrontab = filteredLines.join('\n') + '\n';
|
|
125
|
+
const tempFile = path.join(os.tmpdir(), `gitlo-cron-${Date.now()}`);
|
|
126
|
+
fs.writeFileSync(tempFile, newCrontab);
|
|
127
|
+
(0, child_process_1.execSync)(`crontab ${tempFile}`);
|
|
128
|
+
fs.unlinkSync(tempFile);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(chalk_1.default.red('Error setting up cron job:'), error);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function removeCronJob() {
|
|
137
|
+
try {
|
|
138
|
+
let currentLines = [];
|
|
139
|
+
try {
|
|
140
|
+
const currentCrontab = (0, child_process_1.execSync)('crontab -l', { encoding: 'utf-8' });
|
|
141
|
+
currentLines = currentCrontab.split('\n').filter(line => line.trim());
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return true; // No crontab exists, nothing to remove
|
|
145
|
+
}
|
|
146
|
+
// Remove gitlo jobs
|
|
147
|
+
const filteredLines = currentLines.filter(line => !line.includes(CRON_COMMENT));
|
|
148
|
+
// Write new crontab
|
|
149
|
+
const newCrontab = filteredLines.join('\n') + '\n';
|
|
150
|
+
const tempFile = path.join(os.tmpdir(), `gitlo-cron-${Date.now()}`);
|
|
151
|
+
fs.writeFileSync(tempFile, newCrontab);
|
|
152
|
+
(0, child_process_1.execSync)(`crontab ${tempFile}`);
|
|
153
|
+
fs.unlinkSync(tempFile);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(chalk_1.default.red('Error removing cron job:'), error);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function formatSchedule(schedule) {
|
|
162
|
+
switch (schedule.frequency) {
|
|
163
|
+
case 'hourly':
|
|
164
|
+
return 'Every hour';
|
|
165
|
+
case 'daily':
|
|
166
|
+
return `Daily at ${schedule.time || '02:00'}`;
|
|
167
|
+
case 'weekly':
|
|
168
|
+
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
169
|
+
return `Weekly on ${days[schedule.dayOfWeek ?? 0]} at ${schedule.time || '02:00'}`;
|
|
170
|
+
case 'monthly':
|
|
171
|
+
return `Monthly on the 1st at ${schedule.time || '02:00'}`;
|
|
172
|
+
case 'custom':
|
|
173
|
+
return `Custom: ${schedule.customExpression}`;
|
|
174
|
+
default:
|
|
175
|
+
return 'Unknown';
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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.fetchAllRepos = fetchAllRepos;
|
|
7
|
+
exports.getAuthenticatedUser = getAuthenticatedUser;
|
|
8
|
+
exports.getRepoStats = getRepoStats;
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
async function fetchAllRepos(octokit, options) {
|
|
11
|
+
const repos = [];
|
|
12
|
+
let page = 1;
|
|
13
|
+
const perPage = 100;
|
|
14
|
+
let totalFetched = 0;
|
|
15
|
+
let forksFiltered = 0;
|
|
16
|
+
let privateFiltered = 0;
|
|
17
|
+
let pagesFetched = 0;
|
|
18
|
+
while (true) {
|
|
19
|
+
const { data, headers } = await octokit.rest.repos.listForAuthenticatedUser({
|
|
20
|
+
per_page: perPage,
|
|
21
|
+
page: page,
|
|
22
|
+
sort: 'updated',
|
|
23
|
+
direction: 'desc',
|
|
24
|
+
affiliation: 'owner,collaborator,organization_member',
|
|
25
|
+
});
|
|
26
|
+
if (data.length === 0)
|
|
27
|
+
break;
|
|
28
|
+
pagesFetched++;
|
|
29
|
+
totalFetched += data.length;
|
|
30
|
+
if (options.verbose) {
|
|
31
|
+
console.log(chalk_1.default.gray(` Page ${page}: fetched ${data.length} repos`));
|
|
32
|
+
}
|
|
33
|
+
for (const repo of data) {
|
|
34
|
+
// Skip forks if not included
|
|
35
|
+
if (repo.fork && !options.includeForks) {
|
|
36
|
+
forksFiltered++;
|
|
37
|
+
if (options.verbose) {
|
|
38
|
+
console.log(chalk_1.default.gray(` - Skipping fork: ${repo.name}`));
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Skip private repos if not included
|
|
43
|
+
if (repo.private && !options.includePrivate) {
|
|
44
|
+
privateFiltered++;
|
|
45
|
+
if (options.verbose) {
|
|
46
|
+
console.log(chalk_1.default.gray(` - Skipping private: ${repo.name}`));
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
repos.push({
|
|
51
|
+
name: repo.name,
|
|
52
|
+
cloneUrl: repo.clone_url,
|
|
53
|
+
sshUrl: repo.ssh_url,
|
|
54
|
+
isPrivate: repo.private,
|
|
55
|
+
description: repo.description,
|
|
56
|
+
updatedAt: repo.updated_at,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Check if we've reached the last page
|
|
60
|
+
const linkHeader = headers?.link;
|
|
61
|
+
if (!linkHeader || !linkHeader.includes('rel="next"')) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (data.length < perPage)
|
|
65
|
+
break;
|
|
66
|
+
page++;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
repos,
|
|
70
|
+
totalFetched,
|
|
71
|
+
forksFiltered,
|
|
72
|
+
privateFiltered,
|
|
73
|
+
pagesFetched,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function getAuthenticatedUser(octokit) {
|
|
77
|
+
const { data: user } = await octokit.rest.users.getAuthenticated();
|
|
78
|
+
return user.login;
|
|
79
|
+
}
|
|
80
|
+
async function getRepoStats(octokit) {
|
|
81
|
+
const { data: user } = await octokit.rest.users.getAuthenticated();
|
|
82
|
+
return {
|
|
83
|
+
public: user.public_repos || 0,
|
|
84
|
+
private: user.total_private_repos || 0,
|
|
85
|
+
total: (user.public_repos || 0) + (user.total_private_repos || 0),
|
|
86
|
+
owned: 0, // Will need separate query for this
|
|
87
|
+
};
|
|
88
|
+
}
|