Skip to content

feat(install): enhance binary URL resolution with environment variabl…#690

Open
sang-neo03 wants to merge 4 commits intomainfrom
fix/install-respect-registry
Open

feat(install): enhance binary URL resolution with environment variabl…#690
sang-neo03 wants to merge 4 commits intomainfrom
fix/install-respect-registry

Conversation

@sang-neo03
Copy link
Copy Markdown
Collaborator

@sang-neo03 sang-neo03 commented Apr 28, 2026

Summary

Fix issue #640: enterprise intranet npm install -g @larksuite/cli --registry=<corp-mirror> fails because the postinstall script is hardcoded to registry.npmmirror.com for the
binary download. Add two user-controllable override channels (LARK_CLI_DOWNLOAD_HOST env var and the --registry value), with HTTPS-only protocol guard to prevent file:// /
http:// / ftp:// from bypassing the host allowlist.

Changes

  • scripts/install.js:
    • Replace the hardcoded MIRROR_URL with resolveMirrorUrl(). Resolution order: LARK_CLI_DOWNLOAD_HOST → non-default npm_config_registryregistry.npmmirror.com.
    • Default npmjs registry is skipped in step 2 (it does not host /-/binary/...).
    • Enforce https:// + non-empty hostname for LARK_CLI_DOWNLOAD_HOST (throw on bad input). Apply the same check to the implicit npm_config_registry path, but silently fall
      through on bad input to avoid breaking users with http-only internal registries that npm itself can still use.
    • Convert ALLOWED_HOSTS to a Set and add the resolved mirror host at runtime; checksum verification remains the primary integrity control.
    • Update the failure-help message with the two new override options.
  • scripts/install.test.js: add 13 unit tests covering the new resolver and HTTPS-only guard (file://, http://, ftp://, malformed URL, empty hostname, default-npmjs skip,
    http-registry fall-through, etc.).

Test Plan

  • Unit tests pass: node --test scripts/install.test.js → 29/29
  • Manual local verification:
    • LARK_CLI_DOWNLOAD_HOST=https://corp.example.com → resolved to https://corp.example.com/-/binary/lark-cli/v<ver>/<archive> and admitted by assertAllowedHost
    • LARK_CLI_DOWNLOAD_HOST=file:///tmp / http://... / ftp://... → rejected with LARK_CLI_DOWNLOAD_HOST must be an https:// URL with a hostname
    • npm_config_registry=https://registry.npmjs.org/ → does not derive, still uses npmmirror default
  • QA verification across macOS / Linux / Windows per the QA spec doc

Related Issues

Summary by CodeRabbit

  • New Features

    • Installer now builds an ordered list of download mirrors at runtime, honoring an explicit HTTPS download-host override, optionally using a non-default npm registry mirror, and trying mirrors in order until one succeeds.
    • Mirror hosts are normalized, de-duplicated, and empty/whitespace overrides are ignored.
  • Bug Fixes

    • Stricter validation: only well-formed HTTPS mirror URLs are accepted; unsupported schemes fall back to the default.
    • Improved error guidance covering proxy, registry, and direct download-host overrides.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The installer now computes an ordered mirror chain at runtime via resolveMirrorUrls: it prefers LARK_CLI_DOWNLOAD_HOST (when a valid HTTPS host), then a derived /-/binary/... from non-default npm_config_registry, and finally the hardcoded npmmirror fallback; the allowlist is a Set and resolved hosts are admitted dynamically; download attempts iterate mirrors until success and resolveMirrorUrls is exported.

Changes

Cohort / File(s) Summary
Binary download logic
scripts/install.js
Added and exported resolveMirrorUrls; build ordered download URL list (prefer LARK_CLI_DOWNLOAD_HOST, then derived non-default npm_config_registry, then registry.npmmirror.com); convert ALLOWED_HOSTS to Set, dynamically admit resolved hosts; change download flow to iterate downloadUrls and stop on first success; expand install failure guidance.
URL resolution tests
scripts/install.test.js
New unit tests for resolveMirrorUrls: default fallback behavior, skip public npmjs.org, ordering and de-duplication when npm_config_registry set, strip trailing slashes, treat valid LARK_CLI_DOWNLOAD_HOST as sole source, reject invalid/non-HTTPS hosts, fall back to default for non-https registries.

Sequence Diagram(s)

sequenceDiagram
  participant Installer as Installer
  participant Env as Environment (LARK_CLI_DOWNLOAD_HOST, npm_config_registry)
  participant Resolver as resolveMirrorUrls
  participant Network as Network / curl

  Installer->>Env: read env vars
  Installer->>Resolver: resolveMirrorUrls(env)
  Resolver-->>Installer: ordered mirror list (GitHub first, then mirrors)
  alt GitHub available
    Installer->>Network: download from GitHub
    Network-->>Installer: binary or error
  else iterate mirrors
    Installer->>Network: attempt download from mirror[0]
    alt success
      Network-->>Installer: binary
    else
      Installer->>Network: attempt mirror[1] ... mirror[N]
      Network-->>Installer: binary or final error
    end
  end
  alt final failure
    Installer-->>Installer: emit install guidance (--registry, LARK_CLI_DOWNLOAD_HOST, proxy tips)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

size/M, feature

Suggested reviewers

  • liangshuo-1

Poem

🐰 I sniffed the vars and chased each clue,
Built a mirror chain from hosts I knew,
I vet HTTPS and hop with care,
Try GitHub first, then mirrors in the air,
Binary found — I nibble a carrot! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is partially related to the changeset. It mentions 'enhance binary URL resolution with environment variable' but is truncated and doesn't fully convey the specific issue (enterprise registry support and protocol safety) being addressed.
Description check ✅ Passed The PR description comprehensively covers all required template sections: Summary clearly explains the motivation, Changes section lists concrete modifications, Test Plan details verification steps, and Related Issues links #640.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #640: respect explicit download overrides (LARK_CLI_DOWNLOAD_HOST and derived npm_config_registry), enforce HTTPS protocol, maintain safe fallback to npmmirror, and improve error messaging.
Out of Scope Changes check ✅ Passed All changes are scoped to solving issue #640 and are directly related to binary URL resolution. No unrelated refactoring, dependency updates, or out-of-scope modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/install-respect-registry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/M Single-domain feat or fix with limited business impact label Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/install.js`:
- Around line 42-49: The module currently computes MIRROR_URL at module scope
using resolveMirrorUrl(process.env, archiveName, VERSION), which can throw
before install() runs; move that computation into the guarded path inside the
install() function (or a helper invoked from install()), and inside the existing
try/catch add the derived host to ALLOWED_HOSTS using new
URL(mirrorUrl).hostname; remove the module-scope call to resolveMirrorUrl so no
URL parsing happens during module load and ensure archiveName and VERSION are
referenced from the same scope used by install().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a666a0c2-e820-42cb-a047-38cbbe2aee28

📥 Commits

Reviewing files that changed from the base of the PR and between c09b03f and 4f173b6.

📒 Files selected for processing (2)
  • scripts/install.js
  • scripts/install.test.js

Comment thread scripts/install.js Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.55%. Comparing base (c09b03f) to head (ebbcef8).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #690      +/-   ##
==========================================
+ Coverage   62.92%   63.55%   +0.63%     
==========================================
  Files         484      497      +13     
  Lines       41522    42455     +933     
==========================================
+ Hits        26127    26984     +857     
- Misses      13117    13129      +12     
- Partials     2278     2342      +64     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@ebbcef8e7172b3c3c1478fc87e797e47a651853a

🧩 Skill update

npx skills add larksuite/cli#fix/install-respect-registry -y -g

…dly errors

resolveMirrorUrl was called at module scope, so an invalid
LARK_CLI_DOWNLOAD_HOST (e.g. file://) threw before the try/catch in the
postinstall entrypoint, dumping a raw stack trace instead of the recovery
guidance with proxy/registry/host-override options.

Move resolution into install() via getMirrorUrl() so the throw is caught
and the user sees the actionable help text.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/install.js (1)

127-130: Prefer scoped allowlist admission over mutating global ALLOWED_HOSTS.

Adding the resolved host to a module-global Set makes host permissions sticky for the rest of the process. It’s safer and easier to test if mirror host admission is per-install invocation.

♻️ Suggested refactor (scoped host allowlist)
-function assertAllowedHost(url) {
+function assertAllowedHost(url, allowedHosts = ALLOWED_HOSTS) {
   const { hostname } = new URL(url);
-  if (!ALLOWED_HOSTS.has(hostname)) {
+  if (!allowedHosts.has(hostname)) {
     throw new Error(`Download host not allowed: ${hostname}`);
   }
 }

 function getMirrorUrl(env) {
   const mirrorUrl = resolveMirrorUrl(env, archiveName, VERSION);
-  ALLOWED_HOSTS.add(new URL(mirrorUrl).hostname);
-  return mirrorUrl;
+  return {
+    mirrorUrl,
+    mirrorHost: new URL(mirrorUrl).hostname,
+  };
 }

-function download(url, destPath) {
-  assertAllowedHost(url);
+function download(url, destPath, allowedHosts = ALLOWED_HOSTS) {
+  assertAllowedHost(url, allowedHosts);
   const args = [
@@
 function install() {
-  const mirrorUrl = getMirrorUrl(process.env);
+  const { mirrorUrl, mirrorHost } = getMirrorUrl(process.env);
+  const allowedHosts = new Set(ALLOWED_HOSTS);
+  allowedHosts.add(mirrorHost);
@@
-      download(GITHUB_URL, archivePath);
+      download(GITHUB_URL, archivePath, allowedHosts);
     } catch (err) {
-      download(mirrorUrl, archivePath);
+      download(mirrorUrl, archivePath, allowedHosts);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.js` around lines 127 - 130, The function getMirrorUrl
currently mutates the module-global ALLOWED_HOSTS by calling
ALLOWED_HOSTS.add(new URL(mirrorUrl).hostname); instead make host admission
scoped to the installation call: remove the global mutation from getMirrorUrl,
and either (a) change getMirrorUrl(resolveMirrorUrl, archiveName, VERSION) to
just return mirrorUrl and have the caller create a local Set and add new
URL(mirrorUrl).hostname to that local allowlist, or (b) change getMirrorUrl
signature to accept an allowlist Set param (e.g., allowlist) and add the
hostname to that Set instead of ALLOWED_HOSTS; update all callers to pass a
per-invocation Set so host permission is not sticky across the process. Ensure
references to resolveMirrorUrl, archiveName, and VERSION remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/install.js`:
- Around line 127-130: The function getMirrorUrl currently mutates the
module-global ALLOWED_HOSTS by calling ALLOWED_HOSTS.add(new
URL(mirrorUrl).hostname); instead make host admission scoped to the installation
call: remove the global mutation from getMirrorUrl, and either (a) change
getMirrorUrl(resolveMirrorUrl, archiveName, VERSION) to just return mirrorUrl
and have the caller create a local Set and add new URL(mirrorUrl).hostname to
that local allowlist, or (b) change getMirrorUrl signature to accept an
allowlist Set param (e.g., allowlist) and add the hostname to that Set instead
of ALLOWED_HOSTS; update all callers to pass a per-invocation Set so host
permission is not sticky across the process. Ensure references to
resolveMirrorUrl, archiveName, and VERSION remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5998d761-d6b7-456c-bf76-b2f78460c3b8

📥 Commits

Reviewing files that changed from the base of the PR and between 4f173b6 and a16506f.

📒 Files selected for processing (1)
  • scripts/install.js

@sang-neo03 sang-neo03 force-pushed the fix/install-respect-registry branch from a16506f to 8fbaace Compare April 28, 2026 11:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/install.js (1)

60-60: Avoid hardcoding package name in binary path.

resolveMirrorUrl uses a literal lark-cli even though NAME already defines it. Reusing NAME avoids drift if package naming changes.

Suggested refactor
-  const binaryPath = `/-/binary/lark-cli/v${version}/${archive}`;
+  const binaryPath = `/-/binary/${NAME}/v${version}/${archive}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.js` at line 60, The binaryPath construction hardcodes
"lark-cli" instead of reusing the package name constant; update the template
string in the binaryPath assignment to interpolate the existing NAME constant
(e.g., use `${NAME}`) so it reads the mirror path using NAME along with the
existing version and archive variables, and scan for any other occurrences of
the literal "lark-cli" in this module to replace with NAME for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/install.js`:
- Line 60: The binaryPath construction hardcodes "lark-cli" instead of reusing
the package name constant; update the template string in the binaryPath
assignment to interpolate the existing NAME constant (e.g., use `${NAME}`) so it
reads the mirror path using NAME along with the existing version and archive
variables, and scan for any other occurrences of the literal "lark-cli" in this
module to replace with NAME for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8745d0a-954d-41f0-a411-9e7beaf8f8b5

📥 Commits

Reviewing files that changed from the base of the PR and between a16506f and 8fbaace.

📒 Files selected for processing (1)
  • scripts/install.js

resolveMirrorUrl returned a single URL, so any non-default
npm_config_registry replaced the npmmirror fallback entirely. Corporate
npm proxies (Verdaccio, Artifactory, Nexus) often only serve npm package
metadata and don't host /-/binary/<pkg>/..., turning previously-working
installs into 404s when GitHub is unreachable.

Switch to resolveMirrorUrls returning an ordered chain:
  - LARK_CLI_DOWNLOAD_HOST set → [override] only (explicit user choice;
    no silent leak to npmmirror).
  - Otherwise → [derived_from_registry?, npmmirror_default]; npmmirror
    is always the final entry, restoring the pre-PR safety net.

install() now walks [GITHUB_URL, ...mirrorUrls] and stops at the first
success.
@github-actions github-actions Bot added size/L Large or sensitive change across domains or core paths and removed size/M Single-domain feat or fix with limited business impact labels Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/install.js`:
- Around line 47-49: The loop that builds download candidate URLs still prepends
GITHUB_URL even when LARK_CLI_DOWNLOAD_HOST is explicitly set, which causes
GitHub to be tried first; update the logic around where the candidate host list
is constructed (the code using LARK_CLI_DOWNLOAD_HOST and GITHUB_URL) so that
when process.env.LARK_CLI_DOWNLOAD_HOST (or the LARK_CLI_DOWNLOAD_HOST variable)
is present you do not prepend or include GITHUB_URL — only use the explicit host
(and any allowed fallbacks) so the GitHub attempt is skipped; locate the
host-list construction and loop that references GITHUB_URL and
LARK_CLI_DOWNLOAD_HOST and gate inclusion of GITHUB_URL behind the absence of
LARK_CLI_DOWNLOAD_HOST.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb864672-ca7b-48df-a2be-cb9274fc63f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbaace and 93e6081.

📒 Files selected for processing (2)
  • scripts/install.js
  • scripts/install.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/install.test.js

Comment thread scripts/install.js
The download loop unconditionally tried GITHUB_URL first, even when the
user explicitly named a download host. In locked-down networks, probing
github.com can trigger DLP / firewall alerts and contradicts the
explicit-override semantics ("use only this host, nothing else").

When LARK_CLI_DOWNLOAD_HOST is set, the chain is now just [override].
When it isn't, behavior is unchanged: [GITHUB_URL, derived?, npmmirror].
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/install.js (1)

69-69: Prefer NAME constant in binaryPath to avoid future drift.

This is currently correct, but using the existing constant makes renames safer.

♻️ Proposed small refactor
-  const binaryPath = `/-/binary/lark-cli/v${version}/${archive}`;
+  const binaryPath = `/-/binary/${NAME}/v${version}/${archive}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.js` at line 69, The path string for binaryPath currently
embeds a literal name; replace the literal with the existing NAME constant to
avoid drift: update the template used to build binaryPath (the variable
binaryPath that currently uses archive and version) so it references NAME
instead of the hard-coded segment (e.g., use `${NAME}` in the template alongside
version and archive), ensuring the final path format remains
`/-/binary/<NAME>/v${version}/${archive}`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/install.js`:
- Line 69: The path string for binaryPath currently embeds a literal name;
replace the literal with the existing NAME constant to avoid drift: update the
template used to build binaryPath (the variable binaryPath that currently uses
archive and version) so it references NAME instead of the hard-coded segment
(e.g., use `${NAME}` in the template alongside version and archive), ensuring
the final path format remains `/-/binary/<NAME>/v${version}/${archive}`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1b63f33-baea-4ac3-b2cb-65149e6cd8c4

📥 Commits

Reviewing files that changed from the base of the PR and between 93e6081 and ebbcef8.

📒 Files selected for processing (1)
  • scripts/install.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

通过企业内网源安装失败问题

1 participant