read-package-up
Read the closest package.json file, with normalized data and helpful JSON parse errors.
Repository Health
Technical Analysis
read-package-up walks up the directory tree from a given starting point to locate the nearest package.json file, then parses and normalizes its contents before returning both the resolved data and the file’s path. It builds directly on sindresorhus’s own find-up-simple for the directory traversal and read-pkg for JSON parsing and npm-style normalization, so consumers get consistent behavior without re-implementing either concern.
The package ships pure ESM with matching async (readPackageUp) and sync (readPackageUpSync) entry points, full TypeScript types with overloads that distinguish normalized from raw results, and zero runtime configuration beyond an optional starting directory — making it a drop-in building block for CLIs, bundlers, and dev tooling that need to introspect the current project’s manifest.
What You Get
- Async readPackageUp() and sync readPackageUpSync() entry points with identical behavior
- Returns both the parsed packageJson object and the absolute path it was read from
- Optional normalize option (default true) that applies npm’s normalize-package-data rules
- Full TypeScript definitions with overloads that type the result differently depending on normalize
- More descriptive JSON parse errors than a raw JSON.parse would give, via read-pkg/parse-json
Common Use Cases
- CLI tools resolving the invoking project - Command-line tools use it to find the package.json of whatever project they were invoked inside, so they can read its name, version, or config without knowing the working directory in advance.
- Build tools and bundlers reading project metadata - Bundler and codegen tooling call it to pull fields like name, version, or custom config blocks from the nearest manifest before processing a directory of source files.
- Linters and formatters locating shared config - Linting and formatting tools walk up from a file being linted to find the owning package.json and read config fields embedded in it.
- Library authors reading their own installed package.json - Libraries introspect their own package.json (for versioning or debug output) without hardcoding a relative path that breaks once the code is bundled or moved.
Under The Hood
Architecture The entire package is a thin, ~15-line composition layer: readPackageUp and readPackageUpSync each call findUp/findUpSync (from find-up-simple) to walk parent directories looking for a package.json, then hand the directory it was found in to readPackage/readPackageSync (from read-pkg) for parsing and normalization, returning {packageJson, path} or undefined if nothing was found. There is no internal state, no class hierarchy, and no configuration beyond passing options through to the two underlying packages — the async and sync code paths are structurally identical, which keeps the two entry points from drifting apart.
Tech Stack
Pure ESM (type: module) targeting Node >=20, with zero bundler or build step — index.js and a hand-written index.d.ts are shipped as-is. Runtime dependencies are find-up-simple (directory walking), read-pkg (JSON parsing/normalization), and type-fest (the Except utility type used to compose the Options type). Dev tooling is xo (an opinionated ESLint preset) for linting and ava for tests, both invoked from a single npm test script.
Code Quality
test.js exercises both the async and sync APIs against a fixture directory, asserting the correct package.json is found by walking up from an empty subdirectory, that URL and string cwd values behave identically, and that a nonexistent path (cwd: ’/’) resolves to undefined. CI (.github/workflows/main.yml) runs xo && ava across a Node 20/24 matrix on every push and PR. Types are hand-authored with function overloads that return NormalizedReadResult when normalize is left at its default and ReadResult when it’s explicitly disabled, so consumers get accurate types without a generic parameter.
API Design The public surface is deliberately minimal: two functions (one async, one sync) that share the same options shape and default to zero configuration. Returning undefined rather than throwing when no package.json exists is a considered choice that matches how callers actually want to branch on “not found” versus a real parse failure. Because it composes rather than reimplements find-up-simple and read-pkg, downstream users also inherit any fixes to directory-walking or normalization edge cases from those packages for free.