@jonathanleelx/meddle 0.4.0 → 0.4.1-beta.1

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 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`,可通过 `MEDDLE_BIN_DIR` 环境变量自定义。
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
 
@@ -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 = process.env.MEDDLE_BIN_DIR || path.join(home, 'bin')
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
@@ -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 = 5 * 60 * 1000
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 a long
309
- // timeout for the payload and the regular timeout for the metadata fetch.
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: AbortSignal.timeout(payloadTimeout) })
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
  }
@@ -433,6 +449,24 @@ function printUpdateNotice(info) {
433
449
  )
434
450
  }
435
451
 
452
+ /**
453
+ * Resolve where `meddle update` should install the new binary.
454
+ * 1. MEDDLE_BIN_DIR env (explicit override)
455
+ * 2. the directory of the RUNNING binary — in-place updates, so installs on
456
+ * legacy (~/.meddle/bin) or custom paths stay where they are
457
+ * 3. <home>/bin fallback (npm installs / no running binary)
458
+ * @param {{ home: string, execPath?: string, platform?: string }} opts
459
+ * @returns {string}
460
+ */
461
+ function resolveUpdateBinDir({ home, execPath = process.execPath, platform = os.platform() } = {}) {
462
+ if (process.env.MEDDLE_BIN_DIR) return process.env.MEDDLE_BIN_DIR
463
+ const p = platform === 'win32' ? path.win32 : path
464
+ const exeName = p.basename(execPath).toLowerCase()
465
+ const expected = platform === 'win32' ? 'meddle.exe' : 'meddle'
466
+ if (exeName === expected) return p.dirname(execPath)
467
+ return p.join(home, 'bin')
468
+ }
469
+
436
470
  /**
437
471
  * Fire-and-forget startup check. Never blocks, never throws, never keeps the
438
472
  * process alive. When auto-update is enabled (binary installs) the new binary
@@ -455,7 +489,7 @@ function isSameBinary(a, b) {
455
489
  function runAsyncUpdateCheck(opts) {
456
490
  const home = opts.home || resolveMeddleHome()
457
491
  const installMethod = opts.installMethod || getInstallMethod({ moduleDir: __dirname })
458
- const binDir = opts.binDir || process.env.MEDDLE_BIN_DIR || path.join(home, 'bin')
492
+ const binDir = opts.binDir || resolveUpdateBinDir({ home, execPath: opts.execPath || process.execPath })
459
493
  const delayMs = opts.delayMs === undefined ? 1500 : opts.delayMs
460
494
  const execPath = opts.execPath || process.execPath
461
495
  const exeBase = path.basename(execPath).toLowerCase()
@@ -518,6 +552,7 @@ module.exports = {
518
552
  downloadBinaryAsset,
519
553
  getAutoUpdate,
520
554
  setAutoUpdate,
555
+ resolveUpdateBinDir,
521
556
  runAsyncUpdateCheck,
522
557
  DEFAULT_DOWNLOAD_TIMEOUT_MS,
523
558
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonathanleelx/meddle",
3
- "version": "0.4.0",
3
+ "version": "0.4.1-beta.1",
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",