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

|
|
44
35
|

|
|
45
36
|
|
|
37
|
+
|
|
46
38
|
## ✨ Features
|
|
47
39
|
|
|
48
40
|
- **Search GitHub repositories** by name with fuzzy matching
|
|
49
41
|
- **Download repositories** directly from GitHub URLs or owner/repo shortcuts
|
|
42
|
+
- **Get Releases** instantly download latest release for your system or all systems
|
|
50
43
|
- **Automatic dependency detection** and installation for multiple project types
|
|
51
44
|
- **Smart IDE integration** - automatically opens projects in your preferred editor
|
|
52
45
|
- **Cross-platform support** - works on Windows, macOS, and Linux
|
|
@@ -58,6 +51,10 @@ bun install -g git0
|
|
|
58
51
|
|
|
59
52
|
### Search and Download
|
|
60
53
|
```bash
|
|
54
|
+
# install in bun or node
|
|
55
|
+
npm i -g git0
|
|
56
|
+
bun i -g git0
|
|
57
|
+
|
|
61
58
|
# Search for repositories by name
|
|
62
59
|
gg react starter
|
|
63
60
|
|
|
@@ -98,7 +95,7 @@ GG automatically detects and opens projects in your preferred IDE:
|
|
|
98
95
|
|
|
99
96
|
### GitHub Token (Optional)
|
|
100
97
|
|
|
101
|
-
For higher API rate limits, set your GitHub token:
|
|
98
|
+
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
99
|
|
|
103
100
|
```bash
|
|
104
101
|
export GITHUB_TOKEN=your_github_token_here
|