Everything you can import and call. Each one does exactly what the name says, which is more than I can say for most npm packages.
The main event. Loads packages, resolves dependencies against the registry, renders output, and optionally writes updates. Everything the CLI does, minus the argument parsing.
function check(options: depfreshOptions): Promise<number>Returns an exit code:
0-- no updates found, or updates were written successfully1-- updates available (only whenfailOnOutdated: trueandwrite: false)2-- something went wrong, or strict failure flags tripped (failOnResolutionErrors,failOnNoPackages,strictPostWrite)
import { check, resolveConfig } from 'depfresh'
const options = await resolveConfig({ mode: 'latest', write: true })
const code = await check(options)
if (code === 2) {
console.error('Well that did not work')
}Merges your overrides with config file values (.depfreshrc, depfresh.config.ts, package.json#depfresh) and DEFAULT_OPTIONS. Config file wins over defaults. Your overrides win over everything.
function resolveConfig(overrides?: Partial<depfreshOptions>): Promise<depfreshOptions>Config resolution order (highest priority first):
overridesargument- Config file (
.depfreshrc,depfresh.config.ts,depfresh.config.js) package.json"depfresh"fieldDEFAULT_OPTIONS
import { resolveConfig } from 'depfresh'
// Just defaults + config file
const options = await resolveConfig()
// Override specific options
const options = await resolveConfig({
cwd: '/path/to/project',
mode: 'patch',
concurrency: 4,
})A type helper for config files. Does literally nothing at runtime. Returns exactly what you pass in. But your editor will autocomplete, and that's apparently worth an import.
function defineConfig(options: Partial<depfreshOptions>): Partial<depfreshOptions>// depfresh.config.ts
import { defineConfig } from 'depfresh'
export default defineConfig({
mode: 'minor',
exclude: ['typescript'],
group: true,
})Finds and parses package manifests (package.json, package.yaml) in your project. Respects recursive, ignorePaths, ignoreOtherWorkspaces. Loads workspace catalogs (pnpm, bun, yarn) only when recursive: true, and supports global packages when global: true (single detected manager) or globalAll: true (npm + pnpm + bun).
When both package.yaml and package.json exist in a directory, package.yaml is selected.
function loadPackages(options: depfreshOptions): Promise<PackageMeta[]>import { loadPackages, resolveConfig } from 'depfresh'
const options = await resolveConfig({ recursive: true })
const packages = await loadPackages(options)
for (const pkg of packages) {
console.log(`${pkg.name}: ${pkg.deps.length} dependencies`)
}Resolves every dependency in a package against the registry. Handles caching, concurrency, version filtering, workspace protocol semantics, and the onDependencyResolved callback. This is where the network calls happen.
function resolvePackage(
pkg: PackageMeta,
options: depfreshOptions,
externalCache?: Cache,
externalNpmrc?: NpmrcConfig,
privatePackages?: Set<string>,
): Promise<ResolvedDepChange[]>| Param | Description |
|---|---|
pkg |
The package to resolve |
options |
Full depfresh options (mode, concurrency, timeout, etc.) |
externalCache? |
Reuse a cache across multiple packages. If omitted, creates and closes its own |
externalNpmrc? |
Pre-loaded npmrc config. If omitted, loads from disk |
privatePackages? |
Set of workspace package names normally skipped for local-only refs. Explicit-version workspace: refs still resolve against the registry. |
import { loadPackages, resolvePackage, resolveConfig } from 'depfresh'
const options = await resolveConfig({ mode: 'latest' })
const packages = await loadPackages(options)
for (const pkg of packages) {
const resolved = await resolvePackage(pkg, options)
const updates = resolved.filter(d => d.diff !== 'none')
console.log(`${pkg.name}: ${updates.length} updates available`)
}Extracts dependencies from a parsed package manifest object (JSON or YAML). Handles all standard fields, packageManager, overrides, resolutions, nested overrides, protocols (npm:, jsr:, github:, workspace:), include/exclude filters, and locked version detection.
function parseDependencies(
raw: Record<string, unknown>,
options: depfreshOptions,
): RawDep[]import { parseDependencies, resolveConfig } from 'depfresh'
const options = await resolveConfig()
const raw = JSON.parse(fs.readFileSync('package.json', 'utf-8')) // package.yaml works too after YAML parsing
const deps = parseDependencies(raw, options)
console.log(`Found ${deps.length} dependencies`)
console.log(`${deps.filter(d => d.update).length} will be checked`)Writes resolved changes back to the package file. Preserves indentation, line endings (CRLF too, you're welcome Windows users), and key order. Handles regular manifest files (package.json, package.yaml) and workspace catalog files.
function writePackage(
pkg: PackageMeta,
changes: ResolvedDepChange[],
loglevel?: 'silent' | 'info' | 'debug',
): voidimport { loadPackages, resolvePackage, writePackage, resolveConfig } from 'depfresh'
const options = await resolveConfig({ mode: 'minor' })
const [pkg] = await loadPackages(options)
const resolved = await resolvePackage(pkg, options)
const minorOnly = resolved.filter(d => d.diff === 'minor' || d.diff === 'patch')
writePackage(pkg, minorOnly, 'silent')Lists globally installed packages for one package manager. Auto-detects which PM is available if you don't specify (tries pnpm, then bun, then npm).
function loadGlobalPackages(pm?: string): PackageMeta[]import { loadGlobalPackages } from 'depfresh'
const packages = loadGlobalPackages('npm')
for (const pkg of packages) {
for (const dep of pkg.deps) {
console.log(`${dep.name}@${dep.currentVersion}`)
}
}Lists globally installed packages across npm, pnpm, and bun in one pass. Results are deduplicated by package name. If a package exists in multiple managers, write mode targets every matching manager.
function loadGlobalPackagesAll(): PackageMeta[]import { loadGlobalPackagesAll } from 'depfresh'
const packages = loadGlobalPackagesAll()
for (const dep of packages[0]?.deps ?? []) {
console.log(`${dep.name}@${dep.currentVersion}`)
}Updates a single global package. Shells out to the relevant package manager's install command. Not subtle.
function writeGlobalPackage(
pm: PackageManagerName,
name: string,
version: string,
): voidimport { writeGlobalPackage } from 'depfresh'
writeGlobalPackage('npm', 'typescript', '5.7.0')
// Runs: npm install -g typescript@5.7.0Seven hooks. Called in order. All optional. All async-compatible. Wire them into depfreshOptions and check() will call them at the right moment.
If you need reusable behavior across projects, use options.addons with depfreshAddon objects. Callbacks stay project-local; addons are composable plugins.
check() starts
|
+- loadPackages()
|
+- afterPackagesLoaded(pkgs)
|
+- for each package:
| +- beforePackageStart(pkg)
| +- resolvePackage()
| | +- onDependencyResolved(pkg, dep) <- called per dep as it resolves
| +- afterPackageEnd(pkg)
| |
| +- if writing:
| +- beforePackageWrite(pkg) <- return false to skip
| +- afterPackageWrite(pkg, changes)
|
+- afterPackagesEnd(pkgs)
Called once after all packages have been discovered and parsed, before any resolution starts. Good for filtering, logging, or questioning your life choices.
afterPackagesLoaded?: (pkgs: PackageMeta[]) => void | Promise<void>Called before each package starts resolving. The pkg.deps array is populated but pkg.resolved is still empty.
beforePackageStart?: (pkg: PackageMeta) => void | Promise<void>Called as each individual dependency finishes resolving. This is your streaming hook -- build a progress bar, update a UI, send a webhook, whatever keeps you entertained.
onDependencyResolved?: (pkg: PackageMeta, dep: ResolvedDepChange) => void | Promise<void>Called after each package is fully resolved (and optionally written). pkg.resolved is now populated.
afterPackageEnd?: (pkg: PackageMeta) => void | Promise<void>Called before writing changes to disk. Return false to skip this package. Return true (or nothing) to proceed. This is your last chance to prevent regrettable decisions.
beforePackageWrite?: (pkg: PackageMeta) => boolean | Promise<boolean>Called after the file has been written. The damage is done. Use this for logging, notifications, or post-write commands. Receives the package and the list of changes that were applied.
afterPackageWrite?: (pkg: PackageMeta, changes: ResolvedDepChange[]) => void | Promise<void>Called once after all packages have been processed. The final callback. End of the line.
afterPackagesEnd?: (pkgs: PackageMeta[]) => void | Promise<void>Streaming progress:
let resolved = 0
let total = 0
const options = await resolveConfig({
mode: 'latest',
afterPackagesLoaded(pkgs) {
total = pkgs.reduce((sum, p) => sum + p.deps.filter(d => d.update).length, 0)
console.log(`Checking ${total} dependencies across ${pkgs.length} packages...`)
},
onDependencyResolved(_pkg, dep) {
resolved++
process.stdout.write(`\r[${resolved}/${total}] ${dep.name}`)
},
afterPackagesEnd() {
process.stdout.write('\n')
console.log('Done.')
},
})
await check(options)Addons are executed in array order (options.addons). For each lifecycle event, depfresh calls:
- The legacy callback (if present)
- Each addon hook in order
For beforePackageWrite, returning false from any callback/addon skips writing that package.
import { check, resolveConfig, type depfreshAddon } from 'depfresh'
const metricsAddon: depfreshAddon = {
name: 'metrics',
setup(ctx) {
console.log(`run ${ctx.runId} started at ${ctx.startedAt.toISOString()}`)
},
afterPackageWrite(_ctx, pkg, changes) {
console.log(`${pkg.name}: ${changes.length} updates written`)
},
}
const options = await resolveConfig({
write: true,
addons: [metricsAddon],
})
await check(options)depfresh ships with one built-in addon:
import { createVSCodeAddon } from 'depfresh'
const options = await resolveConfig({
write: true,
addons: [createVSCodeAddon()],
})
await check(options)createVSCodeAddon() syncs the engines.vscode field with the @types/vscode version when writing updates. Niche, but if you're building VS Code extensions, it saves a manual step.
Conditional write -- skip specific packages:
const options = await resolveConfig({
write: true,
mode: 'minor',
beforePackageWrite(pkg) {
// Never auto-update the root package
if (pkg.name === 'my-monorepo-root') {
console.log(`Skipping ${pkg.name}`)
return false
}
return true
},
afterPackageWrite(pkg, changes) {
console.log(`Updated ${pkg.filepath} (${changes.length} changes)`)
},
})
await check(options)