Skip to content

Commit 063b37b

Browse files
[CLI] Raise explicit error for shell-script extensions on Windows (#4846)
* [CLI] Raise explicit error for shell-script extensions on Windows * [CLI] Surface install errors from extension auto-install * [CLI] Drop Windows-specific extension install tests
1 parent b9c14d9 commit 063b37b

3 files changed

Lines changed: 14 additions & 7 deletions

File tree

docs/source/en/guides/cli-extensions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ falls back to installing the repo as a Python package.
3939
A shell script extension is the simplest type. You only need a GitHub repository with an executable file
4040
named `hf-<name>` at the root.
4141

42+
> [!WARNING]
43+
> Shell script extensions are not supported on Windows. If your extension must work on Windows, make it a
44+
> [Python extension](#create-a-python-extension) instead.
45+
4246
### Minimal example
4347

4448
Create a repository named `hf-hello` on GitHub with a single file:

src/huggingface_hub/cli/extensions.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,8 @@ def _auto_install_official_extension(short_name: str) -> Path | None:
375375
out.confirm(f"'{short_name}' is an official Hugging Face extension ({owner}/{repo_name}). Install it?")
376376
except ConfirmationError:
377377
return None
378-
try:
379-
manifest = _install_extension(owner=owner, repo_name=repo_name, short_name=short_name)
380-
return Path(manifest.executable_path).expanduser()
381-
except Exception:
382-
return None
378+
manifest = _install_extension(owner=owner, repo_name=repo_name, short_name=short_name)
379+
return Path(manifest.executable_path).expanduser()
383380

384381

385382
def _load_installed_extension_for_update(name: str) -> ExtensionManifest:
@@ -435,6 +432,11 @@ def _install_extension(
435432
binary = None
436433

437434
if binary is not None:
435+
if os.name == "nt":
436+
raise CLIError(
437+
f"'{owner}/{repo_name}' is a shell-script extension, which is not supported on Windows. "
438+
"Only Python extensions can be installed on Windows."
439+
)
438440
executable_path = _install_binary_extension(
439441
extension_dir=extension_dir, short_name=short_name, binary=binary
440442
)
@@ -500,8 +502,7 @@ def _fetch_latest_commit_sha(*, owner: str, repo_name: str) -> str:
500502

501503

502504
def _fetch_remote_binary(*, owner: str, repo_name: str, short_name: str) -> bytes:
503-
executable_name = _get_executable_name(short_name)
504-
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/HEAD/{executable_name}"
505+
raw_url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/HEAD/hf-{short_name}"
505506
response = _github_request("GET", raw_url)
506507
return response.content
507508

tests/test_cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5140,6 +5140,7 @@ def test_unknown_command_spends_no_api_quota(self, github: _FakeGitHubSession) -
51405140
("Contribute to huggingface/hf-demo development by creating an account on GitHub.", None),
51415141
],
51425142
)
5143+
@pytest.mark.skipif(os.name == "nt", reason="Shell-script extensions are not supported on Windows.")
51435144
def test_install_uses_head_refs_and_a_single_api_call(
51445145
self, github: _FakeGitHubSession, about: str, expected_description: str | None
51455146
) -> None:
@@ -5161,6 +5162,7 @@ def test_install_uses_head_refs_and_a_single_api_call(
51615162
raw_urls = [url for url in github.urls if url.startswith(raw_prefix)]
51625163
assert raw_urls and all(url.removeprefix(raw_prefix).startswith("HEAD/") for url in raw_urls)
51635164

5165+
@pytest.mark.skipif(os.name == "nt", reason="Shell-script extensions are not supported on Windows.")
51645166
def test_install_completes_when_the_api_quota_is_exhausted(self, github: _FakeGitHubSession) -> None:
51655167
# The extension itself comes from the CDN, so only the optional version marker is lost.
51665168
github.responses = {

0 commit comments

Comments
 (0)