Skip to content

feat(download): add --filename and --download-all options#270

Open
opswithranjan wants to merge 4 commits intomasterfrom
ceng-674-cloudsmith-cli-download-command-to-support-filename-or-wider
Open

feat(download): add --filename and --download-all options#270
opswithranjan wants to merge 4 commits intomasterfrom
ceng-674-cloudsmith-cli-download-command-to-support-filename-or-wider

Conversation

@opswithranjan
Copy link
Member

Support downloading packages when multiple share the same name/version
but differ by filename (e.g., .nupkg vs .snupkg).

  • Add --filename option with glob pattern support (fnmatch)
  • Add --download-all flag to download all matching packages
  • Extract _search_packages() shared search logic in core
  • Add resolve_all_packages() for multi-package resolution
  • Add Filename column to multiple packages disambiguation table
  • Update error messages to suggest --filename and --download-all
  • Add unit and integration tests for new functionality
  • Update README with new download examples

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

@opswithranjan opswithranjan requested a review from a team as a code owner March 9, 2026 13:33
Copilot AI review requested due to automatic review settings March 9, 2026 13:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds new download disambiguation capabilities so users can select packages by filename (including glob patterns) and optionally download all matching packages when multiple artifacts share the same name/version.

Changes:

  • Added --filename filtering (exact match via query; glob via client-side fnmatch) and a resolve_all_packages() resolver.
  • Added --download-all mode to download every resolved match and enhanced multi-match output (table includes a Filename column).
  • Updated tests and documentation (README + changelog) to cover the new CLI behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
cloudsmith_cli/core/download.py Extracts shared search logic into _search_packages(), adds filename filtering, adds resolve_all_packages(), and updates multi-match display.
cloudsmith_cli/cli/commands/download.py Adds --filename and --download-all options and implements multi-package download flow.
cloudsmith_cli/core/tests/test_download.py Adds unit tests for filename filtering, multi-resolution, and updated error/table output.
cloudsmith_cli/cli/tests/commands/test_download.py Adds integration-style tests for --filename and --download-all behavior.
README.md Adds download examples for --filename and --download-all (but also removes the logout entry from the command list).
CHANGELOG.md Records the new options and table update, but converts Unreleased into a versioned 1.14.0 entry.
Comments suppressed due to low confidence (1)

cloudsmith_cli/cli/commands/download.py:65

  • The --outfile help text says it's an output file path, but --download-all treats it as an output directory (and --all-files already uses it as a directory). This is user-facing and can be confusing; please update the option help (and/or validation) to clearly document the different semantics by mode, or introduce a separate --output-dir for multi-file/multi-package downloads.
@click.option(
    "--outfile",
    type=click.Path(),
    help="Output file path. If not specified, uses the package filename.",
)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@estebangarcia estebangarcia left a comment

Choose a reason for hiding this comment

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

I feel like this could use with a bit of a refactoring.

We have too many branches going on here. Whether one or many, we always want to do 2 things:

  1. Find the package(s) to download
  2. Download it/them

I would suggest refactoring this to break it down into those 2 steps, regardless of whether you are downloading one or many packages.

@opswithranjan opswithranjan force-pushed the ceng-674-cloudsmith-cli-download-command-to-support-filename-or-wider branch from 7f5ddf8 to 4c38f2d Compare March 13, 2026 11:59
Comment on lines 560 to +585
@@ -440,20 +576,18 @@ def _get_extension_for_format(pkg_format: str) -> str:
return format_extensions.get(pkg_format.lower(), "bin")


def _format_package_size(package: dict) -> str:
def _format_package_size(package):
"""Format package size for display."""
size = package.get("size", 0)
return _format_file_size(size)


def _format_file_size(size: int) -> str:
def _format_file_size(size):
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: I assume the removal of the typing here was a mistake.

Comment on lines +318 to +319
from ..cli.utils import pretty_print_table

Copy link
Contributor

Choose a reason for hiding this comment

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

question: what's the circular import here? I would prefer to extract the common function to another package and keep the import top-level

# Import here to avoid circular imports
from ..cli.utils import pretty_print_table

pretty_print_table(headers, rows)
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: should probably use the new rich table @colinmoynes introduced

safe_name = os.path.basename(filename)
if not safe_name:
raise click.ClickException(
f"Invalid filename '{filename}' — cannot be empty after sanitization."
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: remove the "cannot be empty after sanitization", not really something we should surface to the user.

# ---------------------------------------------------------------------------


def _find_packages(ctx, opts, filter_kwargs, download_all, yes, use_stderr):
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: add typing


def _resolve_download_items(
ctx, opts, packages, owner, repo, all_files, outfile, use_stderr
):
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: add typing

filename = file_info["filename"]
file_url = file_info["cdn_url"]
output_path = os.path.join(output_dir, filename)
def _resolve_all_files_items(ctx, opts, pkg, owner, repo, outfile, use_stderr):
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: add typing

download_url = get_download_url(package)
def _resolve_single_file_item(
ctx, opts, pkg, owner, repo, outfile, multi_package, use_stderr
):
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: add typing

overwrite,
all_files,
use_stderr,
):
Copy link
Contributor

Choose a reason for hiding this comment

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

nab: add typing

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

Development

Successfully merging this pull request may close these issues.

4 participants