diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c16c1e..de736dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ This changelog format is largely based on [Keep A Changelog](https://keepachange #### 🔨 Chores & Documentation +## [0.3.0] - 2022-06-27 + +#### 🚀 New Features & Enhancements + +- Add detection and support for Yarn Berry + ## [0.2.0] - 2022-05-16 #### 🚀 New Features & Enhancements diff --git a/ONE-VERSION.md b/ONE-VERSION.md index 5b4b2e6..770b40e 100644 --- a/ONE-VERSION.md +++ b/ONE-VERSION.md @@ -58,4 +58,4 @@ Although using the same specifier, the entries resolve to two different versions - This library currently only operates on declared dependencies. That is the `dependencies`, `devDependencies`, and `peerDependencies` specified by a workspace - **not** any transitive dependencies. - Resolutions are not yet taken into account. -- Package manager is selected based on the lockfile name in the root of the repo. +- Package manager is selected based on the lockfile name in the root of the repo. Berry is chosen over Yarn classic if a `.yarnrc.yml` file exists at the root of the repo. diff --git a/README.md b/README.md index 073ca25..8e7832a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ One Version to bring them all, and in the darkness bind them.1 **🚨 Enforcement**: Require all workspaces in a monorepo to conform to the [One-Version rule](#one-version-rule). -**📦 Supports multiple package managers**: Support for `yarn` and `pnpm` workspaces. +**📦 Supports multiple package managers**: Support for `yarn` classic, `yarn` berry, and `pnpm` workspaces. **💥 Coordinated upgrades**: Coming Soon! diff --git a/package.json b/package.json index 2a6a66b..9b3d20c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wayfair/one-version", - "version": "0.2.0", + "version": "0.3.0", "description": "Opinionated Monorepo Dependency Management CLI", "bin": "src/index.js", "main": "src/index.js", diff --git a/src/check.js b/src/check.js index 5ac1e83..6c6ab91 100644 --- a/src/check.js +++ b/src/check.js @@ -5,7 +5,7 @@ Note: Currently enforces the specifications match exactly, i.e. `^17` != `17`. const chalk = require("chalk"); const { parseConfig, detectPackageManager } = require("./shared/util"); const { format } = require("./format-output"); -const { checkYarn } = require("./yarn/check"); +const { checkYarn, checkBerry } = require("./yarn/check"); const { checkPnpm } = require("./pnpm/check"); const { UNABLE_TO_DETECT_PACKAGE_MANAGER_ERROR, @@ -16,6 +16,7 @@ const { const PACKAGE_MANGER_API = { pnpm: checkPnpm, yarn: checkYarn, + berry: checkBerry, }; const getCheckPackageApi = (packageManager) => { diff --git a/src/shared/__tests__/util.test.js b/src/shared/__tests__/util.test.js index 08ecb6d..df39c82 100644 --- a/src/shared/__tests__/util.test.js +++ b/src/shared/__tests__/util.test.js @@ -6,8 +6,8 @@ const { } = require("../util"); const fs = require("fs"); -jest.mock('fs', () => ({ - ...jest.requireActual('fs'), +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), existsSync: jest.fn(), })); @@ -127,20 +127,30 @@ describe("findDuplicateDependencies", () => { }); it("returns pnpm if pnpm-lock.yml exists", () => { - fs.existsSync.mockReturnValueOnce(true); - const packageManager = detectPackageManager({pnpm: "pnpm-lock.yaml",}); - expect(packageManager).toBe('pnpm'); + // first check for yarn.lock, then pnpm + fs.existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true); + const packageManager = detectPackageManager(); + expect(packageManager).toBe("pnpm"); }); - it("returns pnpm if yarn.lock exists", () => { - fs.existsSync.mockReturnValueOnce(true); - const packageManager = detectPackageManager({yarn: "yarn.lock"}); - expect(packageManager).toBe('yarn'); + it("returns yarn if yarn.lock exists", () => { + // first check for yarn.lock, then yarnrc + fs.existsSync.mockReturnValueOnce(true).mockReturnValueOnce(false); + const packageManager = detectPackageManager(); + expect(packageManager).toBe("yarn"); + }); + + it("returns berry if yarn.lock and .yarnrc.yml exist", () => { + // first check for yarn.lock, then yarnrc + fs.existsSync.mockReturnValueOnce(true).mockReturnValueOnce(true); + const packageManager = detectPackageManager(); + expect(packageManager).toBe("berry"); }); it("returns empty if package manager is not detected", () => { - fs.existsSync.mockReturnValueOnce(true); - const packageManager = detectPackageManager({}); - expect(packageManager).toBe(''); + // first check for yarn.lock, then pnpm + fs.existsSync.mockReturnValueOnce(false).mockReturnValueOnce(false); + const packageManager = detectPackageManager(); + expect(packageManager).toBe(""); }); }); diff --git a/src/shared/constants.js b/src/shared/constants.js index 1cb4b08..24c2616 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -1,9 +1,8 @@ const CONFIG_FILE = "oneversion.config.json"; -const LOCKFILES = { - yarn: "yarn.lock", - pnpm: "pnpm-lock.yaml", -}; +const YARN_LOCK = "yarn.lock"; +const PNPM_LOCK = "pnpm-lock.yaml"; +const YARN_RC = ".yarnrc.yml"; const DEPENDENCY_TYPES = { DIRECT: "direct", @@ -19,9 +18,11 @@ const NO_CHECK_API_ERROR = `'check' api not supported for package manager:`; module.exports = { CONFIG_FILE, - LOCKFILES, DEPENDENCY_TYPES, UNABLE_TO_DETECT_PACKAGE_MANAGER_ERROR, FAILED_CHECK_ERROR, NO_CHECK_API_ERROR, + YARN_LOCK, + PNPM_LOCK, + YARN_RC, }; diff --git a/src/shared/util.js b/src/shared/util.js index 94e3541..5c5673c 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1,6 +1,12 @@ const { readFileSync, existsSync } = require("fs"); const path = require("path"); -const { DEPENDENCY_TYPES, CONFIG_FILE, LOCKFILES } = require("./constants"); +const { + DEPENDENCY_TYPES, + CONFIG_FILE, + YARN_RC, + YARN_LOCK, + PNPM_LOCK, +} = require("./constants"); /** * Parse a config file if it exists @@ -158,11 +164,15 @@ const findDuplicateDependencies = (dependencies, overrides) => { /** * Detect the package manager being used by the project */ -const detectPackageManager = (lockfiles = LOCKFILES) => { - for (const [packageManager, lockfile] of Object.entries(lockfiles)) { - if (existsSync(lockfile)) { - return packageManager; +const detectPackageManager = () => { + if (existsSync(YARN_LOCK)) { + if (existsSync(YARN_RC)) { + return "berry"; } + return "yarn"; + } + if (existsSync(PNPM_LOCK)) { + return "pnpm"; } return ""; }; diff --git a/src/yarn/berry-api.js b/src/yarn/berry-api.js new file mode 100644 index 0000000..fc88c84 --- /dev/null +++ b/src/yarn/berry-api.js @@ -0,0 +1,24 @@ +const { execSync } = require("child_process"); + +const getWorkspaces = () => { + // http://ndjson.org/ + const ndJSONWorkspaces = execSync("yarn workspaces list --json", { + stdio: "pipe", + }).toString(); + + const workspaces = ndJSONWorkspaces + .replace(/\n*$/, "") // strip out trailing new line + .split("\n") // split on new line + .map((str) => JSON.parse(str)); // parse each workspace + + return workspaces.map(({ location, name }) => ({ + name, + path: location, + })); +}; + +const berryApi = { + getWorkspaces, +}; + +module.exports = berryApi; diff --git a/src/yarn/check.js b/src/yarn/check.js index 3a39e5e..5a8ef54 100644 --- a/src/yarn/check.js +++ b/src/yarn/check.js @@ -3,11 +3,26 @@ const { findDuplicateDependencies, transformDependencies, } = require("../shared/util"); -const { getWorkspaces } = require("./get-workspaces"); +const classicApi = require("./classic-api"); +const berryApi = require("./berry-api"); -const checkYarn = ({ overrides, getPackageRoots = getWorkspaces }) => { +const checkYarn = ({ + overrides, + getPackageRoots = classicApi.getWorkspaces, +}) => { const workspaces = getPackageRoots(); + return _baseYarnCheck({ workspaces, overrides }); +}; + +const checkBerry = ({ + overrides, + getPackageRoots = berryApi.getWorkspaces, +}) => { + const workspaces = getPackageRoots(); + return _baseYarnCheck({ workspaces, overrides }); +}; +const _baseYarnCheck = ({ workspaces, overrides }) => { const packageDeps = workspaces.map(({ path }) => getPackageDeps(path)); const dependenciesByNameAndVersion = transformDependencies(packageDeps); @@ -19,4 +34,4 @@ const checkYarn = ({ overrides, getPackageRoots = getWorkspaces }) => { return { duplicateDependencies }; }; -module.exports = { checkYarn }; +module.exports = { checkYarn, checkBerry }; diff --git a/src/yarn/get-workspaces.js b/src/yarn/classic-api.js similarity index 100% rename from src/yarn/get-workspaces.js rename to src/yarn/classic-api.js