git0 0.1.5 → 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 +246 -206
- 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,44 +204,127 @@ 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
|
}
|
|
218
242
|
|
|
243
|
+
// Show package selection menu organized by platform
|
|
244
|
+
async function showPackageMenu(selectedRepo) {
|
|
245
|
+
const currentPlatform = getCurrentPlatform();
|
|
246
|
+
const releaseChoices = [];
|
|
247
|
+
const limitReleases = 2;
|
|
248
|
+
|
|
249
|
+
// Add section headers and organize by platform
|
|
250
|
+
selectedRepo.allReleases.slice(0, limitReleases).forEach(release => {
|
|
251
|
+
const platforms = ['windows', 'macos', 'linux', 'universal'];
|
|
252
|
+
|
|
253
|
+
platforms.forEach(platform => {
|
|
254
|
+
const assets = release.platformAssets[platform];
|
|
255
|
+
if (assets.length > 0) {
|
|
256
|
+
// Add platform header
|
|
257
|
+
const platformEmoji = {
|
|
258
|
+
windows: '🪟',
|
|
259
|
+
macos: '🍎',
|
|
260
|
+
linux: '🐧',
|
|
261
|
+
universal: '🌐'
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const platformName = {
|
|
265
|
+
windows: 'Windows',
|
|
266
|
+
macos: 'macOS',
|
|
267
|
+
linux: 'Linux',
|
|
268
|
+
universal: 'Universal'
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
|
|
272
|
+
const platformHeader = isCurrentPlatform
|
|
273
|
+
? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
|
|
274
|
+
: chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
|
|
275
|
+
|
|
276
|
+
// Add separator if not first platform in this release
|
|
277
|
+
const needsSeparator = releaseChoices.length > 0 &&
|
|
278
|
+
!releaseChoices[releaseChoices.length - 1].name.includes('────');
|
|
279
|
+
|
|
280
|
+
if (needsSeparator) {
|
|
281
|
+
releaseChoices.push({
|
|
282
|
+
name: chalk.gray('────────────────────────────────'),
|
|
283
|
+
disabled: true
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
releaseChoices.push({
|
|
288
|
+
name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
|
|
289
|
+
disabled: true
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Add assets for this platform
|
|
293
|
+
assets.forEach(asset => {
|
|
294
|
+
const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
|
|
295
|
+
const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
|
|
296
|
+
? chalk.cyan(`[${asset.detectedArch}]`)
|
|
297
|
+
: '';
|
|
298
|
+
|
|
299
|
+
const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
|
|
300
|
+
|
|
301
|
+
releaseChoices.push({
|
|
302
|
+
name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
|
|
303
|
+
value: { release, asset }
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
|
|
311
|
+
log(chalk.yellow('No packages found for download.'));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { selectedPackage } = await inquirer.prompt({
|
|
316
|
+
type: 'list',
|
|
317
|
+
name: 'selectedPackage',
|
|
318
|
+
message: 'Select a package to download:',
|
|
319
|
+
choices: releaseChoices,
|
|
320
|
+
pageSize: 15
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const downloadDir = path.resolve(process.cwd());
|
|
324
|
+
fs.mkdirSync(downloadDir, { recursive: true });
|
|
325
|
+
await downloadPackage(selectedPackage.asset, downloadDir);
|
|
326
|
+
}
|
|
327
|
+
|
|
219
328
|
function getIdeCommand() {
|
|
220
329
|
const ides = [
|
|
221
330
|
{ name: 'Cursor', cmd: 'cursor' },
|
|
@@ -244,7 +353,7 @@ function getIdeCommand() {
|
|
|
244
353
|
export function openInIDE(targetDir) {
|
|
245
354
|
const ide = getIdeCommand();
|
|
246
355
|
if (!ide) {
|
|
247
|
-
|
|
356
|
+
log(chalk.yellow('⚠️ No supported IDE found'));
|
|
248
357
|
return;
|
|
249
358
|
}
|
|
250
359
|
|
|
@@ -259,17 +368,26 @@ export function openInIDE(targetDir) {
|
|
|
259
368
|
shell: process.platform === 'win32'
|
|
260
369
|
}).unref();
|
|
261
370
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
: ['./README.md'];
|
|
265
|
-
|
|
266
|
-
spawn(ide.cmd, args2, {
|
|
267
|
-
detached: true,
|
|
268
|
-
stdio: 'ignore',
|
|
269
|
-
shell: process.platform === 'win32'
|
|
270
|
-
}).unref();
|
|
371
|
+
// open readme after 3 seconds
|
|
372
|
+
setTimeout(() => {
|
|
271
373
|
|
|
272
|
-
|
|
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}`));
|
|
273
391
|
} catch (error) {
|
|
274
392
|
console.error(chalk.red(`❌ Failed to open ${ide.name}:`), error.message);
|
|
275
393
|
}
|
|
@@ -329,14 +447,14 @@ export async function installDependencies(targetDir) {
|
|
|
329
447
|
// Run detections and installations
|
|
330
448
|
Object.entries(detectors).forEach(([name, check]) => {
|
|
331
449
|
if (check()) {
|
|
332
|
-
|
|
450
|
+
// log(chalk.yellow(`⚙️ Detected ${name} project`));
|
|
333
451
|
installers[name]?.();
|
|
334
452
|
}
|
|
335
453
|
});
|
|
336
454
|
}
|
|
337
455
|
|
|
338
456
|
function runCommand(cmd) {
|
|
339
|
-
|
|
457
|
+
log(chalk.cyan(`🚀 Running: ${cmd}`));
|
|
340
458
|
try {
|
|
341
459
|
execSync(cmd, { stdio: 'inherit' });
|
|
342
460
|
} catch (error) {
|
|
@@ -346,24 +464,26 @@ function runCommand(cmd) {
|
|
|
346
464
|
|
|
347
465
|
export async function searchRepositories(query) {
|
|
348
466
|
try {
|
|
349
|
-
const response = await
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
},
|
|
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,
|
|
356
473
|
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
357
474
|
});
|
|
358
|
-
|
|
475
|
+
|
|
476
|
+
if (response.error || !response.items)
|
|
477
|
+
return log("No response")
|
|
478
|
+
|
|
359
479
|
// Check for releases for each repository
|
|
360
480
|
const reposWithReleases = await Promise.all(
|
|
361
|
-
response.
|
|
481
|
+
response.items.map(async (repo) => {
|
|
362
482
|
const releases = await getRepositoryReleases(repo.owner.login, repo.name);
|
|
363
483
|
const currentPlatform = getCurrentPlatform();
|
|
364
484
|
const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
|
|
365
485
|
const categorizedReleases = categorizeReleasesByPlatform(releases);
|
|
366
|
-
|
|
486
|
+
|
|
367
487
|
return {
|
|
368
488
|
...repo,
|
|
369
489
|
hasReleases: releases.length > 0,
|
|
@@ -373,49 +493,50 @@ export async function searchRepositories(query) {
|
|
|
373
493
|
};
|
|
374
494
|
})
|
|
375
495
|
);
|
|
376
|
-
|
|
496
|
+
|
|
377
497
|
return reposWithReleases;
|
|
378
498
|
} catch (error) {
|
|
379
|
-
console.error(chalk.red('Search failed:'), error.
|
|
499
|
+
console.error(chalk.red('Search failed:'), error.message);
|
|
380
500
|
process.exit(1);
|
|
381
501
|
}
|
|
382
502
|
}
|
|
383
503
|
|
|
384
504
|
export async function downloadRepo(repo) {
|
|
385
|
-
const parsed = gitUrlParse(
|
|
505
|
+
const parsed = gitUrlParse(repo);
|
|
386
506
|
const defaultDir = path.resolve(process.cwd(), parsed.name);
|
|
387
507
|
const extractPath = getAvailableDirectoryName(defaultDir);
|
|
388
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
|
+
|
|
389
513
|
fs.mkdirSync(extractPath, { recursive: true });
|
|
390
|
-
|
|
391
|
-
|
|
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'}`;
|
|
392
518
|
|
|
393
519
|
try {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
responseType: 'stream',
|
|
408
|
-
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
409
|
-
});
|
|
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
|
+
}
|
|
410
533
|
}
|
|
411
534
|
|
|
412
|
-
await
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
})
|
|
418
|
-
);
|
|
535
|
+
var response = await grab(url, params);
|
|
536
|
+
|
|
537
|
+
if (response.error)
|
|
538
|
+
response = await grab(url.replace("/master", "/main"), params);
|
|
539
|
+
|
|
419
540
|
|
|
420
541
|
setTimeout(() => {
|
|
421
542
|
openInIDE(extractPath);
|
|
@@ -427,89 +548,7 @@ export async function downloadRepo(repo) {
|
|
|
427
548
|
}
|
|
428
549
|
}
|
|
429
550
|
|
|
430
|
-
|
|
431
|
-
async function showPackageMenu(selectedRepo) {
|
|
432
|
-
const currentPlatform = getCurrentPlatform();
|
|
433
|
-
const releaseChoices = [];
|
|
434
|
-
|
|
435
|
-
// Add section headers and organize by platform
|
|
436
|
-
selectedRepo.allReleases.forEach(release => {
|
|
437
|
-
const platforms = ['windows', 'macos', 'linux', 'universal'];
|
|
438
|
-
|
|
439
|
-
platforms.forEach(platform => {
|
|
440
|
-
const assets = release.platformAssets[platform];
|
|
441
|
-
if (assets.length > 0) {
|
|
442
|
-
// Add platform header
|
|
443
|
-
const platformEmoji = {
|
|
444
|
-
windows: '🪟',
|
|
445
|
-
macos: '🍎',
|
|
446
|
-
linux: '🐧',
|
|
447
|
-
universal: '🌐'
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const platformName = {
|
|
451
|
-
windows: 'Windows',
|
|
452
|
-
macos: 'macOS',
|
|
453
|
-
linux: 'Linux',
|
|
454
|
-
universal: 'Universal'
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
const isCurrentPlatform = platform === currentPlatform.os || platform === 'universal';
|
|
458
|
-
const platformHeader = isCurrentPlatform
|
|
459
|
-
? chalk.green(`${platformEmoji[platform]} ${platformName[platform]} (Your Platform)`)
|
|
460
|
-
: chalk.gray(`${platformEmoji[platform]} ${platformName[platform]}`);
|
|
461
|
-
|
|
462
|
-
// Add separator if not first platform in this release
|
|
463
|
-
const needsSeparator = releaseChoices.length > 0 &&
|
|
464
|
-
!releaseChoices[releaseChoices.length - 1].name.includes('────');
|
|
465
|
-
|
|
466
|
-
if (needsSeparator) {
|
|
467
|
-
releaseChoices.push({
|
|
468
|
-
name: chalk.gray('────────────────────────────────'),
|
|
469
|
-
disabled: true
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
releaseChoices.push({
|
|
474
|
-
name: `${chalk.bold(release.tag_name)} - ${platformHeader}`,
|
|
475
|
-
disabled: true
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// Add assets for this platform
|
|
479
|
-
assets.forEach(asset => {
|
|
480
|
-
const sizeStr = (asset.size / 1024 / 1024).toFixed(2);
|
|
481
|
-
const archInfo = asset.detectedArch !== 'unknown' && asset.detectedArch !== 'universal'
|
|
482
|
-
? chalk.cyan(`[${asset.detectedArch}]`)
|
|
483
|
-
: '';
|
|
484
|
-
|
|
485
|
-
const highlight = isCurrentPlatform ? chalk.white : chalk.gray;
|
|
486
|
-
|
|
487
|
-
releaseChoices.push({
|
|
488
|
-
name: ` ${highlight(`${asset.name} ${archInfo} (${sizeStr} MB)`)}`,
|
|
489
|
-
value: { release, asset }
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
if (releaseChoices.filter(choice => !choice.disabled).length === 0) {
|
|
497
|
-
console.log(chalk.yellow('No packages found for download.'));
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const { selectedPackage } = await inquirer.prompt({
|
|
502
|
-
type: 'list',
|
|
503
|
-
name: 'selectedPackage',
|
|
504
|
-
message: 'Select a package to download:',
|
|
505
|
-
choices: releaseChoices,
|
|
506
|
-
pageSize: 15
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
const downloadDir = path.resolve(process.cwd(), 'downloads');
|
|
510
|
-
fs.mkdirSync(downloadDir, { recursive: true });
|
|
511
|
-
await downloadPackage(selectedPackage.asset, downloadDir);
|
|
512
|
-
}
|
|
551
|
+
function getAvailableDirectoryName(basePath) {
|
|
513
552
|
if (!fs.existsSync(basePath)) return basePath;
|
|
514
553
|
let counter = 2;
|
|
515
554
|
let newPath;
|
|
@@ -521,16 +560,16 @@ async function showPackageMenu(selectedRepo) {
|
|
|
521
560
|
}
|
|
522
561
|
|
|
523
562
|
async function main() {
|
|
563
|
+
printLogo()
|
|
524
564
|
const args = process.argv.slice(2);
|
|
525
565
|
if (!args.length) {
|
|
526
|
-
|
|
566
|
+
log(chalk.yellow('Usage: gg <search-query>'));
|
|
527
567
|
process.exit(1);
|
|
528
568
|
}
|
|
529
569
|
|
|
530
570
|
const query = args.join(' ');
|
|
531
571
|
const currentPlatform = getCurrentPlatform();
|
|
532
|
-
|
|
533
|
-
console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
|
|
572
|
+
|
|
534
573
|
|
|
535
574
|
let repoUrl = null;
|
|
536
575
|
|
|
@@ -551,22 +590,23 @@ async function main() {
|
|
|
551
590
|
|
|
552
591
|
const results = await searchRepositories(query);
|
|
553
592
|
|
|
554
|
-
if (!results.length) {
|
|
555
|
-
|
|
593
|
+
if (!results || !results.length) {
|
|
594
|
+
log(chalk.yellow('No repositories found'));
|
|
556
595
|
return;
|
|
557
596
|
}
|
|
558
597
|
|
|
598
|
+
|
|
559
599
|
const { selectedRepo } = await inquirer.prompt({
|
|
560
600
|
type: 'list',
|
|
561
601
|
name: 'selectedRepo',
|
|
562
602
|
message: 'Select a repository to download:',
|
|
563
603
|
choices: results.map(repo => {
|
|
564
|
-
const packageInfo = repo.hasCompatibleReleases
|
|
565
|
-
? chalk.green(' 📦 Packages available')
|
|
566
|
-
: repo.hasReleases
|
|
567
|
-
? chalk.yellow(' 📦 Packages (other platforms)')
|
|
604
|
+
const packageInfo = repo.hasCompatibleReleases
|
|
605
|
+
? chalk.green(' 📦 Packages available')
|
|
606
|
+
: repo.hasReleases
|
|
607
|
+
? chalk.yellow(' 📦 Packages (other platforms)')
|
|
568
608
|
: '';
|
|
569
|
-
|
|
609
|
+
|
|
570
610
|
return {
|
|
571
611
|
name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
|
|
572
612
|
${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
|
|
@@ -576,7 +616,7 @@ async function main() {
|
|
|
576
616
|
});
|
|
577
617
|
|
|
578
618
|
// If the selected repo has any releases, show download options
|
|
579
|
-
if (selectedRepo.hasReleases) {
|
|
619
|
+
if (selectedRepo.hasReleases || selectedRepo.hasCompatibleReleases) {
|
|
580
620
|
const { downloadChoice } = await inquirer.prompt({
|
|
581
621
|
type: 'list',
|
|
582
622
|
name: 'downloadChoice',
|
|
@@ -593,11 +633,11 @@ async function main() {
|
|
|
593
633
|
}
|
|
594
634
|
|
|
595
635
|
if (downloadChoice === 'source' || downloadChoice === 'both') {
|
|
596
|
-
await downloadRepo(selectedRepo.
|
|
636
|
+
await downloadRepo(selectedRepo.url);
|
|
597
637
|
}
|
|
598
638
|
} else {
|
|
599
639
|
// No packages, just download source
|
|
600
|
-
await downloadRepo(selectedRepo.
|
|
640
|
+
await downloadRepo(selectedRepo.url);
|
|
601
641
|
}
|
|
602
642
|
}
|
|
603
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
|