git0 0.1.6 → 0.2.1
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/gg.js +179 -141
- package/package.json +7 -5
- package/readme.md +5 -2
package/gg.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
-
import
|
|
6
|
+
import grab from 'grab-api.js';
|
|
7
7
|
import * as tar from 'tar'
|
|
8
8
|
import { pipeline } from 'stream/promises';
|
|
9
9
|
import fs from 'fs';
|
|
@@ -15,24 +15,47 @@ const GITHUB_API = 'https://api.github.com/search/repositories';
|
|
|
15
15
|
const RESULTS_PER_PAGE = 10;
|
|
16
16
|
const TOKEN = process.env.GITHUB_TOKEN;
|
|
17
17
|
|
|
18
|
+
const githubHelpUrl = 'https://github.com/settings/personal-access-tokens/new'
|
|
19
|
+
grab('', {
|
|
20
|
+
setDefaults: true,
|
|
21
|
+
debug: false,
|
|
22
|
+
onError: (error) => {
|
|
23
|
+
if (error.includes('403')) {
|
|
24
|
+
log(chalk.red('Rate limit exceeded. Please set env var GITHUB_TOKEN. Help:\n' + githubHelpUrl));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function printLogo() {
|
|
33
|
+
console.log(chalk.cyan(` ___
|
|
34
|
+
__ _(_)‾|_ / _ \\
|
|
35
|
+
/ _ | | __| | | |
|
|
36
|
+
| (_| | | |_| |_| |
|
|
37
|
+
\\__, |_|\\__|\\___/
|
|
38
|
+
|___/`))
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
// Detect current operating system and architecture
|
|
19
42
|
function getCurrentPlatform() {
|
|
20
43
|
const platform = os.platform();
|
|
21
44
|
const arch = os.arch();
|
|
22
|
-
|
|
45
|
+
|
|
23
46
|
const platformMap = {
|
|
24
47
|
'win32': 'windows',
|
|
25
48
|
'darwin': 'macos',
|
|
26
49
|
'linux': 'linux'
|
|
27
50
|
};
|
|
28
|
-
|
|
51
|
+
|
|
29
52
|
const archMap = {
|
|
30
53
|
'x64': 'x86_64',
|
|
31
54
|
'arm64': 'arm64',
|
|
32
55
|
'arm': 'arm',
|
|
33
56
|
'ia32': 'i386'
|
|
34
57
|
};
|
|
35
|
-
|
|
58
|
+
|
|
36
59
|
return {
|
|
37
60
|
os: platformMap[platform] || platform,
|
|
38
61
|
arch: archMap[arch] || arch,
|
|
@@ -44,11 +67,13 @@ function getCurrentPlatform() {
|
|
|
44
67
|
// Get releases for a repository
|
|
45
68
|
async function getRepositoryReleases(owner, repo) {
|
|
46
69
|
try {
|
|
47
|
-
const response = await
|
|
70
|
+
const response = await grab(`https://api.github.com/repos/${owner}/${repo}/releases`, {
|
|
48
71
|
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
|
|
49
|
-
|
|
72
|
+
per_page: 5 // Get latest 5 releases
|
|
73
|
+
|
|
50
74
|
});
|
|
51
|
-
|
|
75
|
+
|
|
76
|
+
return response;
|
|
52
77
|
} catch (error) {
|
|
53
78
|
return [];
|
|
54
79
|
}
|
|
@@ -61,28 +86,28 @@ function categorizeReleasesByPlatform(releases) {
|
|
|
61
86
|
macos: ['mac', 'macos', 'darwin', 'osx', '.dmg', '.pkg'],
|
|
62
87
|
linux: ['linux', 'ubuntu', 'debian', '.deb', '.rpm', '.tar.gz', '.AppImage']
|
|
63
88
|
};
|
|
64
|
-
|
|
89
|
+
|
|
65
90
|
const archKeywords = {
|
|
66
91
|
x86_64: ['x86_64', 'x64', 'amd64', '64'],
|
|
67
92
|
arm64: ['arm64', 'aarch64'],
|
|
68
93
|
arm: ['arm', 'armv7'],
|
|
69
94
|
i386: ['i386', 'x86', '32']
|
|
70
95
|
};
|
|
71
|
-
|
|
96
|
+
|
|
72
97
|
const categorizedReleases = [];
|
|
73
|
-
|
|
74
|
-
releases.forEach(release => {
|
|
98
|
+
|
|
99
|
+
Object.entries(releases).forEach(([key, release]) => {
|
|
75
100
|
const platformAssets = {
|
|
76
101
|
windows: [],
|
|
77
102
|
macos: [],
|
|
78
103
|
linux: [],
|
|
79
104
|
universal: []
|
|
80
105
|
};
|
|
81
|
-
|
|
106
|
+
|
|
82
107
|
release.assets.forEach(asset => {
|
|
83
108
|
const name = asset.name.toLowerCase();
|
|
84
109
|
let categorized = false;
|
|
85
|
-
|
|
110
|
+
|
|
86
111
|
// Check each platform
|
|
87
112
|
Object.entries(platformKeywords).forEach(([platform, keywords]) => {
|
|
88
113
|
if (keywords.some(keyword => name.includes(keyword.toLowerCase()))) {
|
|
@@ -93,7 +118,7 @@ function categorizeReleasesByPlatform(releases) {
|
|
|
93
118
|
detectedArch = arch;
|
|
94
119
|
}
|
|
95
120
|
});
|
|
96
|
-
|
|
121
|
+
|
|
97
122
|
platformAssets[platform].push({
|
|
98
123
|
...asset,
|
|
99
124
|
detectedArch,
|
|
@@ -102,10 +127,10 @@ function categorizeReleasesByPlatform(releases) {
|
|
|
102
127
|
categorized = true;
|
|
103
128
|
}
|
|
104
129
|
});
|
|
105
|
-
|
|
130
|
+
|
|
106
131
|
// If not categorized, check for universal binaries
|
|
107
|
-
if (!categorized && (name.includes('universal') || name.includes('all') ||
|
|
108
|
-
|
|
132
|
+
if (!categorized && (name.includes('universal') || name.includes('all') ||
|
|
133
|
+
(!name.includes('win') && !name.includes('mac') && !name.includes('linux')))) {
|
|
109
134
|
platformAssets.universal.push({
|
|
110
135
|
...asset,
|
|
111
136
|
detectedArch: 'universal',
|
|
@@ -113,7 +138,7 @@ function categorizeReleasesByPlatform(releases) {
|
|
|
113
138
|
});
|
|
114
139
|
}
|
|
115
140
|
});
|
|
116
|
-
|
|
141
|
+
|
|
117
142
|
// Only include releases that have assets
|
|
118
143
|
const hasAssets = Object.values(platformAssets).some(assets => assets.length > 0);
|
|
119
144
|
if (hasAssets) {
|
|
@@ -123,52 +148,53 @@ function categorizeReleasesByPlatform(releases) {
|
|
|
123
148
|
});
|
|
124
149
|
}
|
|
125
150
|
});
|
|
126
|
-
|
|
151
|
+
|
|
127
152
|
return categorizedReleases;
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
// Filter releases for current platform (for compatibility indicator)
|
|
131
156
|
function filterReleasesByPlatform(releases, currentPlatform) {
|
|
132
157
|
const categorized = categorizeReleasesByPlatform(releases);
|
|
133
|
-
return categorized.filter(release =>
|
|
158
|
+
return categorized.filter(release =>
|
|
134
159
|
release.platformAssets[currentPlatform.os].length > 0 ||
|
|
135
160
|
release.platformAssets.universal.length > 0
|
|
136
161
|
);
|
|
137
162
|
}
|
|
138
163
|
|
|
164
|
+
|
|
139
165
|
// Download and install package
|
|
140
166
|
async function downloadPackage(asset, targetDir) {
|
|
141
167
|
const fileName = asset.name;
|
|
142
168
|
const downloadPath = path.join(targetDir, fileName);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
|
|
170
|
+
log(chalk.blue(`📦 Downloading ${fileName}...`));
|
|
171
|
+
printLogo()
|
|
146
172
|
try {
|
|
147
|
-
const response = await
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
const response = await grab(asset.browser_download_url, {
|
|
174
|
+
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
|
|
175
|
+
onStream: async (res) => {
|
|
176
|
+
const nodeStream = (await import('stream'))?.Readable.fromWeb(res);
|
|
177
|
+
await new Promise((resolve, reject) => {
|
|
178
|
+
nodeStream.pipe(fs.createWriteStream(downloadPath)).on('finish', resolve).on('error', reject);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
152
181
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
|
|
158
|
-
|
|
182
|
+
|
|
183
|
+
log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
|
|
184
|
+
|
|
159
185
|
// Try to make executable if it's a binary
|
|
160
186
|
if (process.platform !== 'win32' && !fileName.includes('.')) {
|
|
161
187
|
try {
|
|
162
188
|
fs.chmodSync(downloadPath, '755');
|
|
163
|
-
|
|
189
|
+
log(chalk.green(`✅ Made ${fileName} executable`));
|
|
164
190
|
} catch (error) {
|
|
165
|
-
|
|
191
|
+
log(chalk.yellow(`⚠️ Could not make ${fileName} executable`));
|
|
166
192
|
}
|
|
167
193
|
}
|
|
168
|
-
|
|
194
|
+
|
|
169
195
|
// Provide installation instructions
|
|
170
196
|
provideInstallationInstructions(downloadPath, asset);
|
|
171
|
-
|
|
197
|
+
|
|
172
198
|
} catch (error) {
|
|
173
199
|
console.error(chalk.red(`❌ Failed to download ${fileName}:`), error.message);
|
|
174
200
|
}
|
|
@@ -178,40 +204,38 @@ async function downloadPackage(asset, targetDir) {
|
|
|
178
204
|
function provideInstallationInstructions(filePath, asset) {
|
|
179
205
|
const fileName = asset.name;
|
|
180
206
|
const platform = getCurrentPlatform();
|
|
181
|
-
|
|
182
|
-
console.log(chalk.cyan('\n📋 Installation Instructions:'));
|
|
183
|
-
|
|
207
|
+
|
|
184
208
|
if (platform.platform === 'win32') {
|
|
185
209
|
if (fileName.endsWith('.exe')) {
|
|
186
|
-
|
|
187
|
-
|
|
210
|
+
log(chalk.white(' Run the executable:'));
|
|
211
|
+
log(chalk.gray(` ${filePath}`));
|
|
188
212
|
} else if (fileName.endsWith('.msi')) {
|
|
189
|
-
|
|
190
|
-
|
|
213
|
+
log(chalk.white(' Install the MSI package:'));
|
|
214
|
+
log(chalk.gray(` msiexec /i "${filePath}"`));
|
|
191
215
|
}
|
|
192
216
|
} else if (platform.platform === 'darwin') {
|
|
193
217
|
if (fileName.endsWith('.dmg')) {
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
log(chalk.white(' Mount and install the DMG:'));
|
|
219
|
+
log(chalk.gray(` open "${filePath}"`));
|
|
196
220
|
} else if (fileName.endsWith('.pkg')) {
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
log(chalk.white(' Install the package:'));
|
|
222
|
+
log(chalk.gray(` sudo installer -pkg "${filePath}" -target /`));
|
|
199
223
|
}
|
|
200
224
|
} else {
|
|
201
225
|
if (fileName.endsWith('.deb')) {
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
log(chalk.white(' Install the DEB package:'));
|
|
227
|
+
log(chalk.gray(` sudo dpkg -i "${filePath}"`));
|
|
204
228
|
} else if (fileName.endsWith('.rpm')) {
|
|
205
|
-
|
|
206
|
-
|
|
229
|
+
log(chalk.white(' Install the RPM package:'));
|
|
230
|
+
log(chalk.gray(` sudo rpm -i "${filePath}"`));
|
|
207
231
|
} else if (fileName.endsWith('.AppImage')) {
|
|
208
|
-
|
|
209
|
-
|
|
232
|
+
log(chalk.white(' Run the AppImage:'));
|
|
233
|
+
log(chalk.gray(` chmod +x "${filePath}" && "${filePath}"`));
|
|
210
234
|
} else if (!fileName.includes('.')) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
log(chalk.white(' Binary is ready to use:'));
|
|
236
|
+
log(chalk.gray(` "${filePath}"`));
|
|
237
|
+
log(chalk.white(' Consider moving to PATH:'));
|
|
238
|
+
log(chalk.gray(` sudo mv "${filePath}" /usr/local/bin/`));
|
|
215
239
|
}
|
|
216
240
|
}
|
|
217
241
|
}
|
|
@@ -220,59 +244,60 @@ function provideInstallationInstructions(filePath, asset) {
|
|
|
220
244
|
async function showPackageMenu(selectedRepo) {
|
|
221
245
|
const currentPlatform = getCurrentPlatform();
|
|
222
246
|
const releaseChoices = [];
|
|
223
|
-
|
|
247
|
+
const limitReleases = 2;
|
|
248
|
+
|
|
224
249
|
// Add section headers and organize by platform
|
|
225
|
-
selectedRepo.allReleases.forEach(release => {
|
|
250
|
+
selectedRepo.allReleases.slice(0, limitReleases).forEach(release => {
|
|
226
251
|
const platforms = ['windows', 'macos', 'linux', 'universal'];
|
|
227
|
-
|
|
252
|
+
|
|
228
253
|
platforms.forEach(platform => {
|
|
229
254
|
const assets = release.platformAssets[platform];
|
|
230
255
|
if (assets.length > 0) {
|
|
231
256
|
// Add platform header
|
|
232
257
|
const platformEmoji = {
|
|
233
258
|
windows: '🪟',
|
|
234
|
-
macos: '🍎',
|
|
259
|
+
macos: '🍎',
|
|
235
260
|
linux: '🐧',
|
|
236
261
|
universal: '🌐'
|
|
237
262
|
};
|
|
238
|
-
|
|
263
|
+
|
|
239
264
|
const platformName = {
|
|
240
265
|
windows: 'Windows',
|
|
241
266
|
macos: 'macOS',
|
|
242
267
|
linux: 'Linux',
|
|
243
268
|
universal: 'Universal'
|
|
244
269
|
};
|
|
245
|
-
|
|
270
|
+
|
|
246
271
|
const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
|
|
247
|
-
const platformHeader = isCurrentPlatform
|
|
272
|
+
const platformHeader = isCurrentPlatform
|
|
248
273
|
? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
|
|
249
274
|
: chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
|
|
250
|
-
|
|
275
|
+
|
|
251
276
|
// Add separator if not first platform in this release
|
|
252
|
-
const needsSeparator = releaseChoices.length > 0 &&
|
|
277
|
+
const needsSeparator = releaseChoices.length > 0 &&
|
|
253
278
|
!releaseChoices[releaseChoices.length - 1].name.includes('────');
|
|
254
|
-
|
|
279
|
+
|
|
255
280
|
if (needsSeparator) {
|
|
256
281
|
releaseChoices.push({
|
|
257
282
|
name: chalk.gray('────────────────────────────────'),
|
|
258
283
|
disabled: true
|
|
259
284
|
});
|
|
260
285
|
}
|
|
261
|
-
|
|
286
|
+
|
|
262
287
|
releaseChoices.push({
|
|
263
288
|
name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
|
|
264
289
|
disabled: true
|
|
265
290
|
});
|
|
266
|
-
|
|
291
|
+
|
|
267
292
|
// Add assets for this platform
|
|
268
293
|
assets.forEach(asset => {
|
|
269
294
|
const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
|
|
270
|
-
const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
|
|
271
|
-
? chalk.cyan(`[${asset.detectedArch}]`)
|
|
295
|
+
const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
|
|
296
|
+
? chalk.cyan(`[${asset.detectedArch}]`)
|
|
272
297
|
: '';
|
|
273
|
-
|
|
298
|
+
|
|
274
299
|
const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
|
|
275
|
-
|
|
300
|
+
|
|
276
301
|
releaseChoices.push({
|
|
277
302
|
name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
|
|
278
303
|
value: { release, asset }
|
|
@@ -283,7 +308,7 @@ async function showPackageMenu(selectedRepo) {
|
|
|
283
308
|
});
|
|
284
309
|
|
|
285
310
|
if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
|
|
286
|
-
|
|
311
|
+
log(chalk.yellow('No packages found for download.'));
|
|
287
312
|
return;
|
|
288
313
|
}
|
|
289
314
|
|
|
@@ -295,7 +320,7 @@ async function showPackageMenu(selectedRepo) {
|
|
|
295
320
|
pageSize: 15
|
|
296
321
|
});
|
|
297
322
|
|
|
298
|
-
const downloadDir = path.resolve(process.cwd()
|
|
323
|
+
const downloadDir = path.resolve(process.cwd());
|
|
299
324
|
fs.mkdirSync(downloadDir, { recursive: true });
|
|
300
325
|
await downloadPackage(selectedPackage.asset, downloadDir);
|
|
301
326
|
}
|
|
@@ -328,7 +353,7 @@ function getIdeCommand() {
|
|
|
328
353
|
export function openInIDE(targetDir) {
|
|
329
354
|
const ide = getIdeCommand();
|
|
330
355
|
if (!ide) {
|
|
331
|
-
|
|
356
|
+
log(chalk.yellow('⚠️ No supported IDE found'));
|
|
332
357
|
return;
|
|
333
358
|
}
|
|
334
359
|
|
|
@@ -343,17 +368,26 @@ export function openInIDE(targetDir) {
|
|
|
343
368
|
shell: process.platform === 'win32'
|
|
344
369
|
}).unref();
|
|
345
370
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
: ['./README.md'];
|
|
349
|
-
|
|
350
|
-
spawn(ide.cmd, args2, {
|
|
351
|
-
detached: true,
|
|
352
|
-
stdio: 'ignore',
|
|
353
|
-
shell: process.platform === 'win32'
|
|
354
|
-
}).unref();
|
|
371
|
+
// open readme after 3 seconds
|
|
372
|
+
setTimeout(() => {
|
|
355
373
|
|
|
356
|
-
|
|
374
|
+
const readme = fs.existsSync('./readme.md') ? './readme.md' :
|
|
375
|
+
fs.existsSync('./Readme.md') ? './Readme.md' :
|
|
376
|
+
fs.existsSync('./README.md') ? './README.md' :
|
|
377
|
+
fs.existsSync('./package.json') ? './package.json' :
|
|
378
|
+
null;
|
|
379
|
+
|
|
380
|
+
if (readme)
|
|
381
|
+
spawn(ide.cmd, ide.cmd === 'code-server'
|
|
382
|
+
? [readme, '--open']
|
|
383
|
+
: [readme], {
|
|
384
|
+
detached: true,
|
|
385
|
+
stdio: 'ignore',
|
|
386
|
+
shell: process.platform === 'win32'
|
|
387
|
+
}).unref();
|
|
388
|
+
}, 3000);
|
|
389
|
+
|
|
390
|
+
log(chalk.green(`🚀 Opening ${path.basename(targetDir)} in ${ide.name}`));
|
|
357
391
|
} catch (error) {
|
|
358
392
|
console.error(chalk.red(`❌ Failed to open ${ide.name}:`), error.message);
|
|
359
393
|
}
|
|
@@ -413,14 +447,14 @@ export async function installDependencies(targetDir) {
|
|
|
413
447
|
// Run detections and installations
|
|
414
448
|
Object.entries(detectors).forEach(([name, check]) => {
|
|
415
449
|
if (check()) {
|
|
416
|
-
|
|
450
|
+
// log(chalk.yellow(`⚙️ Detected ${name} project`));
|
|
417
451
|
installers[name]?.();
|
|
418
452
|
}
|
|
419
453
|
});
|
|
420
454
|
}
|
|
421
455
|
|
|
422
456
|
function runCommand(cmd) {
|
|
423
|
-
|
|
457
|
+
log(chalk.cyan(`🚀 Running: ${cmd}`));
|
|
424
458
|
try {
|
|
425
459
|
execSync(cmd, { stdio: 'inherit' });
|
|
426
460
|
} catch (error) {
|
|
@@ -430,24 +464,26 @@ function runCommand(cmd) {
|
|
|
430
464
|
|
|
431
465
|
export async function searchRepositories(query) {
|
|
432
466
|
try {
|
|
433
|
-
const response = await
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
},
|
|
467
|
+
const response = await grab(GITHUB_API, {
|
|
468
|
+
q: `${query} in:name`,
|
|
469
|
+
sort: 'stars',
|
|
470
|
+
order: 'desc',
|
|
471
|
+
per_page: RESULTS_PER_PAGE,
|
|
472
|
+
debug: false,
|
|
440
473
|
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
441
474
|
});
|
|
442
|
-
|
|
475
|
+
|
|
476
|
+
if (response.error || !response.items)
|
|
477
|
+
return log("No response")
|
|
478
|
+
|
|
443
479
|
// Check for releases for each repository
|
|
444
480
|
const reposWithReleases = await Promise.all(
|
|
445
|
-
response.
|
|
481
|
+
response.items.map(async (repo) => {
|
|
446
482
|
const releases = await getRepositoryReleases(repo.owner.login, repo.name);
|
|
447
483
|
const currentPlatform = getCurrentPlatform();
|
|
448
484
|
const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
|
|
449
485
|
const categorizedReleases = categorizeReleasesByPlatform(releases);
|
|
450
|
-
|
|
486
|
+
|
|
451
487
|
return {
|
|
452
488
|
...repo,
|
|
453
489
|
hasReleases: releases.length > 0,
|
|
@@ -457,49 +493,50 @@ export async function searchRepositories(query) {
|
|
|
457
493
|
};
|
|
458
494
|
})
|
|
459
495
|
);
|
|
460
|
-
|
|
496
|
+
|
|
461
497
|
return reposWithReleases;
|
|
462
498
|
} catch (error) {
|
|
463
|
-
console.error(chalk.red('Search failed:'), error.
|
|
499
|
+
console.error(chalk.red('Search failed:'), error.message);
|
|
464
500
|
process.exit(1);
|
|
465
501
|
}
|
|
466
502
|
}
|
|
467
503
|
|
|
468
504
|
export async function downloadRepo(repo) {
|
|
469
|
-
const parsed = gitUrlParse(
|
|
505
|
+
const parsed = gitUrlParse(repo);
|
|
470
506
|
const defaultDir = path.resolve(process.cwd(), parsed.name);
|
|
471
507
|
const extractPath = getAvailableDirectoryName(defaultDir);
|
|
472
508
|
|
|
509
|
+
// if it picks up a larger owner name, slice to the last part
|
|
510
|
+
if (parsed.owner.includes('/'))
|
|
511
|
+
parsed.owner = parsed.owner.split('/').slice(-1).join('');
|
|
512
|
+
|
|
473
513
|
fs.mkdirSync(extractPath, { recursive: true });
|
|
474
|
-
|
|
475
|
-
|
|
514
|
+
log(chalk.blue(`📦 Downloading ${parsed.name} into ${path.basename(extractPath)}...`));
|
|
515
|
+
printLogo()
|
|
516
|
+
|
|
517
|
+
let url = `https://api.github.com/repos/${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'master'}`;
|
|
476
518
|
|
|
477
519
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
responseType: 'stream',
|
|
492
|
-
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
493
|
-
});
|
|
520
|
+
|
|
521
|
+
var params = {
|
|
522
|
+
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
|
|
523
|
+
onStream: async (res) => {
|
|
524
|
+
|
|
525
|
+
const nodeStream = (await import('stream'))?.Readable.fromWeb(res);
|
|
526
|
+
await new Promise((resolve, reject) => {
|
|
527
|
+
nodeStream.pipe(tar.x({
|
|
528
|
+
C: extractPath,
|
|
529
|
+
strip: 1
|
|
530
|
+
})).on('finish', resolve).on('error', reject);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
494
533
|
}
|
|
495
534
|
|
|
496
|
-
await
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
})
|
|
502
|
-
);
|
|
535
|
+
var response = await grab(url, params);
|
|
536
|
+
|
|
537
|
+
if (response.error)
|
|
538
|
+
response = await grab(url.replace("/master", "/main"), params);
|
|
539
|
+
|
|
503
540
|
|
|
504
541
|
setTimeout(() => {
|
|
505
542
|
openInIDE(extractPath);
|
|
@@ -523,16 +560,16 @@ function getAvailableDirectoryName(basePath) {
|
|
|
523
560
|
}
|
|
524
561
|
|
|
525
562
|
async function main() {
|
|
563
|
+
printLogo()
|
|
526
564
|
const args = process.argv.slice(2);
|
|
527
565
|
if (!args.length) {
|
|
528
|
-
|
|
566
|
+
log(chalk.yellow('Usage: gg <search-query>'));
|
|
529
567
|
process.exit(1);
|
|
530
568
|
}
|
|
531
569
|
|
|
532
570
|
const query = args.join(' ');
|
|
533
571
|
const currentPlatform = getCurrentPlatform();
|
|
534
|
-
|
|
535
|
-
console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
|
|
572
|
+
|
|
536
573
|
|
|
537
574
|
let repoUrl = null;
|
|
538
575
|
|
|
@@ -553,22 +590,23 @@ async function main() {
|
|
|
553
590
|
|
|
554
591
|
const results = await searchRepositories(query);
|
|
555
592
|
|
|
556
|
-
if (!results.length) {
|
|
557
|
-
|
|
593
|
+
if (!results || !results.length) {
|
|
594
|
+
log(chalk.yellow('No repositories found'));
|
|
558
595
|
return;
|
|
559
596
|
}
|
|
560
597
|
|
|
598
|
+
|
|
561
599
|
const { selectedRepo } = await inquirer.prompt({
|
|
562
600
|
type: 'list',
|
|
563
601
|
name: 'selectedRepo',
|
|
564
602
|
message: 'Select a repository to download:',
|
|
565
603
|
choices: results.map(repo => {
|
|
566
|
-
const packageInfo = repo.hasCompatibleReleases
|
|
567
|
-
? chalk.green(' 📦 Packages available')
|
|
568
|
-
: repo.hasReleases
|
|
569
|
-
? chalk.yellow(' 📦 Packages (other platforms)')
|
|
604
|
+
const packageInfo = repo.hasCompatibleReleases
|
|
605
|
+
? chalk.green(' 📦 Packages available')
|
|
606
|
+
: repo.hasReleases
|
|
607
|
+
? chalk.yellow(' 📦 Packages (other platforms)')
|
|
570
608
|
: '';
|
|
571
|
-
|
|
609
|
+
|
|
572
610
|
return {
|
|
573
611
|
name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
|
|
574
612
|
${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
|
|
@@ -578,7 +616,7 @@ async function main() {
|
|
|
578
616
|
});
|
|
579
617
|
|
|
580
618
|
// If the selected repo has any releases, show download options
|
|
581
|
-
if (selectedRepo.hasReleases) {
|
|
619
|
+
if (selectedRepo.hasReleases || selectedRepo.hasCompatibleReleases) {
|
|
582
620
|
const { downloadChoice } = await inquirer.prompt({
|
|
583
621
|
type: 'list',
|
|
584
622
|
name: 'downloadChoice',
|
|
@@ -595,11 +633,11 @@ async function main() {
|
|
|
595
633
|
}
|
|
596
634
|
|
|
597
635
|
if (downloadChoice === 'source' || downloadChoice === 'both') {
|
|
598
|
-
await downloadRepo(selectedRepo.
|
|
636
|
+
await downloadRepo(selectedRepo.url);
|
|
599
637
|
}
|
|
600
638
|
} else {
|
|
601
639
|
// No packages, just download source
|
|
602
|
-
await downloadRepo(selectedRepo.
|
|
640
|
+
await downloadRepo(selectedRepo.url);
|
|
603
641
|
}
|
|
604
642
|
}
|
|
605
643
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git0",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A CLI manager for downloading GitHub repos.",
|
|
5
5
|
"author": "vtempest",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
"download"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"axios": "^1.
|
|
14
|
-
"chalk": "^5.
|
|
13
|
+
"axios": "^1.9.0",
|
|
14
|
+
"chalk": "^5.4.1",
|
|
15
15
|
"git-url-parse": "^16.1.0",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"grab-api.js": "^0.9.120",
|
|
17
|
+
"inquirer": "^12.6.3",
|
|
18
|
+
"streamable": "^0.6.0",
|
|
19
|
+
"tar": "^7.4.3"
|
|
18
20
|
},
|
|
19
21
|
"scripts": {
|
|
20
22
|
"demo": "bun gg.js react template",
|
package/readme.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
# Git0: GitHub Zero-Step Repo Downloader
|
|
30
30
|
|
|
31
|
-
A fast and smart CLI tool to search, download, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
|
|
31
|
+
A fast and smart CLI tool to search, download source & releases for your system, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
## 🚀 Installation
|
|
@@ -41,12 +41,15 @@ npm install -g git0
|
|
|
41
41
|
bun install -g git0
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+

|
|
44
45
|

|
|
45
46
|
|
|
47
|
+
|
|
46
48
|
## ✨ Features
|
|
47
49
|
|
|
48
50
|
- **Search GitHub repositories** by name with fuzzy matching
|
|
49
51
|
- **Download repositories** directly from GitHub URLs or owner/repo shortcuts
|
|
52
|
+
- **Get Releases** instantly download latest release for your system or all systems
|
|
50
53
|
- **Automatic dependency detection** and installation for multiple project types
|
|
51
54
|
- **Smart IDE integration** - automatically opens projects in your preferred editor
|
|
52
55
|
- **Cross-platform support** - works on Windows, macOS, and Linux
|
|
@@ -98,7 +101,7 @@ GG automatically detects and opens projects in your preferred IDE:
|
|
|
98
101
|
|
|
99
102
|
### GitHub Token (Optional)
|
|
100
103
|
|
|
101
|
-
For higher API rate limits, set your GitHub token:
|
|
104
|
+
For higher API rate limits, set [your GitHub token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token):
|
|
102
105
|
|
|
103
106
|
```bash
|
|
104
107
|
export GITHUB_TOKEN=your_github_token_here
|