@kaynooo/cli-pkg
v0.16.2
Published
pkg: check an npm package before it is added, for a guessed name, a version under two weeks old and install scripts.
Maintainers
Readme
pkg
Checks an npm package before it is added: a guessed or typosquatted name, a version published less than two weeks ago, and install scripts.
bun install -g @kaynooo/cli-pkg
pkg esbuild
pkg [email protected] @types/node --json
pkg check --from package.json
echo "zod esbuild" | pkg check -
pkg outdatedWhat it prints
- Whether the name exists, the version the request resolves to (an exact version, a dist-tag or a range), the latest version, and the highest stable version at least two weeks old (
safe), each with its publish date. - Weekly downloads (public registry only), licence and repository.
- Warnings for a version under the minimum age (with the
safeversion to use instead; a fresh version clear of an advisory atblockSeverityor above thatsafehas is let through, with a warning naming the GHSAs it fixes),preinstall,installandpostinstallscripts, a deprecated version, and a name that is a near miss of a popular package (one or two edits, other separators or case, or the same name under another scope).
pkg exits 1 when a package is blocked (missing, the request matches no version or is not a valid range, the resolved version is too fresh (unless it fixes an advisory of the safe version at the threshold) or has no publish date on npmjs.org, its licence is outside the allowlist, or an advisory reaches the threshold), 2 when it could not check (the registry unreachable, slower than 10 s or answering badly, a broken .pkgrc.json, bunfig.toml or .npmrc) or on a usage error; other warnings exit 0. --json prints one report, or an array for several names, --min-age <days> changes the 14 days.
Config: licence allowlist and advisory threshold
pkg reads its own settings from a .pkgrc.json in the current folder or any folder above it, then from ~/.config/kaynooo-cli/pkg.json (the older ~/.config/pkg/config.json is read while the new file is missing); each key comes from the nearest file that sets it, and comments are allowed.
{ "licenses": ["MIT", "ISC", "Apache-2.0", "BSD-3-Clause"], "blockSeverity": "high" }licensesis the allowlist of SPDX ids, compared without regard to case. An expression is allowed when one side of anORis and every term of anANDis; aWITHexception is ignored. With an allowlist set,pkg checkrefuses a package whose licence is outside it or missing (a legacySEE LICENSE INorUNLICENSEDcounts as outside), so thebun addthe guard checks throughpkg checkis refused with it. Without the key the licence is only printed.minAge(a whole number of days, 14 when no config sets it) is how old a version must be when--min-ageis not given, incheck,auditandoutdated.concurrency(a positive whole number, 8 when no config sets it) is how many registry lookupsauditandoutdatedrun at once.blockSeverity(low,moderate,highorcritical,highwhen no config sets it) makespkg checkask the registry for the advisories of the exactname@versionand refuse it when one is at that severity or above. The request is the onebun auditmakes: aPOSTof{"name": ["version"]}to<registry>/-/npm/v1/security/advisories/bulk. A registry without that endpoint (a private one, say) leaves the version unrefused with a warning, while an advisory request that fails (unreachable, a 5xx, not JSON) exits 2, since the advisories could not be read.
Auditing a lockfile
pkg audit
pkg audit ~/Workspace/app --level high --prod --ignore GHSA-238p-pmpm-9mq7 --jsonpkg audit [dir] reads the bun.lock of the folder and runs the checks of pkg check on every registry package it pins (workspace, git and link sources left out): the age of the version, install scripts, a deprecated version, a near miss of a popular name and the licence allowlist. It also lists the known advisories of every pinned version, each with its GHSA, severity and the version that fixes it when the range ends at one. Findings come grouped per package, each package looked up on the registry once per run, and all advisories fetched in one request per registry.
--level <low|moderate|high|critical>(moderate) keeps only the advisories at that severity or above,--prodaudits only the packages a production or optional dependency of a workspace reaches, and--ignore <GHSA or id>(repeatable) skips an advisory;blockSeverityis forpkg checkand does not apply here. The three flags are the ones ofaudit.--jsonprints a flat array, one{name, version, license, blocked, warnings, advisories}per package with a finding, each advisory being{id, ghsa, url, title, severity, vulnerable, fixed}.audit --jsonnests the same advisory rows (withoutvulnerable) in{repo, path, findings, error}per repository.--min-age <days>changes the 14 days.- Use
pkg auditto vet one repository in depth (age, install scripts, licences and advisories, with the registry as the source); usews auditto sweep many repositories for advisories only, throughbun audit. - A pin under the minimum age fixing an advisory of the
safeversion atblockSeverityor above (not one--ignoreskips) is let through aspkg checklets it, a warning naming the GHSAs. - It exits 1 when a package is missing, too fresh or outside the allowlist, or an advisory remains after the level and the ignores; install scripts and near misses alone exit 0. When the advisories of a registry could not be read it warns which, never prints a clean result for them, and exits 2 unless something already blocks.
- An
npm:alias ("lodash": "npm:[email protected]") is audited as the package it names.
Moving consumers onto a release
pkg consumers @kaynooo/cli-kit
pkg bump @kaynooo/cli-kit 0.8.0 --dry-runpkg consumers <name>lists everypackage.jsonunder--root(the configured workspace folder,~/Workspaceby default) depending on the package,node_modules,dist,build,vendor,target,coverage, the names in theignorearray of~/.config/kaynooo-cli/config.jsonand dot folders left out and nothing deeper than 5 levels below--root(point--rootat the folder to reach further): the folder, the kind of dependency, the version it asks for and the manager its nearest lockfile pins;--jsonfor the rows.pkg bump <name> [version]waits until the package's registry serves the version (every 5 s,-t 10m; the latest when none is given, an exact version otherwise, never a range or a tag), then vets it with the checks ofpkg check(advisories, licence allowlist) before any folder is touched, and runsbun add --exact --no-cache <name>@<version>in each of those folders, one after another, as the same kind of dependency (--dev,--optional), a line per folder. A version the vetting blocks is refused with its reasons and exit 1;--forcebumps it anyway. A bump adopts a release on purpose, often one just made, so it checks no minimum age unless--min-age <days>asks for one. A dependency declared through an alias of the package ("kay": "npm:@scope/name@~1.2.0") is found and bumped under its own key, keeping its~or^;--only <name|glob>and--except <name|glob>(repeatable) narrow the folders, and a package nothing depends on names similar dependencies. A dependency that is not a registry version (workspace:,link:,file:,git, a URL, another package'snpm:alias,catalog:), apeerDependenciesrange, a folder already on the version and one whose lockfile pins another manager are left alone and said so;--dry-runprints what would run. On a terminal with no--onlyor--except, a checklist asks which consumers to bump (the ones left out last time unchecked, what is unchecked now remembered in~/.config/kaynooo-cli/pkg-excluded.json), then the plan is printed and confirmed;--yesskips both, as a pipe does. A transient registry error is retried until the timeout. It exits 1 when the vetting refuses the version, an install fails or the registry has no such package (at once, without waiting), 2 when the version never shows within the timeout.pkg bump <name> [version] --all-consumersleaves alone a folder whosepackage.jsonor lockfile already has uncommitted changes, so thatgcnever commits them as the bump, and goes on in each folder whose install went through: it runs the folder's owncheckand, only when that passes, commits withgc 'chore(deps): bump <name> to <version>' -- package.json <lockfile>(thebun.lockorbun.lockbnearest the folder, a monorepo's at its root). A folder whose check fails stays uncommitted. It ends on a summary line of the folders bumped and committed, bumped and failing, and skipped, exits 1 when an install, a check or a commit failed, and never pushes. With--dry-runit lists thebun add,checkandgceach folder would run.pkg <name>alone still runspkg check.pkg check --from <package.json>checks every registry dependency of a manifest (dependencies,devDependenciesandoptionalDependencies, each asname@rangeso it resolves what an install would; peer ranges andworkspace:,link:,file:, git, URL andnpm:sources left out). Names also come from stdin, separated by spaces or lines, with-as the argument or when nothing else names a package and stdin is piped; the three sources add up.pkg outdated [dir]lists the dependencies of the folder'spackage.jsonwith a newer version safe to take: thesafeofpkg check(the highest stable version at least--min-agedays old, 14 by default), with the change (major,minor,patch), its age and, while the newest version is still too fresh, thatlatest. The version in use is the highest one the folder'sbun.lockpins that the range allows, else the exact version written, else the first version of the range. It reads the registry's metadata only (no advisories), changes nothing, exits 0 whether or not something is outdated and 2 when the registry could not be read. 50 rows at most (--limit,--full),--jsonfor the rows.
Registries
The metadata comes from the registry the name is installed from. A @scope/ name follows @scope:registry= in the nearest .npmrc or [install.scopes] in the nearest bunfig.toml, walking up from the current directory and ending at the home directory, and a scope key wins over a nearer plain registry=, as it does for npm; the token (_authToken, or token in bunfig, with ${VAR} or $VAR expanded) is sent to that registry only, matched on a path boundary, over https (or http to localhost) only, and never printed. Environment variables are expanded in tokens only, never in a registry address. Every request has a 10 s deadline. Downloads come from the npmjs.org API, so they show - for any other registry. A registry that does not return publish times (Forgejo's does not) leaves the age unchecked, which pkg says in a warning.
The folder's package manager
pkg manager devpkg manager <install|dev|remove|update|exec> prints the command the manager of this folder runs for the action, such as bun add -d, composer require --dev or pnpm dlx. The manager is the one whose lockfile (bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml, package-lock.json, uv.lock) or composer.json is nearest, here or in a parent folder, bun when none is found. --json prints {manager, action, command}.
A shell abbreviation can call it through a small function, in fish function __nd; pkg manager dev; end then abbr -a nd --function __nd.
Maintenance
bun run popularregeneratessrc/popular.json, the 500 most downloaded names from the npm-high-impact list.bun run recordre-records the trimmed registry responses intests/fixtures; the tests never touch the network.
