vibesuite 1.0.1 → 1.0.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/package.json +1 -1
- package/src/utils.js +56 -50
package/package.json
CHANGED
package/src/utils.js
CHANGED
|
@@ -2,6 +2,10 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import https from 'https';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
5
9
|
|
|
6
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -23,81 +27,74 @@ export const PATHS = {
|
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
30
|
+
* Delay utility for rate limiting
|
|
31
|
+
* @param {number} ms - Milliseconds to delay
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
function delay(ms) {
|
|
35
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetch file content from raw.githubusercontent.com
|
|
40
|
+
* This avoids GitHub API rate limits (60 requests/hour for unauthenticated)
|
|
41
|
+
* Raw URLs have much higher limits and work better for file downloads
|
|
28
42
|
* @param {string} relativePath - Path relative to repo root
|
|
29
43
|
* @param {number} retries - Number of retries (default: 3)
|
|
30
44
|
* @returns {Promise<string>} File content
|
|
31
45
|
*/
|
|
32
46
|
export async function fetchFromGitHub(relativePath, retries = 3) {
|
|
33
|
-
// Use GitHub
|
|
34
|
-
const
|
|
47
|
+
// Use raw GitHub content URL to avoid API rate limits
|
|
48
|
+
const rawUrl = `${GITHUB_RAW_URL}/${relativePath}`;
|
|
35
49
|
|
|
36
50
|
return new Promise((resolve, reject) => {
|
|
37
51
|
const attempt = (remainingRetries) => {
|
|
38
|
-
https.get(
|
|
52
|
+
const req = https.get(rawUrl, {
|
|
39
53
|
timeout: 15000,
|
|
40
54
|
headers: {
|
|
41
|
-
'User-Agent': 'vibesuite-cli'
|
|
42
|
-
'Accept': 'application/vnd.github.v3+json'
|
|
55
|
+
'User-Agent': 'vibesuite-cli'
|
|
43
56
|
}
|
|
44
57
|
}, (res) => {
|
|
45
58
|
let data = '';
|
|
46
59
|
res.on('data', chunk => data += chunk);
|
|
47
60
|
res.on('end', () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} else if (json.download_url) {
|
|
57
|
-
// Large files may not have content inline - this is a limitation
|
|
58
|
-
// For now, reject so fallback to local files can occur
|
|
59
|
-
reject(new Error('File too large for API fetch'));
|
|
60
|
-
} else {
|
|
61
|
-
reject(new Error('No content in response'));
|
|
62
|
-
}
|
|
63
|
-
} else if (res.statusCode === 404) {
|
|
64
|
-
reject(new Error('File not found (404)'));
|
|
65
|
-
} else if (res.statusCode === 403) {
|
|
66
|
-
reject(new Error('GitHub API rate limit exceeded (403)'));
|
|
67
|
-
} else {
|
|
68
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
69
|
-
}
|
|
70
|
-
} catch (e) {
|
|
71
|
-
reject(new Error('Failed to parse response'));
|
|
61
|
+
if (res.statusCode === 200) {
|
|
62
|
+
resolve(data);
|
|
63
|
+
} else if (res.statusCode === 404) {
|
|
64
|
+
reject(new Error('File not found (404)'));
|
|
65
|
+
} else if (res.statusCode === 403) {
|
|
66
|
+
reject(new Error('Access forbidden (403)'));
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
72
69
|
}
|
|
73
70
|
});
|
|
74
|
-
})
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
req.on('error', async (err) => {
|
|
75
74
|
if (remainingRetries > 0) {
|
|
76
75
|
setTimeout(() => attempt(remainingRetries - 1), 2000);
|
|
77
76
|
} else {
|
|
78
|
-
|
|
77
|
+
// Fallback to curl if Node https fails (e.g. proxy/network issues)
|
|
78
|
+
try {
|
|
79
|
+
// Ensure we use a timeout with curl too
|
|
80
|
+
const { stdout } = await execAsync(`curl -sL --max-time 15 "${rawUrl}"`);
|
|
81
|
+
if (stdout) resolve(stdout);
|
|
82
|
+
else reject(err);
|
|
83
|
+
} catch (curlErr) {
|
|
84
|
+
reject(err); // Return original error if curl also fails
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
});
|
|
88
|
+
|
|
89
|
+
req.on('timeout', () => {
|
|
90
|
+
req.destroy(new Error('Request timed out'));
|
|
91
|
+
});
|
|
81
92
|
};
|
|
82
93
|
|
|
83
94
|
attempt(retries);
|
|
84
95
|
});
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
function handleResponse(res, resolve, reject) {
|
|
88
|
-
if (res.statusCode === 200) {
|
|
89
|
-
let data = '';
|
|
90
|
-
res.on('data', chunk => data += chunk);
|
|
91
|
-
res.on('end', () => resolve(data));
|
|
92
|
-
} else if (res.statusCode === 404) {
|
|
93
|
-
reject(new Error(`File not found (404)`));
|
|
94
|
-
} else if (res.statusCode === 403) {
|
|
95
|
-
reject(new Error(`GitHub rate limit exceeded or access forbidden (403)`));
|
|
96
|
-
} else {
|
|
97
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
98
|
/**
|
|
102
99
|
* Fetch directory listing from GitHub API
|
|
103
100
|
* @param {string} relativePath - Path relative to repo root
|
|
@@ -156,12 +153,15 @@ export async function downloadFromGitHub(relativePath, destPath) {
|
|
|
156
153
|
|
|
157
154
|
/**
|
|
158
155
|
* Recursively download a directory from GitHub
|
|
156
|
+
* Uses API only for directory listings, raw URLs for file downloads
|
|
157
|
+
* Includes rate limiting delays to avoid hitting API limits
|
|
159
158
|
* @param {string} relativePath - Path relative to repo root
|
|
160
159
|
* @param {string} destPath - Local destination path
|
|
161
160
|
* @param {Function} filter - Optional filter function for items
|
|
161
|
+
* @param {number} delayMs - Delay between file downloads in ms (default: 100)
|
|
162
162
|
* @returns {Promise<number>} Number of files downloaded
|
|
163
163
|
*/
|
|
164
|
-
export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null) {
|
|
164
|
+
export async function downloadDirectoryFromGitHub(relativePath, destPath, filter = null, delayMs = 100) {
|
|
165
165
|
let count = 0;
|
|
166
166
|
|
|
167
167
|
try {
|
|
@@ -173,10 +173,16 @@ export async function downloadDirectoryFromGitHub(relativePath, destPath, filter
|
|
|
173
173
|
if (item.type === 'file') {
|
|
174
174
|
const fileDest = path.join(destPath, item.name);
|
|
175
175
|
const success = await downloadFromGitHub(item.path, fileDest);
|
|
176
|
-
if (success)
|
|
176
|
+
if (success) {
|
|
177
|
+
count++;
|
|
178
|
+
// Add small delay between file downloads to be nice to GitHub
|
|
179
|
+
if (delayMs > 0) {
|
|
180
|
+
await delay(delayMs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
177
183
|
} else if (item.type === 'dir') {
|
|
178
184
|
const subDirDest = path.join(destPath, item.name);
|
|
179
|
-
const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter);
|
|
185
|
+
const subCount = await downloadDirectoryFromGitHub(item.path, subDirDest, filter, delayMs);
|
|
180
186
|
count += subCount;
|
|
181
187
|
}
|
|
182
188
|
}
|