npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@heroku/heroku-credential-manager

v0.1.0

Published

Heroku credential management for the CLI

Readme

Heroku Credential Manager

A library for managing Heroku CLI credentials. It uses the native credential store on macOS, Windows, and GNU/Linux when available, and supports .netrc storage.

Features

  • Credential storage, retrieval, and deletion using native credential stores by default.
  • Selects appropriate credential management tool based on system capabilities.
  • Uses .netrc when native storage is unavailable or when HEROKU_NETRC_WRITE=true.
  • Removes credentials from both native storage and .netrc so stale credentials are cleaned up when storage modes change.
  • Provides an isolated @heroku/heroku-credential-manager/login entry point for injected browser, interactive, and legacy SSO login flows.

Quick Start

Requirements

Node.js 20.5.0 or later is required.

Installation

# npm
npm install @heroku/heroku-credential-manager

# pnpm
pnpm add @heroku/heroku-credential-manager

# yarn
yarn add @heroku/heroku-credential-manager

Usage Examples

import {getAuth, removeAuth, saveAuth} from '@heroku/heroku-credential-manager'

await saveAuth('[email protected]', 'token', ['api.heroku.com'])

const auth = await getAuth('[email protected]', 'api.heroku.com')
// auth is {account: '[email protected]', token: 'token'}

await removeAuth('[email protected]', ['api.heroku.com'])

saveAuth(account, token, hosts, service?) stores the credential. getAuth(account, host, service?) returns the stored account and token; pass undefined for the account to read the host from .netrc. When an account is provided, a native miss falls back only to a matching .netrc login; native backend errors are surfaced. removeAuth(account, hosts, service?, expectedToken?) removes the requested credential. A supplied account guards .netrc removal so only entries with that login are eligible; undefined retains unconditional cleanup of the supplied hosts. The optional expectedToken is a best-effort conditional safeguard against deleting a replacement token, not a guarantee of atomic cross-process cleanup. The optional service defaults to heroku-cli.

Storage behavior

On non-Windows platforms, .netrc writes open the netrc path with O_NOFOLLOW and refuse a symbolic link at that path. Reads through a symbolic link remain supported, and a rejected write leaves the link target unchanged. Dotfile-manager setups that symlink .netrc must use a regular file at the netrc path for operations that write it.

A successful native credential-store save removes matching stale .netrc entries for the supplied hosts. Entries belonging to another login are preserved.

Native backend errors are surfaced rather than hidden by .netrc fallback. Only a confirmed missing native credential falls back to .netrc.

The native handlers throw NativeCredentialNotFoundError for a confirmed missing credential. Consumers calling a handler directly can distinguish that result from an unavailable or degraded backend:

import {NativeCredentialNotFoundError} from '@heroku/heroku-credential-manager'

try {
  handler.getAuth('[email protected]', 'heroku-cli')
} catch (error) {
  if (error instanceof NativeCredentialNotFoundError) {
    // The native credential does not exist.
  } else {
    throw error
  }
}

Top-level removeAuth uses a supplied account to guard .netrc entries by login. Passing undefined removes the supplied hosts regardless of stored login so callers can request unconditional host cleanup after storage-mode changes.

Injected login consumers

Login is intentionally available only from the /login subpath. Consumers provide semantic prompts and a narrow Heroku API client factory, and may inject a Fetch-compatible function, browser opening, output/progress, timers, environment/config, and storage behavior. This keeps command frameworks, @heroku/heroku-fetch, and browser packages outside the credential manager:

import {
  type HerokuApiClientLike,
  Login,
  type LoginDependencies,
} from '@heroku/heroku-credential-manager/login'

import {createPlatformApiClient} from './platform-api-client.js'

// This adapter belongs to the consumer. Create a fresh client authorized with
// the token supplied for each operation; do not capture an ambient CLI token.
const apiClientForToken: LoginDependencies['apiClientForToken'] = token => {
  const client = createPlatformApiClient({token})
  return {
    delete: (path, options) => client.delete(path, options),
    get: (path, options) => client.get(path, options),
  } satisfies HerokuApiClientLike
}

const login = new Login({
  apiClientForToken,
  browser: {open: async url => launchBrowser(url)},
  fetch: fetchImplementation,
  prompt: {
    accessToken: () => promptSecret('Access token'),
    email: previous => promptText('Email', previous),
    loginMethod: () => readLoginKey(),
    organization: previous => promptText('Organization name', previous),
    password: () => promptSecret('Password'),
    secondFactor: () => promptSecret('Two-factor code'),
  },
})

const auth = await login.login({method: 'browser'})
await login.logout(auth)

LoginDependencies.apiClientForToken is a required, consumer-owned factory. Login passes it the token for the current login or logout operation, and the factory must return a client authorized with that token rather than an ambient credential. Its result implements the package-local HerokuApiClientLike contract: exactly generic get(path, options?) and delete(path, options?) methods returning {body, headers, status}. The injected fetch implements FetchLike and is used for OAuth POSTs and non-Platform requests. Request failures with an HTTP status are surfaced as LoginRequestError; its public body is limited to sanitized id, message, and resource fields.

login() supports browser, interactive, and sso, returns a persisted {account, token}, never revokes an existing session during re-login, and rejects cancellation with LoginCancelledError (exitCode is 130 for Ctrl-C and 0 for q). Browser and SSO flows always emit a manual URL; failure to open a browser does not invalidate that flow. The default storage adapter uses this package's native/.netrc APIs. logout(entry) requires the returned credential entry and always attempts local API/Git credential cleanup, including when remote revocation fails. With the canonical heroku-cli credential service it also attempts configured login-state cleanup; isolated custom services intentionally skip the global login.json state. Backing-store failures can prevent guaranteed removal.

Environment-derived HEROKU_HOST and HEROKU_API_URL values are restricted to Heroku domains and exact loopback hosts. Consumers that intentionally target a private or custom HTTPS deployment must provide config.apiUrl explicitly; callers are responsible for treating endpoint configuration as trusted. Base apiUrl and loginHost values must not contain a query or fragment, while ssoUrl is a complete URL and may contain both. For an explicit custom apiUrl or HEROKU_API_URL, omitting gitHost avoids writing the API credential for any Git host; set a trusted gitHost explicitly to opt in. The canonical api.heroku.com endpoint uses the existing heroku-cli native credential service. Other API hosts derive an isolated heroku-cli@<normalized-api-host> service, including an explicit port, so custom native credentials do not collide with production. config.credentialService can override that namespace with a nonempty, NUL-free value. Any service other than heroku-cli skips the global login.json account-selection state to avoid cross-service state collisions. Built-in fetch-based requests reject all redirects and identify themselves with a package-specific User-Agent. CLI adapters must preserve the CLI's existing host allowlist and warning/fallback behavior.

config.timeoutMs limits login acquisition (10 minutes by default), but does not cancel credential persistence after credentials have been acquired. For logout, the same timeout is the remote-revocation deadline: it aborts remote requests, while already-started local credential and login-state cleanup is awaited because those operations are not cancellable. Logout therefore has no overall time bound and remains pending indefinitely if local cleanup never settles. Cleanup passes the logged-out token as expectedToken as defense in depth, but this conditional check does not guarantee cross-process race safety. config.requestTimeoutMs separately limits each HTTP request. In forced-netrc mode (HEROKU_NETRC_WRITE=true), login credentials persist only to .netrc, interactive login prefills the account from the API host's .netrc entry, and login does not read or write native login.json state even when a native credential backend is installed. Logout and top-level removeAuth may still attempt OS-native cleanup so credentials left by an earlier storage mode do not remain stale.

Development

npm ci
npm run typecheck
npm run build
npm run lint
npm run unit
npm run test:package

npm run test:package verifies the packed tarball in isolated runtime and type-checking consumer projects. It uses an isolated home directory so the exercised netrc and login-state paths avoid the user's normal credential files, and verifies that the root runtime entry point does not pull in the separately exported login implementation or its framework/browser dependency concerns.

License

Apache-2.0. See LICENSE.txt for details.

Contributing

We welcome issues and PRs. Please follow conventional commits, keep changes under 200 lines per commit, and ensure tests and type checks pass. See CONTRIBUTING.md for details.