Skip to content

Commit

Permalink
feat(publish): Remove figgy-pudding
Browse files Browse the repository at this point in the history
  • Loading branch information
evocateur committed Nov 18, 2020
1 parent 5346f49 commit caf823e
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 67 deletions.
13 changes: 4 additions & 9 deletions commands/publish/__tests__/get-npm-username.test.js
Expand Up @@ -8,8 +8,6 @@ const getNpmUsername = require("../lib/get-npm-username");

fetch.json.mockImplementation(() => Promise.resolve({ username: "lerna-test" }));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("getNpmUsername", () => {
const origConsoleError = console.error;

Expand All @@ -29,13 +27,12 @@ describe("getNpmUsername", () => {

return Promise.reject(err);
});
const opts = new Map();
opts.set("registry", "such-config-wow");
const opts = { registry: "such-config-wow" };

const username = await getNpmUsername(opts);

expect(username).toBe("lerna-test");
expect(fetch.json).toHaveBeenLastCalledWith("/-/whoami", expect.figgyPudding({ "fetch-retries": 0 }));
expect(fetch.json).toHaveBeenLastCalledWith("/-/whoami", expect.objectContaining({ fetchRetries: 0 }));
});

test("throws an error when successful fetch yields empty username", async () => {
Expand Down Expand Up @@ -63,8 +60,7 @@ describe("getNpmUsername", () => {
return Promise.reject(err);
});

const opts = new Map();
opts.set("registry", "https://registry.npmjs.org/");
const opts = { registry: "https://registry.npmjs.org/" };

await expect(getNpmUsername(opts)).rejects.toThrow(
"Authentication error. Use `npm whoami` to troubleshoot."
Expand All @@ -81,8 +77,7 @@ describe("getNpmUsername", () => {
return Promise.reject(err);
});

const opts = new Map();
opts.set("registry", "http://my-own-private-idaho.com");
const opts = { registry: "http://my-own-private-idaho.com" };

const username = await getNpmUsername(opts);

Expand Down
10 changes: 3 additions & 7 deletions commands/publish/__tests__/get-two-factor-auth-required.test.js
Expand Up @@ -8,8 +8,6 @@ const getTwoFactorAuthRequired = require("../lib/get-two-factor-auth-required");

getProfileData.mockImplementation(() => Promise.resolve({ tfa: {} }));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("getTwoFactorAuthRequired", () => {
const origConsoleError = console.error;

Expand All @@ -30,7 +28,7 @@ describe("getTwoFactorAuthRequired", () => {

const result = await getTwoFactorAuthRequired();
expect(result).toBe(true);
expect(getProfileData).toHaveBeenLastCalledWith(expect.figgyPudding({ "fetch-retries": 0 }));
expect(getProfileData).toHaveBeenLastCalledWith(expect.objectContaining({ fetchRetries: 0 }));
});

it("resolves false if tfa.mode !== 'auth-and-writes'", async () => {
Expand Down Expand Up @@ -80,14 +78,12 @@ describe("getTwoFactorAuthRequired", () => {
return Promise.reject(err);
});

const opts = new Map([["registry", "such-registry-wow"]]);
const opts = { registry: "such-registry-wow" };
const result = await getTwoFactorAuthRequired(opts);

expect(result).toBe(false);
expect(loggingOutput("warn")).toContain(
`Registry "${opts.get(
"registry"
)}" does not support 'npm profile get', skipping two-factor auth check...`
`Registry "${opts.registry}" does not support 'npm profile get', skipping two-factor auth check...`
);
});

Expand Down
8 changes: 3 additions & 5 deletions commands/publish/__tests__/publish-command.test.js
Expand Up @@ -39,8 +39,6 @@ const lernaPublish = require("@lerna-test/command-runner")(require("../command")

gitCheckout.mockImplementation(() => Promise.resolve());

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("PublishCommand", () => {
describe("cli validation", () => {
let cwd;
Expand Down Expand Up @@ -130,20 +128,20 @@ Map {

expect(getNpmUsername).toHaveBeenCalled();
expect(getNpmUsername).toHaveBeenLastCalledWith(
expect.figgyPudding({ registry: "https://registry.npmjs.org/" })
expect.objectContaining({ registry: "https://registry.npmjs.org/" })
);

expect(verifyNpmPackageAccess).toHaveBeenCalled();
expect(verifyNpmPackageAccess).toHaveBeenLastCalledWith(
expect.any(Array),
"lerna-test",
expect.figgyPudding({ registry: "https://registry.npmjs.org/" })
expect.objectContaining({ registry: "https://registry.npmjs.org/" })
);

expect(getTwoFactorAuthRequired).toHaveBeenCalled();
expect(getTwoFactorAuthRequired).toHaveBeenLastCalledWith(
// extra insurance that @lerna/npm-conf is defaulting things correctly
expect.figgyPudding({ otp: undefined })
expect.objectContaining({ otp: undefined })
);

expect(gitCheckout).toHaveBeenCalledWith(
Expand Down
2 changes: 0 additions & 2 deletions commands/publish/__tests__/publish-from-git.test.js
Expand Up @@ -29,8 +29,6 @@ const initFixture = require("@lerna-test/init-fixture")(__dirname);
// file under test
const lernaPublish = require("@lerna-test/command-runner")(require("../command"));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("publish from-git", () => {
it("publishes tagged packages", async () => {
const cwd = await initFixture("normal");
Expand Down
2 changes: 0 additions & 2 deletions commands/publish/__tests__/publish-from-package.test.js
Expand Up @@ -30,8 +30,6 @@ const initFixture = require("@lerna-test/init-fixture")(__dirname);
// file under test
const lernaPublish = require("@lerna-test/command-runner")(require("../command"));

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("publish from-package", () => {
it("publishes unpublished packages", async () => {
const cwd = await initFixture("normal");
Expand Down
20 changes: 9 additions & 11 deletions commands/publish/__tests__/verify-npm-package-access.test.js
Expand Up @@ -15,8 +15,6 @@ access.lsPackages.mockImplementation(() =>
})
);

expect.extend(require("@lerna-test/figgy-pudding-matchers"));

describe("verifyNpmPackageAccess", () => {
const origConsoleError = console.error;

Expand All @@ -36,22 +34,22 @@ describe("verifyNpmPackageAccess", () => {

test("validates that all packages have read-write permission", async () => {
const packages = await getPackages(cwd);
const opts = new Map().set("registry", "https://registry.npmjs.org/");
const opts = { registry: "https://registry.npmjs.org/" };

await verifyNpmPackageAccess(packages, "lerna-test", opts);

expect(access.lsPackages).toHaveBeenLastCalledWith(
"lerna-test",
expect.figgyPudding({
expect.objectContaining({
registry: "https://registry.npmjs.org/",
"fetch-retries": 0,
fetchRetries: 0,
})
);
});

test("allows unpublished packages to pass", async () => {
const packages = await getPackages(cwd);
const opts = new Map().set("registry", "https://registry.npmjs.org/");
const opts = { registry: "https://registry.npmjs.org/" };

access.lsPackages.mockImplementationOnce(() =>
Promise.resolve({
Expand All @@ -68,7 +66,7 @@ describe("verifyNpmPackageAccess", () => {

test("allows null result to pass with warning", async () => {
const packages = await getPackages(cwd);
const opts = new Map().set("registry", "https://registry.npmjs.org/");
const opts = { registry: "https://registry.npmjs.org/" };

access.lsPackages.mockImplementationOnce(() =>
// access.lsPackages() returns null when _no_ results returned
Expand All @@ -85,7 +83,7 @@ describe("verifyNpmPackageAccess", () => {

test("throws EACCESS when any package does not have read-write permission", async () => {
const packages = await getPackages(cwd);
const opts = new Map().set("registry", "https://registry.npmjs.org/");
const opts = { registry: "https://registry.npmjs.org/" };

access.lsPackages.mockImplementationOnce(() =>
Promise.resolve({
Expand All @@ -102,7 +100,7 @@ describe("verifyNpmPackageAccess", () => {
test("passes when npm Enterprise registry returns E500", async () => {
const packages = await getPackages(cwd);
const registry = "http://outdated-npm-enterprise.mycompany.com:12345/";
const opts = new Map().set("registry", registry);
const opts = { registry };

access.lsPackages.mockImplementationOnce(() => {
const err = new Error("npm-enterprise-what");
Expand All @@ -122,7 +120,7 @@ describe("verifyNpmPackageAccess", () => {
test("passes when Artifactory registry returns E404", async () => {
const packages = await getPackages(cwd);
const registry = "https://artifactory-partial-implementation.corpnet.mycompany.com/";
const opts = new Map().set("registry", registry);
const opts = { registry };

access.lsPackages.mockImplementationOnce(() => {
const err = new Error("artifactory-why");
Expand All @@ -141,7 +139,7 @@ describe("verifyNpmPackageAccess", () => {

test("logs unexpected failure message before throwing EWHOAMI", async () => {
const packages = await getPackages(cwd);
const opts = new Map();
const opts = {};

access.lsPackages.mockImplementationOnce(() => {
const err = new Error("gonna-need-a-bigger-boat");
Expand Down
39 changes: 23 additions & 16 deletions commands/publish/lib/fetch-config.js
@@ -1,20 +1,27 @@
"use strict";

const log = require("npmlog");
const figgyPudding = require("figgy-pudding");

module.exports = figgyPudding(
{
"fetch-retries": {},
fetchRetries: "fetch-retries",
log: { default: log },
registry: {},
username: {},
},
{
other() {
// open it up for the sake of tests
return true;
},
}
);
module.exports.getFetchConfig = getFetchConfig;

/**
* Create a merged options object suitable for npm-registry-fetch.
* @param {{ [key: string]: unknown }} options
* @param {Partial<FetchConfig>} [extra]
* @returns {FetchConfig}
*/
function getFetchConfig(options, extra) {
return {
log,
...options,
...extra,
};
}

/**
* @typedef {object} FetchConfig
* @property {number} [fetchRetries]
* @property {typeof log} log
* @property {string} [registry]
* @property {string} [username]
*/
13 changes: 9 additions & 4 deletions commands/publish/lib/get-npm-username.js
@@ -1,16 +1,21 @@
"use strict";

const ValidationError = require("@lerna/validation-error");
const FetchConfig = require("./fetch-config");
const { getFetchConfig } = require("./fetch-config");
const getProfileData = require("./get-profile-data");
const getWhoAmI = require("./get-whoami");

module.exports = getNpmUsername;

function getNpmUsername(_opts) {
const opts = FetchConfig(_opts, {
/**
* Retrieve username of logged-in user.
* @param {import("./fetch-config").FetchConfig} options
* @returns {Promise<string>}
*/
function getNpmUsername(options) {
const opts = getFetchConfig(options, {
// don't wait forever for third-party failures to be dealt with
"fetch-retries": 0,
fetchRetries: 0,
});

opts.log.info("", "Verifying npm credentials");
Expand Down
19 changes: 19 additions & 0 deletions commands/publish/lib/get-profile-data.js
Expand Up @@ -5,6 +5,11 @@ const pulseTillDone = require("@lerna/pulse-till-done");

module.exports = getProfileData;

/**
* Retrieve profile data of logged-in user.
* @param {import("./fetch-config").FetchConfig} opts
* @returns {Promise<ProfileData>}
*/
function getProfileData(opts) {
opts.log.verbose("", "Retrieving npm user profile");

Expand All @@ -18,3 +23,17 @@ function getProfileData(opts) {
);
});
}

/**
* @typedef {object} ProfileData
* @property {{ pending: boolean; mode: 'auth-and-writes' | 'auth-only' }} tfa
* @property {string} name
* @property {string} username legacy field alias of `name`
* @property {string} email
* @property {boolean} email_verified
* @property {string} created
* @property {string} updated
* @property {string} [fullname]
* @property {string} [twitter]
* @property {string} [github]
*/
13 changes: 9 additions & 4 deletions commands/publish/lib/get-two-factor-auth-required.js
@@ -1,15 +1,20 @@
"use strict";

const ValidationError = require("@lerna/validation-error");
const FetchConfig = require("./fetch-config");
const { getFetchConfig } = require("./fetch-config");
const getProfileData = require("./get-profile-data");

module.exports = getTwoFactorAuthRequired;

function getTwoFactorAuthRequired(_opts) {
const opts = FetchConfig(_opts, {
/**
* Determine if the logged-in user has enabled two-factor auth.
* @param {import("./fetch-config").FetchConfig} options
* @returns {Promise<boolean>}
*/
function getTwoFactorAuthRequired(options) {
const opts = getFetchConfig(options, {
// don't wait forever for third-party failures to be dealt with
"fetch-retries": 0,
fetchRetries: 0,
});

opts.log.info("", "Checking two-factor auth mode");
Expand Down
10 changes: 10 additions & 0 deletions commands/publish/lib/get-whoami.js
Expand Up @@ -5,6 +5,11 @@ const pulseTillDone = require("@lerna/pulse-till-done");

module.exports = getWhoAmI;

/**
* Retrieve logged-in user's username via legacy API.
* @param {import("./fetch-config").FetchConfig} opts
* @returns {WhoIAm}
*/
function getWhoAmI(opts) {
opts.log.verbose("", "Retrieving npm username");

Expand All @@ -15,3 +20,8 @@ function getWhoAmI(opts) {
return data;
});
}

/**
* @typedef {object} WhoIAm
* @property {string} username
*/
15 changes: 11 additions & 4 deletions commands/publish/lib/verify-npm-package-access.js
Expand Up @@ -3,14 +3,21 @@
const access = require("libnpmaccess");
const pulseTillDone = require("@lerna/pulse-till-done");
const ValidationError = require("@lerna/validation-error");
const FetchConfig = require("./fetch-config");
const { getFetchConfig } = require("./fetch-config");

module.exports = verifyNpmPackageAccess;

function verifyNpmPackageAccess(packages, username, _opts) {
const opts = FetchConfig(_opts, {
/**
* Throw an error if the logged-in user does not have read-write access to all packages.
* @param {{ name: string; }[]} packages
* @param {string} username
* @param {import("./fetch-config").FetchConfig} options
* @returns {Promise<void>}
*/
function verifyNpmPackageAccess(packages, username, options) {
const opts = getFetchConfig(options, {
// don't wait forever for third-party failures to be dealt with
"fetch-retries": 0,
fetchRetries: 0,
});

opts.log.silly("verifyNpmPackageAccess");
Expand Down
1 change: 0 additions & 1 deletion commands/publish/package.json
Expand Up @@ -53,7 +53,6 @@
"@lerna/run-topologically": "file:../../utils/run-topologically",
"@lerna/validation-error": "file:../../core/validation-error",
"@lerna/version": "file:../version",
"figgy-pudding": "^3.5.1",
"fs-extra": "^9.0.1",
"libnpmaccess": "^4.0.1",
"npm-package-arg": "^8.1.0",
Expand Down

0 comments on commit caf823e

Please sign in to comment.