github-archiver 1.0.6 → 1.0.8

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [1.0.8](https://github.com/mynameistito/github-archiver/compare/v1.0.7...v1.0.8) (2026-01-11)
2
+
3
+ ## [1.0.7](https://github.com/mynameistito/github-archiver/compare/v1.0.6...v1.0.7) (2026-01-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * update version number in source code to match package.json ([f9ef48c](https://github.com/mynameistito/github-archiver/commit/f9ef48c344cf46e2ebb37141a9db211bddf57d47))
9
+
1
10
  ## [1.0.6](https://github.com/mynameistito/github-archiver/compare/v1.0.5...v1.0.6) (2026-01-11)
2
11
 
3
12
 
package/README.md CHANGED
@@ -4,23 +4,21 @@
4
4
  [![npm](https://img.shields.io/npm/v/github-archiver)](https://www.npmjs.com/package/github-archiver)
5
5
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
6
6
 
7
- A powerful command-line tool for mass archiving GitHub repositories with parallel processing, comprehensive error handling, and user-friendly feedback.
7
+ A powerful CLI for mass archiving GitHub repositories with parallel processing and comprehensive error handling.
8
8
 
9
9
  ## Features
10
10
 
11
- ✨ **Mass Archive Repositories** - Archive multiple GitHub repositories in parallel
12
- 🔐 **Secure Authentication** - Token stored locally in `~/.github-archiver/config.json`
13
- ⚡ **Parallel Processing** - Archive multiple repos simultaneously (configurable 1-50 concurrency)
14
- 📋 **Multiple Input Methods** - Load repos from editor, file, or stdin
15
- 🔍 **Validation** - Dry-run mode to validate without archiving
16
- 📊 **Progress Tracking** - Real-time progress bars and ETA
17
- 🛡️ **Error Recovery** - Comprehensive error handling with helpful guidance
18
- 📝 **Detailed Logging** - Structured logging to files and console
11
+ - ✨ **Mass Archive** - Archive multiple repositories in parallel
12
+ - 🔐 **Secure Auth** - Token stored locally at `~/.github-archiver/config.json`
13
+ - ⚡ **Parallel Processing** - Configurable concurrency (1-50)
14
+ - 📋 **Flexible Input** - Load repos from editor, file, or stdin
15
+ - 🔍 **Validation** - Dry-run mode to validate without archiving
16
+ - 📊 **Progress Tracking** - Real-time progress bars and ETA
17
+ - 🛡️ **Error Recovery** - Comprehensive error handling with helpful guidance
18
+ - 📝 **Detailed Logging** - Structured logging to files and console
19
19
 
20
20
  ## Installation
21
21
 
22
- ### npm
23
-
24
22
  ```bash
25
23
  npm install -g github-archiver
26
24
  ```
@@ -30,8 +28,7 @@ npm install -g github-archiver
30
28
  ```bash
31
29
  git clone https://github.com/mynameistito/github-archiver.git
32
30
  cd github-archiver
33
- npm install
34
- npm run build
31
+ npm install && npm run build
35
32
  npm install -g .
36
33
  ```
37
34
 
@@ -44,18 +41,12 @@ npm run dev -- <command>
44
41
 
45
42
  ## Quick Start
46
43
 
47
- ### 1. Authenticate with GitHub
48
-
49
44
  ```bash
45
+ # Authenticate
50
46
  github-archiver auth login
51
- # Paste your GitHub Personal Access Token when prompted
52
- ```
53
47
 
54
- ### 2. Archive Repositories
55
-
56
- ```bash
48
+ # Archive (opens editor to input repos)
57
49
  github-archiver archive
58
- # Opens your default text editor to enter repository URLs
59
50
  ```
60
51
 
61
52
  ## Commands
@@ -64,101 +55,61 @@ github-archiver archive
64
55
 
65
56
  Manage GitHub authentication.
66
57
 
67
- #### `auth login`
68
- Authenticate with GitHub using a Personal Access Token.
69
-
70
- ```bash
71
- github-archiver auth login
72
- ```
73
-
74
- #### `auth logout`
75
- Remove stored GitHub token.
76
-
77
- ```bash
78
- github-archiver auth logout
79
- ```
80
-
81
- #### `auth status`
82
- Check current authentication status.
83
-
84
- ```bash
85
- github-archiver auth status
86
- ```
58
+ | Command | Description |
59
+ |---------|-------------|
60
+ | `auth login` | Authenticate with Personal Access Token |
61
+ | `auth logout` | Remove stored token |
62
+ | `auth status` | Check authentication status |
87
63
 
88
64
  ### `archive`
89
65
 
90
- Archive multiple GitHub repositories.
66
+ Archive multiple repositories.
91
67
 
92
68
  ```bash
93
69
  github-archiver archive [options]
94
70
  ```
95
71
 
96
- #### Options
97
-
98
72
  | Option | Default | Description |
99
73
  |--------|---------|-------------|
100
74
  | `--file <path>` | - | Read repository URLs from file |
101
- | `--stdin` | - | Read repository URLs from stdin |
75
+ | `--stdin` | - | Read from stdin |
102
76
  | `--dry-run` | false | Validate without archiving |
103
- | `--concurrency <n>` | 3 | Number of parallel operations (1-50) |
77
+ | `--concurrency <n>` | 3 | Parallel operations (1-50) |
104
78
  | `--timeout <n>` | 300 | API timeout in seconds (10-3600) |
105
79
  | `--verbose` | false | Enable verbose logging |
106
80
  | `--force` | false | Skip confirmation prompts |
107
81
 
108
- #### Examples
82
+ **Examples:**
109
83
 
110
- **Interactive (opens editor):**
111
84
  ```bash
85
+ # Interactive (opens editor)
112
86
  github-archiver archive
113
- ```
114
87
 
115
- **From file:**
116
- ```bash
88
+ # From file
117
89
  github-archiver archive --file repos.txt
118
- ```
119
90
 
120
- **From stdin:**
121
- ```bash
91
+ # From stdin
122
92
  cat repos.txt | github-archiver archive --stdin
123
- ```
124
93
 
125
- **Dry-run (validate only):**
126
- ```bash
94
+ # Dry-run
127
95
  github-archiver archive --file repos.txt --dry-run
128
- ```
129
96
 
130
- **Fast processing (higher concurrency):**
131
- ```bash
97
+ # High concurrency
132
98
  github-archiver archive --file repos.txt --concurrency 10
133
- ```
134
99
 
135
- **Force without confirmation:**
136
- ```bash
100
+ # Force without confirmation
137
101
  github-archiver archive --file repos.txt --force
138
102
  ```
139
103
 
140
104
  ## Input Format
141
105
 
142
- Repository URLs can be specified in three formats:
106
+ Supported formats:
143
107
 
144
- ### HTTPS Format
145
- ```
146
- https://github.com/owner/repo
147
- https://github.com/owner/repo.git
148
- ```
149
-
150
- ### SSH Format
151
- ```
152
- git@github.com:owner/repo.git
153
- git@github.com:owner/repo
154
- ```
155
-
156
- ### Shorthand Format
157
- ```
158
- owner/repo
159
- ```
108
+ - HTTPS: `https://github.com/owner/repo` or `https://github.com/owner/repo.git`
109
+ - SSH: `git@github.com:owner/repo.git` or `git@github.com:owner/repo`
110
+ - Shorthand: `owner/repo`
160
111
 
161
- ### File Example
112
+ **File Example:**
162
113
 
163
114
  ```
164
115
  # Repositories to archive
@@ -166,22 +117,16 @@ https://github.com/facebook/react
166
117
  torvalds/linux
167
118
  owner/private-repo
168
119
 
169
- # Comments are ignored
120
+ # Comments ignored
170
121
  https://github.com/nodejs/node
171
122
  ```
172
123
 
173
124
  ## GitHub Token Requirements
174
125
 
175
- You need a GitHub Personal Access Token with the following:
176
-
177
126
  - **Scope**: `repo` (Full control of private repositories)
178
- - **Minimum Permissions**: Push access to repositories you want to archive
179
-
180
- ### How to Generate a Token
127
+ - **Minimum Permissions**: Push access to target repositories
181
128
 
182
- 1. Go to https://github.com/settings/tokens/new
183
- 2. Create a new token with `repo` scope
184
- 3. Copy the token and run `github-archiver auth login`
129
+ **Generate token:** https://github.com/settings/tokens/new → Create with `repo` scope → Run `github-archiver auth login`
185
130
 
186
131
  ## Output Example
187
132
 
@@ -219,225 +164,108 @@ Starting to archive repositories... (concurrency: 3)
219
164
 
220
165
  ## Troubleshooting
221
166
 
222
- ### Authentication Issues
167
+ ### Authentication
223
168
 
224
- **Error: No GitHub token found**
225
- - Run: `github-archiver auth login`
226
- - Ensure you have a valid Personal Access Token with `repo` scope
169
+ **No token found**: Run `github-archiver auth login`
227
170
 
228
- **Error: Invalid or expired token**
229
- - Generate a new token at https://github.com/settings/tokens
230
- - Run: `github-archiver auth logout` then `github-archiver auth login`
171
+ **Invalid/expired token**: Generate new token at https://github.com/settings/tokens → `auth logout` → `auth login`
231
172
 
232
- ### Permission Issues
173
+ ### Permissions
233
174
 
234
- **Error: Permission denied for owner/repo**
235
- - Verify you are the repository owner or have push access
236
- - Check that your token has the `repo` scope
237
- - The repository must not be archived yet
175
+ **Permission denied**: Verify repo ownership/push access, check `repo` scope, ensure repo isn't already archived
238
176
 
239
177
  ### Rate Limiting
240
178
 
241
- **Error: GitHub API rate limit exceeded**
242
- - Wait a few minutes (rate limit resets hourly)
243
- - Use lower concurrency: `--concurrency 1`
244
- - Increase timeout: `--timeout 600`
179
+ **Rate limit exceeded**: Wait (resets hourly), lower `--concurrency 1`, increase `--timeout 600`
245
180
 
246
- ### Network Issues
181
+ ### Network
247
182
 
248
- **Error: Network error or timeout**
249
- - Check your internet connection
250
- - GitHub API may be temporarily unavailable
251
- - Try again in a moment
252
- - Increase timeout: `--timeout 600`
183
+ **Network error/timeout**: Check connection, GitHub API may be unavailable, retry with `--timeout 600`
253
184
 
254
185
  ### Repository Not Found
255
186
 
256
- **Error: Repository not found**
257
- - Verify the repository URL is correct
258
- - The repository may have been deleted
259
- - Check your GitHub access to the repository
187
+ **Repository not found**: Verify URL, check if deleted, confirm GitHub access
260
188
 
261
189
  ## Configuration
262
190
 
263
- Configuration is stored at:
264
- - **Linux/macOS**: `~/.github-archiver/config.json`
265
- - **Windows**: `%USERPROFILE%\.github-archiver\config.json`
191
+ **Config**: `~/.github-archiver/config.json` (Linux/macOS) or `%USERPROFILE%\.github-archiver\config.json` (Windows)
266
192
 
267
- Logs are stored at:
268
- - **Linux/macOS**: `~/.github-archiver/logs/`
269
- - **Windows**: `%USERPROFILE%\.github-archiver\logs\`
193
+ **Logs**: `~/.github-archiver/logs/` (Linux/macOS) or `%USERPROFILE%\.github-archiver\logs\` (Windows)
270
194
 
271
195
  ## Architecture
272
196
 
273
197
  ```
274
198
  src/
275
- ├── commands/ # CLI commands (auth, archive)
276
- ├── services/ # GitHub API, archiving, auth management
277
- ├── utils/ # Utilities (parsing, formatting, logging)
278
- ├── types/ # TypeScript type definitions
279
- └── constants/ # Configuration constants and messages
199
+ ├── commands/ # CLI commands (auth, archive)
200
+ ├── services/ # GitHub API, archiving, auth management
201
+ ├── utils/ # Parsing, formatting, logging
202
+ ├── types/ # TypeScript definitions
203
+ └── constants/ # Configuration constants
280
204
 
281
205
  tests/
282
- └── unit/ # Unit tests for utilities
206
+ └── unit/ # Unit tests
283
207
  ```
284
208
 
285
- ### Core Services
209
+ **Core Services:**
286
210
 
287
- - **GitHubService**: Handles GitHub API interactions with retry logic
288
- - **Archiver**: Manages parallel repository archiving with p-queue
289
- - **AuthManager**: Manages secure token storage
290
- - **InputHandler**: Handles input from editor, file, or stdin
291
- - **ProgressDisplay**: Manages progress bar and summary output
211
+ - **GitHubService**: GitHub API interactions with retry logic
212
+ - **Archiver**: Parallel archiving with p-queue
213
+ - **AuthManager**: Secure token storage
214
+ - **InputHandler**: Input from editor, file, or stdin
215
+ - **ProgressDisplay**: Progress bar and summary output
292
216
 
293
217
  ## Development
294
218
 
295
- ### Setup
296
-
297
219
  ```bash
298
220
  npm install
299
- npm run typecheck
300
- npm run test
301
- npm run build
221
+ npm run typecheck # Check TypeScript
222
+ npm run test # Run unit tests
223
+ npm run build # Build production bundle
224
+ npm run lint # Check code style
225
+ npm run format # Auto-format code
302
226
  ```
303
227
 
304
- ### Commands
305
-
306
- | Command | Description |
307
- |---------|-------------|
308
- | `npm run dev -- <cmd>` | Run CLI in development mode |
309
- | `npm run typecheck` | Check TypeScript compilation |
310
- | `npm run test` | Run unit tests |
311
- | `npm run build` | Build production bundle |
312
- | `npm run lint` | Check code style |
313
- | `npm run format` | Auto-format code |
314
-
315
228
  ### Code Standards
316
229
 
317
- This project uses **Ultracite**, a zero-config preset that enforces:
318
-
319
- - Strict TypeScript with no implicit `any`
320
- - Accessibility, performance, and security best practices
321
- - Consistent formatting via Biome
230
+ This project uses **Ultracite** (Biome) for:
231
+ - Strict TypeScript (no implicit `any`)
232
+ - Accessibility, performance, security best practices
233
+ - Consistent formatting
322
234
  - Comprehensive error handling
323
235
 
324
- See `AGENTS.md` for detailed standards.
325
-
326
- ### Testing
327
-
328
- ```bash
329
- npm run test # Run all tests
330
- npm run test:coverage # Run with coverage report
331
- ```
236
+ See `AGENTS.md` for details.
332
237
 
333
238
  ## Release Process
334
239
 
335
- This project uses **semantic-release** for automated versioning and publishing to npm.
336
-
337
- ### How Releases Work
338
-
339
- 1. **Commit Format**: Use [Conventional Commits](https://www.conventionalcommits.org/):
340
- - `feat:` - New features → **minor** version bump (1.0.0 → 1.1.0)
341
- - `fix:` - Bug fixes → **patch** version bump (1.0.0 → 1.0.1)
342
- - `BREAKING CHANGE:` - Breaking changes → **major** version bump (1.0.0 → 2.0.0)
343
- - `chore:`, `docs:`, `test:` - No version bump
344
-
345
- 2. **Automatic Trigger**:
346
- - Push to `main` branch triggers the release workflow
347
- - Semantic release analyzes commits and determines version bump
348
- - Package version is updated, published to npm
349
- - GitHub release is created with changelog
350
- - Git tag is created automatically
351
-
352
- 3. **Trusted Publishing**:
353
- - Uses OpenID Connect (OIDC) for secure, tokenless authentication
354
- - No npm tokens required - eliminates security risks
355
- - Short-lived, cryptographically-signed tokens for each publish
356
- - Works with personal GitHub accounts
357
-
358
- 4. **Node Version Requirements**:
359
- - Package runs on Node 18+ (see `package.json` engines)
360
- - Release workflow uses Node 22+ (semantic-release requirement)
361
- - CI tests on Node 18 and 22 for maximum compatibility
362
-
363
- 5. **Example Workflow**:
364
- ```bash
365
- git checkout main
366
- git pull
367
- # Make your commits with conventional format
368
- git commit -m "feat: add support for custom config file"
369
- git push
370
- # Release happens automatically!
371
- ```
372
-
373
- ### Trusted Publishing Setup
374
-
375
- This project uses npm's **Trusted Publishing** feature for secure, tokenless package publishing.
376
-
377
- **Setup (one-time):**
378
- 1. Go to https://www.npmjs.com/package/github-archiver/settings
379
- 2. Under **Trusted Publisher**, click **GitHub Actions**
380
- 3. Fill in:
381
- - **Organization or user**: `mynameistito`
382
- - **Repository**: `github-archiver`
383
- - **Workflow filename**: `release.yml`
384
- 4. Click **Set up connection**
385
- 5. (Recommended) Enable **"Require two-factor authentication and disallow tokens"**
386
-
387
- That's it! No tokens to manage, rotate, or worry about.</think></tool_call>
388
-
389
- ### Commit Message Examples
240
+ This project uses **semantic-release** for automated versioning and publishing.
390
241
 
391
- ```bash
392
- # Feature (minor version bump)
393
- git commit -m "feat: add support for custom config file"
394
-
395
- # Bug fix (patch version bump)
396
- git commit -m "fix: handle empty repository list gracefully"
242
+ **Commit format** (Conventional Commits):
243
+ - `feat:` minor bump
244
+ - `fix:` patch bump
245
+ - `BREAKING CHANGE:` → major bump
246
+ - `chore:`, `docs:`, `test:` no bump
397
247
 
398
- # Breaking change (major version bump)
399
- git commit -m "feat!: change CLI command structure
400
-
401
- BREAKING CHANGE: The 'auth' subcommand is now required"
402
-
403
- # No release
404
- git commit -m "chore: update dependencies"
405
- git commit -m "docs: clarify installation steps"
406
- git commit -m "test: add unit tests for parser"
407
- ```
408
-
409
- ### Manual Releases
410
-
411
- If you need to publish without merging to main:
412
- ```bash
413
- npm run release -- --no-ci
414
- ```
248
+ Pushing to `main` triggers automatic release. See [docs/RELEASE.md](docs/RELEASE.md) for detailed setup and workflow.
415
249
 
416
250
  ## Contributing
417
251
 
418
- Contributions are welcome! Please:
419
-
420
- 1. Follow the code standards (run `npm run format`)
252
+ 1. Follow code standards (`npm run format`)
421
253
  2. Add tests for new features
422
254
  3. Ensure `npm run typecheck` and `npm run test` pass
423
- 4. Create a pull request with a clear description
255
+ 4. Create pull request with clear description
424
256
 
425
257
  ## License
426
258
 
427
- MIT - See LICENSE file for details
259
+ MIT - See LICENSE file for details.
428
260
 
429
261
  ## Support
430
262
 
431
- Having issues? Check the [Troubleshooting](#troubleshooting) section or open an issue on GitHub.
263
+ Check the [Troubleshooting](#troubleshooting) section or open an issue on GitHub.
432
264
 
433
265
  ## Acknowledgments
434
266
 
435
- - Built with TypeScript, Commander.js, Octokit, and Winston
436
- - Uses Biome for code formatting and linting
437
- - Inspired by the need for efficient GitHub repository management
438
-
439
- ## Version
267
+ Built with TypeScript, Commander.js, Octokit, Winston, and Biome.
440
268
 
441
- Current version: 1.0.0
269
+ ---
442
270
 
443
- For the latest updates and release notes, visit: https://github.com/mynameistito/github-archiver/releases
271
+ Current version: 1.0.0 | [Releases](https://github.com/mynameistito/github-archiver/releases)
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import{Command as Me}from"commander";import{Command as Ae}from"commander";var g={AUTH_SUCCESS:"Successfully authenticated with GitHub",ARCHIVE_SUCCESS:"Repository archived successfully",TOKEN_SAVED:"GitHub token saved successfully",TOKEN_REMOVED:"GitHub token removed",INVALID_TOKEN:"Invalid or expired GitHub token",REPO_NOT_FOUND:"Repository not found",ALREADY_ARCHIVED:"Repository is already archived",PERMISSION_DENIED:"You do not have permission to archive this repository",RATE_LIMITED:"GitHub API rate limit exceeded. Please try again later",NETWORK_ERROR:"Network error. Please check your connection",INVALID_URL:"Invalid GitHub repository URL",NO_TOKEN:'No GitHub token found. Please run "github-archiver auth login" first',OPENING_EDITOR:"Opening text editor for repository URLs...",NO_REPOS_PROVIDED:"No repositories provided",ARCHIVING_START:"Starting to archive repositories...",ARCHIVING_COMPLETE:"Archiving complete",DRY_RUN_MODE:"Running in dry-run mode (no repositories will be archived)",CONFIRM_ARCHIVE:"Are you sure you want to archive these repositories?",ENTER_TOKEN:"Enter your GitHub Personal Access Token (will not be displayed):",CONFIRM_LOGOUT:"Are you sure you want to remove the stored token?"};import{homedir as ie}from"node:os";import{join as A}from"node:path";var w=A(ie(),".github-archiver"),p={APP_DIR:w,CONFIG_FILE:A(w,"config.json"),LOG_DIR:A(w,"logs"),COMBINED_LOG:A(w,"logs","combined.log"),ERROR_LOG:A(w,"logs","errors.log")};import se from"p-queue";var u={INVALID_AUTH:"INVALID_AUTH",REPO_NOT_FOUND:"REPO_NOT_FOUND",ALREADY_ARCHIVED:"ALREADY_ARCHIVED",PERMISSION_DENIED:"PERMISSION_DENIED",RATE_LIMITED:"RATE_LIMITED",NETWORK_ERROR:"NETWORK_ERROR",INVALID_URL:"INVALID_URL",PARSE_ERROR:"PARSE_ERROR",EDITOR_ERROR:"EDITOR_ERROR",CONFIG_ERROR:"CONFIG_ERROR",FILE_ERROR:"FILE_ERROR"},l=class r extends Error{constructor(e,t,i,o=!1){super(t),this.name="ArchiveError",this.code=e,this.statusCode=i,this.retryable=o,Object.setPrototypeOf(this,r.prototype)}};function V(r){return r instanceof l}function U(r,e){return new l(u.INVALID_AUTH,r,e,!0)}function B(r,e){return new l(u.REPO_NOT_FOUND,`Repository ${r}/${e} not found`,404,!1)}function Y(r,e){return new l(u.ALREADY_ARCHIVED,`Repository ${r}/${e} is already archived`,422,!1)}function K(r,e){return new l(u.PERMISSION_DENIED,`Permission denied for ${r}/${e}. You must be the repository owner or have push access.`,403,!1)}function W(){return new l(u.RATE_LIMITED,g.RATE_LIMITED,429,!0)}function x(r){return new l(u.NETWORK_ERROR,r||g.NETWORK_ERROR,void 0,!0)}function z(r){return new l(u.CONFIG_ERROR,r)}function O(r){return new l(u.FILE_ERROR,r)}import{promises as ne}from"node:fs";import y from"winston";var S="info";var T;async function j(r){try{await ne.mkdir(r,{recursive:!0})}catch(e){console.error("Failed to create log directory:",e)}}function H(r){let e=r?.logLevel||S,t=r?.logDir||p.LOG_DIR;return y.createLogger({level:e,format:y.format.combine(y.format.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),y.format.errors({stack:!0}),y.format.json()),defaultMeta:{service:"github-archiver"},transports:[new y.transports.File({filename:`${t}/errors.log`,level:"error"}),new y.transports.File({filename:`${t}/combined.log`})]})}function c(){return T||(T=H()),T}function q(r){T=r}var v=c(),D=class{constructor(e,t){this.results=[];this.completed=0;this.failed=0;this.startTime=0;this.gitHubService=e,this.queue=new se({concurrency:t.concurrency,timeout:t.timeout*1e3,interval:1e3,intervalCap:t.concurrency}),v.info("Archiver initialized",{concurrency:t.concurrency,timeout:t.timeout,dryRun:t.dryRun})}async archiveRepositories(e,t){this.results=[],this.completed=0,this.failed=0,v.info(`Starting to archive ${e.length} repositories`,{dryRun:t.dryRun});let i=e.map(o=>this.queue.add(()=>this.archiveRepository(o,t)));return await Promise.all(i),this.results}async archiveRepository(e,t){let i=Date.now();try{if(t.dryRun){v.info(`[DRY-RUN] Would archive ${e.owner}/${e.repo}`);let s={owner:e.owner,repo:e.repo,success:!0,archived:!1,duration:Date.now()-i,message:"[DRY-RUN] Validation successful"};this.results.push(s),this.completed++;return}let o=await this.gitHubService.validateRepository(e.owner,e.repo);if(!o.exists){v.warn(`Repository not found: ${e.owner}/${e.repo}`);let s={owner:e.owner,repo:e.repo,success:!1,archived:!1,error:"Repository not found",duration:Date.now()-i,message:"Repository does not exist on GitHub"};this.results.push(s),this.failed++;return}if(o.isArchived){v.info(`Repository already archived: ${e.owner}/${e.repo}`);let s={owner:e.owner,repo:e.repo,success:!0,archived:!1,duration:Date.now()-i,message:"Repository is already archived"};this.results.push(s),this.completed++;return}console.log(`\u{1F4E6} Archiving ${e.owner}/${e.repo}...`),await this.gitHubService.archiveRepository(e.owner,e.repo);let n={owner:e.owner,repo:e.repo,success:!0,archived:!0,duration:Date.now()-i,message:"Repository archived successfully"};this.results.push(n),this.completed++,v.info(`\u2713 Archived ${e.owner}/${e.repo}`,{duration:n.duration})}catch(o){if(V(o)&&o.code===u.ALREADY_ARCHIVED){v.info(`Repository already archived: ${e.owner}/${e.repo}`);let a={owner:e.owner,repo:e.repo,success:!0,archived:!1,duration:Date.now()-i,message:"Repository is already archived"};this.results.push(a),this.completed++;return}let n=o instanceof Error?o.message:String(o);v.error(`\u2717 Failed to archive ${e.owner}/${e.repo}: ${n}`);let s={owner:e.owner,repo:e.repo,success:!1,archived:!1,error:n,duration:Date.now()-i,message:`Error: ${n}`};this.results.push(s),this.failed++}}getProgress(){return{completed:this.completed,failed:this.failed,total:this.results.length}}getSummary(){let e=this.results.filter(n=>n.success&&n.archived).length,t=this.results.filter(n=>!n.success).length,i=this.results.filter(n=>n.success&&!n.archived).length,o=Date.now()-this.startTime;return{successful:e,failed:t,skipped:i,totalDuration:o}}};import{Octokit as X}from"octokit";import{promises as I}from"node:fs";import{join as J}from"node:path";var L=class{constructor(e=p.APP_DIR){this.configDir=e,this.configFile=J(e,"config.json")}async loadConfig(){try{let e=process.env.GH_TOKEN,t=await this.loadFileCredentials();return{token:e||t?.token,concurrency:3,timeout:300,logLevel:S,logDir:J(this.configDir,"logs"),configDir:this.configDir}}catch(e){throw z(`Failed to load configuration: ${e instanceof Error?e.message:String(e)}`)}}async saveConfig(e){try{await this.ensureConfigDir(),await I.writeFile(this.configFile,JSON.stringify(e,null,2),"utf-8")}catch(t){throw O(`Failed to save configuration: ${t instanceof Error?t.message:String(t)}`)}}async ensureConfigDir(){try{await I.mkdir(this.configDir,{recursive:!0})}catch(e){throw O(`Failed to create config directory: ${e instanceof Error?e.message:String(e)}`)}}async getStoredCredentials(){try{let e=await I.readFile(this.configFile,"utf-8");return JSON.parse(e)}catch{return null}}async removeStoredCredentials(){try{await I.unlink(this.configFile)}catch(e){if(e.code!=="ENOENT")throw O(`Failed to remove stored credentials: ${e instanceof Error?e.message:String(e)}`)}}async loadFileCredentials(){try{let e=await I.readFile(this.configFile,"utf-8");return JSON.parse(e)}catch{return null}}};var E=class{constructor(e=p.APP_DIR){this.configManager=new L(e)}async saveToken(e){if(!e||typeof e!="string")throw U("Invalid token format");let t=new X({auth:e});try{let{data:i}=await t.rest.users.getAuthenticated(),o={token:e,savedAt:new Date().toISOString(),githubUser:i.login};await this.configManager.saveConfig(o)}catch(i){throw U(`Failed to validate token: ${i instanceof Error?i.message:String(i)}`)}}async getToken(){return process.env.GH_TOKEN?process.env.GH_TOKEN:(await this.configManager.getStoredCredentials())?.token}async removeToken(){await this.configManager.removeStoredCredentials()}async validateToken(e){try{let t=new X({auth:e}),{data:i}=await t.rest.users.getAuthenticated();return{valid:!0,user:i.login}}catch{return{valid:!1}}}getStoredCredentials(){return this.configManager.getStoredCredentials()}ensureConfigDir(){return this.configManager.ensureConfigDir()}loadConfig(){return this.configManager.loadConfig()}};import{Octokit as de}from"octokit";var d=c(),_=class{constructor(e){this.octokit=new de({auth:e})}async archiveRepository(e,t){let i=Date.now();d.debug(`Attempting to archive ${e}/${t}`),await this.retryWithBackoff(async()=>{try{await this.octokit.rest.repos.update({owner:e,repo:t,archived:!0});let o=Date.now()-i;d.info(`Successfully archived ${e}/${t} in ${o}ms`)}catch(o){this.handleArchiveError(o,e,t)}})}handleArchiveError(e,t,i){let o=this.parseOctokitError(e);throw d.error(`Failed to archive ${t}/${i}: ${o.message}`,{status:o.status,code:o.code}),o.status===404?B(t,i):o.status===403||o.message.includes("permission")?K(t,i):o.status===422&&o.message.includes("archived")?Y(t,i):o.message.includes("API rate limit")?W():o.message.includes("ECONNREFUSED")||o.message.includes("ETIMEDOUT")||o.message.includes("EHOSTUNREACH")||o.message.includes("socket hang up")?x(o.message):e}parseOctokitError(e){let t=e instanceof Error?e.message:String(e),i=e;return{message:t,status:i?.status||0,code:i?.response?.data?.message}}async validateRepository(e,t){try{d.debug(`Validating repository ${e}/${t}`);let{data:i}=await this.octokit.rest.repos.get({owner:e,repo:t}),o={exists:!0,isArchived:i.archived};return d.debug(`Repository ${e}/${t} validated`,o),o}catch(i){if(i instanceof Error&&i.message.includes("404"))return d.debug(`Repository ${e}/${t} does not exist`),{exists:!1,isArchived:!1};throw d.error(`Error validating ${e}/${t}:`,i),i}}async getRateLimitStatus(){try{d.debug("Fetching rate limit status");let{data:e}=await this.octokit.rest.rateLimit.get(),t={remaining:e.rate.remaining,reset:new Date(e.rate.reset*1e3),limit:e.rate.limit};return d.debug("Rate limit status retrieved",{remaining:t.remaining,limit:t.limit,resetAt:t.reset.toISOString()}),t}catch(e){throw d.error("Failed to fetch rate limit status:",e),e}}async validateAuth(){try{d.debug("Validating authentication");let{data:e}=await this.octokit.rest.users.getAuthenticated();return d.info(`Authenticated as user: ${e.login}`),e.login}catch(e){throw d.error("Authentication validation failed:",e),x(e instanceof Error?e.message:"Authentication failed")}}async retryWithBackoff(e,t=3){let i;for(let o=0;o<t;o++)try{return await e()}catch(n){i=n;let s=i.message.toLowerCase();if(!(s.includes("rate limit")||s.includes("timeout")||s.includes("econnrefused")||s.includes("etimedout")||s.includes("ehostunreach")||s.includes("socket hang up"))||o===t-1)throw n;let m=1e3*2**o;d.warn(`Retry attempt ${o+1}/${t} after ${m}ms`,{error:s}),await new Promise($=>setTimeout($,m))}throw i||new Error("Unknown error during retry")}};import{promises as fe}from"node:fs";import{createInterface as M}from"node:readline";var P=c(),me=/^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$/,ge=[/^(?:https?:\/\/)?(?:www\.)?github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:\/)?$/i,/^git@github\.com:([\w.-]+)\/([\w.-]+?)(?:\.git)?$/i,/^([\w.-]+)\/([\w.-]+)$/];function Q(r){let e=r.trim();if(!e)throw new l(u.INVALID_URL,"URL cannot be empty");for(let t of ge){let i=e.match(t);if(i){let o=i[1],n=i[2];if(!(o&&n))throw new l(u.INVALID_URL,`Invalid GitHub URL format: ${r}`);if(!Z(o))throw new l(u.INVALID_URL,`Invalid owner name: ${o}. Owner names must contain only alphanumeric characters, hyphens, or periods.`);if(!Z(n))throw new l(u.INVALID_URL,`Invalid repository name: ${n}. Repository names must contain only alphanumeric characters, hyphens, underscores, or periods.`);let s=`https://github.com/${o}/${n}`;return P.debug("Parsed repository URL",{original:e,owner:o,repo:n,normalized:s}),{owner:o,repo:n,url:s}}}throw new l(u.INVALID_URL,`Invalid GitHub URL: ${r}`)}function Z(r){return me.test(r)}function pe(r){let e=[],t=[];for(let[i,o]of r.entries()){let n=i+1,s=o.trim();if(!(!s||s.startsWith("#")))try{let a=Q(s);e.push(a),P.debug(`Line ${n}: Valid repository`,{owner:a.owner,repo:a.repo})}catch(a){let m=a instanceof Error?a.message:String(a);t.push({url:s,error:m,line:n}),P.warn(`Line ${n}: Invalid repository`,{url:s,error:m})}}return P.info("Batch parsing complete",{total:r.length,valid:e.length,invalid:t.length,skipped:r.length-e.length-t.length}),{valid:e,invalid:t}}var k={parseRepositoryUrl:Q,parseRepositoriesBatch:pe};var f=c(),N=class{getRepositoriesFromInteractive(){return f.info("Starting interactive CLI input for repositories"),new Promise(e=>{let t=M({input:process.stdin,output:process.stdout});console.log(""),console.log("\u{1F4DD} Enter repository URLs one at a time:"),console.log(' (Type "done" when finished, or leave empty and press Enter to skip)'),console.log("");let i=[],o=1,n=()=>{t.question(`[${o}] > `,a=>{let m=a.trim();if(m.toLowerCase()==="done"){s();return}if(m===""){o++,n();return}i.push(m),o++,n()})},s=()=>{t.close();let a=k.parseRepositoriesBatch(i);a.invalid.length>0&&f.warn(`Found ${a.invalid.length} invalid repositories`,{errors:a.invalid}),f.info(`Parsed ${a.valid.length} valid repositories`),e({repos:a.valid,errors:a.invalid})};t.on("close",()=>{s()}),n()})}async getRepositoriesFromFile(e){f.info(`Reading repositories from file: ${e}`);try{let i=(await fe.readFile(e,"utf-8")).split(`
2
2
  `),o=k.parseRepositoriesBatch(i);return o.invalid.length>0&&f.warn(`Found ${o.invalid.length} invalid entries in file`,{errors:o.invalid}),f.info(`Parsed ${o.valid.length} valid repositories from file`),{repos:o.valid,errors:o.invalid}}catch(t){let i=t instanceof Error?t.message:String(t);throw f.error(`Failed to read file ${e}: ${i}`),new Error(`Failed to read file: ${i}`)}}getRepositoriesFromStdin(){return f.info("Reading repositories from stdin"),new Promise(e=>{let t=[],i=M({input:process.stdin,output:process.stdout});i.on("line",o=>{t.push(o)}),i.on("close",()=>{let o=k.parseRepositoriesBatch(t);o.invalid.length>0&&f.warn(`Found ${o.invalid.length} invalid entries from stdin`,{errors:o.invalid}),f.info(`Parsed ${o.valid.length} valid repositories from stdin`),e({repos:o.valid,errors:o.invalid})})})}promptForConfirmation(e){return new Promise(t=>{let i=M({input:process.stdin,output:process.stdout});i.question(`${e} [y/N]: `,o=>{i.close();let n=o.toLowerCase()==="y";f.info(`User confirmation: ${n}`),t(n)})})}};function he(r){if(r<1e3)return`${r}ms`;if(r<6e4)return`${(r/1e3).toFixed(1)}s`;let e=Math.floor(r/6e4),t=(r%6e4/1e3).toFixed(0);return`${e}m ${t}s`}function ve(r){if(r===0)return"0 B";let e=1024,t=["B","KB","MB","GB"],i=Math.floor(Math.log(r)/Math.log(e));return`${(r/e**i).toFixed(2)} ${t[i]}`}function Re(r,e){return e===0?"0%":`${(r/e*100).toFixed(1)}%`}function ye(r,e,t=30){if(e===0)return"[ ]";let i=Math.round(r/e*t),o=t-i,n="=".repeat(i),s=" ".repeat(o),a=(r/e*100).toFixed(0);return`[${n}${s}] ${a}%`}function Ee(r,e){let t=Math.max(0,(e-r.length)/2);return" ".repeat(Math.floor(t))+r}function we(r,e){return r.length<=e?r:`${r.substring(0,e-3)}...`}var b={formatDuration:he,formatBytes:ve,formatPercent:Re,createProgressBar:ye,centerText:Ee,truncate:we};var F=class{constructor(){this.lastUpdate=0;this.updateInterval=500;this.startTime=Date.now()}shouldUpdate(){let e=Date.now();return e-this.lastUpdate>=this.updateInterval?(this.lastUpdate=e,!0):!1}getProgressBar(e){let{completed:t,total:i,current:o}=e,n=i>0?t/i*100:0,a=`${b.createProgressBar(t,i,25)} ${t}/${i} (${n.toFixed(0)}%)`;return o&&(a+=` - ${o.owner}/${o.repo}`),a}getSummaryBox(e){let{successful:t,failed:i,skipped:o,totalDuration:n}=e,s=t+i+o,a=b.formatDuration(n);return["\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557","\u2551 Archive Operation Summary \u2551","\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",`\u2551 \u2705 Successful: ${String(t).padEnd(20)} \u2551`,`\u2551 \u26A0\uFE0F Skipped: ${String(o).padEnd(20)} \u2551`,`\u2551 \u274C Failed: ${String(i).padEnd(20)} \u2551`,"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",`\u2551 Total: ${String(s).padEnd(20)} \u2551`,`\u2551 Duration: ${String(a).padEnd(20)} \u2551`,"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"].join(`
3
- `)}getElapsedTime(){let e=Date.now()-this.startTime;return b.formatDuration(e)}getEstimatedTimeRemaining(e,t){if(e===0)return null;let o=(Date.now()-this.startTime)/e,n=(t-e)*o;return n>0?b.formatDuration(n):null}};var R=c();function ee(){return new Ae("archive").description("Archive GitHub repositories").option("--file <path>","Read repository URLs from file").option("--stdin","Read repository URLs from standard input").option("--dry-run","Validate without archiving",!1).option("--concurrency <n>","Number of parallel operations","3").option("--timeout <n>","API timeout in seconds","300").option("--verbose","Verbose logging",!1).option("--force","Skip confirmations",!1).action(async r=>{await Ie(r)})}async function Ie(r){try{R.info("Archive command started",{options:r});let{concurrency:e,timeout:t}=be(r),i=new E(p.APP_DIR),o=new N,n=new F,s=await Ce(i),{repositories:a,parseErrors:m}=await $e(r,o);m.length>0&&Oe(m),a.length===0&&(console.error(`\u274C ${g.NO_REPOS_PROVIDED}`),process.exit(1)),Se(a,r.dryRun),await Te(r,o);let $={dryRun:r.dryRun,concurrency:e,timeout:t,force:r.force,verbose:r.verbose};console.log(""),console.log(`${g.ARCHIVING_START} (concurrency: ${e})`),console.log("");let G=new D(s,$),oe=await De(G,a,$,n);Le(G,oe,n)}catch(e){_e(e)}}function be(r){let e=Number.parseInt(r.concurrency,10),t=Number.parseInt(r.timeout,10);return(e<1||e>50)&&(console.error("\u274C Concurrency must be between 1 and 50"),process.exit(1)),(t<10||t>3600)&&(console.error("\u274C Timeout must be between 10 and 3600 seconds"),process.exit(1)),{concurrency:e,timeout:t}}async function Ce(r){console.log("\u{1F510} Checking authentication...");let e=await r.getToken();e||(console.error(`\u274C ${g.NO_TOKEN}`),console.error(" Run: github-archiver auth login"),process.exit(1));let t=new _(e);try{let i=await t.validateAuth();return console.log(`\u2705 Authenticated as: ${i}`),t}catch(i){console.error("\u274C Authentication failed"),R.error("Auth validation failed:",i),process.exit(1)}}async function $e(r,e){console.log(""),console.log("\u{1F4DD} Getting repositories...");let t=[],i=[];if(r.file){R.info(`Using file input: ${r.file}`);let o=await e.getRepositoriesFromFile(r.file);t=o.repos,i=o.errors}else if(r.stdin){R.info("Using stdin input"),console.log("Enter repository URLs (one per line, press Ctrl+D to finish):");let o=await e.getRepositoriesFromStdin();t=o.repos,i=o.errors}else{R.info("Using interactive CLI input");let o=await e.getRepositoriesFromInteractive();t=o.repos,i=o.errors}return{repositories:t,parseErrors:i}}function Oe(r){console.warn(`\u26A0\uFE0F Found ${r.length} invalid repositories:`);for(let e of r)console.warn(` Line ${e.line}: ${e.error}`);console.warn("")}function Se(r,e){console.log(`\u{1F4CB} Will ${e?"validate":"archive"} ${r.length} repositories:`);for(let t=0;t<Math.min(r.length,5);t++){let i=r[t];i&&console.log(` ${t+1}. ${i.owner}/${i.repo}`)}r.length>5&&console.log(` ... and ${r.length-5} more`)}async function Te(r,e){console.log(""),r.force||r.dryRun?r.dryRun&&console.log(`\u2139\uFE0F ${g.DRY_RUN_MODE}`):await e.promptForConfirmation("Are you sure you want to archive these repositories?")||(console.log("\u274C Cancelled"),process.exit(1))}async function De(r,e,t,i){let o=await r.archiveRepositories(e,t),n=r.getSummary(),s={completed:n.successful+n.failed+n.skipped,failed:n.failed,total:e.length};return i.shouldUpdate()&&console.log(`\r${i.getProgressBar(s)}`),o}function Le(r,e,t){console.log(""),console.log("");let i=r.getSummary();if(console.log(t.getSummaryBox(i)),i.failed>0){console.log(""),console.log("\u274C Failed repositories:");for(let o of e)o.success||console.log(` ${o.owner}/${o.repo}: ${o.message}`)}if(i.skipped>0){console.log(""),console.log("\u26A0\uFE0F Skipped repositories:");for(let o of e)o.success&&!o.archived&&console.log(` ${o.owner}/${o.repo}: ${o.message}`)}i.failed>0?(R.warn(`Archive command completed with ${i.failed} failures`),process.exit(1)):(console.log(""),console.log("\u2705 All repositories processed successfully!"),R.info("Archive command completed successfully"),process.exit(0))}function _e(r){let e=r instanceof Error?r.message:String(r);console.error(`\u274C Error: ${e}`),R.error("Archive command failed:",r),Pe(e),process.exit(1)}function Pe(r){let e=r.toLowerCase();if(e.includes("token")||e.includes("authentication")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Make sure you have a valid GitHub Personal Access Token"),console.log(' 2. The token needs "repo" scope to archive repositories'),console.log(" 3. Run: github-archiver auth login");return}if(e.includes("rate limit")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. GitHub API rate limit has been exceeded"),console.log(" 2. Wait a few minutes and try again"),console.log(" 3. Use lower concurrency: --concurrency 1");return}if(e.includes("permission")||e.includes("403")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. You must be the repository owner or have push access"),console.log(" 2. Check that your GitHub token has correct permissions"),console.log(" 3. Verify you have push access to the repositories");return}if(e.includes("not found")||e.includes("404")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Make sure the repository URL is correct"),console.log(" 2. The repository may have been deleted"),console.log(" 3. Check your GitHub access to the repository");return}(e.includes("network")||e.includes("timeout"))&&(console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Check your internet connection"),console.log(" 2. GitHub API may be temporarily unavailable"),console.log(" 3. Try again in a moment"),console.log(" 4. Increase timeout: --timeout 600"))}import{createInterface as re}from"node:readline";import{Command as C}from"commander";var h=new E(p.APP_DIR);function ke(){return new C("login").description("Set up GitHub authentication with a Personal Access Token").action(async()=>{try{await h.ensureConfigDir();let r=await xe();r||(console.error("No token provided. Aborting."),process.exit(1)),console.log("Validating token..."),await h.saveToken(r);let e=await h.getStoredCredentials();console.log(`\u2713 ${g.AUTH_SUCCESS}`),console.log(` User: ${e?.githubUser}`),c().info("Token saved successfully",{user:e?.githubUser})}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to save token",{error:e}),process.exit(1)}})}function Ne(){return new C("logout").description("Remove stored GitHub authentication token").action(async()=>{try{if(!await h.getStoredCredentials()){console.log("No stored credentials found.");return}if(!await He(g.CONFIRM_LOGOUT)){console.log("Cancelled.");return}await h.removeToken(),console.log(`\u2713 ${g.TOKEN_REMOVED}`),c().info("Token removed")}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to remove token",{error:e}),process.exit(1)}})}function Fe(){return new C("status").description("Check authentication status").action(async()=>{try{let r=await h.getToken();if(!r){console.log("\u2717 Not authenticated"),console.log(' Run "github-archiver auth login" to set up authentication');return}console.log("\u2713 Authenticated");let e=await h.validateToken(r);if(e.valid){console.log(` User: ${e.user}`);let t=await h.getStoredCredentials();t&&console.log(` Saved at: ${new Date(t.savedAt).toLocaleString()}`),c().info("Authentication status check passed")}else console.log("\u2717 Token is invalid or expired"),c().warn("Token validation failed")}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to check auth status",{error:e}),process.exit(1)}})}function Ue(){return new C("validate").description("Validate stored authentication token").action(async()=>{try{let r=await h.getToken();r||(console.log("\u2717 No token found"),process.exit(1)),console.log("Validating token...");let e=await h.validateToken(r);e.valid?(console.log(`\u2713 Token is valid (user: ${e.user})`),c().info("Token validation successful",{user:e.user})):(console.log("\u2717 Token is invalid or expired"),c().warn("Token validation failed"),process.exit(1))}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Token validation error",{error:e}),process.exit(1)}})}function xe(){return new Promise(r=>{let e=re({input:process.stdin,output:process.stdout});e.question(g.ENTER_TOKEN,t=>{e.close(),r(t.trim())})})}function He(r){return new Promise(e=>{let t=re({input:process.stdin,output:process.stdout});t.question(`${r} [y/N]: `,i=>{t.close(),e(i.toLowerCase()==="y")})})}function te(){return new C("auth").description("Manage GitHub authentication").addCommand(ke()).addCommand(Ne()).addCommand(Fe()).addCommand(Ue())}var Ge="1.0.0",Ve="Archive GitHub repositories via CLI";async function Be(){try{await j(p.LOG_DIR);let r=H();q(r);let e=new Me;e.name("github-archiver").description(Ve).version(Ge,"-v, --version","Display version"),e.addCommand(te()),e.addCommand(ee()),e.addHelpCommand(!0),await e.parseAsync(process.argv),process.argv.slice(2).length||e.outputHelp()}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Fatal error: ${e}`),process.exit(1)}}Be();
3
+ `)}getElapsedTime(){let e=Date.now()-this.startTime;return b.formatDuration(e)}getEstimatedTimeRemaining(e,t){if(e===0)return null;let o=(Date.now()-this.startTime)/e,n=(t-e)*o;return n>0?b.formatDuration(n):null}};var R=c();function ee(){return new Ae("archive").description("Archive GitHub repositories").option("--file <path>","Read repository URLs from file").option("--stdin","Read repository URLs from standard input").option("--dry-run","Validate without archiving",!1).option("--concurrency <n>","Number of parallel operations","3").option("--timeout <n>","API timeout in seconds","300").option("--verbose","Verbose logging",!1).option("--force","Skip confirmations",!1).action(async r=>{await Ie(r)})}async function Ie(r){try{R.info("Archive command started",{options:r});let{concurrency:e,timeout:t}=be(r),i=new E(p.APP_DIR),o=new N,n=new F,s=await Ce(i),{repositories:a,parseErrors:m}=await $e(r,o);m.length>0&&Oe(m),a.length===0&&(console.error(`\u274C ${g.NO_REPOS_PROVIDED}`),process.exit(1)),Se(a,r.dryRun),await Te(r,o);let $={dryRun:r.dryRun,concurrency:e,timeout:t,force:r.force,verbose:r.verbose};console.log(""),console.log(`${g.ARCHIVING_START} (concurrency: ${e})`),console.log("");let G=new D(s,$),oe=await De(G,a,$,n);Le(G,oe,n)}catch(e){_e(e)}}function be(r){let e=Number.parseInt(r.concurrency,10),t=Number.parseInt(r.timeout,10);return(e<1||e>50)&&(console.error("\u274C Concurrency must be between 1 and 50"),process.exit(1)),(t<10||t>3600)&&(console.error("\u274C Timeout must be between 10 and 3600 seconds"),process.exit(1)),{concurrency:e,timeout:t}}async function Ce(r){console.log("\u{1F510} Checking authentication...");let e=await r.getToken();e||(console.error(`\u274C ${g.NO_TOKEN}`),console.error(" Run: github-archiver auth login"),process.exit(1));let t=new _(e);try{let i=await t.validateAuth();return console.log(`\u2705 Authenticated as: ${i}`),t}catch(i){console.error("\u274C Authentication failed"),R.error("Auth validation failed:",i),process.exit(1)}}async function $e(r,e){console.log(""),console.log("\u{1F4DD} Getting repositories...");let t=[],i=[];if(r.file){R.info(`Using file input: ${r.file}`);let o=await e.getRepositoriesFromFile(r.file);t=o.repos,i=o.errors}else if(r.stdin){R.info("Using stdin input"),console.log("Enter repository URLs (one per line, press Ctrl+D to finish):");let o=await e.getRepositoriesFromStdin();t=o.repos,i=o.errors}else{R.info("Using interactive CLI input");let o=await e.getRepositoriesFromInteractive();t=o.repos,i=o.errors}return{repositories:t,parseErrors:i}}function Oe(r){console.warn(`\u26A0\uFE0F Found ${r.length} invalid repositories:`);for(let e of r)console.warn(` Line ${e.line}: ${e.error}`);console.warn("")}function Se(r,e){console.log(`\u{1F4CB} Will ${e?"validate":"archive"} ${r.length} repositories:`);for(let t=0;t<Math.min(r.length,5);t++){let i=r[t];i&&console.log(` ${t+1}. ${i.owner}/${i.repo}`)}r.length>5&&console.log(` ... and ${r.length-5} more`)}async function Te(r,e){console.log(""),r.force||r.dryRun?r.dryRun&&console.log(`\u2139\uFE0F ${g.DRY_RUN_MODE}`):await e.promptForConfirmation("Are you sure you want to archive these repositories?")||(console.log("\u274C Cancelled"),process.exit(1))}async function De(r,e,t,i){let o=await r.archiveRepositories(e,t),n=r.getSummary(),s={completed:n.successful+n.failed+n.skipped,failed:n.failed,total:e.length};return i.shouldUpdate()&&console.log(`\r${i.getProgressBar(s)}`),o}function Le(r,e,t){console.log(""),console.log("");let i=r.getSummary();if(console.log(t.getSummaryBox(i)),i.failed>0){console.log(""),console.log("\u274C Failed repositories:");for(let o of e)o.success||console.log(` ${o.owner}/${o.repo}: ${o.message}`)}if(i.skipped>0){console.log(""),console.log("\u26A0\uFE0F Skipped repositories:");for(let o of e)o.success&&!o.archived&&console.log(` ${o.owner}/${o.repo}: ${o.message}`)}i.failed>0?(R.warn(`Archive command completed with ${i.failed} failures`),process.exit(1)):(console.log(""),console.log("\u2705 All repositories processed successfully!"),R.info("Archive command completed successfully"),process.exit(0))}function _e(r){let e=r instanceof Error?r.message:String(r);console.error(`\u274C Error: ${e}`),R.error("Archive command failed:",r),Pe(e),process.exit(1)}function Pe(r){let e=r.toLowerCase();if(e.includes("token")||e.includes("authentication")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Make sure you have a valid GitHub Personal Access Token"),console.log(' 2. The token needs "repo" scope to archive repositories'),console.log(" 3. Run: github-archiver auth login");return}if(e.includes("rate limit")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. GitHub API rate limit has been exceeded"),console.log(" 2. Wait a few minutes and try again"),console.log(" 3. Use lower concurrency: --concurrency 1");return}if(e.includes("permission")||e.includes("403")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. You must be the repository owner or have push access"),console.log(" 2. Check that your GitHub token has correct permissions"),console.log(" 3. Verify you have push access to the repositories");return}if(e.includes("not found")||e.includes("404")){console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Make sure the repository URL is correct"),console.log(" 2. The repository may have been deleted"),console.log(" 3. Check your GitHub access to the repository");return}(e.includes("network")||e.includes("timeout"))&&(console.log(""),console.log("\u{1F4A1} Troubleshooting:"),console.log(" 1. Check your internet connection"),console.log(" 2. GitHub API may be temporarily unavailable"),console.log(" 3. Try again in a moment"),console.log(" 4. Increase timeout: --timeout 600"))}import{createInterface as re}from"node:readline";import{Command as C}from"commander";var h=new E(p.APP_DIR);function ke(){return new C("login").description("Set up GitHub authentication with a Personal Access Token").action(async()=>{try{await h.ensureConfigDir();let r=await xe();r||(console.error("No token provided. Aborting."),process.exit(1)),console.log("Validating token..."),await h.saveToken(r);let e=await h.getStoredCredentials();console.log(`\u2713 ${g.AUTH_SUCCESS}`),console.log(` User: ${e?.githubUser}`),c().info("Token saved successfully",{user:e?.githubUser})}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to save token",{error:e}),process.exit(1)}})}function Ne(){return new C("logout").description("Remove stored GitHub authentication token").action(async()=>{try{if(!await h.getStoredCredentials()){console.log("No stored credentials found.");return}if(!await He(g.CONFIRM_LOGOUT)){console.log("Cancelled.");return}await h.removeToken(),console.log(`\u2713 ${g.TOKEN_REMOVED}`),c().info("Token removed")}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to remove token",{error:e}),process.exit(1)}})}function Fe(){return new C("status").description("Check authentication status").action(async()=>{try{let r=await h.getToken();if(!r){console.log("\u2717 Not authenticated"),console.log(' Run "github-archiver auth login" to set up authentication');return}console.log("\u2713 Authenticated");let e=await h.validateToken(r);if(e.valid){console.log(` User: ${e.user}`);let t=await h.getStoredCredentials();t&&console.log(` Saved at: ${new Date(t.savedAt).toLocaleString()}`),c().info("Authentication status check passed")}else console.log("\u2717 Token is invalid or expired"),c().warn("Token validation failed")}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Failed to check auth status",{error:e}),process.exit(1)}})}function Ue(){return new C("validate").description("Validate stored authentication token").action(async()=>{try{let r=await h.getToken();r||(console.log("\u2717 No token found"),process.exit(1)),console.log("Validating token...");let e=await h.validateToken(r);e.valid?(console.log(`\u2713 Token is valid (user: ${e.user})`),c().info("Token validation successful",{user:e.user})):(console.log("\u2717 Token is invalid or expired"),c().warn("Token validation failed"),process.exit(1))}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Error: ${e}`),c().error("Token validation error",{error:e}),process.exit(1)}})}function xe(){return new Promise(r=>{let e=re({input:process.stdin,output:process.stdout});e.question(g.ENTER_TOKEN,t=>{e.close(),r(t.trim())})})}function He(r){return new Promise(e=>{let t=re({input:process.stdin,output:process.stdout});t.question(`${r} [y/N]: `,i=>{t.close(),e(i.toLowerCase()==="y")})})}function te(){return new C("auth").description("Manage GitHub authentication").addCommand(ke()).addCommand(Ne()).addCommand(Fe()).addCommand(Ue())}var Ge="1.0.6",Ve="Archive GitHub repositories via CLI";async function Be(){try{await j(p.LOG_DIR);let r=H();q(r);let e=new Me;e.name("github-archiver").description(Ve).version(Ge,"-v, --version","Display version"),e.addCommand(te()),e.addCommand(ee()),e.addHelpCommand(!0),await e.parseAsync(process.argv),process.argv.slice(2).length||e.outputHelp()}catch(r){let e=r instanceof Error?r.message:String(r);console.error(`\u2717 Fatal error: ${e}`),process.exit(1)}}Be();
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/commands/archive.ts", "../src/constants/messages.ts", "../src/constants/paths.ts", "../src/services/archiver.ts", "../src/types/error.ts", "../src/utils/errors.ts", "../src/utils/logger.ts", "../src/constants/defaults.ts", "../src/services/auth-manager.ts", "../src/utils/config.ts", "../src/services/github.ts", "../src/utils/input-handler.ts", "../src/utils/parser.ts", "../src/utils/formatting.ts", "../src/utils/progress.ts", "../src/commands/auth.ts"],
4
- "sourcesContent": ["import { Command } from \"commander\";\nimport { createArchiveCommand } from \"./commands/archive\";\nimport { createAuthCommand } from \"./commands/auth\";\nimport { PATHS } from \"./constants/paths\";\nimport { createLogger, initializeLogger, setLogger } from \"./utils/logger\";\n\nconst VERSION = \"1.0.0\";\nconst DESCRIPTION = \"Archive GitHub repositories via CLI\";\n\nasync function main() {\n try {\n await initializeLogger(PATHS.LOG_DIR);\n\n const fileLogger = createLogger();\n\n setLogger(fileLogger);\n\n const program = new Command();\n\n program\n .name(\"github-archiver\")\n .description(DESCRIPTION)\n .version(VERSION, \"-v, --version\", \"Display version\");\n\n program.addCommand(createAuthCommand());\n program.addCommand(createArchiveCommand());\n\n program.addHelpCommand(true);\n\n await program.parseAsync(process.argv);\n\n if (!process.argv.slice(2).length) {\n program.outputHelp();\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Fatal error: ${message}`);\n process.exit(1);\n }\n}\n\nmain();\n", "import { Command } from \"commander\";\nimport { MESSAGES } from \"../constants/messages\";\nimport { PATHS } from \"../constants/paths\";\nimport { Archiver } from \"../services/archiver\";\nimport { AuthManager } from \"../services/auth-manager\";\nimport { GitHubService } from \"../services/github\";\nimport type {\n ArchiveOptions,\n ArchiveResult,\n CommandOptions,\n RepositoryIdentifier,\n} from \"../types\";\nimport { InputHandler } from \"../utils/input-handler\";\nimport { getLogger } from \"../utils/logger\";\nimport { ProgressDisplay } from \"../utils/progress\";\n\nconst logger = getLogger();\n\nexport function createArchiveCommand(): Command {\n return new Command(\"archive\")\n .description(\"Archive GitHub repositories\")\n .option(\"--file <path>\", \"Read repository URLs from file\")\n .option(\"--stdin\", \"Read repository URLs from standard input\")\n .option(\"--dry-run\", \"Validate without archiving\", false)\n .option(\"--concurrency <n>\", \"Number of parallel operations\", \"3\")\n .option(\"--timeout <n>\", \"API timeout in seconds\", \"300\")\n .option(\"--verbose\", \"Verbose logging\", false)\n .option(\"--force\", \"Skip confirmations\", false)\n .action(async (options: CommandOptions) => {\n await archiveCommand(options);\n });\n}\n\nasync function archiveCommand(options: CommandOptions): Promise<void> {\n try {\n logger.info(\"Archive command started\", { options });\n\n const { concurrency, timeout } = validateOptions(options);\n\n const authManager = new AuthManager(PATHS.APP_DIR);\n const inputHandler = new InputHandler();\n const progressDisplay = new ProgressDisplay();\n\n const githubService = await authenticateUser(authManager);\n\n const { repositories, parseErrors } = await getRepositories(\n options,\n inputHandler\n );\n\n if (parseErrors.length > 0) {\n logParseErrors(parseErrors);\n }\n\n if (repositories.length === 0) {\n console.error(`\u274C ${MESSAGES.NO_REPOS_PROVIDED}`);\n process.exit(1);\n }\n\n showRepositoriesPreview(repositories, options.dryRun);\n\n await confirmOperation(options, inputHandler);\n\n const archiveOptions: ArchiveOptions = {\n dryRun: options.dryRun,\n concurrency,\n timeout,\n force: options.force,\n verbose: options.verbose,\n };\n\n console.log(\"\");\n console.log(`${MESSAGES.ARCHIVING_START} (concurrency: ${concurrency})`);\n console.log(\"\");\n\n const archiver = new Archiver(githubService, archiveOptions);\n const results = await archiveRepositories(\n archiver,\n repositories,\n archiveOptions,\n progressDisplay\n );\n\n displayResults(archiver, results, progressDisplay);\n } catch (error) {\n handleArchiveError(error);\n }\n}\n\nfunction validateOptions(options: CommandOptions): {\n concurrency: number;\n timeout: number;\n} {\n const concurrency = Number.parseInt(options.concurrency, 10);\n const timeout = Number.parseInt(options.timeout, 10);\n\n if (concurrency < 1 || concurrency > 50) {\n console.error(\"\u274C Concurrency must be between 1 and 50\");\n process.exit(1);\n }\n\n if (timeout < 10 || timeout > 3600) {\n console.error(\"\u274C Timeout must be between 10 and 3600 seconds\");\n process.exit(1);\n }\n\n return { concurrency, timeout };\n}\n\nasync function authenticateUser(\n authManager: AuthManager\n): Promise<GitHubService> {\n console.log(\"\uD83D\uDD10 Checking authentication...\");\n const token = await authManager.getToken();\n\n if (!token) {\n console.error(`\u274C ${MESSAGES.NO_TOKEN}`);\n console.error(\" Run: github-archiver auth login\");\n process.exit(1);\n }\n\n const githubService = new GitHubService(token);\n\n try {\n const authenticatedUser = await githubService.validateAuth();\n console.log(`\u2705 Authenticated as: ${authenticatedUser}`);\n return githubService;\n } catch (error) {\n console.error(\"\u274C Authentication failed\");\n logger.error(\"Auth validation failed:\", error);\n process.exit(1);\n }\n}\n\nasync function getRepositories(\n options: CommandOptions,\n inputHandler: InputHandler\n): Promise<{\n repositories: RepositoryIdentifier[];\n parseErrors: Array<{ url: string; error: string; line: number }>;\n}> {\n console.log(\"\");\n console.log(\"\uD83D\uDCDD Getting repositories...\");\n\n let repositories: RepositoryIdentifier[] = [];\n let parseErrors: Array<{ url: string; error: string; line: number }> = [];\n\n if (options.file) {\n logger.info(`Using file input: ${options.file}`);\n const result = await inputHandler.getRepositoriesFromFile(options.file);\n repositories = result.repos;\n parseErrors = result.errors;\n } else if (options.stdin) {\n logger.info(\"Using stdin input\");\n console.log(\n \"Enter repository URLs (one per line, press Ctrl+D to finish):\"\n );\n const result = await inputHandler.getRepositoriesFromStdin();\n repositories = result.repos;\n parseErrors = result.errors;\n } else {\n logger.info(\"Using interactive CLI input\");\n const result = await inputHandler.getRepositoriesFromInteractive();\n repositories = result.repos;\n parseErrors = result.errors;\n }\n\n return { repositories, parseErrors };\n}\n\nfunction logParseErrors(\n parseErrors: Array<{ url: string; error: string; line: number }>\n): void {\n console.warn(`\u26A0\uFE0F Found ${parseErrors.length} invalid repositories:`);\n for (const err of parseErrors) {\n console.warn(` Line ${err.line}: ${err.error}`);\n }\n console.warn(\"\");\n}\n\nfunction showRepositoriesPreview(\n repositories: RepositoryIdentifier[],\n dryRun: boolean\n): void {\n console.log(\n `\uD83D\uDCCB Will ${dryRun ? \"validate\" : \"archive\"} ${repositories.length} repositories:`\n );\n for (let index = 0; index < Math.min(repositories.length, 5); index++) {\n const repo = repositories[index];\n if (repo) {\n console.log(` ${index + 1}. ${repo.owner}/${repo.repo}`);\n }\n }\n if (repositories.length > 5) {\n console.log(` ... and ${repositories.length - 5} more`);\n }\n}\n\nasync function confirmOperation(\n options: CommandOptions,\n inputHandler: InputHandler\n): Promise<void> {\n console.log(\"\");\n if (!(options.force || options.dryRun)) {\n const confirmed = await inputHandler.promptForConfirmation(\n \"Are you sure you want to archive these repositories?\"\n );\n\n if (!confirmed) {\n console.log(\"\u274C Cancelled\");\n process.exit(1);\n }\n } else if (options.dryRun) {\n console.log(`\u2139\uFE0F ${MESSAGES.DRY_RUN_MODE}`);\n }\n}\n\nasync function archiveRepositories(\n archiver: Archiver,\n repositories: RepositoryIdentifier[],\n options: ArchiveOptions,\n progressDisplay: ProgressDisplay\n): Promise<ArchiveResult[]> {\n const results = await archiver.archiveRepositories(repositories, options);\n\n const summary = archiver.getSummary();\n const progress = {\n completed: summary.successful + summary.failed + summary.skipped,\n failed: summary.failed,\n total: repositories.length,\n };\n\n if (progressDisplay.shouldUpdate()) {\n console.log(`\\r${progressDisplay.getProgressBar(progress)}`);\n }\n\n return results;\n}\n\nfunction displayResults(\n archiver: Archiver,\n results: ArchiveResult[],\n progressDisplay: ProgressDisplay\n): void {\n console.log(\"\");\n console.log(\"\");\n\n const summary = archiver.getSummary();\n console.log(progressDisplay.getSummaryBox(summary));\n\n if (summary.failed > 0) {\n console.log(\"\");\n console.log(\"\u274C Failed repositories:\");\n for (const r of results) {\n if (!r.success) {\n console.log(` ${r.owner}/${r.repo}: ${r.message}`);\n }\n }\n }\n\n if (summary.skipped > 0) {\n console.log(\"\");\n console.log(\"\u26A0\uFE0F Skipped repositories:\");\n for (const r of results) {\n if (r.success && !r.archived) {\n console.log(` ${r.owner}/${r.repo}: ${r.message}`);\n }\n }\n }\n\n if (summary.failed > 0) {\n logger.warn(`Archive command completed with ${summary.failed} failures`);\n process.exit(1);\n } else {\n console.log(\"\");\n console.log(\"\u2705 All repositories processed successfully!\");\n logger.info(\"Archive command completed successfully\");\n process.exit(0);\n }\n}\n\nfunction handleArchiveError(error: unknown): never {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u274C Error: ${message}`);\n logger.error(\"Archive command failed:\", error);\n\n provideErrorGuidance(message);\n\n process.exit(1);\n}\n\nfunction provideErrorGuidance(message: string): void {\n const lowerMessage = message.toLowerCase();\n\n if (\n lowerMessage.includes(\"token\") ||\n lowerMessage.includes(\"authentication\")\n ) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\n \" 1. Make sure you have a valid GitHub Personal Access Token\"\n );\n console.log(' 2. The token needs \"repo\" scope to archive repositories');\n console.log(\" 3. Run: github-archiver auth login\");\n return;\n }\n\n if (lowerMessage.includes(\"rate limit\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. GitHub API rate limit has been exceeded\");\n console.log(\" 2. Wait a few minutes and try again\");\n console.log(\" 3. Use lower concurrency: --concurrency 1\");\n return;\n }\n\n if (lowerMessage.includes(\"permission\") || lowerMessage.includes(\"403\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. You must be the repository owner or have push access\");\n console.log(\" 2. Check that your GitHub token has correct permissions\");\n console.log(\" 3. Verify you have push access to the repositories\");\n return;\n }\n\n if (lowerMessage.includes(\"not found\") || lowerMessage.includes(\"404\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. Make sure the repository URL is correct\");\n console.log(\" 2. The repository may have been deleted\");\n console.log(\" 3. Check your GitHub access to the repository\");\n return;\n }\n\n if (lowerMessage.includes(\"network\") || lowerMessage.includes(\"timeout\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. Check your internet connection\");\n console.log(\" 2. GitHub API may be temporarily unavailable\");\n console.log(\" 3. Try again in a moment\");\n console.log(\" 4. Increase timeout: --timeout 600\");\n }\n}\n", "export const MESSAGES = {\n AUTH_SUCCESS: \"Successfully authenticated with GitHub\",\n ARCHIVE_SUCCESS: \"Repository archived successfully\",\n TOKEN_SAVED: \"GitHub token saved successfully\",\n TOKEN_REMOVED: \"GitHub token removed\",\n INVALID_TOKEN: \"Invalid or expired GitHub token\",\n REPO_NOT_FOUND: \"Repository not found\",\n ALREADY_ARCHIVED: \"Repository is already archived\",\n PERMISSION_DENIED: \"You do not have permission to archive this repository\",\n RATE_LIMITED: \"GitHub API rate limit exceeded. Please try again later\",\n NETWORK_ERROR: \"Network error. Please check your connection\",\n INVALID_URL: \"Invalid GitHub repository URL\",\n NO_TOKEN:\n 'No GitHub token found. Please run \"github-archiver auth login\" first',\n OPENING_EDITOR: \"Opening text editor for repository URLs...\",\n NO_REPOS_PROVIDED: \"No repositories provided\",\n ARCHIVING_START: \"Starting to archive repositories...\",\n ARCHIVING_COMPLETE: \"Archiving complete\",\n DRY_RUN_MODE: \"Running in dry-run mode (no repositories will be archived)\",\n CONFIRM_ARCHIVE: \"Are you sure you want to archive these repositories?\",\n ENTER_TOKEN:\n \"Enter your GitHub Personal Access Token (will not be displayed):\",\n CONFIRM_LOGOUT: \"Are you sure you want to remove the stored token?\",\n};\n", "import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst appDir = join(homedir(), \".github-archiver\");\n\nexport const PATHS = {\n APP_DIR: appDir,\n CONFIG_FILE: join(appDir, \"config.json\"),\n LOG_DIR: join(appDir, \"logs\"),\n COMBINED_LOG: join(appDir, \"logs\", \"combined.log\"),\n ERROR_LOG: join(appDir, \"logs\", \"errors.log\"),\n};\n", "import PQueue from \"p-queue\";\nimport type {\n ArchiveOptions,\n ArchiveResult,\n RepositoryIdentifier,\n} from \"../types\";\nimport { ErrorCode } from \"../types\";\nimport { isArchiveError } from \"../utils/errors\";\nimport { getLogger } from \"../utils/logger\";\nimport type { GitHubService } from \"./github\";\n\nconst logger = getLogger();\n\nexport class Archiver {\n private readonly queue: PQueue;\n private results: ArchiveResult[] = [];\n private readonly gitHubService: GitHubService;\n private completed = 0;\n private failed = 0;\n private readonly startTime = 0;\n\n constructor(gitHubService: GitHubService, options: ArchiveOptions) {\n this.gitHubService = gitHubService;\n this.queue = new PQueue({\n concurrency: options.concurrency,\n timeout: options.timeout * 1000,\n interval: 1000,\n intervalCap: options.concurrency,\n });\n\n logger.info(\"Archiver initialized\", {\n concurrency: options.concurrency,\n timeout: options.timeout,\n dryRun: options.dryRun,\n });\n }\n\n async archiveRepositories(\n repos: RepositoryIdentifier[],\n options: ArchiveOptions\n ): Promise<ArchiveResult[]> {\n this.results = [];\n this.completed = 0;\n this.failed = 0;\n\n logger.info(`Starting to archive ${repos.length} repositories`, {\n dryRun: options.dryRun,\n });\n\n const tasks = repos.map((repo) =>\n this.queue.add(() => this.archiveRepository(repo, options))\n );\n\n await Promise.all(tasks);\n\n return this.results;\n }\n\n private async archiveRepository(\n repo: RepositoryIdentifier,\n options: ArchiveOptions\n ): Promise<void> {\n const startTime = Date.now();\n\n try {\n if (options.dryRun) {\n logger.info(`[DRY-RUN] Would archive ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"[DRY-RUN] Validation successful\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n const validation = await this.gitHubService.validateRepository(\n repo.owner,\n repo.repo\n );\n\n if (!validation.exists) {\n logger.warn(`Repository not found: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: false,\n archived: false,\n error: \"Repository not found\",\n duration: Date.now() - startTime,\n message: \"Repository does not exist on GitHub\",\n };\n this.results.push(result);\n this.failed++;\n return;\n }\n\n if (validation.isArchived) {\n logger.info(`Repository already archived: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"Repository is already archived\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n console.log(`\uD83D\uDCE6 Archiving ${repo.owner}/${repo.repo}...`);\n await this.gitHubService.archiveRepository(repo.owner, repo.repo);\n\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: true,\n duration: Date.now() - startTime,\n message: \"Repository archived successfully\",\n };\n this.results.push(result);\n this.completed++;\n logger.info(`\u2713 Archived ${repo.owner}/${repo.repo}`, {\n duration: result.duration,\n });\n } catch (error) {\n if (isArchiveError(error) && error.code === ErrorCode.ALREADY_ARCHIVED) {\n logger.info(`Repository already archived: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"Repository is already archived\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n logger.error(\n `\u2717 Failed to archive ${repo.owner}/${repo.repo}: ${errorMessage}`\n );\n\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: false,\n archived: false,\n error: errorMessage,\n duration: Date.now() - startTime,\n message: `Error: ${errorMessage}`,\n };\n this.results.push(result);\n this.failed++;\n }\n }\n\n getProgress(): { completed: number; failed: number; total: number } {\n return {\n completed: this.completed,\n failed: this.failed,\n total: this.results.length,\n };\n }\n\n getSummary(): {\n successful: number;\n failed: number;\n skipped: number;\n totalDuration: number;\n } {\n const successful = this.results.filter(\n (r) => r.success && r.archived\n ).length;\n const failed = this.results.filter((r) => !r.success).length;\n const skipped = this.results.filter((r) => r.success && !r.archived).length;\n const totalDuration = Date.now() - this.startTime;\n\n return { successful, failed, skipped, totalDuration };\n }\n}\n", "export const ErrorCode = {\n INVALID_AUTH: \"INVALID_AUTH\",\n REPO_NOT_FOUND: \"REPO_NOT_FOUND\",\n ALREADY_ARCHIVED: \"ALREADY_ARCHIVED\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n RATE_LIMITED: \"RATE_LIMITED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n INVALID_URL: \"INVALID_URL\",\n PARSE_ERROR: \"PARSE_ERROR\",\n EDITOR_ERROR: \"EDITOR_ERROR\",\n CONFIG_ERROR: \"CONFIG_ERROR\",\n FILE_ERROR: \"FILE_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class ArchiveError extends Error {\n code: ErrorCode;\n statusCode?: number;\n retryable: boolean;\n\n constructor(\n code: ErrorCode,\n message: string,\n statusCode?: number,\n retryable = false\n ) {\n super(message);\n this.name = \"ArchiveError\";\n this.code = code;\n this.statusCode = statusCode;\n this.retryable = retryable;\n Object.setPrototypeOf(this, ArchiveError.prototype);\n }\n}\n", "import { MESSAGES } from \"../constants/messages\";\nimport { ArchiveError, ErrorCode } from \"../types\";\n\nexport function isArchiveError(error: unknown): error is ArchiveError {\n return error instanceof ArchiveError;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (isArchiveError(error)) {\n return error.message;\n }\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n\nexport function createAuthError(\n message: string,\n statusCode?: number\n): ArchiveError {\n return new ArchiveError(ErrorCode.INVALID_AUTH, message, statusCode, true);\n}\n\nexport function createRepoNotFoundError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.REPO_NOT_FOUND,\n `Repository ${owner}/${repo} not found`,\n 404,\n false\n );\n}\n\nexport function createAlreadyArchivedError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.ALREADY_ARCHIVED,\n `Repository ${owner}/${repo} is already archived`,\n 422,\n false\n );\n}\n\nexport function createPermissionError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.PERMISSION_DENIED,\n `Permission denied for ${owner}/${repo}. You must be the repository owner or have push access.`,\n 403,\n false\n );\n}\n\nexport function createRateLimitError(): ArchiveError {\n return new ArchiveError(\n ErrorCode.RATE_LIMITED,\n MESSAGES.RATE_LIMITED,\n 429,\n true\n );\n}\n\nexport function createNetworkError(message?: string): ArchiveError {\n return new ArchiveError(\n ErrorCode.NETWORK_ERROR,\n message || MESSAGES.NETWORK_ERROR,\n undefined,\n true\n );\n}\n\nexport function createInvalidUrlError(url: string): ArchiveError {\n return new ArchiveError(ErrorCode.INVALID_URL, `Invalid GitHub URL: ${url}`);\n}\n\nexport function createConfigError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.CONFIG_ERROR, message);\n}\n\nexport function createFileError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.FILE_ERROR, message);\n}\n\nexport function createEditorError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.EDITOR_ERROR, message);\n}\n", "import { promises as fs } from \"node:fs\";\nimport winston from \"winston\";\nimport { DEFAULT_LOG_LEVEL } from \"../constants/defaults\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config } from \"../types\";\n\nlet loggerInstance: winston.Logger;\n\nexport async function initializeLogger(logDir: string): Promise<void> {\n try {\n await fs.mkdir(logDir, { recursive: true });\n } catch (error) {\n console.error(\"Failed to create log directory:\", error);\n }\n}\n\nexport function createLogger(config?: Partial<Config>): winston.Logger {\n const level = config?.logLevel || DEFAULT_LOG_LEVEL;\n const logDir = config?.logDir || PATHS.LOG_DIR;\n\n return winston.createLogger({\n level,\n format: winston.format.combine(\n winston.format.timestamp({ format: \"YYYY-MM-DD HH:mm:ss\" }),\n winston.format.errors({ stack: true }),\n winston.format.json()\n ),\n defaultMeta: { service: \"github-archiver\" },\n transports: [\n new winston.transports.File({\n filename: `${logDir}/errors.log`,\n level: \"error\",\n }),\n new winston.transports.File({\n filename: `${logDir}/combined.log`,\n }),\n ],\n });\n}\n\nexport function createConsoleLogger(): winston.Logger {\n return winston.createLogger({\n format: winston.format.combine(\n winston.format.colorize(),\n winston.format.printf(({ level, message, ...metadata }) => {\n const meta = Object.keys(metadata).length\n ? JSON.stringify(metadata)\n : \"\";\n return `${level}: ${message} ${meta}`;\n })\n ),\n transports: [new winston.transports.Console()],\n });\n}\n\nexport function getLogger(): winston.Logger {\n if (!loggerInstance) {\n loggerInstance = createLogger();\n }\n return loggerInstance;\n}\n\nexport function setLogger(logger: winston.Logger): void {\n loggerInstance = logger;\n}\n", "export const DEFAULT_CONCURRENCY = 3;\nexport const DEFAULT_TIMEOUT = 300; // seconds\nexport const DEFAULT_LOG_LEVEL = \"info\" as const;\nexport const MAX_RETRIES = 3;\nexport const RETRY_DELAY_MS = 1000; // base delay for exponential backoff\nexport const GITHUB_API_URL = \"https://api.github.com\";\n", "import { Octokit } from \"octokit\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config, StoredCredentials } from \"../types\";\nimport { ConfigManager } from \"../utils/config\";\nimport { createAuthError } from \"../utils/errors\";\n\nexport class AuthManager {\n private readonly configManager: ConfigManager;\n\n constructor(configDir: string = PATHS.APP_DIR) {\n this.configManager = new ConfigManager(configDir);\n }\n\n async saveToken(token: string): Promise<void> {\n if (!token || typeof token !== \"string\") {\n throw createAuthError(\"Invalid token format\");\n }\n\n const octokit = new Octokit({ auth: token });\n\n try {\n const { data } = await octokit.rest.users.getAuthenticated();\n\n const credentials: StoredCredentials = {\n token,\n savedAt: new Date().toISOString(),\n githubUser: data.login,\n };\n\n await this.configManager.saveConfig(credentials);\n } catch (error) {\n throw createAuthError(\n `Failed to validate token: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getToken(): Promise<string | undefined> {\n if (process.env.GH_TOKEN) {\n return process.env.GH_TOKEN;\n }\n\n const credentials = await this.configManager.getStoredCredentials();\n return credentials?.token;\n }\n\n async removeToken(): Promise<void> {\n await this.configManager.removeStoredCredentials();\n }\n\n async validateToken(\n token: string\n ): Promise<{ valid: boolean; user?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return {\n valid: true,\n user: data.login,\n };\n } catch (_error) {\n return {\n valid: false,\n };\n }\n }\n\n getStoredCredentials(): Promise<StoredCredentials | null> {\n return this.configManager.getStoredCredentials();\n }\n\n ensureConfigDir(): Promise<void> {\n return this.configManager.ensureConfigDir();\n }\n\n loadConfig(): Promise<Config> {\n return this.configManager.loadConfig();\n }\n}\n", "import { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n DEFAULT_CONCURRENCY,\n DEFAULT_LOG_LEVEL,\n DEFAULT_TIMEOUT,\n} from \"../constants/defaults\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config, StoredCredentials } from \"../types\";\nimport { createConfigError, createFileError } from \"./errors\";\n\nexport class ConfigManager {\n private readonly configDir: string;\n private readonly configFile: string;\n\n constructor(configDir: string = PATHS.APP_DIR) {\n this.configDir = configDir;\n this.configFile = join(configDir, \"config.json\");\n }\n\n async loadConfig(): Promise<Config> {\n try {\n const token = process.env.GH_TOKEN;\n const fileCredentials = await this.loadFileCredentials();\n\n return {\n token: token || fileCredentials?.token,\n concurrency: DEFAULT_CONCURRENCY,\n timeout: DEFAULT_TIMEOUT,\n logLevel: DEFAULT_LOG_LEVEL,\n logDir: join(this.configDir, \"logs\"),\n configDir: this.configDir,\n };\n } catch (error) {\n throw createConfigError(\n `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async saveConfig(credentials: StoredCredentials): Promise<void> {\n try {\n await this.ensureConfigDir();\n await fs.writeFile(\n this.configFile,\n JSON.stringify(credentials, null, 2),\n \"utf-8\"\n );\n } catch (error) {\n throw createFileError(\n `Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async ensureConfigDir(): Promise<void> {\n try {\n await fs.mkdir(this.configDir, { recursive: true });\n } catch (error) {\n throw createFileError(\n `Failed to create config directory: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getStoredCredentials(): Promise<StoredCredentials | null> {\n try {\n const data = await fs.readFile(this.configFile, \"utf-8\");\n return JSON.parse(data) as StoredCredentials;\n } catch {\n return null;\n }\n }\n\n async removeStoredCredentials(): Promise<void> {\n try {\n await fs.unlink(this.configFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw createFileError(\n `Failed to remove stored credentials: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n }\n\n private async loadFileCredentials(): Promise<StoredCredentials | null> {\n try {\n const data = await fs.readFile(this.configFile, \"utf-8\");\n return JSON.parse(data) as StoredCredentials;\n } catch {\n return null;\n }\n }\n}\n", "import { Octokit } from \"octokit\";\nimport { MAX_RETRIES, RETRY_DELAY_MS } from \"../constants/defaults\";\nimport type { RateLimitInfo } from \"../types\";\nimport {\n createAlreadyArchivedError,\n createNetworkError,\n createPermissionError,\n createRateLimitError,\n createRepoNotFoundError,\n} from \"../utils/errors\";\nimport { getLogger } from \"../utils/logger\";\n\nconst logger = getLogger();\n\ninterface OctokitError {\n status: number;\n response?: {\n data?: {\n message: string;\n };\n };\n message: string;\n}\n\nexport class GitHubService {\n private readonly octokit: Octokit;\n\n constructor(token: string) {\n this.octokit = new Octokit({ auth: token });\n }\n\n async archiveRepository(owner: string, repo: string): Promise<void> {\n const startTime = Date.now();\n logger.debug(`Attempting to archive ${owner}/${repo}`);\n\n await this.retryWithBackoff(async () => {\n try {\n await this.octokit.rest.repos.update({\n owner,\n repo,\n archived: true,\n });\n const duration = Date.now() - startTime;\n logger.info(`Successfully archived ${owner}/${repo} in ${duration}ms`);\n } catch (error) {\n this.handleArchiveError(error, owner, repo);\n }\n });\n }\n\n private handleArchiveError(\n error: unknown,\n owner: string,\n repo: string\n ): never {\n const err = this.parseOctokitError(error);\n logger.error(`Failed to archive ${owner}/${repo}: ${err.message}`, {\n status: err.status,\n code: err.code,\n });\n\n if (err.status === 404) {\n throw createRepoNotFoundError(owner, repo);\n }\n\n if (err.status === 403 || err.message.includes(\"permission\")) {\n throw createPermissionError(owner, repo);\n }\n\n if (err.status === 422 && err.message.includes(\"archived\")) {\n throw createAlreadyArchivedError(owner, repo);\n }\n\n if (err.message.includes(\"API rate limit\")) {\n throw createRateLimitError();\n }\n\n if (\n err.message.includes(\"ECONNREFUSED\") ||\n err.message.includes(\"ETIMEDOUT\") ||\n err.message.includes(\"EHOSTUNREACH\") ||\n err.message.includes(\"socket hang up\")\n ) {\n throw createNetworkError(err.message);\n }\n\n throw error;\n }\n\n private parseOctokitError(error: unknown): {\n message: string;\n status: number;\n code: string | undefined;\n } {\n const message = error instanceof Error ? error.message : String(error);\n const err = error as unknown as OctokitError;\n return {\n message,\n status: err?.status || 0,\n code: err?.response?.data?.message,\n };\n }\n\n async validateRepository(\n owner: string,\n repo: string\n ): Promise<{ exists: boolean; isArchived: boolean }> {\n try {\n logger.debug(`Validating repository ${owner}/${repo}`);\n const { data } = await this.octokit.rest.repos.get({\n owner,\n repo,\n });\n\n const result = {\n exists: true,\n isArchived: data.archived,\n };\n logger.debug(`Repository ${owner}/${repo} validated`, result);\n return result;\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"404\")) {\n logger.debug(`Repository ${owner}/${repo} does not exist`);\n return {\n exists: false,\n isArchived: false,\n };\n }\n\n logger.error(`Error validating ${owner}/${repo}:`, error);\n throw error;\n }\n }\n\n async getRateLimitStatus(): Promise<RateLimitInfo> {\n try {\n logger.debug(\"Fetching rate limit status\");\n const { data } = await this.octokit.rest.rateLimit.get();\n\n const info: RateLimitInfo = {\n remaining: data.rate.remaining,\n reset: new Date(data.rate.reset * 1000),\n limit: data.rate.limit,\n };\n\n logger.debug(\"Rate limit status retrieved\", {\n remaining: info.remaining,\n limit: info.limit,\n resetAt: info.reset.toISOString(),\n });\n\n return info;\n } catch (error) {\n logger.error(\"Failed to fetch rate limit status:\", error);\n throw error;\n }\n }\n\n async validateAuth(): Promise<string> {\n try {\n logger.debug(\"Validating authentication\");\n const { data } = await this.octokit.rest.users.getAuthenticated();\n logger.info(`Authenticated as user: ${data.login}`);\n return data.login;\n } catch (error) {\n logger.error(\"Authentication validation failed:\", error);\n throw createNetworkError(\n error instanceof Error ? error.message : \"Authentication failed\"\n );\n }\n }\n\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n maxRetries: number = MAX_RETRIES\n ): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error as Error;\n\n const message = lastError.message.toLowerCase();\n const isRetryable =\n message.includes(\"rate limit\") ||\n message.includes(\"timeout\") ||\n message.includes(\"econnrefused\") ||\n message.includes(\"etimedout\") ||\n message.includes(\"ehostunreach\") ||\n message.includes(\"socket hang up\");\n\n if (!isRetryable || attempt === maxRetries - 1) {\n throw error;\n }\n\n const delay = RETRY_DELAY_MS * 2 ** attempt;\n logger.warn(\n `Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,\n {\n error: message,\n }\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError || new Error(\"Unknown error during retry\");\n }\n}\n", "import { promises as fs } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport type { RepositoryIdentifier } from \"../types\";\nimport { getLogger } from \"./logger\";\nimport { URLParser } from \"./parser\";\n\nconst logger = getLogger();\n\nexport class InputHandler {\n getRepositoriesFromInteractive(): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(\"Starting interactive CLI input for repositories\");\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log(\"\");\n console.log(\"\uD83D\uDCDD Enter repository URLs one at a time:\");\n console.log(\n ' (Type \"done\" when finished, or leave empty and press Enter to skip)'\n );\n console.log(\"\");\n\n const urls: string[] = [];\n let lineNumber = 1;\n\n const promptNext = () => {\n rl.question(`[${lineNumber}] > `, (input) => {\n const trimmedInput = input.trim();\n\n if (trimmedInput.toLowerCase() === \"done\") {\n finishInput();\n return;\n }\n\n if (trimmedInput === \"\") {\n lineNumber++;\n promptNext();\n return;\n }\n\n urls.push(trimmedInput);\n lineNumber++;\n promptNext();\n });\n };\n\n const finishInput = () => {\n rl.close();\n const result = URLParser.parseRepositoriesBatch(urls);\n\n if (result.invalid.length > 0) {\n logger.warn(`Found ${result.invalid.length} invalid repositories`, {\n errors: result.invalid,\n });\n }\n\n logger.info(`Parsed ${result.valid.length} valid repositories`);\n resolve({\n repos: result.valid,\n errors: result.invalid,\n });\n };\n\n rl.on(\"close\", () => {\n finishInput();\n });\n\n promptNext();\n });\n }\n\n async getRepositoriesFromFile(filePath: string): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(`Reading repositories from file: ${filePath}`);\n\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n const result = URLParser.parseRepositoriesBatch(lines);\n\n if (result.invalid.length > 0) {\n logger.warn(`Found ${result.invalid.length} invalid entries in file`, {\n errors: result.invalid,\n });\n }\n\n logger.info(`Parsed ${result.valid.length} valid repositories from file`);\n return {\n repos: result.valid,\n errors: result.invalid,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`Failed to read file ${filePath}: ${message}`);\n throw new Error(`Failed to read file: ${message}`);\n }\n }\n\n getRepositoriesFromStdin(): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(\"Reading repositories from stdin\");\n\n return new Promise((resolve) => {\n const lines: string[] = [];\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.on(\"line\", (line) => {\n lines.push(line);\n });\n\n rl.on(\"close\", () => {\n const result = URLParser.parseRepositoriesBatch(lines);\n\n if (result.invalid.length > 0) {\n logger.warn(\n `Found ${result.invalid.length} invalid entries from stdin`,\n {\n errors: result.invalid,\n }\n );\n }\n\n logger.info(\n `Parsed ${result.valid.length} valid repositories from stdin`\n );\n resolve({\n repos: result.valid,\n errors: result.invalid,\n });\n });\n });\n }\n\n promptForConfirmation(message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(`${message} [y/N]: `, (answer) => {\n rl.close();\n const confirmed = answer.toLowerCase() === \"y\";\n logger.info(`User confirmation: ${confirmed}`);\n resolve(confirmed);\n });\n });\n }\n}\n", "import type { RepositoryIdentifier } from \"../types\";\nimport { ArchiveError, ErrorCode } from \"../types\";\nimport { getLogger } from \"./logger\";\n\nconst logger = getLogger();\nconst NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$/;\n\nconst GITHUB_URL_PATTERNS: RegExp[] = [\n // https://github.com/owner/repo or https://github.com/owner/repo.git\n /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com[/:]([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?(?:\\/)?$/i,\n // git@github.com:owner/repo.git or git@github.com:owner/repo\n /^git@github\\.com:([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/i,\n // owner/repo (shorthand)\n /^([\\w.-]+)\\/([\\w.-]+)$/,\n];\n\nfunction parseRepositoryUrl(url: string): RepositoryIdentifier {\n const trimmed = url.trim();\n\n if (!trimmed) {\n throw new ArchiveError(ErrorCode.INVALID_URL, \"URL cannot be empty\");\n }\n\n for (const pattern of GITHUB_URL_PATTERNS) {\n const match = trimmed.match(pattern);\n if (match) {\n const owner = match[1];\n const repo = match[2];\n\n if (!(owner && repo)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid GitHub URL format: ${url}`\n );\n }\n\n if (!isValidName(owner)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid owner name: ${owner}. Owner names must contain only alphanumeric characters, hyphens, or periods.`\n );\n }\n\n if (!isValidName(repo)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid repository name: ${repo}. Repository names must contain only alphanumeric characters, hyphens, underscores, or periods.`\n );\n }\n\n const normalizedUrl = `https://github.com/${owner}/${repo}`;\n\n logger.debug(\"Parsed repository URL\", {\n original: trimmed,\n owner,\n repo,\n normalized: normalizedUrl,\n });\n\n return {\n owner,\n repo,\n url: normalizedUrl,\n };\n }\n }\n\n throw new ArchiveError(ErrorCode.INVALID_URL, `Invalid GitHub URL: ${url}`);\n}\n\nfunction isValidName(name: string): boolean {\n return NAME_REGEX.test(name);\n}\n\nfunction parseRepositoriesBatch(urls: string[]): {\n valid: RepositoryIdentifier[];\n invalid: Array<{ url: string; error: string; line: number }>;\n} {\n const valid: RepositoryIdentifier[] = [];\n const invalid: Array<{ url: string; error: string; line: number }> = [];\n\n for (const [index, url] of urls.entries()) {\n const lineNumber = index + 1;\n const trimmed = url.trim();\n\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n\n try {\n const parsed = parseRepositoryUrl(trimmed);\n valid.push(parsed);\n logger.debug(`Line ${lineNumber}: Valid repository`, {\n owner: parsed.owner,\n repo: parsed.repo,\n });\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n invalid.push({\n url: trimmed,\n error: errorMessage,\n line: lineNumber,\n });\n logger.warn(`Line ${lineNumber}: Invalid repository`, {\n url: trimmed,\n error: errorMessage,\n });\n }\n }\n\n logger.info(\"Batch parsing complete\", {\n total: urls.length,\n valid: valid.length,\n invalid: invalid.length,\n skipped: urls.length - valid.length - invalid.length,\n });\n\n return { valid, invalid };\n}\n\nexport const URLParser = {\n parseRepositoryUrl,\n parseRepositoriesBatch,\n};\n", "function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n if (ms < 60_000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60_000);\n const seconds = ((ms % 60_000) / 1000).toFixed(0);\n return `${minutes}m ${seconds}s`;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) {\n return \"0 B\";\n }\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`;\n}\n\nfunction formatPercent(value: number, total: number): string {\n if (total === 0) {\n return \"0%\";\n }\n return `${((value / total) * 100).toFixed(1)}%`;\n}\n\nfunction createProgressBar(\n completed: number,\n total: number,\n width = 30\n): string {\n if (total === 0) {\n return \"[ ]\";\n }\n const filledWidth = Math.round((completed / total) * width);\n const emptyWidth = width - filledWidth;\n const filled = \"=\".repeat(filledWidth);\n const empty = \" \".repeat(emptyWidth);\n const percentage = ((completed / total) * 100).toFixed(0);\n return `[${filled}${empty}] ${percentage}%`;\n}\n\nfunction centerText(text: string, width: number): string {\n const padding = Math.max(0, (width - text.length) / 2);\n return \" \".repeat(Math.floor(padding)) + text;\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n return `${text.substring(0, maxLength - 3)}...`;\n}\n\nexport const Formatting = {\n formatDuration,\n formatBytes,\n formatPercent,\n createProgressBar,\n centerText,\n truncate,\n};\n", "import { Formatting } from \"./formatting\";\n\nexport interface ProgressUpdate {\n completed: number;\n failed: number;\n total: number;\n current?: {\n owner: string;\n repo: string;\n };\n}\n\nexport class ProgressDisplay {\n private readonly startTime: number;\n private lastUpdate = 0;\n private readonly updateInterval = 500; // ms\n\n constructor() {\n this.startTime = Date.now();\n }\n\n shouldUpdate(): boolean {\n const now = Date.now();\n if (now - this.lastUpdate >= this.updateInterval) {\n this.lastUpdate = now;\n return true;\n }\n return false;\n }\n\n getProgressBar(progress: ProgressUpdate): string {\n const { completed, total, current } = progress;\n const percent = total > 0 ? (completed / total) * 100 : 0;\n const bar = Formatting.createProgressBar(completed, total, 25);\n\n let line = `${bar} ${completed}/${total} (${percent.toFixed(0)}%)`;\n\n if (current) {\n line += ` - ${current.owner}/${current.repo}`;\n }\n\n return line;\n }\n\n getSummaryBox(summary: {\n successful: number;\n failed: number;\n skipped: number;\n totalDuration: number;\n }): string {\n const { successful, failed, skipped, totalDuration } = summary;\n const total = successful + failed + skipped;\n const duration = Formatting.formatDuration(totalDuration);\n\n const lines = [\n \"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\",\n \"\u2551 Archive Operation Summary \u2551\",\n \"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\",\n `\u2551 \u2705 Successful: ${String(successful).padEnd(20)} \u2551`,\n `\u2551 \u26A0\uFE0F Skipped: ${String(skipped).padEnd(20)} \u2551`,\n `\u2551 \u274C Failed: ${String(failed).padEnd(20)} \u2551`,\n \"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\",\n `\u2551 Total: ${String(total).padEnd(20)} \u2551`,\n `\u2551 Duration: ${String(duration).padEnd(20)} \u2551`,\n \"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\",\n ];\n\n return lines.join(\"\\n\");\n }\n\n getElapsedTime(): string {\n const elapsed = Date.now() - this.startTime;\n return Formatting.formatDuration(elapsed);\n }\n\n getEstimatedTimeRemaining(completed: number, total: number): string | null {\n if (completed === 0) {\n return null;\n }\n\n const elapsed = Date.now() - this.startTime;\n const avgTime = elapsed / completed;\n const remaining = (total - completed) * avgTime;\n\n return remaining > 0 ? Formatting.formatDuration(remaining) : null;\n }\n}\n", "import { createInterface } from \"node:readline\";\nimport { Command } from \"commander\";\nimport { MESSAGES } from \"../constants/messages\";\nimport { PATHS } from \"../constants/paths\";\nimport { AuthManager } from \"../services/auth-manager\";\nimport { getLogger } from \"../utils/logger\";\n\nconst authManager = new AuthManager(PATHS.APP_DIR);\n\nfunction createLoginCommand(): Command {\n return new Command(\"login\")\n .description(\"Set up GitHub authentication with a Personal Access Token\")\n .action(async () => {\n try {\n await authManager.ensureConfigDir();\n\n const token = await promptForToken();\n\n if (!token) {\n console.error(\"No token provided. Aborting.\");\n process.exit(1);\n }\n\n console.log(\"Validating token...\");\n await authManager.saveToken(token);\n\n const credentials = await authManager.getStoredCredentials();\n console.log(`\u2713 ${MESSAGES.AUTH_SUCCESS}`);\n console.log(` User: ${credentials?.githubUser}`);\n\n getLogger().info(\"Token saved successfully\", {\n user: credentials?.githubUser,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to save token\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createLogoutCommand(): Command {\n return new Command(\"logout\")\n .description(\"Remove stored GitHub authentication token\")\n .action(async () => {\n try {\n const credentials = await authManager.getStoredCredentials();\n\n if (!credentials) {\n console.log(\"No stored credentials found.\");\n return;\n }\n\n const confirmed = await confirmAction(MESSAGES.CONFIRM_LOGOUT);\n\n if (!confirmed) {\n console.log(\"Cancelled.\");\n return;\n }\n\n await authManager.removeToken();\n console.log(`\u2713 ${MESSAGES.TOKEN_REMOVED}`);\n getLogger().info(\"Token removed\");\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to remove token\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createStatusCommand(): Command {\n return new Command(\"status\")\n .description(\"Check authentication status\")\n .action(async () => {\n try {\n const token = await authManager.getToken();\n\n if (!token) {\n console.log(\"\u2717 Not authenticated\");\n console.log(\n ' Run \"github-archiver auth login\" to set up authentication'\n );\n return;\n }\n\n console.log(\"\u2713 Authenticated\");\n\n const validation = await authManager.validateToken(token);\n\n if (validation.valid) {\n console.log(` User: ${validation.user}`);\n const credentials = await authManager.getStoredCredentials();\n if (credentials) {\n console.log(\n ` Saved at: ${new Date(credentials.savedAt).toLocaleString()}`\n );\n }\n getLogger().info(\"Authentication status check passed\");\n } else {\n console.log(\"\u2717 Token is invalid or expired\");\n getLogger().warn(\"Token validation failed\");\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to check auth status\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createValidateCommand(): Command {\n return new Command(\"validate\")\n .description(\"Validate stored authentication token\")\n .action(async () => {\n try {\n const token = await authManager.getToken();\n\n if (!token) {\n console.log(\"\u2717 No token found\");\n process.exit(1);\n }\n\n console.log(\"Validating token...\");\n const validation = await authManager.validateToken(token);\n\n if (validation.valid) {\n console.log(`\u2713 Token is valid (user: ${validation.user})`);\n getLogger().info(\"Token validation successful\", {\n user: validation.user,\n });\n } else {\n console.log(\"\u2717 Token is invalid or expired\");\n getLogger().warn(\"Token validation failed\");\n process.exit(1);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Token validation error\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction promptForToken(): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(MESSAGES.ENTER_TOKEN, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction confirmAction(message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(`${message} [y/N]: `, (answer) => {\n rl.close();\n resolve(answer.toLowerCase() === \"y\");\n });\n });\n}\n\nexport function createAuthCommand(): Command {\n return new Command(\"auth\")\n .description(\"Manage GitHub authentication\")\n .addCommand(createLoginCommand())\n .addCommand(createLogoutCommand())\n .addCommand(createStatusCommand())\n .addCommand(createValidateCommand());\n}\n"],
4
+ "sourcesContent": ["import { Command } from \"commander\";\nimport { createArchiveCommand } from \"./commands/archive\";\nimport { createAuthCommand } from \"./commands/auth\";\nimport { PATHS } from \"./constants/paths\";\nimport { createLogger, initializeLogger, setLogger } from \"./utils/logger\";\n\nconst VERSION = \"1.0.6\";\nconst DESCRIPTION = \"Archive GitHub repositories via CLI\";\n\nasync function main() {\n try {\n await initializeLogger(PATHS.LOG_DIR);\n\n const fileLogger = createLogger();\n\n setLogger(fileLogger);\n\n const program = new Command();\n\n program\n .name(\"github-archiver\")\n .description(DESCRIPTION)\n .version(VERSION, \"-v, --version\", \"Display version\");\n\n program.addCommand(createAuthCommand());\n program.addCommand(createArchiveCommand());\n\n program.addHelpCommand(true);\n\n await program.parseAsync(process.argv);\n\n if (!process.argv.slice(2).length) {\n program.outputHelp();\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Fatal error: ${message}`);\n process.exit(1);\n }\n}\n\nmain();\n", "import { Command } from \"commander\";\nimport { MESSAGES } from \"../constants/messages\";\nimport { PATHS } from \"../constants/paths\";\nimport { Archiver } from \"../services/archiver\";\nimport { AuthManager } from \"../services/auth-manager\";\nimport { GitHubService } from \"../services/github\";\nimport type {\n ArchiveOptions,\n ArchiveResult,\n CommandOptions,\n RepositoryIdentifier,\n} from \"../types\";\nimport { InputHandler } from \"../utils/input-handler\";\nimport { getLogger } from \"../utils/logger\";\nimport { ProgressDisplay } from \"../utils/progress\";\n\nconst logger = getLogger();\n\nexport function createArchiveCommand(): Command {\n return new Command(\"archive\")\n .description(\"Archive GitHub repositories\")\n .option(\"--file <path>\", \"Read repository URLs from file\")\n .option(\"--stdin\", \"Read repository URLs from standard input\")\n .option(\"--dry-run\", \"Validate without archiving\", false)\n .option(\"--concurrency <n>\", \"Number of parallel operations\", \"3\")\n .option(\"--timeout <n>\", \"API timeout in seconds\", \"300\")\n .option(\"--verbose\", \"Verbose logging\", false)\n .option(\"--force\", \"Skip confirmations\", false)\n .action(async (options: CommandOptions) => {\n await archiveCommand(options);\n });\n}\n\nasync function archiveCommand(options: CommandOptions): Promise<void> {\n try {\n logger.info(\"Archive command started\", { options });\n\n const { concurrency, timeout } = validateOptions(options);\n\n const authManager = new AuthManager(PATHS.APP_DIR);\n const inputHandler = new InputHandler();\n const progressDisplay = new ProgressDisplay();\n\n const githubService = await authenticateUser(authManager);\n\n const { repositories, parseErrors } = await getRepositories(\n options,\n inputHandler\n );\n\n if (parseErrors.length > 0) {\n logParseErrors(parseErrors);\n }\n\n if (repositories.length === 0) {\n console.error(`\u274C ${MESSAGES.NO_REPOS_PROVIDED}`);\n process.exit(1);\n }\n\n showRepositoriesPreview(repositories, options.dryRun);\n\n await confirmOperation(options, inputHandler);\n\n const archiveOptions: ArchiveOptions = {\n dryRun: options.dryRun,\n concurrency,\n timeout,\n force: options.force,\n verbose: options.verbose,\n };\n\n console.log(\"\");\n console.log(`${MESSAGES.ARCHIVING_START} (concurrency: ${concurrency})`);\n console.log(\"\");\n\n const archiver = new Archiver(githubService, archiveOptions);\n const results = await archiveRepositories(\n archiver,\n repositories,\n archiveOptions,\n progressDisplay\n );\n\n displayResults(archiver, results, progressDisplay);\n } catch (error) {\n handleArchiveError(error);\n }\n}\n\nfunction validateOptions(options: CommandOptions): {\n concurrency: number;\n timeout: number;\n} {\n const concurrency = Number.parseInt(options.concurrency, 10);\n const timeout = Number.parseInt(options.timeout, 10);\n\n if (concurrency < 1 || concurrency > 50) {\n console.error(\"\u274C Concurrency must be between 1 and 50\");\n process.exit(1);\n }\n\n if (timeout < 10 || timeout > 3600) {\n console.error(\"\u274C Timeout must be between 10 and 3600 seconds\");\n process.exit(1);\n }\n\n return { concurrency, timeout };\n}\n\nasync function authenticateUser(\n authManager: AuthManager\n): Promise<GitHubService> {\n console.log(\"\uD83D\uDD10 Checking authentication...\");\n const token = await authManager.getToken();\n\n if (!token) {\n console.error(`\u274C ${MESSAGES.NO_TOKEN}`);\n console.error(\" Run: github-archiver auth login\");\n process.exit(1);\n }\n\n const githubService = new GitHubService(token);\n\n try {\n const authenticatedUser = await githubService.validateAuth();\n console.log(`\u2705 Authenticated as: ${authenticatedUser}`);\n return githubService;\n } catch (error) {\n console.error(\"\u274C Authentication failed\");\n logger.error(\"Auth validation failed:\", error);\n process.exit(1);\n }\n}\n\nasync function getRepositories(\n options: CommandOptions,\n inputHandler: InputHandler\n): Promise<{\n repositories: RepositoryIdentifier[];\n parseErrors: Array<{ url: string; error: string; line: number }>;\n}> {\n console.log(\"\");\n console.log(\"\uD83D\uDCDD Getting repositories...\");\n\n let repositories: RepositoryIdentifier[] = [];\n let parseErrors: Array<{ url: string; error: string; line: number }> = [];\n\n if (options.file) {\n logger.info(`Using file input: ${options.file}`);\n const result = await inputHandler.getRepositoriesFromFile(options.file);\n repositories = result.repos;\n parseErrors = result.errors;\n } else if (options.stdin) {\n logger.info(\"Using stdin input\");\n console.log(\n \"Enter repository URLs (one per line, press Ctrl+D to finish):\"\n );\n const result = await inputHandler.getRepositoriesFromStdin();\n repositories = result.repos;\n parseErrors = result.errors;\n } else {\n logger.info(\"Using interactive CLI input\");\n const result = await inputHandler.getRepositoriesFromInteractive();\n repositories = result.repos;\n parseErrors = result.errors;\n }\n\n return { repositories, parseErrors };\n}\n\nfunction logParseErrors(\n parseErrors: Array<{ url: string; error: string; line: number }>\n): void {\n console.warn(`\u26A0\uFE0F Found ${parseErrors.length} invalid repositories:`);\n for (const err of parseErrors) {\n console.warn(` Line ${err.line}: ${err.error}`);\n }\n console.warn(\"\");\n}\n\nfunction showRepositoriesPreview(\n repositories: RepositoryIdentifier[],\n dryRun: boolean\n): void {\n console.log(\n `\uD83D\uDCCB Will ${dryRun ? \"validate\" : \"archive\"} ${repositories.length} repositories:`\n );\n for (let index = 0; index < Math.min(repositories.length, 5); index++) {\n const repo = repositories[index];\n if (repo) {\n console.log(` ${index + 1}. ${repo.owner}/${repo.repo}`);\n }\n }\n if (repositories.length > 5) {\n console.log(` ... and ${repositories.length - 5} more`);\n }\n}\n\nasync function confirmOperation(\n options: CommandOptions,\n inputHandler: InputHandler\n): Promise<void> {\n console.log(\"\");\n if (!(options.force || options.dryRun)) {\n const confirmed = await inputHandler.promptForConfirmation(\n \"Are you sure you want to archive these repositories?\"\n );\n\n if (!confirmed) {\n console.log(\"\u274C Cancelled\");\n process.exit(1);\n }\n } else if (options.dryRun) {\n console.log(`\u2139\uFE0F ${MESSAGES.DRY_RUN_MODE}`);\n }\n}\n\nasync function archiveRepositories(\n archiver: Archiver,\n repositories: RepositoryIdentifier[],\n options: ArchiveOptions,\n progressDisplay: ProgressDisplay\n): Promise<ArchiveResult[]> {\n const results = await archiver.archiveRepositories(repositories, options);\n\n const summary = archiver.getSummary();\n const progress = {\n completed: summary.successful + summary.failed + summary.skipped,\n failed: summary.failed,\n total: repositories.length,\n };\n\n if (progressDisplay.shouldUpdate()) {\n console.log(`\\r${progressDisplay.getProgressBar(progress)}`);\n }\n\n return results;\n}\n\nfunction displayResults(\n archiver: Archiver,\n results: ArchiveResult[],\n progressDisplay: ProgressDisplay\n): void {\n console.log(\"\");\n console.log(\"\");\n\n const summary = archiver.getSummary();\n console.log(progressDisplay.getSummaryBox(summary));\n\n if (summary.failed > 0) {\n console.log(\"\");\n console.log(\"\u274C Failed repositories:\");\n for (const r of results) {\n if (!r.success) {\n console.log(` ${r.owner}/${r.repo}: ${r.message}`);\n }\n }\n }\n\n if (summary.skipped > 0) {\n console.log(\"\");\n console.log(\"\u26A0\uFE0F Skipped repositories:\");\n for (const r of results) {\n if (r.success && !r.archived) {\n console.log(` ${r.owner}/${r.repo}: ${r.message}`);\n }\n }\n }\n\n if (summary.failed > 0) {\n logger.warn(`Archive command completed with ${summary.failed} failures`);\n process.exit(1);\n } else {\n console.log(\"\");\n console.log(\"\u2705 All repositories processed successfully!\");\n logger.info(\"Archive command completed successfully\");\n process.exit(0);\n }\n}\n\nfunction handleArchiveError(error: unknown): never {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u274C Error: ${message}`);\n logger.error(\"Archive command failed:\", error);\n\n provideErrorGuidance(message);\n\n process.exit(1);\n}\n\nfunction provideErrorGuidance(message: string): void {\n const lowerMessage = message.toLowerCase();\n\n if (\n lowerMessage.includes(\"token\") ||\n lowerMessage.includes(\"authentication\")\n ) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\n \" 1. Make sure you have a valid GitHub Personal Access Token\"\n );\n console.log(' 2. The token needs \"repo\" scope to archive repositories');\n console.log(\" 3. Run: github-archiver auth login\");\n return;\n }\n\n if (lowerMessage.includes(\"rate limit\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. GitHub API rate limit has been exceeded\");\n console.log(\" 2. Wait a few minutes and try again\");\n console.log(\" 3. Use lower concurrency: --concurrency 1\");\n return;\n }\n\n if (lowerMessage.includes(\"permission\") || lowerMessage.includes(\"403\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. You must be the repository owner or have push access\");\n console.log(\" 2. Check that your GitHub token has correct permissions\");\n console.log(\" 3. Verify you have push access to the repositories\");\n return;\n }\n\n if (lowerMessage.includes(\"not found\") || lowerMessage.includes(\"404\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. Make sure the repository URL is correct\");\n console.log(\" 2. The repository may have been deleted\");\n console.log(\" 3. Check your GitHub access to the repository\");\n return;\n }\n\n if (lowerMessage.includes(\"network\") || lowerMessage.includes(\"timeout\")) {\n console.log(\"\");\n console.log(\"\uD83D\uDCA1 Troubleshooting:\");\n console.log(\" 1. Check your internet connection\");\n console.log(\" 2. GitHub API may be temporarily unavailable\");\n console.log(\" 3. Try again in a moment\");\n console.log(\" 4. Increase timeout: --timeout 600\");\n }\n}\n", "export const MESSAGES = {\n AUTH_SUCCESS: \"Successfully authenticated with GitHub\",\n ARCHIVE_SUCCESS: \"Repository archived successfully\",\n TOKEN_SAVED: \"GitHub token saved successfully\",\n TOKEN_REMOVED: \"GitHub token removed\",\n INVALID_TOKEN: \"Invalid or expired GitHub token\",\n REPO_NOT_FOUND: \"Repository not found\",\n ALREADY_ARCHIVED: \"Repository is already archived\",\n PERMISSION_DENIED: \"You do not have permission to archive this repository\",\n RATE_LIMITED: \"GitHub API rate limit exceeded. Please try again later\",\n NETWORK_ERROR: \"Network error. Please check your connection\",\n INVALID_URL: \"Invalid GitHub repository URL\",\n NO_TOKEN:\n 'No GitHub token found. Please run \"github-archiver auth login\" first',\n OPENING_EDITOR: \"Opening text editor for repository URLs...\",\n NO_REPOS_PROVIDED: \"No repositories provided\",\n ARCHIVING_START: \"Starting to archive repositories...\",\n ARCHIVING_COMPLETE: \"Archiving complete\",\n DRY_RUN_MODE: \"Running in dry-run mode (no repositories will be archived)\",\n CONFIRM_ARCHIVE: \"Are you sure you want to archive these repositories?\",\n ENTER_TOKEN:\n \"Enter your GitHub Personal Access Token (will not be displayed):\",\n CONFIRM_LOGOUT: \"Are you sure you want to remove the stored token?\",\n};\n", "import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst appDir = join(homedir(), \".github-archiver\");\n\nexport const PATHS = {\n APP_DIR: appDir,\n CONFIG_FILE: join(appDir, \"config.json\"),\n LOG_DIR: join(appDir, \"logs\"),\n COMBINED_LOG: join(appDir, \"logs\", \"combined.log\"),\n ERROR_LOG: join(appDir, \"logs\", \"errors.log\"),\n};\n", "import PQueue from \"p-queue\";\nimport type {\n ArchiveOptions,\n ArchiveResult,\n RepositoryIdentifier,\n} from \"../types\";\nimport { ErrorCode } from \"../types\";\nimport { isArchiveError } from \"../utils/errors\";\nimport { getLogger } from \"../utils/logger\";\nimport type { GitHubService } from \"./github\";\n\nconst logger = getLogger();\n\nexport class Archiver {\n private readonly queue: PQueue;\n private results: ArchiveResult[] = [];\n private readonly gitHubService: GitHubService;\n private completed = 0;\n private failed = 0;\n private readonly startTime = 0;\n\n constructor(gitHubService: GitHubService, options: ArchiveOptions) {\n this.gitHubService = gitHubService;\n this.queue = new PQueue({\n concurrency: options.concurrency,\n timeout: options.timeout * 1000,\n interval: 1000,\n intervalCap: options.concurrency,\n });\n\n logger.info(\"Archiver initialized\", {\n concurrency: options.concurrency,\n timeout: options.timeout,\n dryRun: options.dryRun,\n });\n }\n\n async archiveRepositories(\n repos: RepositoryIdentifier[],\n options: ArchiveOptions\n ): Promise<ArchiveResult[]> {\n this.results = [];\n this.completed = 0;\n this.failed = 0;\n\n logger.info(`Starting to archive ${repos.length} repositories`, {\n dryRun: options.dryRun,\n });\n\n const tasks = repos.map((repo) =>\n this.queue.add(() => this.archiveRepository(repo, options))\n );\n\n await Promise.all(tasks);\n\n return this.results;\n }\n\n private async archiveRepository(\n repo: RepositoryIdentifier,\n options: ArchiveOptions\n ): Promise<void> {\n const startTime = Date.now();\n\n try {\n if (options.dryRun) {\n logger.info(`[DRY-RUN] Would archive ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"[DRY-RUN] Validation successful\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n const validation = await this.gitHubService.validateRepository(\n repo.owner,\n repo.repo\n );\n\n if (!validation.exists) {\n logger.warn(`Repository not found: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: false,\n archived: false,\n error: \"Repository not found\",\n duration: Date.now() - startTime,\n message: \"Repository does not exist on GitHub\",\n };\n this.results.push(result);\n this.failed++;\n return;\n }\n\n if (validation.isArchived) {\n logger.info(`Repository already archived: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"Repository is already archived\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n console.log(`\uD83D\uDCE6 Archiving ${repo.owner}/${repo.repo}...`);\n await this.gitHubService.archiveRepository(repo.owner, repo.repo);\n\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: true,\n duration: Date.now() - startTime,\n message: \"Repository archived successfully\",\n };\n this.results.push(result);\n this.completed++;\n logger.info(`\u2713 Archived ${repo.owner}/${repo.repo}`, {\n duration: result.duration,\n });\n } catch (error) {\n if (isArchiveError(error) && error.code === ErrorCode.ALREADY_ARCHIVED) {\n logger.info(`Repository already archived: ${repo.owner}/${repo.repo}`);\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: true,\n archived: false,\n duration: Date.now() - startTime,\n message: \"Repository is already archived\",\n };\n this.results.push(result);\n this.completed++;\n return;\n }\n\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n logger.error(\n `\u2717 Failed to archive ${repo.owner}/${repo.repo}: ${errorMessage}`\n );\n\n const result: ArchiveResult = {\n owner: repo.owner,\n repo: repo.repo,\n success: false,\n archived: false,\n error: errorMessage,\n duration: Date.now() - startTime,\n message: `Error: ${errorMessage}`,\n };\n this.results.push(result);\n this.failed++;\n }\n }\n\n getProgress(): { completed: number; failed: number; total: number } {\n return {\n completed: this.completed,\n failed: this.failed,\n total: this.results.length,\n };\n }\n\n getSummary(): {\n successful: number;\n failed: number;\n skipped: number;\n totalDuration: number;\n } {\n const successful = this.results.filter(\n (r) => r.success && r.archived\n ).length;\n const failed = this.results.filter((r) => !r.success).length;\n const skipped = this.results.filter((r) => r.success && !r.archived).length;\n const totalDuration = Date.now() - this.startTime;\n\n return { successful, failed, skipped, totalDuration };\n }\n}\n", "export const ErrorCode = {\n INVALID_AUTH: \"INVALID_AUTH\",\n REPO_NOT_FOUND: \"REPO_NOT_FOUND\",\n ALREADY_ARCHIVED: \"ALREADY_ARCHIVED\",\n PERMISSION_DENIED: \"PERMISSION_DENIED\",\n RATE_LIMITED: \"RATE_LIMITED\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n INVALID_URL: \"INVALID_URL\",\n PARSE_ERROR: \"PARSE_ERROR\",\n EDITOR_ERROR: \"EDITOR_ERROR\",\n CONFIG_ERROR: \"CONFIG_ERROR\",\n FILE_ERROR: \"FILE_ERROR\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class ArchiveError extends Error {\n code: ErrorCode;\n statusCode?: number;\n retryable: boolean;\n\n constructor(\n code: ErrorCode,\n message: string,\n statusCode?: number,\n retryable = false\n ) {\n super(message);\n this.name = \"ArchiveError\";\n this.code = code;\n this.statusCode = statusCode;\n this.retryable = retryable;\n Object.setPrototypeOf(this, ArchiveError.prototype);\n }\n}\n", "import { MESSAGES } from \"../constants/messages\";\nimport { ArchiveError, ErrorCode } from \"../types\";\n\nexport function isArchiveError(error: unknown): error is ArchiveError {\n return error instanceof ArchiveError;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (isArchiveError(error)) {\n return error.message;\n }\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n\nexport function createAuthError(\n message: string,\n statusCode?: number\n): ArchiveError {\n return new ArchiveError(ErrorCode.INVALID_AUTH, message, statusCode, true);\n}\n\nexport function createRepoNotFoundError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.REPO_NOT_FOUND,\n `Repository ${owner}/${repo} not found`,\n 404,\n false\n );\n}\n\nexport function createAlreadyArchivedError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.ALREADY_ARCHIVED,\n `Repository ${owner}/${repo} is already archived`,\n 422,\n false\n );\n}\n\nexport function createPermissionError(\n owner: string,\n repo: string\n): ArchiveError {\n return new ArchiveError(\n ErrorCode.PERMISSION_DENIED,\n `Permission denied for ${owner}/${repo}. You must be the repository owner or have push access.`,\n 403,\n false\n );\n}\n\nexport function createRateLimitError(): ArchiveError {\n return new ArchiveError(\n ErrorCode.RATE_LIMITED,\n MESSAGES.RATE_LIMITED,\n 429,\n true\n );\n}\n\nexport function createNetworkError(message?: string): ArchiveError {\n return new ArchiveError(\n ErrorCode.NETWORK_ERROR,\n message || MESSAGES.NETWORK_ERROR,\n undefined,\n true\n );\n}\n\nexport function createInvalidUrlError(url: string): ArchiveError {\n return new ArchiveError(ErrorCode.INVALID_URL, `Invalid GitHub URL: ${url}`);\n}\n\nexport function createConfigError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.CONFIG_ERROR, message);\n}\n\nexport function createFileError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.FILE_ERROR, message);\n}\n\nexport function createEditorError(message: string): ArchiveError {\n return new ArchiveError(ErrorCode.EDITOR_ERROR, message);\n}\n", "import { promises as fs } from \"node:fs\";\nimport winston from \"winston\";\nimport { DEFAULT_LOG_LEVEL } from \"../constants/defaults\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config } from \"../types\";\n\nlet loggerInstance: winston.Logger;\n\nexport async function initializeLogger(logDir: string): Promise<void> {\n try {\n await fs.mkdir(logDir, { recursive: true });\n } catch (error) {\n console.error(\"Failed to create log directory:\", error);\n }\n}\n\nexport function createLogger(config?: Partial<Config>): winston.Logger {\n const level = config?.logLevel || DEFAULT_LOG_LEVEL;\n const logDir = config?.logDir || PATHS.LOG_DIR;\n\n return winston.createLogger({\n level,\n format: winston.format.combine(\n winston.format.timestamp({ format: \"YYYY-MM-DD HH:mm:ss\" }),\n winston.format.errors({ stack: true }),\n winston.format.json()\n ),\n defaultMeta: { service: \"github-archiver\" },\n transports: [\n new winston.transports.File({\n filename: `${logDir}/errors.log`,\n level: \"error\",\n }),\n new winston.transports.File({\n filename: `${logDir}/combined.log`,\n }),\n ],\n });\n}\n\nexport function createConsoleLogger(): winston.Logger {\n return winston.createLogger({\n format: winston.format.combine(\n winston.format.colorize(),\n winston.format.printf(({ level, message, ...metadata }) => {\n const meta = Object.keys(metadata).length\n ? JSON.stringify(metadata)\n : \"\";\n return `${level}: ${message} ${meta}`;\n })\n ),\n transports: [new winston.transports.Console()],\n });\n}\n\nexport function getLogger(): winston.Logger {\n if (!loggerInstance) {\n loggerInstance = createLogger();\n }\n return loggerInstance;\n}\n\nexport function setLogger(logger: winston.Logger): void {\n loggerInstance = logger;\n}\n", "export const DEFAULT_CONCURRENCY = 3;\nexport const DEFAULT_TIMEOUT = 300; // seconds\nexport const DEFAULT_LOG_LEVEL = \"info\" as const;\nexport const MAX_RETRIES = 3;\nexport const RETRY_DELAY_MS = 1000; // base delay for exponential backoff\nexport const GITHUB_API_URL = \"https://api.github.com\";\n", "import { Octokit } from \"octokit\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config, StoredCredentials } from \"../types\";\nimport { ConfigManager } from \"../utils/config\";\nimport { createAuthError } from \"../utils/errors\";\n\nexport class AuthManager {\n private readonly configManager: ConfigManager;\n\n constructor(configDir: string = PATHS.APP_DIR) {\n this.configManager = new ConfigManager(configDir);\n }\n\n async saveToken(token: string): Promise<void> {\n if (!token || typeof token !== \"string\") {\n throw createAuthError(\"Invalid token format\");\n }\n\n const octokit = new Octokit({ auth: token });\n\n try {\n const { data } = await octokit.rest.users.getAuthenticated();\n\n const credentials: StoredCredentials = {\n token,\n savedAt: new Date().toISOString(),\n githubUser: data.login,\n };\n\n await this.configManager.saveConfig(credentials);\n } catch (error) {\n throw createAuthError(\n `Failed to validate token: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getToken(): Promise<string | undefined> {\n if (process.env.GH_TOKEN) {\n return process.env.GH_TOKEN;\n }\n\n const credentials = await this.configManager.getStoredCredentials();\n return credentials?.token;\n }\n\n async removeToken(): Promise<void> {\n await this.configManager.removeStoredCredentials();\n }\n\n async validateToken(\n token: string\n ): Promise<{ valid: boolean; user?: string }> {\n try {\n const octokit = new Octokit({ auth: token });\n const { data } = await octokit.rest.users.getAuthenticated();\n return {\n valid: true,\n user: data.login,\n };\n } catch (_error) {\n return {\n valid: false,\n };\n }\n }\n\n getStoredCredentials(): Promise<StoredCredentials | null> {\n return this.configManager.getStoredCredentials();\n }\n\n ensureConfigDir(): Promise<void> {\n return this.configManager.ensureConfigDir();\n }\n\n loadConfig(): Promise<Config> {\n return this.configManager.loadConfig();\n }\n}\n", "import { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n DEFAULT_CONCURRENCY,\n DEFAULT_LOG_LEVEL,\n DEFAULT_TIMEOUT,\n} from \"../constants/defaults\";\nimport { PATHS } from \"../constants/paths\";\nimport type { Config, StoredCredentials } from \"../types\";\nimport { createConfigError, createFileError } from \"./errors\";\n\nexport class ConfigManager {\n private readonly configDir: string;\n private readonly configFile: string;\n\n constructor(configDir: string = PATHS.APP_DIR) {\n this.configDir = configDir;\n this.configFile = join(configDir, \"config.json\");\n }\n\n async loadConfig(): Promise<Config> {\n try {\n const token = process.env.GH_TOKEN;\n const fileCredentials = await this.loadFileCredentials();\n\n return {\n token: token || fileCredentials?.token,\n concurrency: DEFAULT_CONCURRENCY,\n timeout: DEFAULT_TIMEOUT,\n logLevel: DEFAULT_LOG_LEVEL,\n logDir: join(this.configDir, \"logs\"),\n configDir: this.configDir,\n };\n } catch (error) {\n throw createConfigError(\n `Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async saveConfig(credentials: StoredCredentials): Promise<void> {\n try {\n await this.ensureConfigDir();\n await fs.writeFile(\n this.configFile,\n JSON.stringify(credentials, null, 2),\n \"utf-8\"\n );\n } catch (error) {\n throw createFileError(\n `Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async ensureConfigDir(): Promise<void> {\n try {\n await fs.mkdir(this.configDir, { recursive: true });\n } catch (error) {\n throw createFileError(\n `Failed to create config directory: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n async getStoredCredentials(): Promise<StoredCredentials | null> {\n try {\n const data = await fs.readFile(this.configFile, \"utf-8\");\n return JSON.parse(data) as StoredCredentials;\n } catch {\n return null;\n }\n }\n\n async removeStoredCredentials(): Promise<void> {\n try {\n await fs.unlink(this.configFile);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw createFileError(\n `Failed to remove stored credentials: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n }\n\n private async loadFileCredentials(): Promise<StoredCredentials | null> {\n try {\n const data = await fs.readFile(this.configFile, \"utf-8\");\n return JSON.parse(data) as StoredCredentials;\n } catch {\n return null;\n }\n }\n}\n", "import { Octokit } from \"octokit\";\nimport { MAX_RETRIES, RETRY_DELAY_MS } from \"../constants/defaults\";\nimport type { RateLimitInfo } from \"../types\";\nimport {\n createAlreadyArchivedError,\n createNetworkError,\n createPermissionError,\n createRateLimitError,\n createRepoNotFoundError,\n} from \"../utils/errors\";\nimport { getLogger } from \"../utils/logger\";\n\nconst logger = getLogger();\n\ninterface OctokitError {\n status: number;\n response?: {\n data?: {\n message: string;\n };\n };\n message: string;\n}\n\nexport class GitHubService {\n private readonly octokit: Octokit;\n\n constructor(token: string) {\n this.octokit = new Octokit({ auth: token });\n }\n\n async archiveRepository(owner: string, repo: string): Promise<void> {\n const startTime = Date.now();\n logger.debug(`Attempting to archive ${owner}/${repo}`);\n\n await this.retryWithBackoff(async () => {\n try {\n await this.octokit.rest.repos.update({\n owner,\n repo,\n archived: true,\n });\n const duration = Date.now() - startTime;\n logger.info(`Successfully archived ${owner}/${repo} in ${duration}ms`);\n } catch (error) {\n this.handleArchiveError(error, owner, repo);\n }\n });\n }\n\n private handleArchiveError(\n error: unknown,\n owner: string,\n repo: string\n ): never {\n const err = this.parseOctokitError(error);\n logger.error(`Failed to archive ${owner}/${repo}: ${err.message}`, {\n status: err.status,\n code: err.code,\n });\n\n if (err.status === 404) {\n throw createRepoNotFoundError(owner, repo);\n }\n\n if (err.status === 403 || err.message.includes(\"permission\")) {\n throw createPermissionError(owner, repo);\n }\n\n if (err.status === 422 && err.message.includes(\"archived\")) {\n throw createAlreadyArchivedError(owner, repo);\n }\n\n if (err.message.includes(\"API rate limit\")) {\n throw createRateLimitError();\n }\n\n if (\n err.message.includes(\"ECONNREFUSED\") ||\n err.message.includes(\"ETIMEDOUT\") ||\n err.message.includes(\"EHOSTUNREACH\") ||\n err.message.includes(\"socket hang up\")\n ) {\n throw createNetworkError(err.message);\n }\n\n throw error;\n }\n\n private parseOctokitError(error: unknown): {\n message: string;\n status: number;\n code: string | undefined;\n } {\n const message = error instanceof Error ? error.message : String(error);\n const err = error as unknown as OctokitError;\n return {\n message,\n status: err?.status || 0,\n code: err?.response?.data?.message,\n };\n }\n\n async validateRepository(\n owner: string,\n repo: string\n ): Promise<{ exists: boolean; isArchived: boolean }> {\n try {\n logger.debug(`Validating repository ${owner}/${repo}`);\n const { data } = await this.octokit.rest.repos.get({\n owner,\n repo,\n });\n\n const result = {\n exists: true,\n isArchived: data.archived,\n };\n logger.debug(`Repository ${owner}/${repo} validated`, result);\n return result;\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"404\")) {\n logger.debug(`Repository ${owner}/${repo} does not exist`);\n return {\n exists: false,\n isArchived: false,\n };\n }\n\n logger.error(`Error validating ${owner}/${repo}:`, error);\n throw error;\n }\n }\n\n async getRateLimitStatus(): Promise<RateLimitInfo> {\n try {\n logger.debug(\"Fetching rate limit status\");\n const { data } = await this.octokit.rest.rateLimit.get();\n\n const info: RateLimitInfo = {\n remaining: data.rate.remaining,\n reset: new Date(data.rate.reset * 1000),\n limit: data.rate.limit,\n };\n\n logger.debug(\"Rate limit status retrieved\", {\n remaining: info.remaining,\n limit: info.limit,\n resetAt: info.reset.toISOString(),\n });\n\n return info;\n } catch (error) {\n logger.error(\"Failed to fetch rate limit status:\", error);\n throw error;\n }\n }\n\n async validateAuth(): Promise<string> {\n try {\n logger.debug(\"Validating authentication\");\n const { data } = await this.octokit.rest.users.getAuthenticated();\n logger.info(`Authenticated as user: ${data.login}`);\n return data.login;\n } catch (error) {\n logger.error(\"Authentication validation failed:\", error);\n throw createNetworkError(\n error instanceof Error ? error.message : \"Authentication failed\"\n );\n }\n }\n\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n maxRetries: number = MAX_RETRIES\n ): Promise<T> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error as Error;\n\n const message = lastError.message.toLowerCase();\n const isRetryable =\n message.includes(\"rate limit\") ||\n message.includes(\"timeout\") ||\n message.includes(\"econnrefused\") ||\n message.includes(\"etimedout\") ||\n message.includes(\"ehostunreach\") ||\n message.includes(\"socket hang up\");\n\n if (!isRetryable || attempt === maxRetries - 1) {\n throw error;\n }\n\n const delay = RETRY_DELAY_MS * 2 ** attempt;\n logger.warn(\n `Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`,\n {\n error: message,\n }\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw lastError || new Error(\"Unknown error during retry\");\n }\n}\n", "import { promises as fs } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport type { RepositoryIdentifier } from \"../types\";\nimport { getLogger } from \"./logger\";\nimport { URLParser } from \"./parser\";\n\nconst logger = getLogger();\n\nexport class InputHandler {\n getRepositoriesFromInteractive(): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(\"Starting interactive CLI input for repositories\");\n\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log(\"\");\n console.log(\"\uD83D\uDCDD Enter repository URLs one at a time:\");\n console.log(\n ' (Type \"done\" when finished, or leave empty and press Enter to skip)'\n );\n console.log(\"\");\n\n const urls: string[] = [];\n let lineNumber = 1;\n\n const promptNext = () => {\n rl.question(`[${lineNumber}] > `, (input) => {\n const trimmedInput = input.trim();\n\n if (trimmedInput.toLowerCase() === \"done\") {\n finishInput();\n return;\n }\n\n if (trimmedInput === \"\") {\n lineNumber++;\n promptNext();\n return;\n }\n\n urls.push(trimmedInput);\n lineNumber++;\n promptNext();\n });\n };\n\n const finishInput = () => {\n rl.close();\n const result = URLParser.parseRepositoriesBatch(urls);\n\n if (result.invalid.length > 0) {\n logger.warn(`Found ${result.invalid.length} invalid repositories`, {\n errors: result.invalid,\n });\n }\n\n logger.info(`Parsed ${result.valid.length} valid repositories`);\n resolve({\n repos: result.valid,\n errors: result.invalid,\n });\n };\n\n rl.on(\"close\", () => {\n finishInput();\n });\n\n promptNext();\n });\n }\n\n async getRepositoriesFromFile(filePath: string): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(`Reading repositories from file: ${filePath}`);\n\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n const result = URLParser.parseRepositoriesBatch(lines);\n\n if (result.invalid.length > 0) {\n logger.warn(`Found ${result.invalid.length} invalid entries in file`, {\n errors: result.invalid,\n });\n }\n\n logger.info(`Parsed ${result.valid.length} valid repositories from file`);\n return {\n repos: result.valid,\n errors: result.invalid,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`Failed to read file ${filePath}: ${message}`);\n throw new Error(`Failed to read file: ${message}`);\n }\n }\n\n getRepositoriesFromStdin(): Promise<{\n repos: RepositoryIdentifier[];\n errors: Array<{ url: string; error: string; line: number }>;\n }> {\n logger.info(\"Reading repositories from stdin\");\n\n return new Promise((resolve) => {\n const lines: string[] = [];\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.on(\"line\", (line) => {\n lines.push(line);\n });\n\n rl.on(\"close\", () => {\n const result = URLParser.parseRepositoriesBatch(lines);\n\n if (result.invalid.length > 0) {\n logger.warn(\n `Found ${result.invalid.length} invalid entries from stdin`,\n {\n errors: result.invalid,\n }\n );\n }\n\n logger.info(\n `Parsed ${result.valid.length} valid repositories from stdin`\n );\n resolve({\n repos: result.valid,\n errors: result.invalid,\n });\n });\n });\n }\n\n promptForConfirmation(message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(`${message} [y/N]: `, (answer) => {\n rl.close();\n const confirmed = answer.toLowerCase() === \"y\";\n logger.info(`User confirmation: ${confirmed}`);\n resolve(confirmed);\n });\n });\n }\n}\n", "import type { RepositoryIdentifier } from \"../types\";\nimport { ArchiveError, ErrorCode } from \"../types\";\nimport { getLogger } from \"./logger\";\n\nconst logger = getLogger();\nconst NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$/;\n\nconst GITHUB_URL_PATTERNS: RegExp[] = [\n // https://github.com/owner/repo or https://github.com/owner/repo.git\n /^(?:https?:\\/\\/)?(?:www\\.)?github\\.com[/:]([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?(?:\\/)?$/i,\n // git@github.com:owner/repo.git or git@github.com:owner/repo\n /^git@github\\.com:([\\w.-]+)\\/([\\w.-]+?)(?:\\.git)?$/i,\n // owner/repo (shorthand)\n /^([\\w.-]+)\\/([\\w.-]+)$/,\n];\n\nfunction parseRepositoryUrl(url: string): RepositoryIdentifier {\n const trimmed = url.trim();\n\n if (!trimmed) {\n throw new ArchiveError(ErrorCode.INVALID_URL, \"URL cannot be empty\");\n }\n\n for (const pattern of GITHUB_URL_PATTERNS) {\n const match = trimmed.match(pattern);\n if (match) {\n const owner = match[1];\n const repo = match[2];\n\n if (!(owner && repo)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid GitHub URL format: ${url}`\n );\n }\n\n if (!isValidName(owner)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid owner name: ${owner}. Owner names must contain only alphanumeric characters, hyphens, or periods.`\n );\n }\n\n if (!isValidName(repo)) {\n throw new ArchiveError(\n ErrorCode.INVALID_URL,\n `Invalid repository name: ${repo}. Repository names must contain only alphanumeric characters, hyphens, underscores, or periods.`\n );\n }\n\n const normalizedUrl = `https://github.com/${owner}/${repo}`;\n\n logger.debug(\"Parsed repository URL\", {\n original: trimmed,\n owner,\n repo,\n normalized: normalizedUrl,\n });\n\n return {\n owner,\n repo,\n url: normalizedUrl,\n };\n }\n }\n\n throw new ArchiveError(ErrorCode.INVALID_URL, `Invalid GitHub URL: ${url}`);\n}\n\nfunction isValidName(name: string): boolean {\n return NAME_REGEX.test(name);\n}\n\nfunction parseRepositoriesBatch(urls: string[]): {\n valid: RepositoryIdentifier[];\n invalid: Array<{ url: string; error: string; line: number }>;\n} {\n const valid: RepositoryIdentifier[] = [];\n const invalid: Array<{ url: string; error: string; line: number }> = [];\n\n for (const [index, url] of urls.entries()) {\n const lineNumber = index + 1;\n const trimmed = url.trim();\n\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n\n try {\n const parsed = parseRepositoryUrl(trimmed);\n valid.push(parsed);\n logger.debug(`Line ${lineNumber}: Valid repository`, {\n owner: parsed.owner,\n repo: parsed.repo,\n });\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n invalid.push({\n url: trimmed,\n error: errorMessage,\n line: lineNumber,\n });\n logger.warn(`Line ${lineNumber}: Invalid repository`, {\n url: trimmed,\n error: errorMessage,\n });\n }\n }\n\n logger.info(\"Batch parsing complete\", {\n total: urls.length,\n valid: valid.length,\n invalid: invalid.length,\n skipped: urls.length - valid.length - invalid.length,\n });\n\n return { valid, invalid };\n}\n\nexport const URLParser = {\n parseRepositoryUrl,\n parseRepositoriesBatch,\n};\n", "function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n if (ms < 60_000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60_000);\n const seconds = ((ms % 60_000) / 1000).toFixed(0);\n return `${minutes}m ${seconds}s`;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) {\n return \"0 B\";\n }\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`;\n}\n\nfunction formatPercent(value: number, total: number): string {\n if (total === 0) {\n return \"0%\";\n }\n return `${((value / total) * 100).toFixed(1)}%`;\n}\n\nfunction createProgressBar(\n completed: number,\n total: number,\n width = 30\n): string {\n if (total === 0) {\n return \"[ ]\";\n }\n const filledWidth = Math.round((completed / total) * width);\n const emptyWidth = width - filledWidth;\n const filled = \"=\".repeat(filledWidth);\n const empty = \" \".repeat(emptyWidth);\n const percentage = ((completed / total) * 100).toFixed(0);\n return `[${filled}${empty}] ${percentage}%`;\n}\n\nfunction centerText(text: string, width: number): string {\n const padding = Math.max(0, (width - text.length) / 2);\n return \" \".repeat(Math.floor(padding)) + text;\n}\n\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n return `${text.substring(0, maxLength - 3)}...`;\n}\n\nexport const Formatting = {\n formatDuration,\n formatBytes,\n formatPercent,\n createProgressBar,\n centerText,\n truncate,\n};\n", "import { Formatting } from \"./formatting\";\n\nexport interface ProgressUpdate {\n completed: number;\n failed: number;\n total: number;\n current?: {\n owner: string;\n repo: string;\n };\n}\n\nexport class ProgressDisplay {\n private readonly startTime: number;\n private lastUpdate = 0;\n private readonly updateInterval = 500; // ms\n\n constructor() {\n this.startTime = Date.now();\n }\n\n shouldUpdate(): boolean {\n const now = Date.now();\n if (now - this.lastUpdate >= this.updateInterval) {\n this.lastUpdate = now;\n return true;\n }\n return false;\n }\n\n getProgressBar(progress: ProgressUpdate): string {\n const { completed, total, current } = progress;\n const percent = total > 0 ? (completed / total) * 100 : 0;\n const bar = Formatting.createProgressBar(completed, total, 25);\n\n let line = `${bar} ${completed}/${total} (${percent.toFixed(0)}%)`;\n\n if (current) {\n line += ` - ${current.owner}/${current.repo}`;\n }\n\n return line;\n }\n\n getSummaryBox(summary: {\n successful: number;\n failed: number;\n skipped: number;\n totalDuration: number;\n }): string {\n const { successful, failed, skipped, totalDuration } = summary;\n const total = successful + failed + skipped;\n const duration = Formatting.formatDuration(totalDuration);\n\n const lines = [\n \"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\",\n \"\u2551 Archive Operation Summary \u2551\",\n \"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\",\n `\u2551 \u2705 Successful: ${String(successful).padEnd(20)} \u2551`,\n `\u2551 \u26A0\uFE0F Skipped: ${String(skipped).padEnd(20)} \u2551`,\n `\u2551 \u274C Failed: ${String(failed).padEnd(20)} \u2551`,\n \"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\",\n `\u2551 Total: ${String(total).padEnd(20)} \u2551`,\n `\u2551 Duration: ${String(duration).padEnd(20)} \u2551`,\n \"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\",\n ];\n\n return lines.join(\"\\n\");\n }\n\n getElapsedTime(): string {\n const elapsed = Date.now() - this.startTime;\n return Formatting.formatDuration(elapsed);\n }\n\n getEstimatedTimeRemaining(completed: number, total: number): string | null {\n if (completed === 0) {\n return null;\n }\n\n const elapsed = Date.now() - this.startTime;\n const avgTime = elapsed / completed;\n const remaining = (total - completed) * avgTime;\n\n return remaining > 0 ? Formatting.formatDuration(remaining) : null;\n }\n}\n", "import { createInterface } from \"node:readline\";\nimport { Command } from \"commander\";\nimport { MESSAGES } from \"../constants/messages\";\nimport { PATHS } from \"../constants/paths\";\nimport { AuthManager } from \"../services/auth-manager\";\nimport { getLogger } from \"../utils/logger\";\n\nconst authManager = new AuthManager(PATHS.APP_DIR);\n\nfunction createLoginCommand(): Command {\n return new Command(\"login\")\n .description(\"Set up GitHub authentication with a Personal Access Token\")\n .action(async () => {\n try {\n await authManager.ensureConfigDir();\n\n const token = await promptForToken();\n\n if (!token) {\n console.error(\"No token provided. Aborting.\");\n process.exit(1);\n }\n\n console.log(\"Validating token...\");\n await authManager.saveToken(token);\n\n const credentials = await authManager.getStoredCredentials();\n console.log(`\u2713 ${MESSAGES.AUTH_SUCCESS}`);\n console.log(` User: ${credentials?.githubUser}`);\n\n getLogger().info(\"Token saved successfully\", {\n user: credentials?.githubUser,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to save token\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createLogoutCommand(): Command {\n return new Command(\"logout\")\n .description(\"Remove stored GitHub authentication token\")\n .action(async () => {\n try {\n const credentials = await authManager.getStoredCredentials();\n\n if (!credentials) {\n console.log(\"No stored credentials found.\");\n return;\n }\n\n const confirmed = await confirmAction(MESSAGES.CONFIRM_LOGOUT);\n\n if (!confirmed) {\n console.log(\"Cancelled.\");\n return;\n }\n\n await authManager.removeToken();\n console.log(`\u2713 ${MESSAGES.TOKEN_REMOVED}`);\n getLogger().info(\"Token removed\");\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to remove token\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createStatusCommand(): Command {\n return new Command(\"status\")\n .description(\"Check authentication status\")\n .action(async () => {\n try {\n const token = await authManager.getToken();\n\n if (!token) {\n console.log(\"\u2717 Not authenticated\");\n console.log(\n ' Run \"github-archiver auth login\" to set up authentication'\n );\n return;\n }\n\n console.log(\"\u2713 Authenticated\");\n\n const validation = await authManager.validateToken(token);\n\n if (validation.valid) {\n console.log(` User: ${validation.user}`);\n const credentials = await authManager.getStoredCredentials();\n if (credentials) {\n console.log(\n ` Saved at: ${new Date(credentials.savedAt).toLocaleString()}`\n );\n }\n getLogger().info(\"Authentication status check passed\");\n } else {\n console.log(\"\u2717 Token is invalid or expired\");\n getLogger().warn(\"Token validation failed\");\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Failed to check auth status\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction createValidateCommand(): Command {\n return new Command(\"validate\")\n .description(\"Validate stored authentication token\")\n .action(async () => {\n try {\n const token = await authManager.getToken();\n\n if (!token) {\n console.log(\"\u2717 No token found\");\n process.exit(1);\n }\n\n console.log(\"Validating token...\");\n const validation = await authManager.validateToken(token);\n\n if (validation.valid) {\n console.log(`\u2713 Token is valid (user: ${validation.user})`);\n getLogger().info(\"Token validation successful\", {\n user: validation.user,\n });\n } else {\n console.log(\"\u2717 Token is invalid or expired\");\n getLogger().warn(\"Token validation failed\");\n process.exit(1);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\u2717 Error: ${message}`);\n getLogger().error(\"Token validation error\", { error: message });\n process.exit(1);\n }\n });\n}\n\nfunction promptForToken(): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(MESSAGES.ENTER_TOKEN, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction confirmAction(message: string): Promise<boolean> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(`${message} [y/N]: `, (answer) => {\n rl.close();\n resolve(answer.toLowerCase() === \"y\");\n });\n });\n}\n\nexport function createAuthCommand(): Command {\n return new Command(\"auth\")\n .description(\"Manage GitHub authentication\")\n .addCommand(createLoginCommand())\n .addCommand(createLogoutCommand())\n .addCommand(createStatusCommand())\n .addCommand(createValidateCommand());\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["Command", "Command", "MESSAGES", "homedir", "join", "appDir", "PATHS", "PQueue", "ErrorCode", "ArchiveError", "_ArchiveError", "code", "message", "statusCode", "retryable", "isArchiveError", "error", "ArchiveError", "createAuthError", "message", "statusCode", "ArchiveError", "ErrorCode", "createRepoNotFoundError", "owner", "repo", "createAlreadyArchivedError", "createPermissionError", "createRateLimitError", "MESSAGES", "createNetworkError", "createConfigError", "message", "ArchiveError", "ErrorCode", "createFileError", "fs", "winston", "DEFAULT_LOG_LEVEL", "loggerInstance", "initializeLogger", "logDir", "fs", "error", "createLogger", "config", "level", "DEFAULT_LOG_LEVEL", "PATHS", "winston", "getLogger", "loggerInstance", "createLogger", "setLogger", "logger", "logger", "getLogger", "Archiver", "gitHubService", "options", "PQueue", "repos", "tasks", "repo", "startTime", "result", "validation", "error", "isArchiveError", "ErrorCode", "errorMessage", "successful", "r", "failed", "skipped", "totalDuration", "Octokit", "fs", "join", "ConfigManager", "configDir", "PATHS", "join", "token", "fileCredentials", "DEFAULT_LOG_LEVEL", "error", "createConfigError", "credentials", "fs", "createFileError", "data", "AuthManager", "configDir", "PATHS", "ConfigManager", "token", "createAuthError", "octokit", "Octokit", "data", "credentials", "error", "Octokit", "logger", "getLogger", "GitHubService", "token", "Octokit", "owner", "repo", "startTime", "duration", "error", "err", "createRepoNotFoundError", "createPermissionError", "createAlreadyArchivedError", "createRateLimitError", "createNetworkError", "message", "data", "result", "info", "fn", "maxRetries", "lastError", "attempt", "delay", "resolve", "fs", "createInterface", "logger", "getLogger", "NAME_REGEX", "GITHUB_URL_PATTERNS", "parseRepositoryUrl", "url", "trimmed", "ArchiveError", "ErrorCode", "pattern", "match", "owner", "repo", "isValidName", "normalizedUrl", "name", "parseRepositoriesBatch", "urls", "valid", "invalid", "index", "lineNumber", "parsed", "error", "errorMessage", "URLParser", "logger", "getLogger", "InputHandler", "resolve", "rl", "createInterface", "urls", "lineNumber", "promptNext", "input", "trimmedInput", "finishInput", "result", "URLParser", "filePath", "lines", "fs", "error", "message", "line", "answer", "confirmed", "formatDuration", "ms", "minutes", "seconds", "formatBytes", "bytes", "k", "sizes", "formatPercent", "value", "total", "createProgressBar", "completed", "width", "filledWidth", "emptyWidth", "filled", "empty", "percentage", "centerText", "text", "padding", "truncate", "maxLength", "Formatting", "ProgressDisplay", "now", "progress", "completed", "total", "current", "percent", "line", "Formatting", "summary", "successful", "failed", "skipped", "totalDuration", "duration", "elapsed", "avgTime", "remaining", "logger", "getLogger", "createArchiveCommand", "Command", "options", "archiveCommand", "concurrency", "timeout", "validateOptions", "authManager", "AuthManager", "PATHS", "inputHandler", "InputHandler", "progressDisplay", "ProgressDisplay", "githubService", "authenticateUser", "repositories", "parseErrors", "getRepositories", "logParseErrors", "MESSAGES", "showRepositoriesPreview", "confirmOperation", "archiveOptions", "archiver", "Archiver", "results", "archiveRepositories", "displayResults", "error", "handleArchiveError", "token", "GitHubService", "authenticatedUser", "result", "err", "dryRun", "index", "repo", "summary", "progress", "r", "message", "provideErrorGuidance", "lowerMessage", "createInterface", "Command", "authManager", "AuthManager", "PATHS", "createLoginCommand", "Command", "token", "promptForToken", "credentials", "MESSAGES", "getLogger", "error", "message", "createLogoutCommand", "confirmAction", "createStatusCommand", "validation", "createValidateCommand", "resolve", "rl", "createInterface", "answer", "createAuthCommand", "VERSION", "DESCRIPTION", "main", "initializeLogger", "PATHS", "fileLogger", "createLogger", "setLogger", "program", "Command", "createAuthCommand", "createArchiveCommand", "error", "message"]
7
7
  }
@@ -0,0 +1,123 @@
1
+ # Release Process
2
+
3
+ This document details the automated release process for GitHub Archiver CLI using semantic-release.
4
+
5
+ ## How Releases Work
6
+
7
+ ### 1. Commit Format
8
+
9
+ Use [Conventional Commits](https://www.conventionalcommits.org/):
10
+
11
+ - `feat:` - New features → **minor** version bump (1.0.0 → 1.1.0)
12
+ - `fix:` - Bug fixes → **patch** version bump (1.0.0 → 1.0.1)
13
+ - `BREAKING CHANGE:` - Breaking changes → **major** version bump (1.0.0 → 2.0.0)
14
+ - `chore:`, `docs:`, `test:` - No version bump
15
+
16
+ ### 2. Automatic Trigger
17
+
18
+ Push to `main` branch triggers the release workflow:
19
+
20
+ 1. Semantic release analyzes commits and determines version bump
21
+ 2. Package version is updated, published to npm
22
+ 3. GitHub release is created with changelog
23
+ 4. Git tag is created automatically
24
+
25
+ ### 3. Trusted Publishing
26
+
27
+ Uses OpenID Connect (OIDC) for secure, tokenless authentication:
28
+
29
+ - No npm tokens required - eliminates security risks
30
+ - Short-lived, cryptographically-signed tokens for each publish
31
+ - Works with personal GitHub accounts
32
+
33
+ ### 4. Node Version Requirements
34
+
35
+ - Package runs on Node 18+ (see `package.json` engines)
36
+ - Release workflow uses Node 22+ (semantic-release requirement)
37
+ - CI tests on Node 18 and 22 for maximum compatibility
38
+
39
+ ### 5. Example Workflow
40
+
41
+ ```bash
42
+ git checkout main
43
+ git pull
44
+ # Make your commits with conventional format
45
+ git commit -m "feat: add support for custom config file"
46
+ git push
47
+ # Release happens automatically!
48
+ ```
49
+
50
+ ## Trusted Publishing Setup
51
+
52
+ This project uses npm's **Trusted Publishing** feature for secure, tokenless package publishing.
53
+
54
+ ### Setup (one-time)
55
+
56
+ 1. Go to https://www.npmjs.com/package/github-archiver/settings
57
+ 2. Under **Trusted Publisher**, click **GitHub Actions**
58
+ 3. Fill in:
59
+ - **Organization or user**: `mynameistito`
60
+ - **Repository**: `github-archiver`
61
+ - **Workflow filename**: `release.yml`
62
+ 4. Click **Set up connection**
63
+ 5. (Recommended) Enable **"Require two-factor authentication and disallow tokens"**
64
+
65
+ That's it! No tokens to manage, rotate, or worry about.
66
+
67
+ ## Commit Message Examples
68
+
69
+ ```bash
70
+ # Feature (minor version bump)
71
+ git commit -m "feat: add support for custom config file"
72
+
73
+ # Bug fix (patch version bump)
74
+ git commit -m "fix: handle empty repository list gracefully"
75
+
76
+ # Breaking change (major version bump)
77
+ git commit -m "feat!: change CLI command structure
78
+
79
+ BREAKING CHANGE: The 'auth' subcommand is now required"
80
+
81
+ # No release
82
+ git commit -m "chore: update dependencies"
83
+ git commit -m "docs: clarify installation steps"
84
+ git commit -m "test: add unit tests for parser"
85
+ ```
86
+
87
+ ## Manual Releases
88
+
89
+ If you need to publish without merging to main:
90
+
91
+ ```bash
92
+ npm run release -- --no-ci
93
+ ```
94
+
95
+ ## Release Workflow Details
96
+
97
+ The `.github/workflows/release.yml` file handles:
98
+
99
+ - Running tests on Node 18 and 22
100
+ - Building the project
101
+ - Publishing to npm using Trusted Publishing
102
+ - Creating GitHub releases with changelog
103
+ - Managing version tags automatically
104
+
105
+ ## Troubleshooting
106
+
107
+ ### Release Not Triggered
108
+
109
+ - Ensure commits follow conventional commit format
110
+ - Check that workflow file is named `release.yml`
111
+ - Verify GitHub Actions permissions are enabled
112
+
113
+ ### Publish Failed
114
+
115
+ - Check npm Trusted Publisher configuration
116
+ - Ensure package name in package.json matches npm registry
117
+ - Verify semantic-release version range is valid
118
+
119
+ ### Version Bump Incorrect
120
+
121
+ - Review commit history for proper prefixes
122
+ - Check that `BREAKING CHANGE:` is in commit body (footer)
123
+ - Ensure no duplicate release tags exist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-archiver",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Archive GitHub repositories via CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",