From bf586f56f14f213ac7d3e4c1ee85ef8456872c3c Mon Sep 17 00:00:00 2001 From: Elliot Nelson Date: Tue, 31 Jan 2023 03:51:19 -0500 Subject: [PATCH] Allow the caller to override the list of supported tools (#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Provide an interface for overriding the tool implementation list * Feedback * Use an options interface instead of a parameter * Apply suggestions from code review --------- Co-authored-by: Mateusz BurzyƄski --- .changeset/many-pumpkin-behavior.md | 5 +++ .changeset/odd-lies-perform.md | 5 +++ packages/find-root/src/index.test.ts | 11 +++++ packages/find-root/src/index.ts | 64 +++++++++++++++++++++++++--- packages/get-packages/src/index.ts | 35 ++++++++++++--- 5 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 .changeset/many-pumpkin-behavior.md create mode 100644 .changeset/odd-lies-perform.md diff --git a/.changeset/many-pumpkin-behavior.md b/.changeset/many-pumpkin-behavior.md new file mode 100644 index 0000000..cb61a30 --- /dev/null +++ b/.changeset/many-pumpkin-behavior.md @@ -0,0 +1,5 @@ +--- +"@manypkg/find-root": minor +--- + +The `findRoot` and `findRootSync` methods now take an optional list of `Tool` implementations, allowing the caller to restrict the desired types of monorepo discovered, or provide a custom monorepo tool implementation. diff --git a/.changeset/odd-lies-perform.md b/.changeset/odd-lies-perform.md new file mode 100644 index 0000000..b49c21a --- /dev/null +++ b/.changeset/odd-lies-perform.md @@ -0,0 +1,5 @@ +--- +"@manypkg/get-packages": minor +--- + +The `getPackages` and `getPackagesSync` methods now take an optional list of `Tool` implementations, allowing the caller to restrict the desired types of monorepo discovered, or provide a custom monorepo tool implementation. diff --git a/packages/find-root/src/index.test.ts b/packages/find-root/src/index.test.ts index 5ce27a8..83a4683 100644 --- a/packages/find-root/src/index.test.ts +++ b/packages/find-root/src/index.test.ts @@ -64,6 +64,17 @@ const runTests = (findRoot: FindRoot) => { rootDir: tmpPath, }); }); + + test("it will not find a lerna monorepo if only PnpmTool is allowed", async () => { + let tmpPath = f.copy("basic-lerna"); + await expect(async () => + findRoot(path.join(tmpPath, "packages", "package-one", "src"), { + tools: [PnpmTool], + }) + ).rejects.toThrowError( + /No monorepo matching the list of supported monorepos could be found upwards from directory / + ); + }); }; describe("findRoot", () => { diff --git a/packages/find-root/src/index.ts b/packages/find-root/src/index.ts index ddef3d3..9470c00 100644 --- a/packages/find-root/src/index.ts +++ b/packages/find-root/src/index.ts @@ -19,28 +19,65 @@ import { * monorepo implementations first, with tools based on custom file schemas * checked last. */ -const defaultOrder: Tool[] = [YarnTool, PnpmTool, LernaTool, RushTool]; +const DEFAULT_TOOLS: Tool[] = [ + YarnTool, + PnpmTool, + LernaTool, + RushTool, + RootTool, +]; const isNoEntryError = (err: unknown): boolean => !!err && typeof err === "object" && "code" in err && err.code === "ENOENT"; export class NoPkgJsonFound extends Error { + directory: string; + constructor(directory: string) { + super(`No package.json could be found upwards from directory ${directory}`); + this.directory = directory; + } +} + +export class NoMatchingMonorepoFound extends Error { directory: string; constructor(directory: string) { super( - `No package.json could be found upwards from the directory ${directory}` + `No monorepo matching the list of supported monorepos could be found upwards from directory ${directory}` ); this.directory = directory; } } -export async function findRoot(cwd: string): Promise { +/** + * Configuration options for `findRoot` and `findRootSync` functions. + */ +export interface FindRootOptions { + /** + * Override the list of monorepo tool implementations that are used during the search. + */ + tools?: Tool[]; +} + +/** + * Given a starting folder, search that folder and its parents until a supported monorepo + * is found, and return a `MonorepoRoot` object with the discovered directory and a + * corresponding monorepo `Tool` object. + * + * By default, all predefined `Tool` implementations are included in the search -- the + * caller can provide a list of desired tools to restrict the types of monorepos discovered, + * or to provide a custom tool implementation. + */ +export async function findRoot( + cwd: string, + options: FindRootOptions = {} +): Promise { let monorepoRoot: MonorepoRoot | undefined; + const tools = options.tools || DEFAULT_TOOLS; await findUp( async (directory) => { return Promise.all( - defaultOrder.map(async (tool): Promise => { + tools.map(async (tool): Promise => { if (await tool.isMonorepoRoot(directory)) { return { tool: tool, @@ -64,6 +101,10 @@ export async function findRoot(cwd: string): Promise { return monorepoRoot; } + if (!tools.includes(RootTool)) { + throw new NoMatchingMonorepoFound(cwd); + } + // 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). @@ -91,12 +132,19 @@ export async function findRoot(cwd: string): Promise { }; } -export function findRootSync(cwd: string): MonorepoRoot { +/** + * A synchronous version of {@link findRoot}. + */ +export function findRootSync( + cwd: string, + options: FindRootOptions = {} +): MonorepoRoot { let monorepoRoot: MonorepoRoot | undefined; + const tools = options.tools || DEFAULT_TOOLS; findUpSync( (directory) => { - for (const tool of defaultOrder) { + for (const tool of tools) { if (tool.isMonorepoRootSync(directory)) { monorepoRoot = { tool: tool, @@ -113,6 +161,10 @@ export function findRootSync(cwd: string): MonorepoRoot { return monorepoRoot; } + if (!tools.includes(RootTool)) { + throw new NoMatchingMonorepoFound(cwd); + } + // 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). diff --git a/packages/get-packages/src/index.ts b/packages/get-packages/src/index.ts index 190d3ca..ce91fdc 100644 --- a/packages/get-packages/src/index.ts +++ b/packages/get-packages/src/index.ts @@ -1,6 +1,6 @@ import path from "path"; -import { findRoot, findRootSync } from "@manypkg/find-root"; -import { Packages, MonorepoRoot } from "@manypkg/tools"; +import { findRoot, findRootSync, FindRootOptions } from "@manypkg/find-root"; +import { Packages, MonorepoRoot, Tool } from "@manypkg/tools"; export type { Tool, Package, Packages } from "@manypkg/tools"; @@ -16,8 +16,25 @@ export class PackageJsonMissingNameError extends Error { } } -export async function getPackages(dir: string): Promise { - const monorepoRoot: MonorepoRoot = await findRoot(dir); +/** + * Configuration options for `getPackages` and `getPackagesSync` functions. + */ +export interface GetPackagesOptions extends FindRootOptions {} + +/** + * Given a starting folder, search that folder and its parents until a supported monorepo + * is found, and return the collection of packages and a corresponding monorepo `Tool` + * object. + * + * By default, all predefined `Tool` implementations are included in the search -- the + * caller can provide a list of desired tools to restrict the types of monorepos discovered, + * or to provide a custom tool implementation. + */ +export async function getPackages( + dir: string, + options?: GetPackagesOptions +): Promise { + const monorepoRoot: MonorepoRoot = await findRoot(dir, options); const packages: Packages = await monorepoRoot.tool.getPackages(dir); validatePackages(packages); @@ -25,8 +42,14 @@ export async function getPackages(dir: string): Promise { return packages; } -export function getPackagesSync(dir: string): Packages { - const monorepoRoot: MonorepoRoot = findRootSync(dir); +/** + * A synchronous version of {@link getPackages}. + */ +export function getPackagesSync( + dir: string, + options?: GetPackagesOptions +): Packages { + const monorepoRoot: MonorepoRoot = findRootSync(dir, options); const packages: Packages = monorepoRoot.tool.getPackagesSync(dir); validatePackages(packages);