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.
Files changed (3) hide show
  1. package/gg.js +276 -0
  2. package/package.json +27 -0
  3. 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
+ ![preview](https://i.imgur.com/K22NiBq.png)
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