@manypkg/find-root
Advanced tools
Comparing version 1.1.0 to 2.0.0
# @manypkg/find-root | ||
## 2.0.0 | ||
### Major Changes | ||
- [#151](https://github.com/Thinkmill/manypkg/pull/151) [`a01efc9`](https://github.com/Thinkmill/manypkg/commit/a01efc9c25900b7d21b6d517a2021b021f8b3922) Thanks [@elliot-nelson](https://github.com/elliot-nelson)! - The `find-root` package now returns a new `MonorepoRoot` interface, instead of a string. This interface provides a `rootDir` for the discovered monorepo, and a `tool` object, which is an object using the new `Tool` interface provided by `@manypkg/tools`. | ||
* [#165](https://github.com/Thinkmill/manypkg/pull/165) [`7b9c4f6`](https://github.com/Thinkmill/manypkg/commit/7b9c4f6d9a73de8b3cc45af5abc8af47f6b9206c) Thanks [@Andarist](https://github.com/Andarist)! - Removed support for Bolt monorepos. | ||
- [#162](https://github.com/Thinkmill/manypkg/pull/162) [`f046017`](https://github.com/Thinkmill/manypkg/commit/f046017af2349f0c1bbc5b25224da0ede8ddc2d6) Thanks [@Andarist](https://github.com/Andarist)! - Increased the transpilation target of the source files to `node@14.x`. At the same time added this as `package.json#engines` to explicitly declare the minimum node version supported by this package. | ||
### Patch Changes | ||
- Updated dependencies [[`a01efc9`](https://github.com/Thinkmill/manypkg/commit/a01efc9c25900b7d21b6d517a2021b021f8b3922)]: | ||
- @manypkg/tools@1.0.0 | ||
## 1.1.0 | ||
@@ -4,0 +19,0 @@ |
@@ -0,1 +1,2 @@ | ||
import { MonorepoRoot } from "@manypkg/tools"; | ||
export declare class NoPkgJsonFound extends Error { | ||
@@ -5,3 +6,3 @@ directory: string; | ||
} | ||
export declare function findRoot(cwd: string): Promise<string>; | ||
export declare function findRootSync(cwd: string): string; | ||
export declare function findRoot(cwd: string): Promise<MonorepoRoot>; | ||
export declare function findRootSync(cwd: string): MonorepoRoot; |
{ | ||
"name": "@manypkg/find-root", | ||
"version": "1.1.0", | ||
"main": "dist/find-root.cjs.js", | ||
"module": "dist/find-root.esm.js", | ||
"version": "2.0.0", | ||
"license": "MIT", | ||
"main": "dist/manypkg-find-root.cjs.js", | ||
"module": "dist/manypkg-find-root.esm.js", | ||
"dependencies": { | ||
"@babel/runtime": "^7.5.5", | ||
"@manypkg/tools": "^1.0.0", | ||
"@types/node": "^12.7.1", | ||
@@ -15,3 +15,6 @@ "find-up": "^4.1.0", | ||
"fixturez": "^1.1.0" | ||
}, | ||
"engines": { | ||
"node": ">=14.18.0" | ||
} | ||
} |
# @manypkg/find-root | ||
> Find the root of a monorepo with Yarn workspaces, Bolt or pnpm | ||
> Find the root of a monorepo with Yarn workspaces, Lerna, pnpm or Rush | ||
@@ -5,0 +5,0 @@ ## Install |
import { findRoot, findRootSync } from "."; | ||
import fixturez from "fixturez"; | ||
import path from "path"; | ||
import fs from "fs-extra"; | ||
import { LernaTool, PnpmTool, RootTool, YarnTool } from "@manypkg/tools"; | ||
let f = fixturez(__dirname); | ||
@@ -13,6 +14,9 @@ | ||
let tmpPath = f.copy("basic"); | ||
let packagesRoot = await findRoot( | ||
let monorepoRoot = await findRoot( | ||
path.join(tmpPath, "packages", "package-one", "src") | ||
); | ||
expect(packagesRoot).toBe(tmpPath); | ||
expect(monorepoRoot).toEqual({ | ||
tool: YarnTool, | ||
rootDir: tmpPath, | ||
}); | ||
}); | ||
@@ -22,9 +26,12 @@ | ||
let tmpPath = f.copy("basic-lerna"); | ||
let packagesRoot = await findRoot( | ||
let monorepoRoot = await findRoot( | ||
path.join(tmpPath, "packages", "package-one", "src") | ||
); | ||
expect(packagesRoot).toBe(tmpPath); | ||
expect(monorepoRoot).toEqual({ | ||
tool: LernaTool, | ||
rootDir: tmpPath, | ||
}); | ||
}); | ||
test("it returns the root of a lerna monorepo with useWorkspaces=true", async () => { | ||
test("treats a lerna monorepo with useWorkspaces on as a yarn monorepo", async () => { | ||
let tmpPath = f.copy("basic"); | ||
@@ -34,7 +41,9 @@ // technically legal placement for lerna.json, but broken in practice | ||
// to be encountered "before" the root manifest and its valid workspaces config. | ||
await fs.outputJSON(path.join(tmpPath, "packages", "lerna.json"), {useWorkspaces: true}) | ||
let packagesRoot = await findRoot( | ||
let monorepoRoot = await findRoot( | ||
path.join(tmpPath, "packages", "package-one", "src") | ||
); | ||
expect(packagesRoot).toBe(tmpPath); | ||
expect(monorepoRoot).toEqual({ | ||
tool: YarnTool, | ||
rootDir: tmpPath, | ||
}); | ||
}); | ||
@@ -44,6 +53,9 @@ | ||
let tmpPath = f.copy("basic-pnpm"); | ||
let packagesRoot = await findRoot( | ||
let monorepoRoot = await findRoot( | ||
path.join(tmpPath, "packages", "package-one", "src") | ||
); | ||
expect(packagesRoot).toBe(tmpPath); | ||
expect(monorepoRoot).toEqual({ | ||
tool: PnpmTool, | ||
rootDir: tmpPath, | ||
}); | ||
}); | ||
@@ -53,4 +65,7 @@ | ||
let tmpPath = f.copy("single-pkg"); | ||
let packagesRoot = await findRoot(path.join(tmpPath, "src")); | ||
expect(packagesRoot).toBe(tmpPath); | ||
let monorepoRoot = await findRoot(path.join(tmpPath, "src")); | ||
expect(monorepoRoot).toEqual({ | ||
tool: RootTool, | ||
rootDir: tmpPath, | ||
}); | ||
}); | ||
@@ -57,0 +72,0 @@ }; |
206
src/index.ts
@@ -5,2 +5,24 @@ import findUp, { sync as findUpSync } from "find-up"; | ||
import { | ||
Tool, | ||
RootTool, | ||
MonorepoRoot, | ||
LernaTool, | ||
PnpmTool, | ||
RushTool, | ||
YarnTool, | ||
} from "@manypkg/tools"; | ||
/** | ||
* A default ordering for monorepo tool checks. | ||
* | ||
* This ordering is designed to check the most typical package.json-based | ||
* monorepo implementations first, with tools based on custom file schemas | ||
* checked last. | ||
*/ | ||
const defaultOrder: Tool[] = [YarnTool, PnpmTool, LernaTool, RushTool]; | ||
const isNoEntryError = (err: unknown): boolean => | ||
!!err && typeof err === "object" && "code" in err && err.code === "ENOENT"; | ||
export class NoPkgJsonFound extends Error { | ||
@@ -16,121 +38,88 @@ directory: string; | ||
async function hasWorkspacesConfiguredViaPkgJson( | ||
directory: string, | ||
firstPkgJsonDirRef: { current: string | undefined } | ||
) { | ||
try { | ||
let pkgJson = await fs.readJson(path.join(directory, "package.json")); | ||
if (firstPkgJsonDirRef.current === undefined) { | ||
firstPkgJsonDirRef.current = directory; | ||
} | ||
if (pkgJson.workspaces || pkgJson.bolt) { | ||
return directory; | ||
} | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
throw err; | ||
} | ||
} | ||
} | ||
export async function findRoot(cwd: string): Promise<MonorepoRoot> { | ||
let monorepoRoot: MonorepoRoot | undefined; | ||
async function hasWorkspacesConfiguredViaLerna(directory: string) { | ||
try { | ||
let lernaJson = await fs.readJson(path.join(directory, "lerna.json")); | ||
if (lernaJson.useWorkspaces !== true) { | ||
return directory; | ||
} | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
throw err; | ||
} | ||
} | ||
} | ||
await findUp( | ||
async (directory) => { | ||
return Promise.all( | ||
defaultOrder.map(async (tool): Promise<MonorepoRoot | undefined> => { | ||
if (await tool.isMonorepoRoot(directory)) { | ||
return { | ||
tool: tool, | ||
rootDir: directory, | ||
}; | ||
} | ||
}) | ||
) | ||
.then((x) => x.find((value) => value)) | ||
.then((result) => { | ||
if (result) { | ||
monorepoRoot = result; | ||
return directory; | ||
} | ||
}); | ||
}, | ||
{ cwd, type: "directory" } | ||
); | ||
async function hasWorkspacesConfiguredViaPnpm(directory: string) { | ||
// @ts-ignore | ||
let pnpmWorkspacesFileExists = await fs.exists( | ||
path.join(directory, "pnpm-workspace.yaml") | ||
); | ||
if (pnpmWorkspacesFileExists) { | ||
return directory; | ||
if (monorepoRoot) { | ||
return monorepoRoot; | ||
} | ||
} | ||
export async function findRoot(cwd: string): Promise<string> { | ||
let firstPkgJsonDirRef: { current: string | undefined } = { | ||
current: undefined | ||
}; | ||
let dir = await findUp( | ||
directory => { | ||
return Promise.all([ | ||
hasWorkspacesConfiguredViaLerna(directory), | ||
hasWorkspacesConfiguredViaPkgJson(directory, firstPkgJsonDirRef), | ||
hasWorkspacesConfiguredViaPnpm(directory) | ||
]).then(x => x.find(dir => dir)); | ||
// If there is no monorepo root, but we can find a single package json file, we will | ||
// return a "RootTool" repo, which is the special case where we just have a root package | ||
// with no monorepo implementation (i.e.: a normal package folder). | ||
let rootDir = await findUp( | ||
async (directory) => { | ||
try { | ||
await fs.access(path.join(directory, "package.json")); | ||
return directory; | ||
} catch (err) { | ||
if (!isNoEntryError(err)) { | ||
throw err; | ||
} | ||
} | ||
}, | ||
{ cwd, type: "directory" } | ||
); | ||
if (firstPkgJsonDirRef.current === undefined) { | ||
if (!rootDir) { | ||
throw new NoPkgJsonFound(cwd); | ||
} | ||
if (dir === undefined) { | ||
return firstPkgJsonDirRef.current; | ||
} | ||
return dir; | ||
} | ||
function hasWorkspacesConfiguredViaPkgJsonSync( | ||
directory: string, | ||
firstPkgJsonDirRef: { current: string | undefined } | ||
) { | ||
try { | ||
const pkgJson = fs.readJsonSync(path.join(directory, "package.json")); | ||
if (firstPkgJsonDirRef.current === undefined) { | ||
firstPkgJsonDirRef.current = directory; | ||
} | ||
if (pkgJson.workspaces || pkgJson.bolt) { | ||
return directory; | ||
} | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
throw err; | ||
} | ||
} | ||
return { | ||
tool: RootTool, | ||
rootDir, | ||
}; | ||
} | ||
function hasWorkspacesConfiguredViaLernaSync(directory: string) { | ||
try { | ||
let lernaJson = fs.readJsonSync(path.join(directory, "lerna.json")); | ||
if (lernaJson.useWorkspaces !== true) { | ||
return directory; | ||
} | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
throw err; | ||
} | ||
} | ||
} | ||
export function findRootSync(cwd: string): MonorepoRoot { | ||
let monorepoRoot: MonorepoRoot | undefined; | ||
function hasWorkspacesConfiguredViaPnpmSync(directory: string) { | ||
// @ts-ignore | ||
let pnpmWorkspacesFileExists = fs.existsSync( | ||
path.join(directory, "pnpm-workspace.yaml") | ||
findUpSync( | ||
(directory) => { | ||
for (const tool of defaultOrder) { | ||
if (tool.isMonorepoRootSync(directory)) { | ||
monorepoRoot = { | ||
tool: tool, | ||
rootDir: directory, | ||
}; | ||
return directory; | ||
} | ||
} | ||
}, | ||
{ cwd, type: "directory" } | ||
); | ||
if (pnpmWorkspacesFileExists) { | ||
return directory; | ||
if (monorepoRoot) { | ||
return monorepoRoot; | ||
} | ||
} | ||
export function findRootSync(cwd: string) { | ||
let firstPkgJsonDirRef: { current: string | undefined } = { | ||
current: undefined | ||
}; | ||
let dir = findUpSync( | ||
directory => { | ||
return [ | ||
hasWorkspacesConfiguredViaLernaSync(directory), | ||
hasWorkspacesConfiguredViaPkgJsonSync(directory, firstPkgJsonDirRef), | ||
hasWorkspacesConfiguredViaPnpmSync(directory) | ||
].find(dir => dir); | ||
// If there is no monorepo root, but we can find a single package json file, we will | ||
// return a "RootTool" repo, which is the special case where we just have a root package | ||
// with no monorepo implementation (i.e.: a normal package folder). | ||
const rootDir = findUpSync( | ||
(directory) => { | ||
const exists = fs.existsSync(path.join(directory, "package.json")); | ||
return exists ? directory : undefined; | ||
}, | ||
@@ -140,9 +129,10 @@ { cwd, type: "directory" } | ||
if (firstPkgJsonDirRef.current === undefined) { | ||
if (!rootDir) { | ||
throw new NoPkgJsonFound(cwd); | ||
} | ||
if (dir === undefined) { | ||
return firstPkgJsonDirRef.current; | ||
} | ||
return dir; | ||
return { | ||
tool: RootTool, | ||
rootDir, | ||
}; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
18335
526
1
+ Added@manypkg/tools@^1.0.0
+ Added@manypkg/tools@1.1.2(transitive)
+ Added@nodelib/fs.scandir@2.1.5(transitive)
+ Added@nodelib/fs.stat@2.0.5(transitive)
+ Added@nodelib/fs.walk@1.2.8(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedfast-glob@3.3.2(transitive)
+ Addedfastq@1.17.1(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedjju@1.4.0(transitive)
+ Addedjs-yaml@4.1.0(transitive)
+ Addedmerge2@1.4.1(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedqueue-microtask@1.2.3(transitive)
+ Addedreusify@1.0.4(transitive)
+ Addedrun-parallel@1.2.0(transitive)
+ Addedto-regex-range@5.0.1(transitive)
- Removed@babel/runtime@^7.5.5
- Removed@babel/runtime@7.26.0(transitive)
- Removedregenerator-runtime@0.14.1(transitive)