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/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
+ }