Skip to content

Commit

Permalink
Adding support for pnpm (#15)
Browse files Browse the repository at this point in the history
* Adding support for pnpm

* Self review
  • Loading branch information
joeldenning authored Feb 8, 2020
1 parent e64a1f9 commit 9c4a534
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 21 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# narn

Never have to switch between npm and yarn commands ever again.
Never have to switch between yarn, npm, and pnpm commands ever again.

`narn` is a CLI that detects whether your current npm package is using npm or yarn. It then spawns the correct one with the correct arguments. The arguments to narn itself are exactly the same as if you're using yarn. The command will be converted to npm's syntax if the current package is managed by npm.
`narn` is a CLI that detects whether your current npm package is using npm, yarn, or pnpm. It then spawns the correct one with the correct arguments. The arguments to narn itself are exactly the same as if you're using yarn. The command will be converted to npm or pnpm's syntax if the current package is managed by npm / pnpm.

## Installation

Expand Down
27 changes: 23 additions & 4 deletions bin/narn.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#!/usr/bin/env node
const { spawn } = require("child_process");
const { detectYarn, getYarnArgs, getNpmArgs } = require("../lib/narn-lib.js");
const {
detectNpm,
detectPnpm,
getYarnArgs,
getNpmArgs
} = require("../lib/narn-lib.js");
const fs = require("fs");
const path = require("path");

Expand All @@ -10,16 +15,30 @@ const narnPackageJson = JSON.parse(

async function runPackageManager() {
const narnArgs = process.argv.slice(2);
const isYarn = await detectYarn(narnArgs);
let command = isYarn ? "yarn" : "npm";
const [isNpm, isPnpm] = await Promise.all([
detectNpm(narnArgs),
detectPnpm(narnArgs)
]);

let command;

if (isPnpm) {
command = "pnpm";
} else if (isNpm) {
command = "npm";
} else {
command = "yarn";
}

let commandArgs;

const firstArg = narnArgs.length > 0 ? narnArgs[0] : null;
if (firstArg === "--version" || firstArg === "-v") {
console.info(`narn version ${narnPackageJson.version}`);
commandArgs = narnArgs;
} else {
commandArgs = isYarn ? getYarnArgs(narnArgs) : getNpmArgs(narnArgs);
commandArgs =
command === "yarn" ? getYarnArgs(narnArgs) : getNpmArgs(narnArgs, isPnpm);
}

command = commandArgs.command || command;
Expand Down
42 changes: 34 additions & 8 deletions lib/narn-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@ const path = require("path");
const minimist = require("minimist");
const validateNpmPackageName = require("validate-npm-package-name");

exports.detectYarn = function detectYarn(args) {
exports.detectPnpm = function detectNpm(args) {
if (args && args.length > 0 && args[0] === "global") {
// all global commands go through yarn
return Promise.resolve(true);
return Promise.resolve(false);
} else {
return new Promise((resolve, reject) => {
fs.access(
path.resolve(process.cwd(), "pnpm-lock.yaml"),
fs.constants.F_OK,
noPnpmLock => {
resolve(!Boolean(noPnpmLock));
}
);
});
}
};

exports.detectNpm = function detectNpm(args) {
if (args && args.length > 0 && args[0] === "global") {
// all global commands go through yarn
return Promise.resolve(false);
} else {
return new Promise((resolve, reject) => {
fs.access(
path.resolve(process.cwd(), "package-lock.json"),
fs.constants.F_OK,
noPackageLock => {
resolve(Boolean(noPackageLock));
resolve(!Boolean(noPackageLock));
}
);
});
Expand All @@ -22,8 +39,8 @@ exports.detectYarn = function detectYarn(args) {

exports.getYarnArgs = narnArgs => narnArgs;

exports.getNpmArgs = narnArgs => {
const yarnArgs = minimist(narnArgs, { boolean: ["dev", "D"] });
exports.getNpmArgs = (narnArgs, isPnpm = false) => {
const yarnArgs = minimist(narnArgs, { boolean: ["dev", "D", "--latest"] });
const yarnCommands = yarnArgs._;
const yarnTarget = yarnCommands.length > 0 ? yarnCommands[0] : "install";
const yarnSubCommands = yarnCommands.slice(1);
Expand All @@ -50,9 +67,18 @@ exports.getNpmArgs = narnArgs => {
npmArgs = [];
break;
case "upgrade-interactive":
result = ["-iu", "&&", "npm", "install"];
result.command = "ncu";
return result;
if (isPnpm) {
npmTarget = "update";
npmArgs = ["--interactive"];
if (yarnArgs.latest) {
npmArgs.push("--latest");
}
break;
} else {
result = ["-iu", "&&", "npm", "install"];
result.command = "ncu";
return result;
}
case "publish":
result = [];
result.command = "np";
Expand Down
39 changes: 39 additions & 0 deletions test/detect-package-manager.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { detectNpm, detectPnpm } = require("../lib/narn-lib");
const fs = require("fs");

jest.mock("fs", () => ({
access: jest.fn(),
constants: {
F_OK: 1
}
}));

describe("package manager detection", () => {
beforeEach(() => {
fs.access.mockReset();
});

it("properly detects pnpm packages", async () => {
fs.access.mockImplementationOnce((path, mode, errBack) => {
if (path.includes("pnpm-lock.yaml")) {
errBack(false);
} else {
errBack(true);
}
});
const isPnpm = await detectPnpm(["add", "[email protected]"]);
expect(isPnpm).toBe(true);
});

it("properly detects pnpm packages", async () => {
fs.access.mockImplementationOnce((path, mode, errBack) => {
if (path.includes("package-lock.json")) {
errBack(false);
} else {
errBack(true);
}
});
const isNpm = await detectNpm(["add", "[email protected]"]);
expect(isNpm).toBe(true);
});
});
14 changes: 7 additions & 7 deletions test/global.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { detectYarn } = require("../lib/narn-lib");
const { detectNpm } = require("../lib/narn-lib");
const fs = require("fs");

jest.mock("fs", () => ({
Expand All @@ -14,20 +14,20 @@ describe("narn global commands", () => {
});

it("supports installing global packages", async () => {
const isYarn = await detectYarn(["global", "add", "[email protected]"]);
expect(isYarn).toBe(true);
const isNpm = await detectNpm(["global", "add", "[email protected]"]);
expect(isNpm).toBe(false);
});

it("supports uninstalling global packages", async () => {
const isYarn = await detectYarn(["global", "remove", "lodash"]);
expect(isYarn).toBe(true);
const isNpm = await detectNpm(["global", "remove", "lodash"]);
expect(isNpm).toBe(false);
});

it("doesn't think everything is global", async () => {
fs.access.mockImplementationOnce((path, mode, errBack) => {
errBack(false);
});
const isYarn = await detectYarn(["add", "[email protected]"]);
expect(isYarn).toBe(false);
const isNpm = await detectNpm(["add", "[email protected]"]);
expect(isNpm).toBe(true);
});
});
14 changes: 14 additions & 0 deletions test/upgrade-interactive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,18 @@ describe("narn upgrade-interactive", () => {
expected.command = "ncu";
expect(getNpmArgs(["upgrade-interactive", "--latest"])).toEqual(expected);
});

it("Runs pnpm update for pnpm projects", () => {
const isPnpm = true;
expect(getNpmArgs(["upgrade-interactive"], isPnpm)).toEqual([
"update",
"--interactive"
]);

expect(getNpmArgs(["upgrade-interactive", "--latest"], isPnpm)).toEqual([
"update",
"--interactive",
"--latest"
]);
});
});

0 comments on commit 9c4a534

Please sign in to comment.