@baphy/npm
v1.2.0
Published
Detect whether a GitHub repository is a monorepo by analyzing its git tree
Maintainers
Readme
@baphy/npm
Detect whether a repository is a monorepo by analyzing its git tree
Overview
@baphy/npm receives a flat list of file paths from any git tree source and tells you:
isMonoRepo— whether the repository is a monorepopackages— the list of detected packages, each with the path to itspackage.jsontruncated— whether the source truncated the tree (passed through from your input)
Platform-agnostic — works with GitHub, GitLab, Bitbucket, or any other source. Map your data to a string[] of paths before calling.
Zero dependencies · Synchronous · O(n) · < 7ms on a 100k-entry tree
Installation
npm install @baphy/npmUsage
import { detectMonoRepo } from '@baphy/npm'
// Map your source data to a string[] of paths, then call detectMonoRepo.
// The second argument mirrors any truncation flag from your source (optional, defaults to false).
// GitHub example:
// GET /repos/{owner}/{repo}/git/trees/{sha}?recursive=1
const gitTree = await fetch(url, { headers }).then(r => r.json())
const result = detectMonoRepo(
gitTree.tree.map((item: { path: string }) => item.path),
gitTree.truncated,
)
console.log(result)
// {
// isMonoRepo: true,
// packages: [
// { path: 'packages/turbo', packageJsonPath: 'packages/turbo/package.json' },
// { path: 'packages/create-turbo', packageJsonPath: 'packages/create-turbo/package.json' },
// ...
// ],
// truncated: false
// }Fetching package names (GitHub example)
The packageJsonPath field lets you retrieve each package's metadata with a second API call:
const { packages } = detectMonoRepo(
gitTree.tree.map((item: { path: string }) => item.path),
gitTree.truncated,
)
const details = await Promise.all(
packages.map(async (pkg) => {
const res = await fetch(
`https://api.github.com/repos/{owner}/{repo}/contents/${pkg.packageJsonPath}`,
{ headers: { Authorization: `Bearer ${token}` } }
)
const { content } = await res.json()
const { name, version, private: isPrivate } = JSON.parse(
Buffer.from(content, 'base64').toString()
)
return { ...pkg, name, version, isPrivate }
})
)API
detectMonoRepo(paths, truncated?)
function detectMonoRepo(paths: string[], truncated?: boolean): MonoRepoResultParameters
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| paths | string[] | — | Flat list of file paths from any git tree source |
| truncated | boolean | false | Pass through the truncation flag from your source |
Returns
interface MonoRepoResult {
isMonoRepo: boolean
packages: MonoRepoPackage[]
/** true when the source truncated the tree — results may be incomplete */
truncated: boolean
}
interface MonoRepoPackage {
/** Directory path, e.g. "." or "packages/npm" */
path: string
/** Path to the package.json for this package */
packageJsonPath: string
}When isMonoRepo is false, packages still includes the root package if a root package.json exists:
detectMonoRepo(['package.json', 'src/index.ts'])
// {
// isMonoRepo: false,
// packages: [{ path: '.', packageJsonPath: 'package.json' }],
// truncated: false
// }When
truncatedistrue, the tree was cut short before all files were returned. AfalseisMonoRepomay not be reliable. Always checktruncatedbefore acting on the result.
Types
import type { MonoRepoPackage, MonoRepoResult } from '@baphy/npm'Detection Strategy
| Tool | Indicator file(s) | Signal |
|---|---|---|
| pnpm workspaces | pnpm-workspace.yaml · pnpm-workspace.yml | Definitive |
| Lerna | lerna.json | Definitive |
| Nx | nx.json | Definitive |
| Rush | rush.json | Definitive |
| Moon | .moon/workspace.yml | Definitive |
| npm / yarn workspaces | (inferred — content not readable from tree) | Structural |
| Turborepo | turbo.json | Soft |
Definitive — file presence alone confirms a monorepo, even if no packages are discovered.
Soft — turbo.json can exist in single-package repos; treated as definitive only when workspace packages are also found.
Structural — when none of the config files above are present but more than one package.json is found exactly one level deep inside packages/, apps/, libs/, services/, tools/, or plugins/, a monorepo is inferred (the workspaces field in the root package.json cannot be read from the git tree).
Performance
Benchmarked with vitest bench on a 100,000-entry tree (GitHub's maximum non-truncated response):
| Tree size | Mean time | |---|---| | 100 items | ~0.005 ms | | 1,000 items | ~0.056 ms | | 10,000 items | ~0.61 ms | | 100,000 items | ~6.7 ms |
The single-pass O(n) algorithm scales linearly with tree size. Reproduce locally:
npm run benchLicense
MIT © pilmee
