npm

package
v1.64.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package npm provides the npm registry client used by the conformance suite. The exported surface grows by step: S04 lands the `*Client` constructor and the `*HTTPError` envelope; S06 Step 2 adds the packument read methods (`GetPackument`, `GetAbbreviatedPackument`, `GetVersion`) and the `Packument`/`Version`/`Dist` types (see packument.go); S06 Step 4 adds the dist-tag list/set/delete methods (see disttag.go). The remaining per-operation methods (publish, tarball, etc.) land in later S06 steps.

Spec anchors:

  • S04 §HTTPError block: ResponseBody is truncated to 4 KiB.
  • S04 §per-format Client block: constructor takes baseURL, a Credential, and an *http.Client (nil ⇒ http.DefaultClient).
  • S04 AC #32: `*HTTPError` round-trips through `errors.As`.
  • S04 AC #33: 4 KiB tail truncation marker ends ResponseBody.
  • S06 §Client error: `*HTTPError.ContentType` carries the response Content-Type verbatim; empty for the StatusCode == 0 cases.
  • Plan extra: the Authorization request header is scrubbed via the shared `pkg/conformance/redact` primitives before any error surface or log line is rendered.

Index

Constants

This section is empty.

Variables

View Source
var ErrCredentialExtraEnv = errors.New("npm credential env var must not be passed through RunOpts.ExtraEnv; use the --_auth flag instead")

ErrCredentialExtraEnv is returned by Run (wrapped, matchable with errors.Is) when RunOpts.ExtraEnv carries an npm credential key. Such a value reaches the child verbatim and matches none of redact's content patterns, so it would survive both the streaming scrubber and the post-run content scan and leak into captured output. The credential belongs on the --_auth flag, declared secret-bearing at construction. It is exported so callers outside this package can distinguish this rejection from cliexec's spawn/unavailable sentinels.

Functions

func BuildPublishBody added in v1.43.0

func BuildPublishBody(name, version string, tarball []byte, registryURL, tag string) ([]byte, error)

BuildPublishBody constructs the JSON publish payload for one version per S06 §Operation: Publish. tarball is the raw gzipped tarball bytes; registryURL is the absolute base used to build dist.tarball. tag is the dist-tag this publish sets; pass "" for the default "latest". The scope-aware dist.tarball URL rule (strip scope from the filename for scoped names) and the literal-name _attachments key rule (scope retained) are both applied here.

The output is json.MarshalIndent(v, "", " ") plus a trailing newline over ordered structs, so the field order (`_id`, `name`, `dist-tags`, `versions`, `_attachments`; within `dist`: `shasum`, `tarball`, `integrity`) is fixed and the bytes are byte-for-byte stable for the AC #4 golden comparison.

func BuildTarball added in v1.43.0

func BuildTarball(name, version string) ([]byte, error)

BuildTarball returns a minimal valid npm tarball: a gzipped tar containing exactly one file, "package/package.json", with the supplied name and version. The tar entry modtime is the Unix epoch so the bytes are deterministic across runs (SHA-stable fixtures). The builder is in-process; nothing writes to disk. See S06 §Fixture conventions.

Types

type AuditResponse added in v1.50.0

type AuditResponse struct {
	// Body is the parsed JSON body when the registry returns 2xx, or nil
	// when the registry returned a 307 redirect.
	Body map[string]any
	// RedirectLocation is set when the registry returned 307; empty
	// otherwise.
	RedirectLocation string
}

AuditResponse is the result of an AuditBulk call per S06 §Client types and §Operation: Security advisories bulk.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the npm protocol client. S04 landed the construction surface and the `*HTTPError` envelope; the packument read methods land in packument.go (S06 Step 2), the dist-tag methods in disttag.go (S06 Step 4), and the remaining per-operation methods in later S06 steps.

func New

func New(baseURL string, cred conformance.Credential, hc *http.Client) *Client

New returns an npm client. baseURL is the registry root; cred is the user-supplied secret carried into protocol exchanges; hc is the transport (nil ⇒ http.DefaultClient). The constructor does not perform I/O; connection is lazy.

func (*Client) AuditBulk added in v1.50.0

func (c *Client) AuditBulk(ctx context.Context, body map[string][]string) (*AuditResponse, error)

AuditBulk POSTs /-/npm/v1/security/advisories/bulk per S06 §Operation: Security advisories bulk. A 307 is not followed: it populates AuditResponse.RedirectLocation and returns a nil error (the 307-only sentinel rule). Any other 3xx (e.g. 302, 308) and every other non-2xx surface as *HTTPError per S06 §Status-code policy and §Client error.

func (*Client) DeleteDistTag added in v1.45.0

func (c *Client) DeleteDistTag(ctx context.Context, name, tag string) error

DeleteDistTag DELETEs /-/package/{name}/dist-tags/{tag} per S06 §Operation: Dist-tag delete. No request body.

func (*Client) Deprecate added in v1.50.0

func (c *Client) Deprecate(ctx context.Context, name string, packument *Packument, versions []string, message string) error

Deprecate PUTs name's packument with versions[v].deprecated set on every v in versions. Pass "" as message to un-deprecate. packument is the current packument (typically obtained via GetPackument); the supplied *Packument is treated as immutable — Deprecate allocates a new byte slice for the PUT body and does not modify packument.Raw. The client does no semver range parsing and does not re-fetch — orchestration (fetch + range resolution) lives in pkg/conformance/npm. Per S06 §Operation: Deprecate. A 404 (package absent) or 401/403 (auth) surfaces as *HTTPError.

func (*Client) GetAbbreviatedPackument added in v1.40.0

func (c *Client) GetAbbreviatedPackument(ctx context.Context, name string) (*Packument, string, error)

GetAbbreviatedPackument fetches the abbreviated packument for name. Sends Accept: application/vnd.npm.install-v1+json. The second return value is the response Content-Type header verbatim. Per S06 §Operation: Packument fetch.

func (*Client) GetPackument added in v1.40.0

func (c *Client) GetPackument(ctx context.Context, name string) (*Packument, string, error)

GetPackument fetches the full packument for name. Sends Accept: application/json. The second return value is the response Content-Type header verbatim. Per S06 §Operation: Packument fetch and §`pkg/client/npm` method surface.

func (*Client) GetTarball added in v1.50.0

func (c *Client) GetTarball(ctx context.Context, tarballURL string) ([]byte, string, error)

GetTarball downloads the tarball at tarballURL and returns the raw gzipped bytes plus the response Content-Type (S06 §Operation: Tarball download). tarballURL is taken verbatim from a packument's dist.tarball field, so the registry may serve it from a different host — a CDN or an object-storage redirect.

The flow follows S06 §Operation: Tarball download and §Tarball-URL SSRF:

  1. Validate the initial URL against the SSRF allow-list before any network I/O. A rejection returns an *HTTPError with StatusCode 0 and no request ever reaches the transport.
  2. Issue the GET through a method-local *http.Client that disables net/http's own redirect following, so the shared client's host-locked CheckRedirect (S04 §HTTP client policy) does not block a legitimate cross-host redirect.
  3. On 2xx, return the body bytes verbatim (for the caller to SHA-verify) and the response Content-Type.
  4. On 3xx, parse Location: a missing, unparseable, non-absolute, or non-http(s) Location is a malformed redirect (StatusCode 0); otherwise re-validate the target against the SSRF allow-list and re-issue a single GET. No second redirect is followed — a second 3xx (or any non-2xx) surfaces as an *HTTPError carrying that response's status and body.
  5. Any other status on the first response surfaces as an *HTTPError per S06 §Client error, carrying the wire status and body.

The request carries the configured credential and User-Agent on every hop per S06 §Request shape common to every operation; the SSRF allow-list, not credential stripping, is the control that keeps the untrusted tarball URL from reaching an internal address. This is a deliberate divergence from the npm CLI, which strips Authorization on a cross-origin redirect: S06 pins the allow-list as the control, and a hostile registry able to redirect the credential elsewhere already received it on the initial GET, so stripping would add no protection in this single-credential, operator-chosen-target model.

A StatusCode == 0 *HTTPError marks the client-side rejection and malformed-redirect cases; a non-zero StatusCode marks a wire-layer non-success. Callers branch on StatusCode after errors.As (S06 §Client error).

func (*Client) GetVersion added in v1.40.0

func (c *Client) GetVersion(ctx context.Context, name, version string) (*Version, string, error)

GetVersion fetches the per-version document for name@version. version may be a literal version or "latest". The second return value is the response Content-Type header verbatim. Per S06 §Operation: Per-version document.

func (*Client) ListDistTags added in v1.45.0

func (c *Client) ListDistTags(ctx context.Context, name string) (map[string]string, error)

ListDistTags GETs /-/package/{name}/dist-tags per S06 §Operation: Dist-tag list, decoding the response body into the tag->version map. A received 2xx whose body cannot be used — over the size cap, or not decodable as the tag->version object — is a failed operation surfaced as *HTTPError, so every ListDistTags failure reaches the caller through the one error type they errors.As on, matching the non-2xx and transport paths (and the Maven metadata read's over-cap/decode-failure guards).

func (*Client) Ping added in v1.50.0

func (c *Client) Ping(ctx context.Context) error

Ping GETs /-/ping and returns nil on 2xx per S06 §Operation: ping. The 2xx body ({} per the catalog) is not inspected; any non-2xx or transport failure surfaces as *HTTPError.

func (*Client) Publish added in v1.43.0

func (c *Client) Publish(ctx context.Context, name string, body []byte) error

Publish sends a single-version publish to /{name}. body is the exact JSON payload (see §Operation: Publish for the canonical request-body shape; BuildPublishBody is the constructor). Any 2xx is success per S06 §Status-code policy; a non-2xx surfaces as *HTTPError.

The package name is path-encoded once (S06 §Request shape common to every operation): a scoped name `@scope/foo` reaches the wire as `/@scope%2Ffoo`, an unscoped name unchanged.

func (*Client) Search added in v1.50.0

func (c *Client) Search(ctx context.Context, params SearchParams) (*SearchResponse, error)

Search GETs /-/v1/search?text=... with the supplied parameters per S06 §Operation: Search. Pass zero values for unused query params.

func (*Client) SetDistTag added in v1.45.0

func (c *Client) SetDistTag(ctx context.Context, name, tag, version string) error

SetDistTag PUTs /-/package/{name}/dist-tags/{tag} with version as the body per S06 §Operation: Dist-tag set. Equivalent to `npm dist-tag add name@version tag`. The body is the JSON string "<version>" (e.g. "1.2.3", including the quotes), not a JSON object — json.Marshal on a plain Go string produces exactly that shape, and newRequest sets Content-Type: application/json for a PUT carrying a body.

func (*Client) Whoami added in v1.50.0

func (c *Client) Whoami(ctx context.Context) (string, error)

Whoami GETs /-/whoami and returns the resolved username per S06 §Operation: whoami. A 401 (invalid or missing credential) surfaces as *HTTPError.

type Dist added in v1.40.0

type Dist struct {
	Tarball      string `json:"tarball"`
	Shasum       string `json:"shasum,omitempty"`
	Integrity    string `json:"integrity,omitempty"`
	FileCount    int    `json:"fileCount,omitempty"`
	UnpackedSize int64  `json:"unpackedSize,omitempty"`
}

Dist is the dist block of a version object per S06 §Client types.

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor is the npm CLIExecutor: it wraps the cross-format pkg/conformance/cliexec.Executor and extends its closed env allow-list with the five npm-specific entries (S06 §`npm` `CLIExecutor` env-var allow-list additions). Construct it with NewExecutor; the zero value is not usable.

func NewExecutor

func NewExecutor() *Executor

NewExecutor resolves the npm binary on PATH, pins its version once at construction (S04 §Version pinning), and returns an Executor whose driver-invocation env allow-list is the S04 defaults plus the five npm entries from S06 §`npm` `CLIExecutor` env-var allow-list additions. It also declares npm's --_auth credential flag as secret-bearing via cliexec.WithSecretFlags so its value is scrubbed from captured CLI output. Like cliexec.NewExecutor, it never returns an error: an unresolvable binary or a failed version probe leaves HasCLI() == false so driver-using tests skip.

Credentials belong on the --_auth flag, not RunOpts.ExtraEnv, which is appended to the child verbatim; Run rejects "_auth"-style ExtraEnv keys for that reason (see Run).

func (*Executor) HasCLI

func (e *Executor) HasCLI() bool

HasCLI reports whether the npm binary resolved on PATH and its version probe succeeded at construction.

func (*Executor) Name

func (e *Executor) Name() string

Name reports the driver identifier ("npm").

func (*Executor) Run

Run invokes the npm CLI with the supplied argv through the wrapped cross-format executor, honoring ctx for cancellation. RunOpts.ExtraEnv is appended per invocation per S04 §RunOpts; it is not part of the static allow-list.

Run first rejects any ExtraEnv entry whose key is an npm credential key: a key ending in _auth (the base64 username:password form) or _authToken (the bearer form), bare or scoped (npm_config__auth, npm_config_//host/:_authToken). cliexec appends ExtraEnv verbatim and these key shapes match none of redact's content patterns, so such a value would leak into captured output; the returned error wraps ErrCredentialExtraEnv for errors.Is. The match is on the key suffix, not a substring, so non-secret config such as npm_config_auth_type is not rejected.

func (*Executor) Version

func (e *Executor) Version() string

Version reports the npm version pinned at construction.

type HTTPError

type HTTPError struct {
	// Method is the HTTP method of the failing request (e.g., "GET").
	Method string

	// URL is the absolute URL the client attempted.
	URL string

	// StatusCode is the HTTP status code observed. Zero for
	// transport-level failures that never received a status line.
	StatusCode int

	// ContentType is the response Content-Type header verbatim. It is
	// empty for the StatusCode == 0 cases (transport failure,
	// client-side URL rejection, malformed redirect) where no usable
	// response was received. See S06 §Client error.
	ContentType string

	// ResponseBody is the body the server returned, truncated to
	// 4 KiB inclusive of the `... [truncated]` marker when the wire
	// body exceeded that limit. See S04 AC #33.
	ResponseBody string
}

HTTPError is returned by client methods when the registry responds with an unexpected status, or when the request fails at the transport layer in a way that yields a partial response. The fields mirror the spec block in `docs/specs/S04-contracts.md` §HTTPError.

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error implements the error interface for *HTTPError. The format is stable for end-user display; it must not include any value drawn from the Authorization request header.

type Packument added in v1.40.0

type Packument struct {
	Name     string             `json:"name"`
	Modified string             `json:"modified,omitempty"` // abbreviated only
	DistTags map[string]string  `json:"dist-tags"`
	Versions map[string]Version `json:"versions"`

	// Full-packument fields. omitempty so the same struct decodes both
	// abbreviated and full responses; tests inspect emptiness to assert
	// format adherence.
	ID     string            `json:"_id,omitempty"`
	Rev    string            `json:"_rev,omitempty"`
	Time   map[string]string `json:"time,omitempty"`
	Readme string            `json:"readme,omitempty"`

	// Raw carries the verbatim response bytes, populated by
	// UnmarshalJSON. The json:"-" tag keeps the default unmarshaler from
	// touching it; UnmarshalJSON fills it before delegating the typed
	// decode. See S06 §Client types ("Raw population").
	Raw json.RawMessage `json:"-"`
}

Packument is the npm package metadata document per S06 §Client types. The same struct decodes both the abbreviated and the full response; full-only fields carry omitempty so an abbreviated response decodes without them. Tests that must distinguish format adherence inspect Raw rather than the typed fields.

func (*Packument) UnmarshalJSON added in v1.40.0

func (p *Packument) UnmarshalJSON(data []byte) error

UnmarshalJSON copies data into Raw, then delegates the typed decode to a method-private alias type (the standard idiom for avoiding UnmarshalJSON recursion) per S06 §Client types ("Raw population"). The Raw copy is independent of the input slice so a caller reusing the decode buffer cannot mutate the stored bytes after the fact.

type SearchObject added in v1.50.0

type SearchObject struct {
	Package map[string]any `json:"package"`
	Score   map[string]any `json:"score"`
}

SearchObject is one entry in a SearchResponse. The npm search result shape is loosely specified beyond package/score, so both are decoded as open maps per S06 §Client types.

type SearchParams added in v1.50.0

type SearchParams struct {
	Text                             string
	Size, From                       int
	Quality, Popularity, Maintenance float64
}

SearchParams carries the query parameters for a registry search per S06 §Operation: Search. Pass zero values for unused query params.

type SearchResponse added in v1.50.0

type SearchResponse struct {
	Objects []SearchObject `json:"objects"`
	Total   int            `json:"total"`
	Time    string         `json:"time"`
}

SearchResponse is the decoded body of a successful search per S06 §Operation: Search: objects (array), total (int), time (string).

type Version added in v1.40.0

type Version struct {
	Name         string            `json:"name"`
	Version      string            `json:"version"`
	Dist         Dist              `json:"dist"`
	Deprecated   string            `json:"deprecated,omitempty"`
	Dependencies map[string]string `json:"dependencies,omitempty"`

	// Raw carries the verbatim version-object bytes, populated by
	// UnmarshalJSON. See S06 §Client types ("Raw population").
	Raw json.RawMessage `json:"-"`
}

Version is a single version object — one entry in a packument's versions map, or the body of a per-version document. Per S06 §Client types.

func (*Version) UnmarshalJSON added in v1.40.0

func (v *Version) UnmarshalJSON(data []byte) error

UnmarshalJSON copies data into Raw, then delegates the typed decode to a method-private alias type per S06 §Client types ("Raw population"). The Raw copy is independent of the input slice for the same reason as Packument.UnmarshalJSON.

Directories

Path Synopsis
internal
npmstub command
Command npmstub is a real `npm` binary used as a test fixture by pkg/client/npm's Executor tests; TestMain compiles it as "npm" onto the test PATH.
Command npmstub is a real `npm` binary used as a test fixture by pkg/client/npm's Executor tests; TestMain compiles it as "npm" onto the test PATH.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL