@jonathanleelx/meddle 0.4.0 → 0.4.1-beta.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/README.md +1 -1
- package/bin/commands/update.js +2 -1
- package/bin/lib/update-check.js +46 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ irm https://raw.githubusercontent.com/JonathanLee-LX/meddle/main/scripts/install
|
|
|
34
34
|
| Windows x64 | `meddle-windows-x64.exe` |
|
|
35
35
|
| Windows ARM64 | `meddle-windows-arm64.exe` |
|
|
36
36
|
|
|
37
|
-
二进制产物随 [GitHub Release](https://github.com/JonathanLee-LX/meddle/releases) 发布,也可手动下载。安装目录默认 `~/.meddle/bin
|
|
37
|
+
二进制产物随 [GitHub Release](https://github.com/JonathanLee-LX/meddle/releases) 发布,也可手动下载。安装目录默认 `~/.local/bin`(已有 `~/.meddle/bin` 旧安装时原地升级),可通过 `MEDDLE_BIN_DIR` 环境变量自定义。`meddle update` 始终更新到当前运行二进制所在目录。
|
|
38
38
|
|
|
39
39
|
### 命令
|
|
40
40
|
|
package/bin/commands/update.js
CHANGED
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
getAutoUpdate,
|
|
26
26
|
setAutoUpdate,
|
|
27
27
|
inferChannel,
|
|
28
|
+
resolveUpdateBinDir,
|
|
28
29
|
} = require('../lib/update-check')
|
|
29
30
|
|
|
30
31
|
const PACKAGE_NAME = '@jonathanleelx/meddle'
|
|
@@ -103,7 +104,7 @@ async function resolveLatest({ force = false, notify = true, channel = 'stable',
|
|
|
103
104
|
|
|
104
105
|
async function installVersion(version) {
|
|
105
106
|
if (!isNpmInstall()) {
|
|
106
|
-
const binDir =
|
|
107
|
+
const binDir = resolveUpdateBinDir({ home })
|
|
107
108
|
const destFile = path.join(binDir, os.platform() === 'win32' ? 'meddle.exe' : 'meddle')
|
|
108
109
|
output.info(`下载 meddle ${version} (${os.platform()}/${os.arch()})...`)
|
|
109
110
|
let progressShown = false
|
package/bin/lib/update-check.js
CHANGED
|
@@ -25,7 +25,7 @@ const DEFAULT_GITHUB_LATEST_URL = `https://github.com/${REPO}/releases/latest`
|
|
|
25
25
|
const DEFAULT_DOWNLOAD_BASE_URL = `https://github.com/${REPO}/releases/download`
|
|
26
26
|
const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
|
27
27
|
const DEFAULT_TIMEOUT_MS = 5000
|
|
28
|
-
const DEFAULT_DOWNLOAD_TIMEOUT_MS =
|
|
28
|
+
const DEFAULT_DOWNLOAD_TIMEOUT_MS = 120 * 1000
|
|
29
29
|
|
|
30
30
|
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/
|
|
31
31
|
|
|
@@ -305,8 +305,10 @@ async function downloadBinaryAsset(opts) {
|
|
|
305
305
|
const assetUrl = `${baseUrl}/v${version}/${asset}`
|
|
306
306
|
const shaUrl = `${assetUrl}.sha256`
|
|
307
307
|
|
|
308
|
-
// The binary is large (100MB+); the checksum sidecar is tiny. Use
|
|
309
|
-
// timeout for the payload
|
|
308
|
+
// The binary is large (100MB+); the checksum sidecar is tiny. Use an IDLE
|
|
309
|
+
// timeout for the payload (reset on every received chunk — a slow-but-steady
|
|
310
|
+
// transfer is never killed by a total wall-time budget) and the regular
|
|
311
|
+
// timeout for the metadata fetch.
|
|
310
312
|
const fetchPayload = async () => {
|
|
311
313
|
let lastError = null
|
|
312
314
|
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
@@ -314,8 +316,18 @@ async function downloadBinaryAsset(opts) {
|
|
|
314
316
|
await new Promise((r) => setTimeout(r, retryDelayMs * (attempt - 1)))
|
|
315
317
|
}
|
|
316
318
|
onAttempt(attempt, maxRetries + 1)
|
|
319
|
+
let idleTimer = null
|
|
320
|
+
const controller = new AbortController()
|
|
321
|
+
const resetIdle = () => {
|
|
322
|
+
if (idleTimer) clearTimeout(idleTimer)
|
|
323
|
+
idleTimer = setTimeout(() => {
|
|
324
|
+
controller.abort(new Error('download stalled (idle timeout)'))
|
|
325
|
+
}, payloadTimeout)
|
|
326
|
+
if (typeof idleTimer.unref === 'function') idleTimer.unref()
|
|
327
|
+
}
|
|
328
|
+
resetIdle()
|
|
317
329
|
try {
|
|
318
|
-
const response = await doFetch(assetUrl, { signal:
|
|
330
|
+
const response = await doFetch(assetUrl, { signal: controller.signal })
|
|
319
331
|
if (!response.ok) throw new Error(`download failed (${response.status})`)
|
|
320
332
|
const total = Number(response.headers.get('content-length')) || 0
|
|
321
333
|
const chunks = []
|
|
@@ -323,6 +335,7 @@ async function downloadBinaryAsset(opts) {
|
|
|
323
335
|
if (response.body && typeof response.body.getReader === 'function') {
|
|
324
336
|
const reader = response.body.getReader()
|
|
325
337
|
for (;;) {
|
|
338
|
+
resetIdle()
|
|
326
339
|
const { done, value } = await reader.read()
|
|
327
340
|
if (done) break
|
|
328
341
|
chunks.push(Buffer.from(value))
|
|
@@ -330,13 +343,16 @@ async function downloadBinaryAsset(opts) {
|
|
|
330
343
|
onProgress({ received, total })
|
|
331
344
|
}
|
|
332
345
|
} else {
|
|
346
|
+
resetIdle()
|
|
333
347
|
const buf = Buffer.from(await response.arrayBuffer())
|
|
334
348
|
chunks.push(buf)
|
|
335
349
|
received = buf.length
|
|
336
350
|
onProgress({ received, total })
|
|
337
351
|
}
|
|
352
|
+
if (idleTimer) clearTimeout(idleTimer)
|
|
338
353
|
return Buffer.concat(chunks)
|
|
339
354
|
} catch (err) {
|
|
355
|
+
if (idleTimer) clearTimeout(idleTimer)
|
|
340
356
|
lastError = err
|
|
341
357
|
if (attempt > maxRetries) break
|
|
342
358
|
}
|
|
@@ -344,11 +360,16 @@ async function downloadBinaryAsset(opts) {
|
|
|
344
360
|
throw lastError || new Error(`download failed for ${assetUrl}`)
|
|
345
361
|
}
|
|
346
362
|
|
|
363
|
+
// Fetch the tiny checksum sidecar and CONSUME its body right away. The
|
|
364
|
+
// sidecar timeout must not span the (potentially multi-minute) payload
|
|
365
|
+
// download — otherwise the sidecar body stream aborts before it is read
|
|
366
|
+
// and the whole install fails with "operation aborted" right after the
|
|
367
|
+
// payload reached 100%.
|
|
347
368
|
const shaResponse = await doFetch(shaUrl, { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) })
|
|
348
369
|
if (!shaResponse.ok) throw new Error(`checksum sidecar missing (${shaResponse.status})`)
|
|
370
|
+
const shaText = await shaResponse.text()
|
|
349
371
|
|
|
350
372
|
const payload = await fetchPayload()
|
|
351
|
-
const shaText = await shaResponse.text()
|
|
352
373
|
const expected = shaText.trim().split(/\s+/)[0]
|
|
353
374
|
if (!/^[0-9a-f]{64}$/i.test(expected)) throw new Error(`invalid checksum sidecar`)
|
|
354
375
|
if (sha256Hex(payload) !== expected.toLowerCase()) {
|
|
@@ -433,6 +454,24 @@ function printUpdateNotice(info) {
|
|
|
433
454
|
)
|
|
434
455
|
}
|
|
435
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Resolve where `meddle update` should install the new binary.
|
|
459
|
+
* 1. MEDDLE_BIN_DIR env (explicit override)
|
|
460
|
+
* 2. the directory of the RUNNING binary — in-place updates, so installs on
|
|
461
|
+
* legacy (~/.meddle/bin) or custom paths stay where they are
|
|
462
|
+
* 3. <home>/bin fallback (npm installs / no running binary)
|
|
463
|
+
* @param {{ home: string, execPath?: string, platform?: string }} opts
|
|
464
|
+
* @returns {string}
|
|
465
|
+
*/
|
|
466
|
+
function resolveUpdateBinDir({ home, execPath = process.execPath, platform = os.platform() } = {}) {
|
|
467
|
+
if (process.env.MEDDLE_BIN_DIR) return process.env.MEDDLE_BIN_DIR
|
|
468
|
+
const p = platform === 'win32' ? path.win32 : path
|
|
469
|
+
const exeName = p.basename(execPath).toLowerCase()
|
|
470
|
+
const expected = platform === 'win32' ? 'meddle.exe' : 'meddle'
|
|
471
|
+
if (exeName === expected) return p.dirname(execPath)
|
|
472
|
+
return p.join(home, 'bin')
|
|
473
|
+
}
|
|
474
|
+
|
|
436
475
|
/**
|
|
437
476
|
* Fire-and-forget startup check. Never blocks, never throws, never keeps the
|
|
438
477
|
* process alive. When auto-update is enabled (binary installs) the new binary
|
|
@@ -455,7 +494,7 @@ function isSameBinary(a, b) {
|
|
|
455
494
|
function runAsyncUpdateCheck(opts) {
|
|
456
495
|
const home = opts.home || resolveMeddleHome()
|
|
457
496
|
const installMethod = opts.installMethod || getInstallMethod({ moduleDir: __dirname })
|
|
458
|
-
const binDir = opts.binDir ||
|
|
497
|
+
const binDir = opts.binDir || resolveUpdateBinDir({ home, execPath: opts.execPath || process.execPath })
|
|
459
498
|
const delayMs = opts.delayMs === undefined ? 1500 : opts.delayMs
|
|
460
499
|
const execPath = opts.execPath || process.execPath
|
|
461
500
|
const exeBase = path.basename(execPath).toLowerCase()
|
|
@@ -518,6 +557,7 @@ module.exports = {
|
|
|
518
557
|
downloadBinaryAsset,
|
|
519
558
|
getAutoUpdate,
|
|
520
559
|
setAutoUpdate,
|
|
560
|
+
resolveUpdateBinDir,
|
|
521
561
|
runAsyncUpdateCheck,
|
|
522
562
|
DEFAULT_DOWNLOAD_TIMEOUT_MS,
|
|
523
563
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jonathanleelx/meddle",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1-beta.2",
|
|
4
4
|
"description": "A simple development proxy server with custom routing rules, mock responses, HTTPS interception, and a plugin system",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "index.js",
|