Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- **Breaking:** `cloudsmith login`, `cloudsmith logout`, and `--save-config` on `cloudsmith auth` and `cloudsmith tokens` now set `credentials.ini` to mode `0600`. Only your user can read or change the file and its API key. Previously, the CLI created the file with mode `0660`, so all members of your group could read and change the API key. When the CLI writes an existing `credentials.ini` that you own, it also sets the mode to `0600`. The CLI does not change the mode when it only reads the file. Other users in your group can no longer read a `credentials.ini` that the CLI writes. On Windows, the CLI does not change the access to the file.
- `cloudsmith copy`, `move`, and `resync` with `--skip-errors` now ignore an API error, the same as `cloudsmith push`. The CLI prints the error one time, then `Skipping error and moving on.`, and exits with code 0. Previously, the CLI also printed the error a second time as an unexpected error (`Error: ApiException: ...`), sent an error report, and exited with code 1.
- The `--skip-errors` help text no longer says "when copying packages" for `move`, `resync`, and `push`.
- An empty optional trailing part in an `OWNER/REPO` argument now means that you did not give the part. For example, `cloudsmith repos get myorg/` lists the repositories in `myorg`. Before, it requested a repository with an empty name and printed a row of `None` values. `cloudsmith metrics entitlements myorg/` now works the same as `myorg`, and a `push` target such as `myorg/myrepo/ubuntu/` works the same as `myorg/myrepo/ubuntu`. An empty `OWNER` before a `REPO`, such as `/myrepo`, now gives a usage error.
- `cloudsmith credential-helper install pnpm`, `uninstall pnpm`, and `list` now read and write `.npmrc` as UTF-8, as npm and pnpm do. On Windows, a home directory with characters such as Cyrillic or CJK letters caused a `UnicodeEncodeError`, and UTF-8 text in `.npmrc` was read incorrectly. The CLI keeps bytes that are not UTF-8 unchanged, and it reads a `.npmrc` that starts with a UTF-8 byte order mark.

## [1.30.0] - 2026-10-22
Expand Down
24 changes: 24 additions & 0 deletions cloudsmith_cli/cli/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Tests for the OWNER/REPO path validators."""

import click
import pytest

from ..validators import validate_optional_owner_repo, validate_owner_repo


def test_optional_owner_repo_drops_empty_trailing_repo():
assert validate_optional_owner_repo(None, None, "myorg/") == ["myorg"]


def test_optional_owner_repo_treats_all_empty_parts_as_not_given():
assert validate_optional_owner_repo(None, None, "/") is None


def test_optional_owner_repo_rejects_blank_owner():
with pytest.raises(click.BadParameter, match="cannot be blank"):
validate_optional_owner_repo(None, None, "/myrepo")


def test_required_repo_left_blank_raises():
with pytest.raises(click.BadParameter, match="cannot be blank"):
validate_owner_repo(None, None, "myorg/")
24 changes: 17 additions & 7 deletions cloudsmith_cli/cli/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,12 @@ def validate_untrusted_proxy(proxy, allowed_suffixes=()):


def validate_slashes(
param, value, minimum=2, maximum=None, form=None, allow_blank=False
):
param: click.Parameter | None,
value: str,
minimum: int = 2,
maximum: int | None = None,
form: str | None = None,
) -> list[str]:
"""Ensure that parameter has slashes and minimum parts."""
try:
value = value.split("/")
Expand All @@ -174,20 +178,26 @@ def validate_slashes(
raise click.BadParameter(f"Must be in the form of {form}", param=param)

value = [v.strip() for v in value]
if not allow_blank and not all(value):
# Shell completion leaves "OWNER/", so an empty optional trailing part is not given.
while len(value) > minimum and not value[-1]:
value.pop()

if not all(value):
raise click.BadParameter("Individual values cannot be blank", param=param)

return value


def validate_optional_owner_repo(ctx, param, value):
def validate_optional_owner_repo(
ctx: click.Context | None, param: click.Parameter | None, value: str | None
) -> list[str] | None:
"""Ensure that owner/repo is formatted correctly, where owner and repo are optional."""
# pylint: disable=unused-argument
form = "OWNER/REPO"
if value is None:
return None

return validate_slashes(
param, value, minimum=0, maximum=2, form=form, allow_blank=True
)
return validate_slashes(param, value, minimum=0, maximum=2, form=form) or None


def validate_required_owner_optional_repo(ctx, param, value):
Expand Down
Loading