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 ¶
- Variables
- func BuildPublishBody(name, version string, tarball []byte, registryURL, tag string) ([]byte, error)
- func BuildTarball(name, version string) ([]byte, error)
- type AuditResponse
- type Client
- func (c *Client) AuditBulk(ctx context.Context, body map[string][]string) (*AuditResponse, error)
- func (c *Client) DeleteDistTag(ctx context.Context, name, tag string) error
- func (c *Client) Deprecate(ctx context.Context, name string, packument *Packument, versions []string, ...) error
- func (c *Client) GetAbbreviatedPackument(ctx context.Context, name string) (*Packument, string, error)
- func (c *Client) GetPackument(ctx context.Context, name string) (*Packument, string, error)
- func (c *Client) GetTarball(ctx context.Context, tarballURL string) ([]byte, string, error)
- func (c *Client) GetVersion(ctx context.Context, name, version string) (*Version, string, error)
- func (c *Client) ListDistTags(ctx context.Context, name string) (map[string]string, error)
- func (c *Client) Ping(ctx context.Context) error
- func (c *Client) Publish(ctx context.Context, name string, body []byte) error
- func (c *Client) Search(ctx context.Context, params SearchParams) (*SearchResponse, error)
- func (c *Client) SetDistTag(ctx context.Context, name, tag, version string) error
- func (c *Client) Whoami(ctx context.Context) (string, error)
- type Dist
- type Executor
- type HTTPError
- type Packument
- type SearchObject
- type SearchParams
- type SearchResponse
- type Version
Constants ¶
This section is empty.
Variables ¶
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
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
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
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
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
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:
- 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.
- 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.
- On 2xx, return the body bytes verbatim (for the caller to SHA-verify) and the response Content-Type.
- 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.
- 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
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
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
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
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
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.
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 ¶
HasCLI reports whether the npm binary resolved on PATH and its version probe succeeded at construction.
func (*Executor) Run ¶
func (e *Executor) Run(ctx context.Context, argv []string, opts conformance.RunOpts) (conformance.CLIResult, error)
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.
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.
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
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
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
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.
Source Files
¶
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. |