From 52d4de23ed2827df3557280e6d0788f8ef1a1f27 Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Mon, 15 Apr 2024 15:16:50 +0900 Subject: [PATCH 1/6] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20test=20fixture?= =?UTF-8?q?s=20on=20pnpm=20workspace=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/fixtures/pnpm-workspace-project/package.json | 13 +++++++++++++ .../packages/sub/package.json | 12 ++++++++++++ .../fixtures/pnpm-workspace-project/pnpm-lock.yaml | 11 +++++++++++ .../pnpm-workspace-project/pnpm-workspace.yaml | 0 4 files changed, 36 insertions(+) create mode 100644 tests/fixtures/pnpm-workspace-project/package.json create mode 100644 tests/fixtures/pnpm-workspace-project/packages/sub/package.json create mode 100644 tests/fixtures/pnpm-workspace-project/pnpm-lock.yaml create mode 100644 tests/fixtures/pnpm-workspace-project/pnpm-workspace.yaml diff --git a/tests/fixtures/pnpm-workspace-project/package.json b/tests/fixtures/pnpm-workspace-project/package.json new file mode 100644 index 00000000..313d760f --- /dev/null +++ b/tests/fixtures/pnpm-workspace-project/package.json @@ -0,0 +1,13 @@ +{ + "name": "pnpm-workspace-project", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/fixtures/pnpm-workspace-project/packages/sub/package.json b/tests/fixtures/pnpm-workspace-project/packages/sub/package.json new file mode 100644 index 00000000..12084a0f --- /dev/null +++ b/tests/fixtures/pnpm-workspace-project/packages/sub/package.json @@ -0,0 +1,12 @@ +{ + "name": "sub", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/fixtures/pnpm-workspace-project/pnpm-lock.yaml b/tests/fixtures/pnpm-workspace-project/pnpm-lock.yaml new file mode 100644 index 00000000..db4e02c7 --- /dev/null +++ b/tests/fixtures/pnpm-workspace-project/pnpm-lock.yaml @@ -0,0 +1,11 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + packages/sub: {} diff --git a/tests/fixtures/pnpm-workspace-project/pnpm-workspace.yaml b/tests/fixtures/pnpm-workspace-project/pnpm-workspace.yaml new file mode 100644 index 00000000..e69de29b From 3125b9627143b38dcd5a9a90b605f0b693778863 Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Mon, 15 Apr 2024 15:35:43 +0900 Subject: [PATCH 2/6] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20test=20case=20?= =?UTF-8?q?for=20finding=20pnpm=20workspace=20yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/pnpm-workspace.spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/pnpm-workspace.spec.js diff --git a/tests/pnpm-workspace.spec.js b/tests/pnpm-workspace.spec.js new file mode 100644 index 00000000..6464c90c --- /dev/null +++ b/tests/pnpm-workspace.spec.js @@ -0,0 +1,21 @@ +/** + * @fileoverview tests for pnpm workspace install packages at root + * @author Wataru Nishimura + */ + +import { describe, test, expect } from "vitest"; +import { fileURLToPath } from "node:url"; +import { join } from "path"; +import { findPnpmWorkspaceYaml } from "../lib/utils/npm-utils.js"; + +const __filename = fileURLToPath(import.meta.url); // eslint-disable-line no-underscore-dangle -- commonjs convention + +describe("pnpm workspace install packages at root", () => { + const pnpmWithWorkspaceDir = join(__filename, "../fixtures/pnpm-workspace-project"); + + test("find pnpm-workspace.yaml", () => { + const pnpmWorkspaceYaml = findPnpmWorkspaceYaml(pnpmWithWorkspaceDir); + + expect(pnpmWorkspaceYaml).toBeTruthy(); + }); +}); From 51606aa969a7db194d186bc24a0dd2e53dcaf677 Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Mon, 15 Apr 2024 15:39:53 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implements=20findPnp?= =?UTF-8?q?mWorkspaceYaml=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/npm-utils.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/utils/npm-utils.js b/lib/utils/npm-utils.js index f8a4dd2c..c5fc4a2e 100644 --- a/lib/utils/npm-utils.js +++ b/lib/utils/npm-utils.js @@ -39,6 +39,23 @@ function findPackageJson(startDir) { return null; } +/** + * Find the pnpm-workspace.yaml at package root. + * @param {string} [startDir=process.cwd()] Starting directory, default is process.cwd() + * @returns {boolean} Whether a pnpm-workspace.yaml is found in current path. + */ +function findPnpmWorkspaceYaml(startDir) { + const dir = path.resolve(startDir || process.cwd()); + + const yamlFile = path.join(dir, "pnpm-workspace.yaml"); + + if (!fs.existsSync(yamlFile) || !fs.statSync(yamlFile).isFile()) { + return false; + } + + return true; +} + //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ @@ -189,6 +206,7 @@ export { installSyncSaveDev, fetchPeerDependencies, findPackageJson, + findPnpmWorkspaceYaml, checkDeps, checkDevDeps, checkPackageJson, From 44c27351c8d462f0c5d31ebcb9c6a1b255537b4c Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Thu, 18 Apr 2024 02:32:22 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20pnpm=20workspa?= =?UTF-8?q?ce=20option=20when=20executing=20at=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/npm-utils.js | 6 ++++-- tests/pnpm-workspace.spec.js | 21 ++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/utils/npm-utils.js b/lib/utils/npm-utils.js index c5fc4a2e..b6fbaf80 100644 --- a/lib/utils/npm-utils.js +++ b/lib/utils/npm-utils.js @@ -68,8 +68,10 @@ function findPnpmWorkspaceYaml(startDir) { */ function installSyncSaveDev(packages, packageManager = "npm") { const packageList = Array.isArray(packages) ? packages : [packages]; - const installCmd = packageManager === "yarn" ? "add" : "install"; - const installProcess = spawn.sync(packageManager, [installCmd, "-D"].concat(packageList), { stdio: "inherit" }); + const installCmd = packageManager === "npm" ? "install" : "add"; + + const pnpmWorkspaceRootOption = packageManager === "pnpm" && findPnpmWorkspaceYaml() ? "-w" : ""; + const installProcess = spawn.sync(packageManager, [installCmd, "-D"].concat(packageList), pnpmWorkspaceRootOption, { stdio: "inherit" }); const error = installProcess.error; if (error && error.code === "ENOENT") { diff --git a/tests/pnpm-workspace.spec.js b/tests/pnpm-workspace.spec.js index 6464c90c..cce0b158 100644 --- a/tests/pnpm-workspace.spec.js +++ b/tests/pnpm-workspace.spec.js @@ -3,19 +3,34 @@ * @author Wataru Nishimura */ -import { describe, test, expect } from "vitest"; +import { describe, it, expect, assert, afterEach } from "vitest"; import { fileURLToPath } from "node:url"; import { join } from "path"; -import { findPnpmWorkspaceYaml } from "../lib/utils/npm-utils.js"; +import { findPnpmWorkspaceYaml, installSyncSaveDev } from "../lib/utils/npm-utils.js"; +import sinon from "sinon"; +import spawn from "cross-spawn"; const __filename = fileURLToPath(import.meta.url); // eslint-disable-line no-underscore-dangle -- commonjs convention describe("pnpm workspace install packages at root", () => { const pnpmWithWorkspaceDir = join(__filename, "../fixtures/pnpm-workspace-project"); - test("find pnpm-workspace.yaml", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("find pnpm-workspace.yaml", () => { const pnpmWorkspaceYaml = findPnpmWorkspaceYaml(pnpmWithWorkspaceDir); expect(pnpmWorkspaceYaml).toBeTruthy(); }); + + it("should invoke pnpm to install a single desired package", () => { + const stub = sinon.stub(spawn, "sync").returns({ stdout: 0 }); + + installSyncSaveDev("desired-package", "pnpm"); + assert(stub.calledOnce); + assert.strictEqual(stub.firstCall.args[0], "pnpm"); + assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "desired-package"]); + }); }); From f47dc3bde20892b675babebc2b17251fd9bf8c48 Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Sun, 21 Apr 2024 02:30:16 +0900 Subject: [PATCH 5/6] =?UTF-8?q?test:=20=F0=9F=92=8D=20pnpm=20and=20pnpm=20?= =?UTF-8?q?workspace=20support=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config-generator.js | 2 +- lib/utils/npm-utils.js | 12 +++++++++--- tests/utils/npm-utils.spec.js | 15 ++++++++++++--- ...npm-workspace.spec.js => workspace-support.js} | 7 ++++--- 4 files changed, 26 insertions(+), 10 deletions(-) rename tests/{pnpm-workspace.spec.js => workspace-support.js} (83%) diff --git a/lib/config-generator.js b/lib/config-generator.js index 170b053b..43918b77 100644 --- a/lib/config-generator.js +++ b/lib/config-generator.js @@ -263,7 +263,7 @@ export default [\n${exportContent}];`; })).packageManager; log.info("☕️Installing..."); - installSyncSaveDev(this.result.devDependencies, packageManager); + installSyncSaveDev(this.result.devDependencies, this.cwd, packageManager); await writeFile(configPath, this.result.configContent); // import("eslint") won't work in some cases. diff --git a/lib/utils/npm-utils.js b/lib/utils/npm-utils.js index b6fbaf80..d431db7d 100644 --- a/lib/utils/npm-utils.js +++ b/lib/utils/npm-utils.js @@ -63,15 +63,21 @@ function findPnpmWorkspaceYaml(startDir) { /** * Install node modules synchronously and save to devDependencies in package.json * @param {string|string[]} packages Node module or modules to install + * @param {string} cwd working directory * @param {string} packageManager Package manager to use for installation. * @returns {void} */ -function installSyncSaveDev(packages, packageManager = "npm") { +function installSyncSaveDev(packages, cwd = process.cwd(), packageManager = "npm") { const packageList = Array.isArray(packages) ? packages : [packages]; const installCmd = packageManager === "npm" ? "install" : "add"; - const pnpmWorkspaceRootOption = packageManager === "pnpm" && findPnpmWorkspaceYaml() ? "-w" : ""; - const installProcess = spawn.sync(packageManager, [installCmd, "-D"].concat(packageList), pnpmWorkspaceRootOption, { stdio: "inherit" }); + // When cmd executed at pnpm workspace, apply "-w" option. + const pnpmWorkspaceRootOption = packageManager === "pnpm" && findPnpmWorkspaceYaml(cwd) ? "-w" : ""; + + // filter nullish values and create options. + const installOptions = [installCmd, "-D"].concat(pnpmWorkspaceRootOption).concat(packageList).filter(value => !!value); + + const installProcess = spawn.sync(packageManager, installOptions, { stdio: "inherit", cwd }); const error = installProcess.error; if (error && error.code === "ENOENT") { diff --git a/tests/utils/npm-utils.spec.js b/tests/utils/npm-utils.spec.js index 1cb89837..f4ac61aa 100644 --- a/tests/utils/npm-utils.spec.js +++ b/tests/utils/npm-utils.spec.js @@ -157,7 +157,7 @@ describe("npmUtils", () => { it("should invoke npm to install a single desired package", () => { const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - installSyncSaveDev("desired-package", "npm"); + installSyncSaveDev("desired-package", process.cwd(), "npm"); assert(stub.calledOnce); assert.strictEqual(stub.firstCall.args[0], "npm"); assert.deepStrictEqual(stub.firstCall.args[1], ["install", "-D", "desired-package"]); @@ -167,18 +167,27 @@ describe("npmUtils", () => { it("should invoke yarn to install a single desired package", () => { const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - installSyncSaveDev("desired-package", "yarn"); + installSyncSaveDev("desired-package", process.cwd(), "yarn"); assert(stub.calledOnce); assert.strictEqual(stub.firstCall.args[0], "yarn"); assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "desired-package"]); stub.restore(); }); + it("should invoke pnpm to install a single desired package", () => { + const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); + + installSyncSaveDev("desired-package", process.cwd(), "pnpm"); + assert(stub.calledOnce); + assert.strictEqual(stub.firstCall.args[0], "pnpm"); + assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "desired-package"]); + }); + it("should accept an array of packages to install", () => { const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - installSyncSaveDev(["first-package", "second-package"], "npm"); + installSyncSaveDev(["first-package", "second-package"], process.cwd(), "npm"); assert(stub.calledOnce); assert.strictEqual(stub.firstCall.args[0], "npm"); assert.deepStrictEqual(stub.firstCall.args[1], ["install", "-D", "first-package", "second-package"]); diff --git a/tests/pnpm-workspace.spec.js b/tests/workspace-support.js similarity index 83% rename from tests/pnpm-workspace.spec.js rename to tests/workspace-support.js index cce0b158..2de23fcb 100644 --- a/tests/pnpm-workspace.spec.js +++ b/tests/workspace-support.js @@ -25,12 +25,13 @@ describe("pnpm workspace install packages at root", () => { expect(pnpmWorkspaceYaml).toBeTruthy(); }); - it("should invoke pnpm to install a single desired package", () => { + it("should invoke pnpm with workspace option to install a single desired packages", async () => { const stub = sinon.stub(spawn, "sync").returns({ stdout: 0 }); - installSyncSaveDev("desired-package", "pnpm"); + installSyncSaveDev("desired-package", pnpmWithWorkspaceDir, "pnpm"); assert(stub.calledOnce); assert.strictEqual(stub.firstCall.args[0], "pnpm"); - assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "desired-package"]); + assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "-w", "desired-package"]); + stub.restore(); }); }); From cba9611983ae243d216a7d08042bb39436d2e572 Mon Sep 17 00:00:00 2001 From: Wataru Nishimura Date: Sat, 27 Apr 2024 16:12:47 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20pnpm=20and=20yarn-le?= =?UTF-8?q?gacy=20supoort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/npm-utils.js | 27 ++++++++++++++++++- .../package.json | 16 +++++++++++ .../packages/sub/package.json | 12 +++++++++ .../yarn-legacy-workspace-project/yarn.lock | 4 +++ ...e-support.js => workspace-support.spec.js} | 18 +++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/yarn-legacy-workspace-project/package.json create mode 100644 tests/fixtures/yarn-legacy-workspace-project/packages/sub/package.json create mode 100644 tests/fixtures/yarn-legacy-workspace-project/yarn.lock rename tests/{workspace-support.js => workspace-support.spec.js} (63%) diff --git a/lib/utils/npm-utils.js b/lib/utils/npm-utils.js index d431db7d..82104c54 100644 --- a/lib/utils/npm-utils.js +++ b/lib/utils/npm-utils.js @@ -205,6 +205,30 @@ function isPackageTypeModule(pkgJSONPath) { return false; } +/** + * check if yarn legacy workspace enabled + * @param {string} pkgJSONPath path to package.json + * @returns {boolean} return true if the package.json includes worksapces and private option is "true" + */ +function isYarnLegacyWorkspaceEnabled(pkgJSONPath) { + if (pkgJSONPath) { + const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8")); + + if (pkgJSONContents.private === "false") { + return false; + } + + const workspaceOption = pkgJSONContents.workspace; + + if (!workspaceOption || !Array.isArray(workspaceOption)) { + return false; + } + + return true; + } + + return false; +} //------------------------------------------------------------------------------ // Public Interface @@ -218,5 +242,6 @@ export { checkDeps, checkDevDeps, checkPackageJson, - isPackageTypeModule + isPackageTypeModule, + isYarnLegacyWorkspaceEnabled }; diff --git a/tests/fixtures/yarn-legacy-workspace-project/package.json b/tests/fixtures/yarn-legacy-workspace-project/package.json new file mode 100644 index 00000000..f6547553 --- /dev/null +++ b/tests/fixtures/yarn-legacy-workspace-project/package.json @@ -0,0 +1,16 @@ +{ + "name": "pnpm-workspace-project", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": {}, + "private": true, + "workspaces": "sub" +} diff --git a/tests/fixtures/yarn-legacy-workspace-project/packages/sub/package.json b/tests/fixtures/yarn-legacy-workspace-project/packages/sub/package.json new file mode 100644 index 00000000..12084a0f --- /dev/null +++ b/tests/fixtures/yarn-legacy-workspace-project/packages/sub/package.json @@ -0,0 +1,12 @@ +{ + "name": "sub", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/fixtures/yarn-legacy-workspace-project/yarn.lock b/tests/fixtures/yarn-legacy-workspace-project/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/tests/fixtures/yarn-legacy-workspace-project/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/tests/workspace-support.js b/tests/workspace-support.spec.js similarity index 63% rename from tests/workspace-support.js rename to tests/workspace-support.spec.js index 2de23fcb..f45ad6a2 100644 --- a/tests/workspace-support.js +++ b/tests/workspace-support.spec.js @@ -14,17 +14,25 @@ const __filename = fileURLToPath(import.meta.url); // eslint-disable-line no-und describe("pnpm workspace install packages at root", () => { const pnpmWithWorkspaceDir = join(__filename, "../fixtures/pnpm-workspace-project"); + const yarnLegacyWithWorkspaceDir = join(__filename, "../fixtures/yarn-legacy-workspace-project"); afterEach(() => { sinon.verifyAndRestore(); }); + /** + * pnpm recognizes whether workspace is enabled by `pnpm-workspace.yaml`. + * This test case tests function to find `pnpm-workspace.yaml`. + */ it("find pnpm-workspace.yaml", () => { const pnpmWorkspaceYaml = findPnpmWorkspaceYaml(pnpmWithWorkspaceDir); expect(pnpmWorkspaceYaml).toBeTruthy(); }); + /** + * at project root, `pnpm add` needs to be applied "-w" option. + */ it("should invoke pnpm with workspace option to install a single desired packages", async () => { const stub = sinon.stub(spawn, "sync").returns({ stdout: 0 }); @@ -34,4 +42,14 @@ describe("pnpm workspace install packages at root", () => { assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "-w", "desired-package"]); stub.restore(); }); + + it("should invoke yarn legacy with workspace option to install a single desired packages", async () => { + const stub = sinon.stub(spawn, "sync").returns({ stdout: 0 }); + + installSyncSaveDev("desired-package", yarnLegacyWithWorkspaceDir, "yarn"); + assert(stub.calledOnce); + assert.strictEqual(stub.firstCall.args[0], "yarn"); + assert.deepStrictEqual(stub.firstCall.args[1], ["add", "-D", "-W", "desired-package"]); + }); + });