git-ripper 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/package.json +1 -1
- package/src/configManager.js +160 -0
- package/src/index.js +97 -2
package/README.md
CHANGED
|
@@ -94,14 +94,16 @@ git-ripper https://github.com/username/repository/tree/branch/folder --zip="my-a
|
|
|
94
94
|
### Command Line Options
|
|
95
95
|
|
|
96
96
|
| Option | Description | Default |
|
|
97
|
-
| -------------------------- | ---------------------------------------- | ----------------- |
|
|
97
|
+
| -------------------------- | ---------------------------------------- | ----------------- | --- | -------------------------- | ------------------------- | --- |
|
|
98
98
|
| `-o, --output <directory>` | Specify output directory | Current directory |
|
|
99
99
|
| `--gh-token <token>` | GitHub Personal Access Token | - |
|
|
100
100
|
| `--zip [filename]` | Create ZIP archive of downloaded content | - |
|
|
101
101
|
| `--no-resume` | Disable resume functionality | - |
|
|
102
102
|
| `--force-restart` | Ignore existing checkpoints and restart | - |
|
|
103
|
-
| `--list-checkpoints` | List all saved download checkpoints | - |
|
|
104
|
-
|
|
|
103
|
+
| `--list-checkpoints` | List all saved download checkpoints | - | | `config set-token <token>` | Save GitHub token locally | - |
|
|
104
|
+
| `config get-token` | Show saved token (masked) | - |
|
|
105
|
+
| `config remove-token` | Remove saved token | - |
|
|
106
|
+
| `config show` | Show current configuration | - | | `-V, --version` | Show version number | - |
|
|
105
107
|
| `-h, --help` | Show help | - |
|
|
106
108
|
|
|
107
109
|
## Authentication (Private Repositories & Rate Limits)
|
|
@@ -136,12 +138,62 @@ You can use either a **Fine-grained token** (Recommended) or a **Classic token**
|
|
|
136
138
|
|
|
137
139
|
### Using the Token
|
|
138
140
|
|
|
141
|
+
#### Option 1: Save Token Locally (Recommended)
|
|
142
|
+
|
|
143
|
+
Save your token once and use it automatically for all future downloads:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Save the token
|
|
147
|
+
git-ripper config set-token ghp_YourTokenHere
|
|
148
|
+
|
|
149
|
+
# Now just download - token is used automatically
|
|
150
|
+
git-ripper https://github.com/username/private-repo/tree/main/src
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Option 2: Environment Variable
|
|
154
|
+
|
|
155
|
+
Set the `GIT_RIPPER_TOKEN` environment variable:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Windows (PowerShell)
|
|
159
|
+
$env:GIT_RIPPER_TOKEN = "ghp_YourTokenHere"
|
|
160
|
+
|
|
161
|
+
# Linux/Mac
|
|
162
|
+
export GIT_RIPPER_TOKEN="ghp_YourTokenHere"
|
|
163
|
+
|
|
164
|
+
# Then download
|
|
165
|
+
git-ripper https://github.com/username/private-repo/tree/main/src
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Option 3: Command Line Flag
|
|
169
|
+
|
|
139
170
|
Pass the token using the `--gh-token` flag:
|
|
140
171
|
|
|
141
172
|
```bash
|
|
142
173
|
git-ripper https://github.com/username/private-repo/tree/main/src --gh-token ghp_YourTokenHere
|
|
143
174
|
```
|
|
144
175
|
|
|
176
|
+
#### Token Priority
|
|
177
|
+
|
|
178
|
+
When multiple tokens are available, git-ripper uses this priority:
|
|
179
|
+
|
|
180
|
+
1. `--gh-token` command line flag (highest)
|
|
181
|
+
2. `GIT_RIPPER_TOKEN` environment variable
|
|
182
|
+
3. Saved token from `config set-token`
|
|
183
|
+
|
|
184
|
+
#### Managing Saved Tokens
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# View current configuration
|
|
188
|
+
git-ripper config show
|
|
189
|
+
|
|
190
|
+
# View saved token (masked)
|
|
191
|
+
git-ripper config get-token
|
|
192
|
+
|
|
193
|
+
# Remove saved token
|
|
194
|
+
git-ripper config remove-token
|
|
195
|
+
```
|
|
196
|
+
|
|
145
197
|
> **Security Note:** Be careful not to share your token or commit it to public repositories.
|
|
146
198
|
|
|
147
199
|
## Examples
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration manager for git-ripper
|
|
8
|
+
* Handles persistent storage of settings like GitHub tokens
|
|
9
|
+
*/
|
|
10
|
+
class ConfigManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.configDir = path.join(os.homedir(), ".git-ripper");
|
|
13
|
+
this.configFile = path.join(this.configDir, "config.json");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ensures the config directory exists
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
_ensureConfigDir() {
|
|
21
|
+
if (!fs.existsSync(this.configDir)) {
|
|
22
|
+
fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Loads the configuration from disk
|
|
28
|
+
* @returns {Object} - The configuration object
|
|
29
|
+
*/
|
|
30
|
+
loadConfig() {
|
|
31
|
+
try {
|
|
32
|
+
if (fs.existsSync(this.configFile)) {
|
|
33
|
+
const data = fs.readFileSync(this.configFile, "utf8");
|
|
34
|
+
return JSON.parse(data);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// If config is corrupted, return empty config
|
|
38
|
+
console.warn(
|
|
39
|
+
chalk.yellow(`Warning: Could not read config file: ${error.message}`),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Saves the configuration to disk
|
|
47
|
+
* @param {Object} config - The configuration object to save
|
|
48
|
+
*/
|
|
49
|
+
saveConfig(config) {
|
|
50
|
+
this._ensureConfigDir();
|
|
51
|
+
try {
|
|
52
|
+
fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2), {
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
mode: 0o600, // Read/write for owner only
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`Failed to save config: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets the GitHub token in the config
|
|
63
|
+
* @param {string} token - The GitHub Personal Access Token
|
|
64
|
+
*/
|
|
65
|
+
setToken(token) {
|
|
66
|
+
if (!token || typeof token !== "string" || token.trim() === "") {
|
|
67
|
+
throw new Error("Token cannot be empty");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const config = this.loadConfig();
|
|
71
|
+
config.github_token = token.trim();
|
|
72
|
+
config.token_saved_at = new Date().toISOString();
|
|
73
|
+
this.saveConfig(config);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets the GitHub token from config
|
|
78
|
+
* @returns {string|null} - The token or null if not set
|
|
79
|
+
*/
|
|
80
|
+
getToken() {
|
|
81
|
+
const config = this.loadConfig();
|
|
82
|
+
return config.github_token || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Removes the GitHub token from config
|
|
87
|
+
* @returns {boolean} - True if token was removed, false if no token existed
|
|
88
|
+
*/
|
|
89
|
+
removeToken() {
|
|
90
|
+
const config = this.loadConfig();
|
|
91
|
+
if (config.github_token) {
|
|
92
|
+
delete config.github_token;
|
|
93
|
+
delete config.token_saved_at;
|
|
94
|
+
this.saveConfig(config);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Gets the token with priority: CLI flag > env var > stored config
|
|
102
|
+
* @param {string|null} cliToken - Token passed via CLI flag
|
|
103
|
+
* @returns {string|null} - The resolved token or null
|
|
104
|
+
*/
|
|
105
|
+
resolveToken(cliToken = null) {
|
|
106
|
+
// Priority 1: CLI flag
|
|
107
|
+
if (cliToken) {
|
|
108
|
+
return cliToken;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Priority 2: Environment variable
|
|
112
|
+
const envToken = process.env.GIT_RIPPER_TOKEN;
|
|
113
|
+
if (envToken) {
|
|
114
|
+
return envToken;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Priority 3: Stored config
|
|
118
|
+
return this.getToken();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Masks a token for display (shows first 4 and last 4 characters)
|
|
123
|
+
* @param {string} token - The token to mask
|
|
124
|
+
* @returns {string} - The masked token
|
|
125
|
+
*/
|
|
126
|
+
maskToken(token) {
|
|
127
|
+
if (!token || token.length < 12) {
|
|
128
|
+
return "****";
|
|
129
|
+
}
|
|
130
|
+
return `${token.substring(0, 4)}${"*".repeat(token.length - 8)}${token.substring(token.length - 4)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Shows the current configuration
|
|
135
|
+
* @returns {Object} - Configuration summary
|
|
136
|
+
*/
|
|
137
|
+
showConfig() {
|
|
138
|
+
const config = this.loadConfig();
|
|
139
|
+
const envToken = process.env.GIT_RIPPER_TOKEN;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
hasStoredToken: !!config.github_token,
|
|
143
|
+
maskedToken:
|
|
144
|
+
config.github_token ? this.maskToken(config.github_token) : null,
|
|
145
|
+
tokenSavedAt: config.token_saved_at || null,
|
|
146
|
+
hasEnvToken: !!envToken,
|
|
147
|
+
configPath: this.configFile,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Gets the path to the config file
|
|
153
|
+
* @returns {string} - The config file path
|
|
154
|
+
*/
|
|
155
|
+
getConfigPath() {
|
|
156
|
+
return this.configFile;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { ConfigManager };
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "./downloader.js";
|
|
8
8
|
import { downloadAndArchive } from "./archiver.js";
|
|
9
9
|
import { ResumeManager } from "./resumeManager.js";
|
|
10
|
+
import { ConfigManager } from "./configManager.js";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { dirname, join, resolve, basename } from "node:path";
|
|
12
13
|
import fs from "node:fs";
|
|
@@ -61,6 +62,90 @@ const validateOutputDirectory = (outputDir) => {
|
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
const initializeCLI = () => {
|
|
65
|
+
const configManager = new ConfigManager();
|
|
66
|
+
|
|
67
|
+
// Add config subcommand
|
|
68
|
+
const configCmd = program
|
|
69
|
+
.command("config")
|
|
70
|
+
.description("Manage git-ripper configuration");
|
|
71
|
+
|
|
72
|
+
configCmd
|
|
73
|
+
.command("set-token <token>")
|
|
74
|
+
.description("Save GitHub Personal Access Token locally")
|
|
75
|
+
.action((token) => {
|
|
76
|
+
try {
|
|
77
|
+
configManager.setToken(token);
|
|
78
|
+
console.log(chalk.green("Token saved successfully!"));
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.yellow(
|
|
81
|
+
"Security note: Keep your config file secure. Location: " +
|
|
82
|
+
configManager.getConfigPath(),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
configCmd
|
|
92
|
+
.command("get-token")
|
|
93
|
+
.description("Show saved GitHub token (masked)")
|
|
94
|
+
.action(() => {
|
|
95
|
+
const token = configManager.getToken();
|
|
96
|
+
if (token) {
|
|
97
|
+
console.log(`Saved token: ${configManager.maskToken(token)}`);
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.yellow("No token saved."));
|
|
100
|
+
console.log(
|
|
101
|
+
"Use 'git-ripper config set-token <token>' to save a token.",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
configCmd
|
|
107
|
+
.command("remove-token")
|
|
108
|
+
.description("Remove saved GitHub token")
|
|
109
|
+
.action(() => {
|
|
110
|
+
if (configManager.removeToken()) {
|
|
111
|
+
console.log(chalk.green("Token removed successfully."));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.yellow("No token was saved."));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
configCmd
|
|
118
|
+
.command("show")
|
|
119
|
+
.description("Show current configuration")
|
|
120
|
+
.action(() => {
|
|
121
|
+
const config = configManager.showConfig();
|
|
122
|
+
console.log(chalk.cyan("\nGit-ripper Configuration:"));
|
|
123
|
+
console.log(` Config file: ${config.configPath}`);
|
|
124
|
+
console.log(
|
|
125
|
+
` Stored token: ${
|
|
126
|
+
config.hasStoredToken ?
|
|
127
|
+
chalk.green(config.maskedToken)
|
|
128
|
+
: chalk.gray("Not set")
|
|
129
|
+
}`,
|
|
130
|
+
);
|
|
131
|
+
if (config.tokenSavedAt) {
|
|
132
|
+
console.log(
|
|
133
|
+
` Token saved: ${new Date(config.tokenSavedAt).toLocaleString()}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
console.log(
|
|
137
|
+
` Environment token (GIT_RIPPER_TOKEN): ${
|
|
138
|
+
config.hasEnvToken ? chalk.green("Set") : chalk.gray("Not set")
|
|
139
|
+
}`,
|
|
140
|
+
);
|
|
141
|
+
console.log(
|
|
142
|
+
chalk.gray(
|
|
143
|
+
"\n Token priority: --gh-token flag > GIT_RIPPER_TOKEN env > stored token",
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
console.log();
|
|
147
|
+
});
|
|
148
|
+
|
|
64
149
|
program
|
|
65
150
|
.version(packageJson.version)
|
|
66
151
|
.description("Clone specific folders from GitHub repositories")
|
|
@@ -187,11 +272,21 @@ const initializeCLI = () => {
|
|
|
187
272
|
const archiveName =
|
|
188
273
|
typeof options.zip === "string" ? options.zip : null;
|
|
189
274
|
|
|
275
|
+
// Resolve token with priority: CLI flag > env var > stored config
|
|
276
|
+
const resolvedToken = configManager.resolveToken(options.ghToken);
|
|
277
|
+
if (resolvedToken && !options.ghToken) {
|
|
278
|
+
const source =
|
|
279
|
+
process.env.GIT_RIPPER_TOKEN ?
|
|
280
|
+
"environment variable"
|
|
281
|
+
: "saved config";
|
|
282
|
+
console.log(chalk.gray(`Using GitHub token from ${source}`));
|
|
283
|
+
}
|
|
284
|
+
|
|
190
285
|
// Prepare download options
|
|
191
286
|
const downloadOptions = {
|
|
192
287
|
resume: options.resume !== false, // Default to true unless --no-resume
|
|
193
288
|
forceRestart: options.forceRestart || false,
|
|
194
|
-
token:
|
|
289
|
+
token: resolvedToken,
|
|
195
290
|
};
|
|
196
291
|
|
|
197
292
|
let operationType = createArchive ? "archive" : "download";
|
|
@@ -217,7 +312,7 @@ const initializeCLI = () => {
|
|
|
217
312
|
parsedUrl.branch,
|
|
218
313
|
parsedUrl.folderPath,
|
|
219
314
|
outputPath,
|
|
220
|
-
|
|
315
|
+
resolvedToken,
|
|
221
316
|
);
|
|
222
317
|
} else {
|
|
223
318
|
console.log(`Downloading folder to: ${options.output}`);
|