Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Added an PNPM credential helper for Cloudsmith registries. `cloudsmith credential-helper install pnpm` installs an `pnpm-credential-cloudsmith` launcher binary and registers it in `~/.npmrc`, so npm authenticates to Cloudsmith registries automatically using your existing CLI credentials — no manual `npm login` required. Custom Cloudsmith registry domains are discovered via the API and cached locally; add extra hostnames with `--domain` (repeatable), disable discovery with `--no-discover`, or preview changes with `--dry-run`. Manage installed helpers with `cloudsmith credential-helper uninstall pnpm` and `cloudsmith credential-helper list`.

## [1.24.0] - 2026-08-18

### Added
Expand Down
8 changes: 8 additions & 0 deletions cloudsmith_cli/cli/commands/credential_helper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .docker import docker as docker_cmd
from .generic import generic as generic_cmd
from .manage import install_cmd, list_cmd, uninstall_cmd
from .pnpm import pnpm as pnpm_cmd


@click.group()
Expand All @@ -27,11 +28,18 @@ def credential_helper():
# Install Docker credential helper
$ cloudsmith credential-helper install docker

# Install pnpm credential helper
$ cloudsmith credential-helper install pnpm

# Test Docker credential helper directly
$ echo "docker.cloudsmith.io" | cloudsmith credential-helper docker

# Test pnpm credential helper directly
$ cloudsmith credential-helper pnpm npm.cloudsmith.io
"""


credential_helper.add_command(pnpm_cmd, name="pnpm")
credential_helper.add_command(docker_cmd, name="docker")
credential_helper.add_command(generic_cmd, name="generic")
credential_helper.add_command(install_cmd, name="install")
Expand Down
33 changes: 24 additions & 9 deletions cloudsmith_cli/cli/commands/credential_helper/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

import click

from cloudsmith_cli.credential_helpers.generic import PartialInstallError
from cloudsmith_cli.credential_helpers.pnpm.installer import PNPMInstaller

from ....credential_helpers.docker.installer import DockerInstaller
from ... import utils
from ...decorators import (
Expand All @@ -27,6 +30,7 @@

_INSTALLERS: dict[str, type] = {
"docker": DockerInstaller,
"pnpm": PNPMInstaller,
}


Expand All @@ -40,7 +44,7 @@ def _get_installer(name: str):

Returns
-------
DockerInstaller
BaseInstaller
An instance of the appropriate installer class.

Raises
Expand Down Expand Up @@ -85,7 +89,7 @@ def _get_installer(name: str):
"--no-discover",
is_flag=True,
default=False,
help="Disable automatic discovery of custom Docker domains.",
help="Disable automatic discovery of custom domains.",
)
@click.option(
"--refresh",
Expand All @@ -110,25 +114,30 @@ def install_cmd(
) -> None:
"""Install a credential helper launcher and configure the package manager.

HELPER is the name of the credential helper to install (e.g. ``docker``).
HELPER is the name of the credential helper to install (e.g. ``docker``, ``pnpm``).

Important for pnpm: The tokenHelper directive is only honored in the
user-level ~/.npmrc file, not in a project-level .npmrc. This is a pnpm
security restriction. The absolute path to the launcher is automatically
calculated and configured.

Examples:

\b
# Install Docker credential helper
$ cloudsmith credential-helper install docker
# Install credential helper
$ cloudsmith credential-helper install HELPER

\b
# Install with a custom domain
$ cloudsmith credential-helper install docker --domain my.registry.example.com
$ cloudsmith credential-helper install HELPER --domain my.registry.example.com

\b
# Preview without making changes
$ cloudsmith credential-helper install docker --dry-run
$ cloudsmith credential-helper install HELPER --dry-run

\b
# Disable automatic custom-domain discovery
$ cloudsmith credential-helper install docker --no-discover
$ cloudsmith credential-helper install HELPER --no-discover
"""
installer = _get_installer(helper)
try:
Expand All @@ -146,6 +155,11 @@ def install_cmd(
raise click.ClickException(
f"Failed to install {helper!r} credential helper: {exc}"
)
except PartialInstallError as exc:
actions = exc.actions
ec = exc.exit_code
else:
ec = 0

use_stderr = utils.should_use_stderr(opts)
warnings = [a for a in actions if a.startswith("WARNING")]
Expand All @@ -157,14 +171,15 @@ def install_cmd(
"warnings": warnings,
}
if utils.maybe_print_as_json(opts, data):
return
sys.exit(ec)

if dry_run:
click.echo("Dry run — no changes will be made:", err=use_stderr)
for action in normal:
click.echo(f" {action}" if dry_run else action, err=use_stderr)
for warning in warnings:
click.secho(f" {warning}" if dry_run else warning, err=True, fg="yellow")
sys.exit(ec)
Comment thread
tigh-latte marked this conversation as resolved.


# ---------------------------------------------------------------------------
Expand Down
51 changes: 51 additions & 0 deletions cloudsmith_cli/cli/commands/credential_helper/pnpm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2026 Cloudsmith Ltd
"""
Comment thread
Copilot marked this conversation as resolved.
pnpm credential helper command.

Implements the pnpm credential helper protocol for Cloudsmith registries.
"""

import sys

import click

from ....credential_helpers.pnpm import execute
from ...decorators import common_api_auth_options, resolve_credentials


@click.command()
@click.argument("repo", required=False, default="npm.cloudsmith.io")
@common_api_auth_options
@resolve_credentials
def pnpm(opts, repo):
"""
Input (arg, optional):
Server URL as plain text (e.g. "npm.cloudsmith.io")

Output (stdout):
Text: <cloudsmith-token>

\b
Exit codes:
0: Success
1: Error (no credentials available, not a Cloudsmith registry, etc.)

\b
Environment variables:
CLOUDSMITH_API_KEY: API key for authentication (optional)
CLOUDSMITH_ORG: Organisation slug (required for custom domain support)
"""

exit_code, stdout, stderr = execute(
repo,
credential=opts.credential,
api_host=opts.api_host,
org=opts.org,
)

if stdout is not None:
click.echo(stdout, nl=False)
if stderr is not None:
click.echo(stderr, err=True)

sys.exit(exit_code)
Loading