From 9ca759bd11fb3573eef3814cd8eb893ed054fc69 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Tue, 12 May 2026 12:10:29 -0500 Subject: [PATCH] fix(npm): show timeout hint on first retry (#1538) Co-Authored-By: jieshu666 --- docs/INSTALL.md | 35 ++++++++++++++++++++ npm/deepseek-tui/scripts/install.js | 8 ++++- npm/deepseek-tui/test/install.test.js | 1 + npm/deepseek-tui/test/postinstall.test.js | 39 +++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 0c482145..21e85485 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -439,6 +439,41 @@ target/debug/build/libsqlite3-sys-*/build-script-build # fine — the AV is blocking Cargo's process-spawning path specifically. ``` +### npm binary download times out + +If `deepseek` waits several seconds and prints `connect ETIMEDOUT` or +`EAI_AGAIN` while fetching from `github.com`, the npm wrapper installed +successfully but the prebuilt binary download from GitHub Releases is blocked +or unreliable on your network. This download is separate from the npm registry +package download. + +Use one of these paths: + +1. Set a proxy and retry: + + ```bash + export HTTPS_PROXY=http://your-proxy:port + deepseek + ``` + +2. Mirror the release assets internally and set `DEEPSEEK_TUI_RELEASE_BASE_URL`: + + ```bash + export DEEPSEEK_TUI_RELEASE_BASE_URL=https://your-mirror.example.com/DeepSeek-TUI/ + deepseek + ``` + + The directory must contain `deepseek-artifacts-sha256.txt` and the platform + binaries from the GitHub release. + +3. Install via Cargo, which builds locally and does not download GitHub release + assets. See [Section 3](#3-install-via-cargo-any-tier-1-rust-target). + +4. Download both `deepseek` and `deepseek-tui` manually from the + [Releases page](https://github.com/Hmbown/DeepSeek-TUI/releases), place them + in a directory on `PATH`, and make them executable. See + [Section 4](#4-manual-download-from-github-releases). + --- ## 7. Verifying your install diff --git a/npm/deepseek-tui/scripts/install.js b/npm/deepseek-tui/scripts/install.js index 884aa311..17d20c5d 100644 --- a/npm/deepseek-tui/scripts/install.js +++ b/npm/deepseek-tui/scripts/install.js @@ -195,7 +195,7 @@ function installFailureHint(error) { " If GitHub is unavailable on this network, mirror the release assets and set:", " DEEPSEEK_TUI_RELEASE_BASE_URL=https:////", " The directory must contain deepseek-artifacts-sha256.txt and the platform binaries.", - " See docs/INSTALL.md#npm-download-is-slow-or-times-out-from-mainland-china.", + " See docs/INSTALL.md#npm-binary-download-times-out.", ].join("\n"); } @@ -790,6 +790,12 @@ async function withRetry(label, fn, context = "runtime") { logInfo( `${label} failed (attempt ${attempt}/${attemptLimit}): ${err.message}; retrying in ${wait} ms`, ); + if (attempt === 1) { + const hint = installFailureHint(err); + if (hint) { + process.stderr.write(`${hint}\n`); + } + } await sleep(wait); } } diff --git a/npm/deepseek-tui/test/install.test.js b/npm/deepseek-tui/test/install.test.js index 3753f015..b14c9200 100644 --- a/npm/deepseek-tui/test/install.test.js +++ b/npm/deepseek-tui/test/install.test.js @@ -39,6 +39,7 @@ test("install failure hint explains release base override for blocked GitHub dow assert.match(hint, /DEEPSEEK_TUI_RELEASE_BASE_URL/); assert.match(hint, /deepseek-artifacts-sha256\.txt/); assert.match(hint, /platform binaries/); + assert.match(hint, /#npm-binary-download-times-out/); } finally { if (previous === undefined) { delete process.env.DEEPSEEK_TUI_RELEASE_BASE_URL; diff --git a/npm/deepseek-tui/test/postinstall.test.js b/npm/deepseek-tui/test/postinstall.test.js index 5b72f14c..10e1ac7e 100644 --- a/npm/deepseek-tui/test/postinstall.test.js +++ b/npm/deepseek-tui/test/postinstall.test.js @@ -89,3 +89,42 @@ test("optional install still swallows wrapped http 5xx failures", async () => { } } }); + +test("withRetry prints install hint on first retryable failure", async () => { + const previousWrite = process.stderr.write; + const previousSetTimeout = global.setTimeout; + let stderr = ""; + let attempts = 0; + process.stderr.write = (chunk) => { + stderr += String(chunk); + return true; + }; + global.setTimeout = (callback) => { + callback(); + return 0; + }; + + try { + const result = await _internal.withRetry( + "fetch https://github.com/example", + async () => { + attempts += 1; + if (attempts === 1) { + const err = new Error("connect ETIMEDOUT 20.205.243.166:443"); + err.code = "ETIMEDOUT"; + throw err; + } + return "ok"; + }, + "runtime", + ); + + assert.equal(result, "ok"); + assert.equal(attempts, 2); + assert.match(stderr, /deepseek-tui install hint:/); + assert.match(stderr, /#npm-binary-download-times-out/); + } finally { + process.stderr.write = previousWrite; + global.setTimeout = previousSetTimeout; + } +});