freebuff 0.0.121 → 0.0.122
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/http.js +44 -2
- package/index.js +120 -64
- package/package.json +1 -1
package/http.js
CHANGED
|
@@ -142,13 +142,29 @@ function createReleaseHttpClient({
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
async function httpGet(url, options = {}) {
|
|
145
|
+
const redirectCount = options.redirectCount || 0
|
|
145
146
|
const reqOptions = await buildRequestOptions(url, options)
|
|
146
147
|
|
|
147
148
|
return new Promise((resolve, reject) => {
|
|
148
149
|
const req = httpsModule.get(reqOptions, (res) => {
|
|
149
|
-
if (
|
|
150
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode)) {
|
|
150
151
|
res.resume()
|
|
151
|
-
|
|
152
|
+
|
|
153
|
+
if (!res.headers.location) {
|
|
154
|
+
reject(
|
|
155
|
+
new Error(`Redirect ${res.statusCode} missing Location header.`),
|
|
156
|
+
)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (redirectCount >= (options.maxRedirects ?? 10)) {
|
|
160
|
+
reject(new Error('Too many redirects.'))
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
httpGet(new URL(res.headers.location, url).href, {
|
|
165
|
+
...options,
|
|
166
|
+
redirectCount: redirectCount + 1,
|
|
167
|
+
})
|
|
152
168
|
.then(resolve)
|
|
153
169
|
.catch(reject)
|
|
154
170
|
return
|
|
@@ -165,9 +181,35 @@ function createReleaseHttpClient({
|
|
|
165
181
|
})
|
|
166
182
|
}
|
|
167
183
|
|
|
184
|
+
async function withRetries(
|
|
185
|
+
operation,
|
|
186
|
+
{
|
|
187
|
+
maxAttempts = 1,
|
|
188
|
+
baseDelayMs = 1000,
|
|
189
|
+
shouldRetry = () => true,
|
|
190
|
+
onRetry = () => {},
|
|
191
|
+
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
192
|
+
},
|
|
193
|
+
) {
|
|
194
|
+
for (let attempt = 1; ; attempt++) {
|
|
195
|
+
try {
|
|
196
|
+
return await operation(attempt)
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (attempt >= maxAttempts || !shouldRetry(error)) {
|
|
199
|
+
throw error
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const delayMs = baseDelayMs * 2 ** (attempt - 1)
|
|
203
|
+
await onRetry({ error, attempt, nextAttempt: attempt + 1, delayMs })
|
|
204
|
+
await sleep(delayMs)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
168
209
|
return {
|
|
169
210
|
getProxyUrl,
|
|
170
211
|
httpGet,
|
|
212
|
+
withRetries,
|
|
171
213
|
}
|
|
172
214
|
}
|
|
173
215
|
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const http = require('http')
|
|
|
6
6
|
const https = require('https')
|
|
7
7
|
const os = require('os')
|
|
8
8
|
const path = require('path')
|
|
9
|
+
const { pipeline } = require('stream/promises')
|
|
9
10
|
const zlib = require('zlib')
|
|
10
11
|
|
|
11
12
|
const tar = require('tar')
|
|
@@ -99,11 +100,13 @@ function createConfig(packageName) {
|
|
|
99
100
|
tempDownloadDir: path.join(configDir, '.freebuff-download-temp'),
|
|
100
101
|
userAgent: `${packageName}-cli`,
|
|
101
102
|
requestTimeout: 20000,
|
|
103
|
+
downloadRequestTimeout: 120000,
|
|
104
|
+
downloadMaxAttempts: 3,
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
const CONFIG = createConfig(packageName)
|
|
106
|
-
const {
|
|
109
|
+
const { httpGet, withRetries } = createReleaseHttpClient({
|
|
107
110
|
env: process.env,
|
|
108
111
|
userAgent: CONFIG.userAgent,
|
|
109
112
|
requestTimeout: CONFIG.requestTimeout,
|
|
@@ -509,79 +512,142 @@ function createProgressBar(percentage, width = 30) {
|
|
|
509
512
|
return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']'
|
|
510
513
|
}
|
|
511
514
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const downloadUrl = `${
|
|
522
|
-
process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
|
|
523
|
-
}/api/releases/download/${version}/${fileName}`
|
|
515
|
+
function isRetryableDownloadStatus(statusCode) {
|
|
516
|
+
return (
|
|
517
|
+
statusCode === 408 ||
|
|
518
|
+
statusCode === 425 ||
|
|
519
|
+
statusCode === 429 ||
|
|
520
|
+
statusCode >= 500
|
|
521
|
+
)
|
|
522
|
+
}
|
|
524
523
|
|
|
525
|
-
|
|
524
|
+
function isRetryableDownloadError(error) {
|
|
525
|
+
if (error && typeof error.retryable === 'boolean') return error.retryable
|
|
526
|
+
return !['EACCES', 'ENOSPC', 'EPERM', 'EROFS'].includes(error?.code)
|
|
527
|
+
}
|
|
526
528
|
|
|
529
|
+
function prepareTempDownloadDir() {
|
|
527
530
|
if (fs.existsSync(CONFIG.tempDownloadDir)) {
|
|
528
531
|
fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
529
532
|
}
|
|
530
533
|
fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
534
|
+
}
|
|
531
535
|
|
|
532
|
-
|
|
536
|
+
async function downloadAndExtract(downloadUrl, version, targetKey) {
|
|
537
|
+
let attempts = 0
|
|
533
538
|
|
|
534
|
-
|
|
539
|
+
try {
|
|
540
|
+
return await withRetries(
|
|
541
|
+
async (attempt) => {
|
|
542
|
+
attempts = attempt
|
|
543
|
+
prepareTempDownloadDir()
|
|
544
|
+
term.write('Downloading...')
|
|
545
|
+
|
|
546
|
+
const res = await httpGet(downloadUrl, {
|
|
547
|
+
timeout: CONFIG.downloadRequestTimeout,
|
|
548
|
+
})
|
|
535
549
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
550
|
+
if (res.statusCode !== 200) {
|
|
551
|
+
res.resume()
|
|
552
|
+
const error = new Error(`Download failed: HTTP ${res.statusCode}`)
|
|
553
|
+
error.statusCode = res.statusCode
|
|
554
|
+
error.retryable = isRetryableDownloadStatus(res.statusCode)
|
|
555
|
+
throw error
|
|
556
|
+
}
|
|
542
557
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
const totalSize = parseInt(res.headers['content-length'] || '0', 10)
|
|
559
|
+
let downloadedSize = 0
|
|
560
|
+
let lastProgressTime = Date.now()
|
|
561
|
+
|
|
562
|
+
res.on('data', (chunk) => {
|
|
563
|
+
downloadedSize += chunk.length
|
|
564
|
+
const now = Date.now()
|
|
565
|
+
if (now - lastProgressTime >= 100 || downloadedSize === totalSize) {
|
|
566
|
+
lastProgressTime = now
|
|
567
|
+
if (totalSize > 0) {
|
|
568
|
+
const pct = Math.round((downloadedSize / totalSize) * 100)
|
|
569
|
+
term.write(
|
|
570
|
+
`Downloading... ${createProgressBar(pct)} ${pct}% of ${formatBytes(
|
|
571
|
+
totalSize,
|
|
572
|
+
)}`,
|
|
573
|
+
)
|
|
574
|
+
} else {
|
|
575
|
+
term.write(`Downloading... ${formatBytes(downloadedSize)}`)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
await pipeline(
|
|
581
|
+
res,
|
|
582
|
+
zlib.createGunzip(),
|
|
583
|
+
tar.x({ cwd: CONFIG.tempDownloadDir }),
|
|
558
584
|
)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
585
|
+
|
|
586
|
+
const tempBinaryPath = path.join(
|
|
587
|
+
CONFIG.tempDownloadDir,
|
|
588
|
+
CONFIG.binaryName,
|
|
589
|
+
)
|
|
590
|
+
if (!fs.existsSync(tempBinaryPath)) {
|
|
591
|
+
const files = fs.readdirSync(CONFIG.tempDownloadDir)
|
|
592
|
+
const error = new Error(
|
|
593
|
+
`Binary not found after extraction. Expected: ${CONFIG.binaryName}, Available files: ${files.join(', ')}`,
|
|
594
|
+
)
|
|
595
|
+
error.retryable = false
|
|
596
|
+
throw error
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return tempBinaryPath
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
maxAttempts: CONFIG.downloadMaxAttempts,
|
|
603
|
+
shouldRetry: isRetryableDownloadError,
|
|
604
|
+
onRetry: ({ nextAttempt, delayMs }) => {
|
|
605
|
+
term.writeLine(
|
|
606
|
+
`Download interrupted. Retrying in ${delayMs / 1000}s (${nextAttempt}/${CONFIG.downloadMaxAttempts})...`,
|
|
607
|
+
)
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
)
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (fs.existsSync(CONFIG.tempDownloadDir)) {
|
|
613
|
+
fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
562
614
|
}
|
|
563
|
-
})
|
|
564
615
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
616
|
+
trackUpdateFailed(error.message, version, {
|
|
617
|
+
stage: 'download',
|
|
618
|
+
statusCode: error.statusCode,
|
|
619
|
+
target: targetKey,
|
|
620
|
+
attempts,
|
|
621
|
+
})
|
|
622
|
+
throw error
|
|
623
|
+
}
|
|
624
|
+
}
|
|
572
625
|
|
|
573
|
-
|
|
626
|
+
async function downloadBinary(version, targetKey = getDownloadTargetKey()) {
|
|
627
|
+
const fileName = PLATFORM_TARGETS[targetKey]
|
|
574
628
|
|
|
575
|
-
if (!
|
|
576
|
-
const files = fs.readdirSync(CONFIG.tempDownloadDir)
|
|
577
|
-
fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
629
|
+
if (!fileName) {
|
|
578
630
|
const error = new Error(
|
|
579
|
-
`
|
|
631
|
+
`Unsupported platform: ${process.platform} ${process.arch}`,
|
|
580
632
|
)
|
|
581
|
-
trackUpdateFailed(error.message, version, {
|
|
633
|
+
trackUpdateFailed(error.message, version, {
|
|
634
|
+
stage: 'platform_check',
|
|
635
|
+
target: targetKey,
|
|
636
|
+
})
|
|
582
637
|
throw error
|
|
583
638
|
}
|
|
584
639
|
|
|
640
|
+
const downloadUrl = `${
|
|
641
|
+
process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
|
|
642
|
+
}/api/releases/download/${version}/${fileName}`
|
|
643
|
+
|
|
644
|
+
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
645
|
+
const tempBinaryPath = await downloadAndExtract(
|
|
646
|
+
downloadUrl,
|
|
647
|
+
version,
|
|
648
|
+
targetKey,
|
|
649
|
+
)
|
|
650
|
+
|
|
585
651
|
if (process.platform !== 'win32') {
|
|
586
652
|
fs.chmodSync(tempBinaryPath, 0o755)
|
|
587
653
|
}
|
|
@@ -650,11 +716,6 @@ async function ensureBinaryExists() {
|
|
|
650
716
|
if (!version) {
|
|
651
717
|
console.error('❌ Failed to determine latest version')
|
|
652
718
|
console.error('Please check your internet connection and try again')
|
|
653
|
-
if (!getProxyUrl()) {
|
|
654
|
-
console.error(
|
|
655
|
-
'If you are behind a proxy, set the HTTPS_PROXY environment variable',
|
|
656
|
-
)
|
|
657
|
-
}
|
|
658
719
|
process.exit(1)
|
|
659
720
|
}
|
|
660
721
|
|
|
@@ -664,11 +725,6 @@ async function ensureBinaryExists() {
|
|
|
664
725
|
term.clearLine()
|
|
665
726
|
console.error('❌ Failed to download freebuff:', error.message)
|
|
666
727
|
console.error('Please check your internet connection and try again')
|
|
667
|
-
if (!getProxyUrl()) {
|
|
668
|
-
console.error(
|
|
669
|
-
'If you are behind a proxy, set the HTTPS_PROXY environment variable',
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
728
|
process.exit(1)
|
|
673
729
|
}
|
|
674
730
|
}
|