github-archiver 1.0.1 → 1.0.4

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/notes.md DELETED
File without changes
package/scripts/build.ts DELETED
@@ -1,14 +0,0 @@
1
- import { build } from "esbuild";
2
-
3
- await build({
4
- entryPoints: ["src/index.ts"],
5
- bundle: true,
6
- platform: "node",
7
- target: "node18",
8
- outfile: "dist/index.js",
9
- external: ["octokit", "commander", "p-queue", "winston", "open"],
10
- sourcemap: true,
11
- minify: true,
12
- });
13
-
14
- console.log("✓ Build complete: dist/index.js");
@@ -1,344 +0,0 @@
1
- import { Command } from "commander";
2
- import { MESSAGES } from "../constants/messages";
3
- import { PATHS } from "../constants/paths";
4
- import { Archiver } from "../services/archiver";
5
- import { AuthManager } from "../services/auth-manager";
6
- import { GitHubService } from "../services/github";
7
- import type {
8
- ArchiveOptions,
9
- ArchiveResult,
10
- CommandOptions,
11
- RepositoryIdentifier,
12
- } from "../types";
13
- import { InputHandler } from "../utils/input-handler";
14
- import { getLogger } from "../utils/logger";
15
- import { ProgressDisplay } from "../utils/progress";
16
-
17
- const logger = getLogger();
18
-
19
- export function createArchiveCommand(): Command {
20
- return new Command("archive")
21
- .description("Archive GitHub repositories")
22
- .option("--file <path>", "Read repository URLs from file")
23
- .option("--stdin", "Read repository URLs from standard input")
24
- .option("--dry-run", "Validate without archiving", false)
25
- .option("--concurrency <n>", "Number of parallel operations", "3")
26
- .option("--timeout <n>", "API timeout in seconds", "300")
27
- .option("--verbose", "Verbose logging", false)
28
- .option("--force", "Skip confirmations", false)
29
- .action(async (options: CommandOptions) => {
30
- await archiveCommand(options);
31
- });
32
- }
33
-
34
- async function archiveCommand(options: CommandOptions): Promise<void> {
35
- try {
36
- logger.info("Archive command started", { options });
37
-
38
- const { concurrency, timeout } = validateOptions(options);
39
-
40
- const authManager = new AuthManager(PATHS.APP_DIR);
41
- const inputHandler = new InputHandler();
42
- const progressDisplay = new ProgressDisplay();
43
-
44
- const githubService = await authenticateUser(authManager);
45
-
46
- const { repositories, parseErrors } = await getRepositories(
47
- options,
48
- inputHandler
49
- );
50
-
51
- if (parseErrors.length > 0) {
52
- logParseErrors(parseErrors);
53
- }
54
-
55
- if (repositories.length === 0) {
56
- console.error(`❌ ${MESSAGES.NO_REPOS_PROVIDED}`);
57
- process.exit(1);
58
- }
59
-
60
- showRepositoriesPreview(repositories, options.dryRun);
61
-
62
- await confirmOperation(options, inputHandler);
63
-
64
- const archiveOptions: ArchiveOptions = {
65
- dryRun: options.dryRun,
66
- concurrency,
67
- timeout,
68
- force: options.force,
69
- verbose: options.verbose,
70
- };
71
-
72
- console.log("");
73
- console.log(`${MESSAGES.ARCHIVING_START} (concurrency: ${concurrency})`);
74
- console.log("");
75
-
76
- const archiver = new Archiver(githubService, archiveOptions);
77
- const results = await archiveRepositories(
78
- archiver,
79
- repositories,
80
- archiveOptions,
81
- progressDisplay
82
- );
83
-
84
- displayResults(archiver, results, progressDisplay);
85
- } catch (error) {
86
- handleArchiveError(error);
87
- }
88
- }
89
-
90
- function validateOptions(options: CommandOptions): {
91
- concurrency: number;
92
- timeout: number;
93
- } {
94
- const concurrency = Number.parseInt(options.concurrency, 10);
95
- const timeout = Number.parseInt(options.timeout, 10);
96
-
97
- if (concurrency < 1 || concurrency > 50) {
98
- console.error("❌ Concurrency must be between 1 and 50");
99
- process.exit(1);
100
- }
101
-
102
- if (timeout < 10 || timeout > 3600) {
103
- console.error("❌ Timeout must be between 10 and 3600 seconds");
104
- process.exit(1);
105
- }
106
-
107
- return { concurrency, timeout };
108
- }
109
-
110
- async function authenticateUser(
111
- authManager: AuthManager
112
- ): Promise<GitHubService> {
113
- console.log("🔐 Checking authentication...");
114
- const token = await authManager.getToken();
115
-
116
- if (!token) {
117
- console.error(`❌ ${MESSAGES.NO_TOKEN}`);
118
- console.error(" Run: github-archiver auth login");
119
- process.exit(1);
120
- }
121
-
122
- const githubService = new GitHubService(token);
123
-
124
- try {
125
- const authenticatedUser = await githubService.validateAuth();
126
- console.log(`✅ Authenticated as: ${authenticatedUser}`);
127
- return githubService;
128
- } catch (error) {
129
- console.error("❌ Authentication failed");
130
- logger.error("Auth validation failed:", error);
131
- process.exit(1);
132
- }
133
- }
134
-
135
- async function getRepositories(
136
- options: CommandOptions,
137
- inputHandler: InputHandler
138
- ): Promise<{
139
- repositories: RepositoryIdentifier[];
140
- parseErrors: Array<{ url: string; error: string; line: number }>;
141
- }> {
142
- console.log("");
143
- console.log("📝 Getting repositories...");
144
-
145
- let repositories: RepositoryIdentifier[] = [];
146
- let parseErrors: Array<{ url: string; error: string; line: number }> = [];
147
-
148
- if (options.file) {
149
- logger.info(`Using file input: ${options.file}`);
150
- const result = await inputHandler.getRepositoriesFromFile(options.file);
151
- repositories = result.repos;
152
- parseErrors = result.errors;
153
- } else if (options.stdin) {
154
- logger.info("Using stdin input");
155
- console.log(
156
- "Enter repository URLs (one per line, press Ctrl+D to finish):"
157
- );
158
- const result = await inputHandler.getRepositoriesFromStdin();
159
- repositories = result.repos;
160
- parseErrors = result.errors;
161
- } else {
162
- logger.info("Using interactive CLI input");
163
- const result = await inputHandler.getRepositoriesFromInteractive();
164
- repositories = result.repos;
165
- parseErrors = result.errors;
166
- }
167
-
168
- return { repositories, parseErrors };
169
- }
170
-
171
- function logParseErrors(
172
- parseErrors: Array<{ url: string; error: string; line: number }>
173
- ): void {
174
- console.warn(`⚠️ Found ${parseErrors.length} invalid repositories:`);
175
- for (const err of parseErrors) {
176
- console.warn(` Line ${err.line}: ${err.error}`);
177
- }
178
- console.warn("");
179
- }
180
-
181
- function showRepositoriesPreview(
182
- repositories: RepositoryIdentifier[],
183
- dryRun: boolean
184
- ): void {
185
- console.log(
186
- `📋 Will ${dryRun ? "validate" : "archive"} ${repositories.length} repositories:`
187
- );
188
- for (let index = 0; index < Math.min(repositories.length, 5); index++) {
189
- const repo = repositories[index];
190
- if (repo) {
191
- console.log(` ${index + 1}. ${repo.owner}/${repo.repo}`);
192
- }
193
- }
194
- if (repositories.length > 5) {
195
- console.log(` ... and ${repositories.length - 5} more`);
196
- }
197
- }
198
-
199
- async function confirmOperation(
200
- options: CommandOptions,
201
- inputHandler: InputHandler
202
- ): Promise<void> {
203
- console.log("");
204
- if (!(options.force || options.dryRun)) {
205
- const confirmed = await inputHandler.promptForConfirmation(
206
- "Are you sure you want to archive these repositories?"
207
- );
208
-
209
- if (!confirmed) {
210
- console.log("❌ Cancelled");
211
- process.exit(1);
212
- }
213
- } else if (options.dryRun) {
214
- console.log(`ℹ️ ${MESSAGES.DRY_RUN_MODE}`);
215
- }
216
- }
217
-
218
- async function archiveRepositories(
219
- archiver: Archiver,
220
- repositories: RepositoryIdentifier[],
221
- options: ArchiveOptions,
222
- progressDisplay: ProgressDisplay
223
- ): Promise<ArchiveResult[]> {
224
- const results = await archiver.archiveRepositories(repositories, options);
225
-
226
- const summary = archiver.getSummary();
227
- const progress = {
228
- completed: summary.successful + summary.failed + summary.skipped,
229
- failed: summary.failed,
230
- total: repositories.length,
231
- };
232
-
233
- if (progressDisplay.shouldUpdate()) {
234
- console.log(`\r${progressDisplay.getProgressBar(progress)}`);
235
- }
236
-
237
- return results;
238
- }
239
-
240
- function displayResults(
241
- archiver: Archiver,
242
- results: ArchiveResult[],
243
- progressDisplay: ProgressDisplay
244
- ): void {
245
- console.log("");
246
- console.log("");
247
-
248
- const summary = archiver.getSummary();
249
- console.log(progressDisplay.getSummaryBox(summary));
250
-
251
- if (summary.failed > 0) {
252
- console.log("");
253
- console.log("❌ Failed repositories:");
254
- for (const r of results) {
255
- if (!r.success) {
256
- console.log(` ${r.owner}/${r.repo}: ${r.message}`);
257
- }
258
- }
259
- }
260
-
261
- if (summary.skipped > 0) {
262
- console.log("");
263
- console.log("⚠️ Skipped repositories:");
264
- for (const r of results) {
265
- if (r.success && !r.archived) {
266
- console.log(` ${r.owner}/${r.repo}: ${r.message}`);
267
- }
268
- }
269
- }
270
-
271
- if (summary.failed > 0) {
272
- logger.warn(`Archive command completed with ${summary.failed} failures`);
273
- process.exit(1);
274
- } else {
275
- console.log("");
276
- console.log("✅ All repositories processed successfully!");
277
- logger.info("Archive command completed successfully");
278
- process.exit(0);
279
- }
280
- }
281
-
282
- function handleArchiveError(error: unknown): never {
283
- const message = error instanceof Error ? error.message : String(error);
284
- console.error(`❌ Error: ${message}`);
285
- logger.error("Archive command failed:", error);
286
-
287
- provideErrorGuidance(message);
288
-
289
- process.exit(1);
290
- }
291
-
292
- function provideErrorGuidance(message: string): void {
293
- const lowerMessage = message.toLowerCase();
294
-
295
- if (
296
- lowerMessage.includes("token") ||
297
- lowerMessage.includes("authentication")
298
- ) {
299
- console.log("");
300
- console.log("💡 Troubleshooting:");
301
- console.log(
302
- " 1. Make sure you have a valid GitHub Personal Access Token"
303
- );
304
- console.log(' 2. The token needs "repo" scope to archive repositories');
305
- console.log(" 3. Run: github-archiver auth login");
306
- return;
307
- }
308
-
309
- if (lowerMessage.includes("rate limit")) {
310
- console.log("");
311
- console.log("💡 Troubleshooting:");
312
- console.log(" 1. GitHub API rate limit has been exceeded");
313
- console.log(" 2. Wait a few minutes and try again");
314
- console.log(" 3. Use lower concurrency: --concurrency 1");
315
- return;
316
- }
317
-
318
- if (lowerMessage.includes("permission") || lowerMessage.includes("403")) {
319
- console.log("");
320
- console.log("💡 Troubleshooting:");
321
- console.log(" 1. You must be the repository owner or have push access");
322
- console.log(" 2. Check that your GitHub token has correct permissions");
323
- console.log(" 3. Verify you have push access to the repositories");
324
- return;
325
- }
326
-
327
- if (lowerMessage.includes("not found") || lowerMessage.includes("404")) {
328
- console.log("");
329
- console.log("💡 Troubleshooting:");
330
- console.log(" 1. Make sure the repository URL is correct");
331
- console.log(" 2. The repository may have been deleted");
332
- console.log(" 3. Check your GitHub access to the repository");
333
- return;
334
- }
335
-
336
- if (lowerMessage.includes("network") || lowerMessage.includes("timeout")) {
337
- console.log("");
338
- console.log("💡 Troubleshooting:");
339
- console.log(" 1. Check your internet connection");
340
- console.log(" 2. GitHub API may be temporarily unavailable");
341
- console.log(" 3. Try again in a moment");
342
- console.log(" 4. Increase timeout: --timeout 600");
343
- }
344
- }
@@ -1,184 +0,0 @@
1
- import { createInterface } from "node:readline";
2
- import { Command } from "commander";
3
- import { MESSAGES } from "../constants/messages";
4
- import { PATHS } from "../constants/paths";
5
- import { AuthManager } from "../services/auth-manager";
6
- import { getLogger } from "../utils/logger";
7
-
8
- const authManager = new AuthManager(PATHS.APP_DIR);
9
-
10
- function createLoginCommand(): Command {
11
- return new Command("login")
12
- .description("Set up GitHub authentication with a Personal Access Token")
13
- .action(async () => {
14
- try {
15
- await authManager.ensureConfigDir();
16
-
17
- const token = await promptForToken();
18
-
19
- if (!token) {
20
- console.error("No token provided. Aborting.");
21
- process.exit(1);
22
- }
23
-
24
- console.log("Validating token...");
25
- await authManager.saveToken(token);
26
-
27
- const credentials = await authManager.getStoredCredentials();
28
- console.log(`✓ ${MESSAGES.AUTH_SUCCESS}`);
29
- console.log(` User: ${credentials?.githubUser}`);
30
-
31
- getLogger().info("Token saved successfully", {
32
- user: credentials?.githubUser,
33
- });
34
- } catch (error) {
35
- const message = error instanceof Error ? error.message : String(error);
36
- console.error(`✗ Error: ${message}`);
37
- getLogger().error("Failed to save token", { error: message });
38
- process.exit(1);
39
- }
40
- });
41
- }
42
-
43
- function createLogoutCommand(): Command {
44
- return new Command("logout")
45
- .description("Remove stored GitHub authentication token")
46
- .action(async () => {
47
- try {
48
- const credentials = await authManager.getStoredCredentials();
49
-
50
- if (!credentials) {
51
- console.log("No stored credentials found.");
52
- return;
53
- }
54
-
55
- const confirmed = await confirmAction(MESSAGES.CONFIRM_LOGOUT);
56
-
57
- if (!confirmed) {
58
- console.log("Cancelled.");
59
- return;
60
- }
61
-
62
- await authManager.removeToken();
63
- console.log(`✓ ${MESSAGES.TOKEN_REMOVED}`);
64
- getLogger().info("Token removed");
65
- } catch (error) {
66
- const message = error instanceof Error ? error.message : String(error);
67
- console.error(`✗ Error: ${message}`);
68
- getLogger().error("Failed to remove token", { error: message });
69
- process.exit(1);
70
- }
71
- });
72
- }
73
-
74
- function createStatusCommand(): Command {
75
- return new Command("status")
76
- .description("Check authentication status")
77
- .action(async () => {
78
- try {
79
- const token = await authManager.getToken();
80
-
81
- if (!token) {
82
- console.log("✗ Not authenticated");
83
- console.log(
84
- ' Run "github-archiver auth login" to set up authentication'
85
- );
86
- return;
87
- }
88
-
89
- console.log("✓ Authenticated");
90
-
91
- const validation = await authManager.validateToken(token);
92
-
93
- if (validation.valid) {
94
- console.log(` User: ${validation.user}`);
95
- const credentials = await authManager.getStoredCredentials();
96
- if (credentials) {
97
- console.log(
98
- ` Saved at: ${new Date(credentials.savedAt).toLocaleString()}`
99
- );
100
- }
101
- getLogger().info("Authentication status check passed");
102
- } else {
103
- console.log("✗ Token is invalid or expired");
104
- getLogger().warn("Token validation failed");
105
- }
106
- } catch (error) {
107
- const message = error instanceof Error ? error.message : String(error);
108
- console.error(`✗ Error: ${message}`);
109
- getLogger().error("Failed to check auth status", { error: message });
110
- process.exit(1);
111
- }
112
- });
113
- }
114
-
115
- function createValidateCommand(): Command {
116
- return new Command("validate")
117
- .description("Validate stored authentication token")
118
- .action(async () => {
119
- try {
120
- const token = await authManager.getToken();
121
-
122
- if (!token) {
123
- console.log("✗ No token found");
124
- process.exit(1);
125
- }
126
-
127
- console.log("Validating token...");
128
- const validation = await authManager.validateToken(token);
129
-
130
- if (validation.valid) {
131
- console.log(`✓ Token is valid (user: ${validation.user})`);
132
- getLogger().info("Token validation successful", {
133
- user: validation.user,
134
- });
135
- } else {
136
- console.log("✗ Token is invalid or expired");
137
- getLogger().warn("Token validation failed");
138
- process.exit(1);
139
- }
140
- } catch (error) {
141
- const message = error instanceof Error ? error.message : String(error);
142
- console.error(`✗ Error: ${message}`);
143
- getLogger().error("Token validation error", { error: message });
144
- process.exit(1);
145
- }
146
- });
147
- }
148
-
149
- function promptForToken(): Promise<string> {
150
- return new Promise((resolve) => {
151
- const rl = createInterface({
152
- input: process.stdin,
153
- output: process.stdout,
154
- });
155
-
156
- rl.question(MESSAGES.ENTER_TOKEN, (answer) => {
157
- rl.close();
158
- resolve(answer.trim());
159
- });
160
- });
161
- }
162
-
163
- function confirmAction(message: string): Promise<boolean> {
164
- return new Promise((resolve) => {
165
- const rl = createInterface({
166
- input: process.stdin,
167
- output: process.stdout,
168
- });
169
-
170
- rl.question(`${message} [y/N]: `, (answer) => {
171
- rl.close();
172
- resolve(answer.toLowerCase() === "y");
173
- });
174
- });
175
- }
176
-
177
- export function createAuthCommand(): Command {
178
- return new Command("auth")
179
- .description("Manage GitHub authentication")
180
- .addCommand(createLoginCommand())
181
- .addCommand(createLogoutCommand())
182
- .addCommand(createStatusCommand())
183
- .addCommand(createValidateCommand());
184
- }
@@ -1,6 +0,0 @@
1
- export const DEFAULT_CONCURRENCY = 3;
2
- export const DEFAULT_TIMEOUT = 300; // seconds
3
- export const DEFAULT_LOG_LEVEL = "info" as const;
4
- export const MAX_RETRIES = 3;
5
- export const RETRY_DELAY_MS = 1000; // base delay for exponential backoff
6
- export const GITHUB_API_URL = "https://api.github.com";
@@ -1,24 +0,0 @@
1
- export const MESSAGES = {
2
- AUTH_SUCCESS: "Successfully authenticated with GitHub",
3
- ARCHIVE_SUCCESS: "Repository archived successfully",
4
- TOKEN_SAVED: "GitHub token saved successfully",
5
- TOKEN_REMOVED: "GitHub token removed",
6
- INVALID_TOKEN: "Invalid or expired GitHub token",
7
- REPO_NOT_FOUND: "Repository not found",
8
- ALREADY_ARCHIVED: "Repository is already archived",
9
- PERMISSION_DENIED: "You do not have permission to archive this repository",
10
- RATE_LIMITED: "GitHub API rate limit exceeded. Please try again later",
11
- NETWORK_ERROR: "Network error. Please check your connection",
12
- INVALID_URL: "Invalid GitHub repository URL",
13
- NO_TOKEN:
14
- 'No GitHub token found. Please run "github-archiver auth login" first',
15
- OPENING_EDITOR: "Opening text editor for repository URLs...",
16
- NO_REPOS_PROVIDED: "No repositories provided",
17
- ARCHIVING_START: "Starting to archive repositories...",
18
- ARCHIVING_COMPLETE: "Archiving complete",
19
- DRY_RUN_MODE: "Running in dry-run mode (no repositories will be archived)",
20
- CONFIRM_ARCHIVE: "Are you sure you want to archive these repositories?",
21
- ENTER_TOKEN:
22
- "Enter your GitHub Personal Access Token (will not be displayed):",
23
- CONFIRM_LOGOUT: "Are you sure you want to remove the stored token?",
24
- };
@@ -1,12 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join } from "node:path";
3
-
4
- const appDir = join(homedir(), ".github-archiver");
5
-
6
- export const PATHS = {
7
- APP_DIR: appDir,
8
- CONFIG_FILE: join(appDir, "config.json"),
9
- LOG_DIR: join(appDir, "logs"),
10
- COMBINED_LOG: join(appDir, "logs", "combined.log"),
11
- ERROR_LOG: join(appDir, "logs", "errors.log"),
12
- };
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import { Command } from "commander";
2
- import { createArchiveCommand } from "./commands/archive";
3
- import { createAuthCommand } from "./commands/auth";
4
- import { PATHS } from "./constants/paths";
5
- import { createLogger, initializeLogger, setLogger } from "./utils/logger";
6
-
7
- const VERSION = "1.0.0";
8
- const DESCRIPTION = "Archive GitHub repositories via CLI";
9
-
10
- async function main() {
11
- try {
12
- await initializeLogger(PATHS.LOG_DIR);
13
-
14
- const fileLogger = createLogger();
15
-
16
- setLogger(fileLogger);
17
-
18
- const program = new Command();
19
-
20
- program
21
- .name("github-archiver")
22
- .description(DESCRIPTION)
23
- .version(VERSION, "-v, --version", "Display version");
24
-
25
- program.addCommand(createAuthCommand());
26
- program.addCommand(createArchiveCommand());
27
-
28
- program.addHelpCommand(true);
29
-
30
- await program.parseAsync(process.argv);
31
-
32
- if (!process.argv.slice(2).length) {
33
- program.outputHelp();
34
- }
35
- } catch (error) {
36
- const message = error instanceof Error ? error.message : String(error);
37
- console.error(`✗ Fatal error: ${message}`);
38
- process.exit(1);
39
- }
40
- }
41
-
42
- main();