git0 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. package/gg.js +359 -31
  2. package/package.json +3 -2
  3. package/readme.md +7 -6
package/gg.js CHANGED
@@ -9,12 +9,213 @@ import { pipeline } from 'stream/promises';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
11
  import gitUrlParse from 'git-url-parse';
12
-
12
+ import os from 'os';
13
13
 
14
14
  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
+ // Detect current operating system and architecture
19
+ function getCurrentPlatform() {
20
+ const platform = os.platform();
21
+ const arch = os.arch();
22
+
23
+ const platformMap = {
24
+ 'win32': 'windows',
25
+ 'darwin': 'macos',
26
+ 'linux': 'linux'
27
+ };
28
+
29
+ const archMap = {
30
+ 'x64': 'x86_64',
31
+ 'arm64': 'arm64',
32
+ 'arm': 'arm',
33
+ 'ia32': 'i386'
34
+ };
35
+
36
+ return {
37
+ os: platformMap[platform] || platform,
38
+ arch: archMap[arch] || arch,
39
+ platform,
40
+ architecture: arch
41
+ };
42
+ }
43
+
44
+ // Get releases for a repository
45
+ async function getRepositoryReleases(owner, repo) {
46
+ try {
47
+ const response = await axios.get(`https://api.github.com/repos/${owner}/${repo}/releases`, {
48
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {},
49
+ params: { per_page: 5 } // Get latest 5 releases
50
+ });
51
+ return response.data;
52
+ } catch (error) {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ // Categorize releases by platform
58
+ function categorizeReleasesByPlatform(releases) {
59
+ const platformKeywords = {
60
+ windows: ['win', 'windows', 'win32', 'win64', '.exe', '.msi'],
61
+ macos: ['mac', 'macos', 'darwin', 'osx', '.dmg', '.pkg'],
62
+ linux: ['linux', 'ubuntu', 'debian', '.deb', '.rpm', '.tar.gz', '.AppImage']
63
+ };
64
+
65
+ const archKeywords = {
66
+ x86_64: ['x86_64', 'x64', 'amd64', '64'],
67
+ arm64: ['arm64', 'aarch64'],
68
+ arm: ['arm', 'armv7'],
69
+ i386: ['i386', 'x86', '32']
70
+ };
71
+
72
+ const categorizedReleases = [];
73
+
74
+ releases.forEach(release => {
75
+ const platformAssets = {
76
+ windows: [],
77
+ macos: [],
78
+ linux: [],
79
+ universal: []
80
+ };
81
+
82
+ release.assets.forEach(asset => {
83
+ const name = asset.name.toLowerCase();
84
+ let categorized = false;
85
+
86
+ // Check each platform
87
+ Object.entries(platformKeywords).forEach(([platform, keywords]) => {
88
+ if (keywords.some(keyword => name.includes(keyword.toLowerCase()))) {
89
+ // Determine architecture
90
+ let detectedArch = 'unknown';
91
+ Object.entries(archKeywords).forEach(([arch, archKeys]) => {
92
+ if (archKeys.some(archKey => name.includes(archKey.toLowerCase()))) {
93
+ detectedArch = arch;
94
+ }
95
+ });
96
+
97
+ platformAssets[platform].push({
98
+ ...asset,
99
+ detectedArch,
100
+ platform
101
+ });
102
+ categorized = true;
103
+ }
104
+ });
105
+
106
+ // If not categorized, check for universal binaries
107
+ if (!categorized && (name.includes('universal') || name.includes('all') ||
108
+ (!name.includes('win') && !name.includes('mac') && !name.includes('linux')))) {
109
+ platformAssets.universal.push({
110
+ ...asset,
111
+ detectedArch: 'universal',
112
+ platform: 'universal'
113
+ });
114
+ }
115
+ });
116
+
117
+ // Only include releases that have assets
118
+ const hasAssets = Object.values(platformAssets).some(assets => assets.length > 0);
119
+ if (hasAssets) {
120
+ categorizedReleases.push({
121
+ ...release,
122
+ platformAssets
123
+ });
124
+ }
125
+ });
126
+
127
+ return categorizedReleases;
128
+ }
129
+
130
+ // Filter releases for current platform (for compatibility indicator)
131
+ function filterReleasesByPlatform(releases, currentPlatform) {
132
+ const categorized = categorizeReleasesByPlatform(releases);
133
+ return categorized.filter(release =>
134
+ release.platformAssets[currentPlatform.os].length > 0 ||
135
+ release.platformAssets.universal.length > 0
136
+ );
137
+ }
138
+
139
+ // Download and install package
140
+ async function downloadPackage(asset, targetDir) {
141
+ const fileName = asset.name;
142
+ const downloadPath = path.join(targetDir, fileName);
143
+
144
+ console.log(chalk.blue(`📦 Downloading ${fileName}...`));
145
+
146
+ try {
147
+ const response = await axios({
148
+ url: asset.browser_download_url,
149
+ method: 'GET',
150
+ responseType: 'stream',
151
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
152
+ });
153
+
154
+ const writer = fs.createWriteStream(downloadPath);
155
+ await pipeline(response.data, writer);
156
+
157
+ console.log(chalk.green(`✅ Downloaded ${fileName} to ${downloadPath}`));
158
+
159
+ // Try to make executable if it's a binary
160
+ if (process.platform !== 'win32' && !fileName.includes('.')) {
161
+ try {
162
+ fs.chmodSync(downloadPath, '755');
163
+ console.log(chalk.green(`✅ Made ${fileName} executable`));
164
+ } catch (error) {
165
+ console.log(chalk.yellow(`⚠️ Could not make ${fileName} executable`));
166
+ }
167
+ }
168
+
169
+ // Provide installation instructions
170
+ provideInstallationInstructions(downloadPath, asset);
171
+
172
+ } catch (error) {
173
+ console.error(chalk.red(`❌ Failed to download ${fileName}:`), error.message);
174
+ }
175
+ }
176
+
177
+ // Provide platform-specific installation instructions
178
+ function provideInstallationInstructions(filePath, asset) {
179
+ const fileName = asset.name;
180
+ const platform = getCurrentPlatform();
181
+
182
+ console.log(chalk.cyan('\n📋 Installation Instructions:'));
183
+
184
+ if (platform.platform === 'win32') {
185
+ if (fileName.endsWith('.exe')) {
186
+ console.log(chalk.white(' Run the executable:'));
187
+ console.log(chalk.gray(` ${filePath}`));
188
+ } else if (fileName.endsWith('.msi')) {
189
+ console.log(chalk.white(' Install the MSI package:'));
190
+ console.log(chalk.gray(` msiexec /i "${filePath}"`));
191
+ }
192
+ } else if (platform.platform === 'darwin') {
193
+ if (fileName.endsWith('.dmg')) {
194
+ console.log(chalk.white(' Mount and install the DMG:'));
195
+ console.log(chalk.gray(` open "${filePath}"`));
196
+ } else if (fileName.endsWith('.pkg')) {
197
+ console.log(chalk.white(' Install the package:'));
198
+ console.log(chalk.gray(` sudo installer -pkg "${filePath}" -target /`));
199
+ }
200
+ } else {
201
+ if (fileName.endsWith('.deb')) {
202
+ console.log(chalk.white(' Install the DEB package:'));
203
+ console.log(chalk.gray(` sudo dpkg -i "${filePath}"`));
204
+ } else if (fileName.endsWith('.rpm')) {
205
+ console.log(chalk.white(' Install the RPM package:'));
206
+ console.log(chalk.gray(` sudo rpm -i "${filePath}"`));
207
+ } else if (fileName.endsWith('.AppImage')) {
208
+ console.log(chalk.white(' Run the AppImage:'));
209
+ console.log(chalk.gray(` chmod +x "${filePath}" && "${filePath}"`));
210
+ } else if (!fileName.includes('.')) {
211
+ console.log(chalk.white(' Binary is ready to use:'));
212
+ console.log(chalk.gray(` "${filePath}"`));
213
+ console.log(chalk.white(' Consider moving to PATH:'));
214
+ console.log(chalk.gray(` sudo mv "${filePath}" /usr/local/bin/`));
215
+ }
216
+ }
217
+ }
218
+
18
219
  function getIdeCommand() {
19
220
  const ides = [
20
221
  { name: 'Cursor', cmd: 'cursor' },
@@ -58,7 +259,6 @@ export function openInIDE(targetDir) {
58
259
  shell: process.platform === 'win32'
59
260
  }).unref();
60
261
 
61
-
62
262
  const args2 = ide.cmd === 'code-server'
63
263
  ? ['./readme.md', '--open']
64
264
  : ['./README.md'];
@@ -89,7 +289,6 @@ export async function installDependencies(targetDir) {
89
289
 
90
290
  // Install commands for each project type
91
291
  const installers = {
92
-
93
292
  nodejs: () => {
94
293
  try {
95
294
  execSync(
@@ -144,6 +343,7 @@ function runCommand(cmd) {
144
343
  console.error(chalk.red(`❌ Failed: ${cmd}`));
145
344
  }
146
345
  }
346
+
147
347
  export async function searchRepositories(query) {
148
348
  try {
149
349
  const response = await axios.get(GITHUB_API, {
@@ -155,12 +355,32 @@ export async function searchRepositories(query) {
155
355
  },
156
356
  headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
157
357
  });
158
- return response.data.items;
358
+
359
+ // Check for releases for each repository
360
+ const reposWithReleases = await Promise.all(
361
+ response.data.items.map(async (repo) => {
362
+ const releases = await getRepositoryReleases(repo.owner.login, repo.name);
363
+ const currentPlatform = getCurrentPlatform();
364
+ const compatibleReleases = filterReleasesByPlatform(releases, currentPlatform);
365
+ const categorizedReleases = categorizeReleasesByPlatform(releases);
366
+
367
+ return {
368
+ ...repo,
369
+ hasReleases: releases.length > 0,
370
+ hasCompatibleReleases: compatibleReleases.length > 0,
371
+ releases: compatibleReleases,
372
+ allReleases: categorizedReleases
373
+ };
374
+ })
375
+ );
376
+
377
+ return reposWithReleases;
159
378
  } catch (error) {
160
379
  console.error(chalk.red('Search failed:'), error.response?.data?.message || error.message);
161
380
  process.exit(1);
162
381
  }
163
382
  }
383
+
164
384
  export async function downloadRepo(repo) {
165
385
  const parsed = gitUrlParse(`https://github.com/${repo}`);
166
386
  const defaultDir = path.resolve(process.cwd(), parsed.name);
@@ -168,24 +388,20 @@ export async function downloadRepo(repo) {
168
388
 
169
389
  fs.mkdirSync(extractPath, { recursive: true });
170
390
  console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${path.basename(extractPath)}...`));
171
- let url = `${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
391
+ let url = `https://api.github.com/repos/${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
172
392
 
173
- fs.mkdirSync(extractPath, { recursive: true });
174
- console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${repo.name}...`));
175
-
176
- console.log(url);
177
393
  try {
178
394
  let response;
179
- try{
180
- response= await axios({
181
- url,
182
- method: 'GET',
183
- responseType: 'stream',
184
- headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
185
- });
395
+ try {
396
+ response = await axios({
397
+ url,
398
+ method: 'GET',
399
+ responseType: 'stream',
400
+ headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
401
+ });
186
402
  } catch (error) {
187
- url = url.replace("/main", "/master")
188
- response= await axios({
403
+ url = url.replace("/main", "/master");
404
+ response = await axios({
189
405
  url,
190
406
  method: 'GET',
191
407
  responseType: 'stream',
@@ -204,14 +420,96 @@ export async function downloadRepo(repo) {
204
420
  setTimeout(() => {
205
421
  openInIDE(extractPath);
206
422
  }, 1000);
207
- installDependencies(extractPath); // Add this line
423
+ installDependencies(extractPath);
208
424
  } catch (error) {
209
425
  console.error(chalk.red('Download failed:'), error.message);
210
426
  process.exit(1);
211
427
  }
212
428
  }
213
429
 
214
- function getAvailableDirectoryName(basePath) {
430
+ // Show package selection menu organized by platform
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
+ }
215
513
  if (!fs.existsSync(basePath)) return basePath;
216
514
  let counter = 2;
217
515
  let newPath;
@@ -230,6 +528,9 @@ async function main() {
230
528
  }
231
529
 
232
530
  const query = args.join(' ');
531
+ const currentPlatform = getCurrentPlatform();
532
+
533
+ console.log(chalk.gray(`🖥️ Detected platform: ${currentPlatform.os} ${currentPlatform.arch}`));
233
534
 
234
535
  let repoUrl = null;
235
536
 
@@ -243,11 +544,7 @@ async function main() {
243
544
  parsed = gitUrlParse(`https://github.com/${query}`);
244
545
  }
245
546
 
246
-
247
547
  if (parsed && parsed.owner && parsed.name) {
248
- // Reconstruct the canonical HTTPS URL for download
249
- // repoUrl = `https://github.com/${parsed.owner}/${parsed.name}`;
250
- // console.log(chalk.green(`Detected GitHub repo: ${repoUrl}`));
251
548
  await downloadRepo(parsed.href);
252
549
  return;
253
550
  }
@@ -263,14 +560,45 @@ async function main() {
263
560
  type: 'list',
264
561
  name: 'selectedRepo',
265
562
  message: 'Select a repository to download:',
266
- choices: results.map(repo => ({
267
- name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
268
- ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}`,
269
- value: repo
270
- }))
563
+ choices: results.map(repo => {
564
+ const packageInfo = repo.hasCompatibleReleases
565
+ ? chalk.green(' 📦 Packages available')
566
+ : repo.hasReleases
567
+ ? chalk.yellow(' 📦 Packages (other platforms)')
568
+ : '';
569
+
570
+ return {
571
+ name: `${chalk.bold(repo.full_name)} - ${chalk.gray(repo.description || 'No description')}
572
+ ${chalk.yellow(`★ ${repo.stargazers_count}`)} | ${chalk.blue(repo.language || 'Unknown')}${packageInfo}`,
573
+ value: repo
574
+ };
575
+ })
271
576
  });
272
577
 
273
- await downloadRepo(selectedRepo.url);
578
+ // If the selected repo has any releases, show download options
579
+ if (selectedRepo.hasReleases) {
580
+ const { downloadChoice } = await inquirer.prompt({
581
+ type: 'list',
582
+ name: 'downloadChoice',
583
+ message: 'This repository has downloadable packages. What would you like to do?',
584
+ choices: [
585
+ { name: '📦 Download package/binary', value: 'package' },
586
+ { name: '📂 Download source code', value: 'source' },
587
+ { name: '📦📂 Download both package and source', value: 'both' }
588
+ ]
589
+ });
590
+
591
+ if (downloadChoice === 'package' || downloadChoice === 'both') {
592
+ await showPackageMenu(selectedRepo);
593
+ }
594
+
595
+ if (downloadChoice === 'source' || downloadChoice === 'both') {
596
+ await downloadRepo(selectedRepo.html_url);
597
+ }
598
+ } else {
599
+ // No packages, just download source
600
+ await downloadRepo(selectedRepo.html_url);
601
+ }
274
602
  }
275
603
 
276
- main();
604
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git0",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A CLI manager for downloading GitHub repos.",
5
5
  "author": "vtempest",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "type": "module",
24
24
  "bin": {
25
- "gg": "./gg.js"
25
+ "gg": "./gg.js",
26
+ "git0": "./gg.js"
26
27
  }
27
28
  }
package/readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://i.imgur.com/zG1QI1q.png" />
2
+ <img src="https://i.imgur.com/poOtI3N.png" />
3
3
  </p>
4
4
  <p align="center">
5
5
  <a href="https://discord.gg/SJdBqBz3tV">
@@ -26,7 +26,7 @@
26
26
  </p>
27
27
 
28
28
 
29
- # GG - GitHub Repo Downloader
29
+ # Git0: GitHub Zero-Step Repo Downloader
30
30
 
31
31
  A fast and smart CLI tool to search, download, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
32
32
 
@@ -34,11 +34,11 @@ A fast and smart CLI tool to search, download, and instantly set up GitHub repos
34
34
  ## 🚀 Installation
35
35
 
36
36
  ```bash
37
- npm install -g git-gg
37
+ npm install -g git0
38
38
  ```
39
39
 
40
40
  ```bash
41
- bun install -g git-gg
41
+ bun install -g git0
42
42
  ```
43
43
 
44
44
  ![preview](https://i.imgur.com/K22NiBq.png)
@@ -65,10 +65,11 @@ gg react starter
65
65
  gg https://github.com/facebook/react
66
66
 
67
67
  # Download using owner/repo shorthand
68
- gg facebook/react
68
+ ## gg and git0 both work
69
+ git0 facebook/react
69
70
 
70
71
  ## Use without installing
71
- npx git-gg react starter
72
+ npx git0 react starter
72
73
  ```
73
74
 
74
75
  ### Supported Project Types