diff --git a/.changeset/cool-goats-kiss.md b/.changeset/cool-goats-kiss.md new file mode 100644 index 0000000..67dfb39 --- /dev/null +++ b/.changeset/cool-goats-kiss.md @@ -0,0 +1,5 @@ +--- +"@manypkg/find-root": patch +--- + +Remove find-up dependency diff --git a/.changeset/spotty-owls-pull.md b/.changeset/spotty-owls-pull.md new file mode 100644 index 0000000..8ade087 --- /dev/null +++ b/.changeset/spotty-owls-pull.md @@ -0,0 +1,6 @@ +--- +"@manypkg/find-root": patch +"@manypkg/tools": patch +--- + +Remove fs-extra from dependencies diff --git a/README.md b/README.md index 34f565b..3a3898c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,64 @@ As an example, let's say there are two packages which both have a `dist` dir, `m yarn manypkg exec rm -rf dist ``` +## Configuration + +Manypkg supports a number of options. Options can be provided using the `manypkg` key in your root `package.json` file: + +```json +{ + "name": "monorepo-root", + "private": true, + "manypkg": {} +} +``` + +### `defaultBranch` + +Used by the [Incorrect `repository` field](#incorrect-repository-field) rule to determine the correct name for repositories. The default value is `master`. + +```json +{ + "manypkg": { + "defaultBranch": "master" + } +} +``` + +### `ignoredRules` + +Used to determine which checks/fixes should ignored by the Manypkg cli. The default value is `[]` (all checks/fixes enabled). + +```json +{ + "manypkg": { + "ignoredRules": [] + } +} +``` + +To ignore a rule, find the rule in the [Checks section](#checks) below and add its "Key" value to the array. For example, to disable the [External mismatch rule](#external-mismatch): + +```json +{ + "manypkg": { + "ignoredRules": ["EXTERNAL_MISMATCH"] + } +} +``` + +### `workspaceProtocol` + +Used to determine whether the `workspace:` protocol for internal packages is required (`require`) or allowed (`allow`). The default value is `allow`. + +```json +{ + "manypkg": { + "workspaceProtocol": "allow" + } +} +``` + ## Dictionary - **private package** - A package that has `private: true`/is not published. It does not refer to a package published to a private registry here. @@ -92,6 +150,8 @@ yarn manypkg exec rm -rf dist ## External mismatch +Key: `EXTERNAL_MISMATCH` + The ranges for all dependencies(excluding `peerDependencies`) on external packages should exactly match(`===`). It's important to note that this check does not enforce that only a single version of an external package is installed, only that two versions of an external package will never be installed because they're specified as dependencies of internal packages. ### Why it's a rule @@ -208,6 +268,8 @@ There are some cases where you might want to intentionally have conflicts betwee ## Internal mismatch +Key: `INTERNAL_MISMATCH` + The ranges for all regular dependencies, devDependencies and optionalDependencies(not peerDependencies) on internal packages should include the version of the internal package. ### Why it's a rule @@ -220,6 +282,8 @@ If the range is a [caret range](https://github.com/npm/node-semver#caret-ranges- ## Invalid dev and peer dependency relationship +Key: `INVALID_DEV_AND_PEER_DEPENDENCY_RELATIONSHIP` + All `peerDependencies` should also be specified in `devDependencies` and the range specified in `devDependencies` should be a subset of the range for that dependency in `peerDependencies`. ### Why it's a rule @@ -232,6 +296,8 @@ The range for the dependency specified in `peerDependencies` is added to `devDep ## Root has devDependencies +Key: `ROOT_HAS_DEV_DEPENDENCIES` + The root package should not have any `devDependencies`, instead all dependencies should be in `dependencies` ### Why it's a rule @@ -244,6 +310,8 @@ All `devDependencies` in the root `package.json` are moved to `dependencies`. ## Multiple dependency types +Key: `MULTIPLE_DEPENDENCY_TYPES` + A dependency shouldn't be specified in more than one of `dependencies`, `devDependencies` or `optionalDependencies`. ### How it's fixed @@ -252,6 +320,8 @@ The dep is removed from `devDependencies` or `optionalDependencies` if it's also ## Invalid package name +Key: `INVALID_PACKAGE_NAME` + There are rules from npm about what a package name can be and a package will fail to publish if those rules are not met. ### Why it's a rule @@ -264,6 +334,8 @@ This requires manual fixing as automatically fixing this may lead to valid but i ## Unsorted dependencies +Key: `UNSORTED_DEPENDENCIES` + Dependencies in the dependency fields(`dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`) should be sorted alphabetically. ### Why it's a rule @@ -276,6 +348,8 @@ This is fixed by sorting deps by key alphabetically. ## Incorrect `repository` field +Key: `INCORRECT_REPOSITORY_FIELD` + If a GitHub repo URL is in the `repository` field in the root `package.json`, all of the packages should have a `repository` field which goes into the directory of the package. ### Why it's a rule @@ -288,6 +362,8 @@ This is fixed by setting the correct URL. ## `workspace:` protocol required +Key: `WORKSPACE_REQUIRED` + If `"workspaceProtocol": "require"` is set in the `manypkg` config in the root `package.json`, all dependencies on internal packages are required to use the `workspace:` protocol. ### Why it's a rule diff --git a/packages/find-root/package.json b/packages/find-root/package.json index 6753b08..43f2d1d 100644 --- a/packages/find-root/package.json +++ b/packages/find-root/package.json @@ -10,9 +10,7 @@ "main": "dist/manypkg-find-root.cjs.js", "module": "dist/manypkg-find-root.esm.js", "dependencies": { - "@manypkg/tools": "^1.1.1", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0" + "@manypkg/tools": "^1.1.1" }, "devDependencies": { "@types/node": "^16.11.7", diff --git a/packages/find-root/src/index.ts b/packages/find-root/src/index.ts index 072138d..916afa9 100644 --- a/packages/find-root/src/index.ts +++ b/packages/find-root/src/index.ts @@ -1,6 +1,6 @@ -import findUp, { sync as findUpSync } from "find-up"; import path from "path"; -import fs from "fs-extra"; +import fs from "fs"; +import fsp from "fs/promises"; import { Tool, @@ -98,7 +98,7 @@ export async function findRoot( } }); }, - { cwd, type: "directory" } + cwd ); if (monorepoRoot) { @@ -115,7 +115,7 @@ export async function findRoot( let rootDir = await findUp( async (directory) => { try { - await fs.access(path.join(directory, "package.json")); + await fsp.access(path.join(directory, "package.json")); return directory; } catch (err) { if (!isNoEntryError(err)) { @@ -123,7 +123,7 @@ export async function findRoot( } } }, - { cwd, type: "directory" } + cwd ); if (!rootDir) { @@ -158,7 +158,7 @@ export function findRootSync( } } }, - { cwd, type: "directory" } + cwd ); if (monorepoRoot) { @@ -177,7 +177,7 @@ export function findRootSync( const exists = fs.existsSync(path.join(directory, "package.json")); return exists ? directory : undefined; }, - { cwd, type: "directory" } + cwd ); if (!rootDir) { @@ -189,3 +189,31 @@ export function findRootSync( rootDir, }; } + +async function findUp(matcher: (directory: string) => Promise, cwd: string) { + let directory = path.resolve(cwd); + const { root } = path.parse(directory); + + while (directory && directory !== root) { + const filePath = await matcher(directory); + if (filePath) { + return path.resolve(directory, filePath); + } + + directory = path.dirname(directory); + } +} + +function findUpSync(matcher: (directory: string) => string | undefined, cwd: string) { + let directory = path.resolve(cwd); + const { root } = path.parse(directory); + + while (directory && directory !== root) { + const filePath = matcher(directory); + if (filePath) { + return path.resolve(directory, filePath); + } + + directory = path.dirname(directory); + } +} diff --git a/packages/tools/package.json b/packages/tools/package.json index a51a5f3..057055c 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -9,7 +9,6 @@ "license": "MIT", "main": "dist/manypkg-tools.cjs.js", "dependencies": { - "fs-extra": "^8.1.0", "globby": "^11.0.0", "jju": "^1.4.0", "js-yaml": "^4.1.0" diff --git a/packages/tools/src/BoltTool.ts b/packages/tools/src/BoltTool.ts index 8a4005f..a4b60ca 100644 --- a/packages/tools/src/BoltTool.ts +++ b/packages/tools/src/BoltTool.ts @@ -1,11 +1,11 @@ import path from "path"; -import fs from "fs-extra"; import { Tool, PackageJSON, Packages, InvalidMonorepoError } from "./Tool"; import { expandPackageGlobs, expandPackageGlobsSync, } from "./expandPackageGlobs"; +import { readJson, readJsonSync } from "./utils"; export interface BoltPackageJSON extends PackageJSON { bolt?: { @@ -18,9 +18,7 @@ export const BoltTool: Tool = { async isMonorepoRoot(directory: string): Promise { try { - const pkgJson = (await fs.readJson( - path.join(directory, "package.json") - )) as BoltPackageJSON; + const pkgJson = await readJson(directory, "package.json") as BoltPackageJSON; if (pkgJson.bolt && pkgJson.bolt.workspaces) { return true; } @@ -35,9 +33,7 @@ export const BoltTool: Tool = { isMonorepoRootSync(directory: string): boolean { try { - const pkgJson = fs.readJsonSync( - path.join(directory, "package.json") - ) as BoltPackageJSON; + const pkgJson = readJsonSync(directory, "package.json") as BoltPackageJSON; if (pkgJson.bolt && pkgJson.bolt.workspaces) { return true; } @@ -54,9 +50,7 @@ export const BoltTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson = (await fs.readJson( - path.join(rootDir, "package.json") - )) as BoltPackageJSON; + const pkgJson = await readJson(rootDir, "package.json") as BoltPackageJSON; if (!pkgJson.bolt || !pkgJson.bolt.workspaces) { throw new InvalidMonorepoError( `Directory ${rootDir} is not a valid ${BoltTool.type} monorepo root: missing bolt.workspaces entry` @@ -88,9 +82,7 @@ export const BoltTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson = fs.readJsonSync( - path.join(rootDir, "package.json") - ) as BoltPackageJSON; + const pkgJson = readJsonSync(rootDir, "package.json") as BoltPackageJSON; if (!pkgJson.bolt || !pkgJson.bolt.workspaces) { throw new InvalidMonorepoError( `Directory ${directory} is not a valid ${BoltTool.type} monorepo root: missing bolt.workspaces entry` diff --git a/packages/tools/src/LernaTool.ts b/packages/tools/src/LernaTool.ts index 06028dc..51cee9a 100644 --- a/packages/tools/src/LernaTool.ts +++ b/packages/tools/src/LernaTool.ts @@ -1,9 +1,7 @@ import path from "path"; -import fs from "fs-extra"; import { Tool, - Package, PackageJSON, Packages, InvalidMonorepoError, @@ -12,6 +10,7 @@ import { expandPackageGlobs, expandPackageGlobsSync, } from "./expandPackageGlobs"; +import { readJson, readJsonSync } from "./utils"; export interface LernaJson { useWorkspaces?: boolean; @@ -23,9 +22,7 @@ export const LernaTool: Tool = { async isMonorepoRoot(directory: string): Promise { try { - const lernaJson = (await fs.readJson( - path.join(directory, "lerna.json") - )) as LernaJson; + const lernaJson = await readJson(directory, "lerna.json") as LernaJson; if (lernaJson.useWorkspaces !== true) { return true; } @@ -40,9 +37,7 @@ export const LernaTool: Tool = { isMonorepoRootSync(directory: string): boolean { try { - const lernaJson = fs.readJsonSync( - path.join(directory, "lerna.json") - ) as LernaJson; + const lernaJson = readJsonSync(directory, "lerna.json") as LernaJson; if (lernaJson.useWorkspaces !== true) { return true; } @@ -59,10 +54,8 @@ export const LernaTool: Tool = { const rootDir = path.resolve(directory); try { - const lernaJson = await fs.readJson(path.join(rootDir, "lerna.json")); - const pkgJson = (await fs.readJson( - path.join(rootDir, "package.json") - )) as PackageJSON; + const lernaJson = await readJson(rootDir, "lerna.json") as LernaJson; + const pkgJson = await readJson(rootDir, "package.json") as PackageJSON; const packageGlobs: string[] = lernaJson.packages || ["packages/*"]; return { @@ -89,12 +82,8 @@ export const LernaTool: Tool = { const rootDir = path.resolve(directory); try { - const lernaJson = fs.readJsonSync( - path.join(rootDir, "lerna.json") - ) as LernaJson; - const pkgJson = fs.readJsonSync( - path.join(rootDir, "package.json") - ) as PackageJSON; + const lernaJson = readJsonSync(rootDir, "lerna.json") as LernaJson; + const pkgJson = readJsonSync(rootDir, "package.json") as PackageJSON; const packageGlobs: string[] = lernaJson.packages || ["packages/*"]; return { diff --git a/packages/tools/src/PnpmTool.ts b/packages/tools/src/PnpmTool.ts index ccddb9d..a542a75 100644 --- a/packages/tools/src/PnpmTool.ts +++ b/packages/tools/src/PnpmTool.ts @@ -1,6 +1,7 @@ import path from "path"; import yaml from "js-yaml"; -import fs from "fs-extra"; +import fs from "fs"; +import fsp from "fs/promises"; import { Tool, @@ -12,9 +13,10 @@ import { expandPackageGlobs, expandPackageGlobsSync, } from "./expandPackageGlobs"; +import { readJson, readJsonSync } from "./utils"; async function readYamlFile(path: string): Promise { - return fs.promises.readFile(path, 'utf8').then(data => yaml.load(data)) as Promise; + return fsp.readFile(path, 'utf8').then(data => yaml.load(data)) as Promise; } function readYamlFileSync(path: string): T { return yaml.load(fs.readFileSync(path, 'utf8')) as T; @@ -70,9 +72,7 @@ export const PnpmTool: Tool = { const manifest = await readYamlFile<{ packages?: string[] }>( path.join(rootDir, "pnpm-workspace.yaml") ); - const pkgJson = (await fs.readJson( - path.join(rootDir, "package.json") - )) as PackageJSON; + const pkgJson = await readJson(rootDir, "package.json") as PackageJSON; const packageGlobs: string[] = manifest.packages!; return { @@ -102,9 +102,7 @@ export const PnpmTool: Tool = { const manifest = readYamlFileSync<{ packages?: string[] }>( path.join(rootDir, "pnpm-workspace.yaml") ); - const pkgJson = fs.readJsonSync( - path.join(rootDir, "package.json") - ) as PackageJSON; + const pkgJson = readJsonSync(rootDir, "package.json") as PackageJSON; const packageGlobs: string[] = manifest.packages!; return { diff --git a/packages/tools/src/RootTool.ts b/packages/tools/src/RootTool.ts index d31b18f..2ab2c0e 100644 --- a/packages/tools/src/RootTool.ts +++ b/packages/tools/src/RootTool.ts @@ -1,5 +1,4 @@ import path from "path"; -import fs from "fs-extra"; import { Tool, @@ -8,7 +7,7 @@ import { Packages, InvalidMonorepoError, } from "./Tool"; -import { expandPackageGlobs } from "./expandPackageGlobs"; +import { readJson, readJsonSync } from "./utils"; export const RootTool: Tool = { type: "root", @@ -27,9 +26,7 @@ export const RootTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson: PackageJSON = await fs.readJson( - path.join(rootDir, "package.json") - ); + const pkgJson = await readJson(rootDir, "package.json") as PackageJSON; const pkg: Package = { dir: rootDir, relativeDir: ".", @@ -56,9 +53,7 @@ export const RootTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson: PackageJSON = fs.readJsonSync( - path.join(rootDir, "package.json") - ); + const pkgJson = readJsonSync(rootDir, "package.json") as PackageJSON; const pkg: Package = { dir: rootDir, relativeDir: ".", diff --git a/packages/tools/src/RushTool.ts b/packages/tools/src/RushTool.ts index 72dd05d..3921e95 100644 --- a/packages/tools/src/RushTool.ts +++ b/packages/tools/src/RushTool.ts @@ -1,5 +1,6 @@ import path from "path"; -import fs from "fs-extra"; +import fs from "fs"; +import fsp from "fs/promises"; import jju from "jju"; import { @@ -9,6 +10,7 @@ import { Packages, InvalidMonorepoError, } from "./Tool"; +import { readJson, readJsonSync } from "./utils"; interface RushJson { projects: RushProject[]; @@ -24,7 +26,7 @@ export const RushTool: Tool = { async isMonorepoRoot(directory: string): Promise { try { - await fs.readFile(path.join(directory, "rush.json"), "utf8"); + await fsp.readFile(path.join(directory, "rush.json"), "utf8"); return true; } catch (err) { if (err && (err as { code: string }).code === "ENOENT") { @@ -32,7 +34,6 @@ export const RushTool: Tool = { } throw err; } - return false; }, isMonorepoRootSync(directory: string): boolean { @@ -45,14 +46,13 @@ export const RushTool: Tool = { } throw err; } - return false; }, async getPackages(directory: string): Promise { const rootDir = path.resolve(directory); try { - const rushText: string = await fs.readFile( + const rushText: string = await fsp.readFile( path.join(rootDir, "rush.json"), "utf8" ); @@ -69,7 +69,7 @@ export const RushTool: Tool = { return { dir, relativeDir: path.relative(directory, dir), - packageJson: await fs.readJson(path.join(dir, "package.json")), + packageJson: await readJson(dir, "package.json"), }; }) ); @@ -107,9 +107,7 @@ export const RushTool: Tool = { path.resolve(rootDir, project.projectFolder) ); const packages: Package[] = directories.map((dir: string) => { - const packageJson: PackageJSON = fs.readJsonSync( - path.join(dir, "package.json") - ); + const packageJson: PackageJSON = readJsonSync(dir, "package.json"); return { dir, relativeDir: path.relative(directory, dir), diff --git a/packages/tools/src/YarnTool.ts b/packages/tools/src/YarnTool.ts index 6d80b22..406be58 100644 --- a/packages/tools/src/YarnTool.ts +++ b/packages/tools/src/YarnTool.ts @@ -1,9 +1,7 @@ import path from "path"; -import fs from "fs-extra"; import { Tool, - Package, PackageJSON, Packages, InvalidMonorepoError, @@ -12,6 +10,7 @@ import { expandPackageGlobs, expandPackageGlobsSync, } from "./expandPackageGlobs"; +import { readJson, readJsonSync } from "./utils"; export interface YarnPackageJSON extends PackageJSON { workspaces?: string[] | { packages: string[] }; @@ -22,9 +21,7 @@ export const YarnTool: Tool = { async isMonorepoRoot(directory: string): Promise { try { - const pkgJson = (await fs.readJson( - path.join(directory, "package.json") - )) as YarnPackageJSON; + const pkgJson = await readJson(directory, "package.json") as YarnPackageJSON; if (pkgJson.workspaces) { if ( Array.isArray(pkgJson.workspaces) || @@ -44,9 +41,7 @@ export const YarnTool: Tool = { isMonorepoRootSync(directory: string): boolean { try { - const pkgJson = fs.readJsonSync( - path.join(directory, "package.json") - ) as YarnPackageJSON; + const pkgJson = readJsonSync(directory, "package.json") as YarnPackageJSON; if (pkgJson.workspaces) { if ( Array.isArray(pkgJson.workspaces) || @@ -68,9 +63,7 @@ export const YarnTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson = (await fs.readJson( - path.join(rootDir, "package.json") - )) as YarnPackageJSON; + const pkgJson = await readJson(rootDir, "package.json") as YarnPackageJSON; const packageGlobs: string[] = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces!.packages; @@ -99,9 +92,7 @@ export const YarnTool: Tool = { const rootDir = path.resolve(directory); try { - const pkgJson = fs.readJsonSync( - path.join(rootDir, "package.json") - ) as YarnPackageJSON; + const pkgJson = readJsonSync(rootDir, "package.json") as YarnPackageJSON; const packageGlobs: string[] = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces!.packages; diff --git a/packages/tools/src/expandPackageGlobs.ts b/packages/tools/src/expandPackageGlobs.ts index 3058c31..4c0ee2c 100644 --- a/packages/tools/src/expandPackageGlobs.ts +++ b/packages/tools/src/expandPackageGlobs.ts @@ -1,8 +1,9 @@ import path from "path"; -import fs from "fs-extra"; +import fsp from "fs/promises"; import globby from "globby"; -import { Tool, Package, PackageJSON, Packages, MonorepoRoot } from "./Tool"; +import { Package, PackageJSON } from "./Tool"; +import { readJsonSync } from "./utils"; /** * This internal method takes a list of one or more directory globs and the absolute path @@ -25,20 +26,20 @@ export async function expandPackageGlobs( const discoveredPackages: Array = await Promise.all( directories.map((dir) => - fs - .readJson(path.join(dir, "package.json")) + fsp + .readFile(path.join(dir, "package.json"), "utf-8") .catch((err) => { if (err && (err as { code: string }).code === "ENOENT") { return undefined; } throw err; }) - .then((result: PackageJSON | undefined) => { + .then((result) => { if (result) { return { dir: path.resolve(dir), relativeDir: path.relative(directory, dir), - packageJson: result, + packageJson: JSON.parse(result), }; } }) @@ -68,9 +69,7 @@ export function expandPackageGlobsSync( const discoveredPackages: Array = directories.map( (dir) => { try { - const packageJson: PackageJSON = fs.readJsonSync( - path.join(dir, "package.json") - ); + const packageJson: PackageJSON = readJsonSync(dir, "package.json"); return { dir: path.resolve(dir), relativeDir: path.relative(directory, dir), diff --git a/packages/tools/src/utils.ts b/packages/tools/src/utils.ts new file mode 100644 index 0000000..9decd1e --- /dev/null +++ b/packages/tools/src/utils.ts @@ -0,0 +1,13 @@ +import fs from "fs"; +import fsp from "fs/promises"; +import path from "path"; + +export const readJson = async (directory: string, file: string) => JSON.parse((await fsp.readFile( + path.join(directory, file), + "utf-8" + ))); + +export const readJsonSync = (directory: string, file: string) => JSON.parse(fs.readFileSync( + path.join(directory, file), + "utf-8" + ));