git0 0.1.4
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 +276 -0
- package/package.json +27 -0
- package/readme.md +123 -0
package/gg.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { execSync, spawn } from 'child_process';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import * as tar from 'tar'
|
|
8
|
+
import { pipeline } from 'stream/promises';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import gitUrlParse from 'git-url-parse';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const GITHUB_API = 'https://api.github.com/search/repositories';
|
|
15
|
+
const RESULTS_PER_PAGE = 10;
|
|
16
|
+
const TOKEN = process.env.GITHUB_TOKEN;
|
|
17
|
+
|
|
18
|
+
function getIdeCommand() {
|
|
19
|
+
const ides = [
|
|
20
|
+
{ name: 'Cursor', cmd: 'cursor' },
|
|
21
|
+
{ name: 'Windsurf', cmd: 'windsurf' },
|
|
22
|
+
{ name: 'VSCode', cmd: 'code' },
|
|
23
|
+
{ name: 'Code Server', cmd: 'code-server' },
|
|
24
|
+
{ name: 'Neovim', cmd: 'nvim' }
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const ide of ides) {
|
|
28
|
+
try {
|
|
29
|
+
execSync(
|
|
30
|
+
process.platform === 'win32'
|
|
31
|
+
? `where ${ide.cmd}`
|
|
32
|
+
: `command -v ${ide.cmd}`,
|
|
33
|
+
{ stdio: 'ignore' }
|
|
34
|
+
);
|
|
35
|
+
return ide;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function openInIDE(targetDir) {
|
|
44
|
+
const ide = getIdeCommand();
|
|
45
|
+
if (!ide) {
|
|
46
|
+
console.log(chalk.yellow('⚠️ No supported IDE found'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const args = ide.cmd === 'code-server'
|
|
52
|
+
? [targetDir, '--open']
|
|
53
|
+
: [targetDir];
|
|
54
|
+
|
|
55
|
+
spawn(ide.cmd, args, {
|
|
56
|
+
detached: true,
|
|
57
|
+
stdio: 'ignore',
|
|
58
|
+
shell: process.platform === 'win32'
|
|
59
|
+
}).unref();
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const args2 = ide.cmd === 'code-server'
|
|
63
|
+
? ['./readme.md', '--open']
|
|
64
|
+
: ['./README.md'];
|
|
65
|
+
|
|
66
|
+
spawn(ide.cmd, args2, {
|
|
67
|
+
detached: true,
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
shell: process.platform === 'win32'
|
|
70
|
+
}).unref();
|
|
71
|
+
|
|
72
|
+
console.log(chalk.green(`🚀 Opening ${path.basename(targetDir)} in ${ide.name}`));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(chalk.red(`❌ Failed to open ${ide.name}:`), error.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function installDependencies(targetDir) {
|
|
79
|
+
process.chdir(targetDir);
|
|
80
|
+
|
|
81
|
+
// Project type detection
|
|
82
|
+
const detectors = {
|
|
83
|
+
nodejs: () => fs.existsSync('package.json'),
|
|
84
|
+
docker: () => fs.existsSync('Dockerfile'),
|
|
85
|
+
python: () => fs.existsSync('requirements.txt') || fs.existsSync('setup.py'),
|
|
86
|
+
rust: () => fs.existsSync('Cargo.toml'),
|
|
87
|
+
go: () => fs.existsSync('go.mod')
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Install commands for each project type
|
|
91
|
+
const installers = {
|
|
92
|
+
|
|
93
|
+
nodejs: () => {
|
|
94
|
+
try {
|
|
95
|
+
execSync(
|
|
96
|
+
process.platform === 'win32'
|
|
97
|
+
? `where bun`
|
|
98
|
+
: `command -v bun`,
|
|
99
|
+
{ stdio: 'ignore' }
|
|
100
|
+
);
|
|
101
|
+
runCommand('bun install');
|
|
102
|
+
runCommand('bun run dev; bun run start');
|
|
103
|
+
} catch (error) {
|
|
104
|
+
runCommand('npm install');
|
|
105
|
+
runCommand('npm run dev; npm run start');
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
docker: () => {
|
|
109
|
+
if (fs.existsSync('docker-compose.yml')) {
|
|
110
|
+
runCommand('sudo docker-compose up -d');
|
|
111
|
+
} else if (fs.existsSync('Dockerfile')) {
|
|
112
|
+
runCommand('sudo docker build -t project .');
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
python: () => {
|
|
116
|
+
runCommand('python -m venv .venv');
|
|
117
|
+
runCommand('source .venv/bin/activate');
|
|
118
|
+
|
|
119
|
+
if (fs.existsSync('requirements.txt')) {
|
|
120
|
+
runCommand('pip install -r requirements.txt');
|
|
121
|
+
}
|
|
122
|
+
if (fs.existsSync('setup.py')) {
|
|
123
|
+
runCommand('pip install -e .');
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
rust: () => runCommand('cargo build'),
|
|
127
|
+
go: () => runCommand('go mod tidy')
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Run detections and installations
|
|
131
|
+
Object.entries(detectors).forEach(([name, check]) => {
|
|
132
|
+
if (check()) {
|
|
133
|
+
console.log(chalk.yellow(`⚙️ Detected ${name} project`));
|
|
134
|
+
installers[name]?.();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runCommand(cmd) {
|
|
140
|
+
console.log(chalk.cyan(`🚀 Running: ${cmd}`));
|
|
141
|
+
try {
|
|
142
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(chalk.red(`❌ Failed: ${cmd}`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export async function searchRepositories(query) {
|
|
148
|
+
try {
|
|
149
|
+
const response = await axios.get(GITHUB_API, {
|
|
150
|
+
params: {
|
|
151
|
+
q: `${query} in:name`,
|
|
152
|
+
sort: 'stars',
|
|
153
|
+
order: 'desc',
|
|
154
|
+
per_page: RESULTS_PER_PAGE
|
|
155
|
+
},
|
|
156
|
+
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
157
|
+
});
|
|
158
|
+
return response.data.items;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(chalk.red('Search failed:'), error.response?.data?.message || error.message);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function downloadRepo(repo) {
|
|
165
|
+
const parsed = gitUrlParse(`https://github.com/${repo}`);
|
|
166
|
+
const defaultDir = path.resolve(process.cwd(), parsed.name);
|
|
167
|
+
const extractPath = getAvailableDirectoryName(defaultDir);
|
|
168
|
+
|
|
169
|
+
fs.mkdirSync(extractPath, { recursive: true });
|
|
170
|
+
console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${path.basename(extractPath)}...`));
|
|
171
|
+
let url = `${parsed.owner}/${parsed.name}/tarball/${parsed.default_branch || 'main'}`;
|
|
172
|
+
|
|
173
|
+
fs.mkdirSync(extractPath, { recursive: true });
|
|
174
|
+
console.log(chalk.blue(`📦 Downloading ${parsed.full_name} into ${repo.name}...`));
|
|
175
|
+
|
|
176
|
+
console.log(url);
|
|
177
|
+
try {
|
|
178
|
+
let response;
|
|
179
|
+
try{
|
|
180
|
+
response= await axios({
|
|
181
|
+
url,
|
|
182
|
+
method: 'GET',
|
|
183
|
+
responseType: 'stream',
|
|
184
|
+
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
url = url.replace("/main", "/master")
|
|
188
|
+
response= await axios({
|
|
189
|
+
url,
|
|
190
|
+
method: 'GET',
|
|
191
|
+
responseType: 'stream',
|
|
192
|
+
headers: TOKEN ? { Authorization: `token ${TOKEN}` } : {}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await pipeline(
|
|
197
|
+
response.data,
|
|
198
|
+
tar.x({
|
|
199
|
+
C: extractPath,
|
|
200
|
+
strip: 1
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
openInIDE(extractPath);
|
|
206
|
+
}, 1000);
|
|
207
|
+
installDependencies(extractPath); // Add this line
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error(chalk.red('Download failed:'), error.message);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getAvailableDirectoryName(basePath) {
|
|
215
|
+
if (!fs.existsSync(basePath)) return basePath;
|
|
216
|
+
let counter = 2;
|
|
217
|
+
let newPath;
|
|
218
|
+
while (true) {
|
|
219
|
+
newPath = `${basePath}-${counter}`;
|
|
220
|
+
if (!fs.existsSync(newPath)) return newPath;
|
|
221
|
+
counter++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function main() {
|
|
226
|
+
const args = process.argv.slice(2);
|
|
227
|
+
if (!args.length) {
|
|
228
|
+
console.log(chalk.yellow('Usage: gg <search-query>'));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const query = args.join(' ');
|
|
233
|
+
|
|
234
|
+
let repoUrl = null;
|
|
235
|
+
|
|
236
|
+
// Try parsing as a GitHub URL or shorthand
|
|
237
|
+
let parsed = null;
|
|
238
|
+
|
|
239
|
+
if (query.includes('github.com') || query.startsWith('git@github.com:') || query.startsWith('https://') || query.startsWith('git://')) {
|
|
240
|
+
parsed = gitUrlParse(query);
|
|
241
|
+
} else if (/^[\w-]+\/[\w.-]+$/.test(query)) {
|
|
242
|
+
// Shorthand owner/repo
|
|
243
|
+
parsed = gitUrlParse(`https://github.com/${query}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
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
|
+
await downloadRepo(parsed.href);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const results = await searchRepositories(query);
|
|
256
|
+
|
|
257
|
+
if (!results.length) {
|
|
258
|
+
console.log(chalk.yellow('No repositories found'));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const { selectedRepo } = await inquirer.prompt({
|
|
263
|
+
type: 'list',
|
|
264
|
+
name: 'selectedRepo',
|
|
265
|
+
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
|
+
}))
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await downloadRepo(selectedRepo.url);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "git0",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "A CLI manager for downloading GitHub repos.",
|
|
5
|
+
"author": "vtempest",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"github",
|
|
9
|
+
"cli",
|
|
10
|
+
"download"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axios": "^1.7.2",
|
|
14
|
+
"chalk": "^5.3.0",
|
|
15
|
+
"git-url-parse": "^16.1.0",
|
|
16
|
+
"inquirer": "^9.2.16",
|
|
17
|
+
"tar": "^6.2.1"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"demo": "bun gg.js react template",
|
|
21
|
+
"publish:npm": "npm version patch && npm publish --access public"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"gg": "./gg.js"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://i.imgur.com/zG1QI1q.png" />
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://discord.gg/SJdBqBz3tV">
|
|
6
|
+
<img src="https://img.shields.io/discord/1110227955554209923.svg?label=Chat&logo=Discord&colorB=7289da&style=flat"
|
|
7
|
+
alt="Join Discord" />
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://github.com/vtempest/gg/discussions">
|
|
10
|
+
<img alt="GitHub Stars" src="https://img.shields.io/github/stars/vtempest/gg" /></a>
|
|
11
|
+
<a href="https://github.com/vtempest/gg/discussions">
|
|
12
|
+
<img alt="GitHub Discussions"
|
|
13
|
+
src="https://img.shields.io/github/discussions/vtempest/gg" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/vtempest/gg/pulse" alt="Activity">
|
|
16
|
+
<img src="https://img.shields.io/github/commit-activity/m/vtempest/gg" />
|
|
17
|
+
</a>
|
|
18
|
+
<img src="https://img.shields.io/github/last-commit/vtempest/gg.svg?style=flat-square" alt="GitHub last commit" />
|
|
19
|
+
</p>
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square"
|
|
22
|
+
alt="PRs Welcome" />
|
|
23
|
+
<a href="https://codespaces.new/vtempest/gg">
|
|
24
|
+
<img src="https://github.com/codespaces/badge.svg" width="150" height="20" />
|
|
25
|
+
</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# GG - GitHub Repo Downloader
|
|
30
|
+
|
|
31
|
+
A fast and smart CLI tool to search, download, and instantly set up GitHub repositories with automatic dependency installation and IDE integration.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## 🚀 Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g git-gg
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun install -g git-gg
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
## ✨ Features
|
|
47
|
+
|
|
48
|
+
- **Search GitHub repositories** by name with fuzzy matching
|
|
49
|
+
- **Download repositories** directly from GitHub URLs or owner/repo shortcuts
|
|
50
|
+
- **Automatic dependency detection** and installation for multiple project types
|
|
51
|
+
- **Smart IDE integration** - automatically opens projects in your preferred editor
|
|
52
|
+
- **Cross-platform support** - works on Windows, macOS, and Linux
|
|
53
|
+
- **Conflict resolution** - handles directory naming conflicts automatically
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## 🎯 Usage
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Search and Download
|
|
60
|
+
```bash
|
|
61
|
+
# Search for repositories by name
|
|
62
|
+
gg react starter
|
|
63
|
+
|
|
64
|
+
# Direct download from GitHub URL
|
|
65
|
+
gg https://github.com/facebook/react
|
|
66
|
+
|
|
67
|
+
# Download using owner/repo shorthand
|
|
68
|
+
gg facebook/react
|
|
69
|
+
|
|
70
|
+
## Use without installing
|
|
71
|
+
npx git-gg react starter
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Supported Project Types
|
|
75
|
+
|
|
76
|
+
GG automatically detects and sets up the following project types:
|
|
77
|
+
|
|
78
|
+
| Project Type | Detection | Installation |
|
|
79
|
+
|-------------|-----------|-------------|
|
|
80
|
+
| **Node.js** | `package.json` | `bun install` (fallback to `npm install`) |
|
|
81
|
+
| **Docker** | `Dockerfile`, `docker-compose.yml` | `docker-compose up -d` or `docker build` |
|
|
82
|
+
| **Python** | `requirements.txt`, `setup.py` | Virtual environment + pip install |
|
|
83
|
+
| **Rust** | `Cargo.toml` | `cargo build` |
|
|
84
|
+
| **Go** | `go.mod` | `go mod tidy` |
|
|
85
|
+
|
|
86
|
+
### Supported IDEs
|
|
87
|
+
|
|
88
|
+
GG automatically detects and opens projects in your preferred IDE:
|
|
89
|
+
|
|
90
|
+
- **Cursor** (`cursor`)
|
|
91
|
+
- **Windsurf** (`windsurf`)
|
|
92
|
+
- **VS Code** (`code`)
|
|
93
|
+
- **Code Server** (`code-server`)
|
|
94
|
+
- **Neovim** (`nvim`)
|
|
95
|
+
|
|
96
|
+
## 🔧 Configuration
|
|
97
|
+
|
|
98
|
+
### GitHub Token (Optional)
|
|
99
|
+
|
|
100
|
+
For higher API rate limits, set your GitHub token:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export GITHUB_TOKEN=your_github_token_here
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Without a token, you're limited to 60 requests per hour. With a token, you get 5,000 requests per hour.
|
|
107
|
+
|
|
108
|
+
### What Happens After Download
|
|
109
|
+
|
|
110
|
+
1. **Repository is downloaded** to your current directory
|
|
111
|
+
2. **Project type is detected** automatically
|
|
112
|
+
3. **Dependencies are installed** based on project type
|
|
113
|
+
4. **IDE is launched** automatically (if available)
|
|
114
|
+
5. **Development server starts** (for Node.js projects)
|
|
115
|
+
|
|
116
|
+
If a directory with the same name exists, GG automatically appends a number (e.g., `react-2`, `react-3`).
|
|
117
|
+
|
|
118
|
+
## 🎉 Why GG?
|
|
119
|
+
|
|
120
|
+
- **Fast**: Skip the manual git clone, cd, install dance
|
|
121
|
+
- **Smart**: Automatically detects what kind of project you're working with
|
|
122
|
+
- **Convenient**: Opens your IDE and starts development servers automatically
|
|
123
|
+
- **Reliable**: Handles edge cases like directory conflicts and missing dependencies
|