Skip to content

Commit 433275e

Browse files
committedDec 18, 2018
feat(npm-publish): Use libnpm/publish instead of subprocess execution
1 parent 5bf0c13 commit 433275e

File tree

6 files changed

+127
-116
lines changed

6 files changed

+127
-116
lines changed
 

‎commands/publish/__tests__/publish-command.test.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,17 @@ Set {
314314
});
315315

316316
describe("--registry", () => {
317+
const confWithRegistry = registry =>
318+
expect.objectContaining({
319+
sources: expect.objectContaining({
320+
cli: {
321+
data: expect.objectContaining({
322+
registry,
323+
}),
324+
},
325+
}),
326+
});
327+
317328
it("passes registry to npm commands", async () => {
318329
const testDir = await initFixture("normal");
319330
const registry = "https://my-private-registry";
@@ -322,8 +333,8 @@ Set {
322333

323334
expect(npmPublish).toHaveBeenCalledWith(
324335
expect.objectContaining({ name: "package-1" }),
325-
undefined, // dist-tag
326-
expect.objectContaining({ registry })
336+
"latest", // dist-tag
337+
confWithRegistry(registry)
327338
);
328339
});
329340

@@ -335,8 +346,8 @@ Set {
335346

336347
expect(npmPublish).toHaveBeenCalledWith(
337348
expect.objectContaining({ name: "package-1" }),
338-
undefined, // dist-tag
339-
expect.objectContaining({ registry: "https://registry.npmjs.org/" })
349+
"latest", // dist-tag
350+
confWithRegistry("https://registry.npmjs.org/")
340351
);
341352

342353
const logMessages = loggingOutput("warn");

‎commands/publish/__tests__/publish-lifecycle-scripts.test.js

-7
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ describe("lifecycle scripts", () => {
3131
expect(runLifecycle).toHaveBeenCalledWith(expect.objectContaining({ name: "lifecycle" }), script);
3232
});
3333

34-
// all leaf package lifecycles _EXCEPT_ postpublish are called in packDirectory()
35-
expect(runLifecycle).toHaveBeenCalledWith(
36-
expect.objectContaining({ name: "package-1" }),
37-
expect.stringMatching("postpublish")
38-
);
39-
4034
// package-2 lacks version lifecycle scripts
4135
expect(runLifecycle).not.toHaveBeenCalledWith(
4236
expect.objectContaining({ name: "package-2" }),
@@ -56,7 +50,6 @@ describe("lifecycle scripts", () => {
5650
["lifecycle", "prepublishOnly"],
5751
["lifecycle", "prepack"],
5852
["lifecycle", "postpack"],
59-
["package-1", "postpublish"],
6053
["lifecycle", "postpublish"],
6154
]);
6255

‎package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use strict";
22

3-
jest.mock("@lerna/child-process");
4-
jest.mock("@lerna/has-npm-version");
5-
jest.mock("@lerna/log-packed");
3+
jest.mock("@lerna/run-lifecycle");
4+
jest.mock("libnpm/publish");
65
jest.mock("fs-extra");
76

87
// mocked modules
9-
const ChildProcessUtilities = require("@lerna/child-process");
108
const fs = require("fs-extra");
9+
const publish = require("libnpm/publish");
10+
const runLifecycle = require("@lerna/run-lifecycle");
1111

1212
// helpers
1313
const path = require("path");
@@ -17,8 +17,12 @@ const Package = require("@lerna/package");
1717
const npmPublish = require("..");
1818

1919
describe("npm-publish", () => {
20+
const mockTarData = Buffer.from("MOCK");
21+
22+
fs.readFile.mockImplementation(() => Promise.resolve(mockTarData));
2023
fs.remove.mockResolvedValue();
21-
ChildProcessUtilities.exec.mockResolvedValue();
24+
publish.mockResolvedValue();
25+
runLifecycle.mockResolvedValue();
2226

2327
const rootPath = path.normalize("/test");
2428
const pkg = new Package(
@@ -27,96 +31,84 @@ describe("npm-publish", () => {
2731
rootPath
2832
);
2933

30-
// technically decorated in npmPack, stubbed here
34+
// technically decorated in ../../commands/publish, stubbed here
3135
pkg.tarball = {
3236
filename: "test-1.10.100.tgz",
3337
};
3438

35-
it("runs npm publish in a directory with --tag support", async () => {
36-
const result = await npmPublish(pkg, "published-tag", { npmClient: "npm" });
39+
it("pipelines input package", async () => {
40+
const opts = new Map();
41+
const result = await npmPublish(pkg, "latest", opts);
3742

3843
expect(result).toBe(pkg);
39-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
40-
"npm",
41-
["publish", "--ignore-scripts", "--tag", "published-tag", "test-1.10.100.tgz"],
42-
{
43-
cwd: pkg.location,
44-
env: {},
45-
pkg,
46-
}
47-
);
48-
expect(fs.remove).toHaveBeenLastCalledWith(path.join(pkg.location, pkg.tarball.filename));
4944
});
5045

51-
it("does not pass --tag when none present (npm default)", async () => {
52-
await npmPublish(pkg, undefined, { npmClient: "npm" });
53-
54-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
55-
"npm",
56-
["publish", "--ignore-scripts", "test-1.10.100.tgz"],
57-
{
58-
cwd: pkg.location,
59-
env: {},
60-
pkg,
61-
}
62-
);
46+
it("calls libnpm/publish with correct arguments", async () => {
47+
const opts = new Map();
48+
49+
await npmPublish(pkg, "published-tag", opts);
50+
51+
expect(publish).toHaveBeenCalledWith({ name: "test", version: "1.10.100" }, mockTarData, {
52+
projectScope: "test",
53+
tag: "published-tag",
54+
});
6355
});
6456

65-
it("trims trailing whitespace in tag parameter", async () => {
66-
await npmPublish(pkg, "trailing-tag ", { npmClient: "npm" });
67-
68-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
69-
"npm",
70-
["publish", "--ignore-scripts", "--tag", "trailing-tag", "test-1.10.100.tgz"],
71-
{
72-
cwd: pkg.location,
73-
env: {},
74-
pkg,
75-
}
57+
it("falls back to opts.tag for dist-tag", async () => {
58+
const opts = new Map([["tag", "custom-default"]]);
59+
60+
await npmPublish(pkg, undefined, opts);
61+
62+
expect(publish).toHaveBeenCalledWith(
63+
{ name: "test", version: "1.10.100" },
64+
mockTarData,
65+
expect.objectContaining({ tag: "custom-default" })
7666
);
7767
});
7868

79-
it("supports custom registry", async () => {
80-
const registry = "https://custom-registry/npmPublish";
81-
82-
await npmPublish(pkg, "custom-registry", { npmClient: "npm", registry });
83-
84-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
85-
"npm",
86-
["publish", "--ignore-scripts", "--tag", "custom-registry", "test-1.10.100.tgz"],
87-
{
88-
cwd: pkg.location,
89-
env: {
90-
npm_config_registry: registry,
91-
},
92-
pkg,
93-
}
69+
it("falls back to default tag", async () => {
70+
await npmPublish(pkg);
71+
72+
expect(publish).toHaveBeenCalledWith(
73+
{ name: "test", version: "1.10.100" },
74+
mockTarData,
75+
expect.objectContaining({ tag: "latest" })
9476
);
9577
});
9678

97-
describe("with npmClient yarn", () => {
98-
it("appends --new-version to avoid interactive prompt", async () => {
99-
await npmPublish(pkg, "yarn-publish", { npmClient: "yarn" });
100-
101-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
102-
"yarn",
103-
[
104-
"publish",
105-
"--ignore-scripts",
106-
"--tag",
107-
"yarn-publish",
108-
"--new-version",
109-
pkg.version,
110-
"--non-interactive",
111-
"--no-git-tag-version",
112-
"test-1.10.100.tgz",
113-
],
114-
{
115-
cwd: pkg.location,
116-
env: {},
117-
pkg,
118-
}
119-
);
79+
it("calls publish lifecycles", async () => {
80+
await npmPublish(pkg, "lifecycles");
81+
82+
// figgy-pudding Proxy hanky-panky defeats jest wizardry
83+
const aFiggyPudding = expect.objectContaining({ __isFiggyPudding: true });
84+
85+
expect(runLifecycle).toHaveBeenCalledWith(pkg, "publish", aFiggyPudding);
86+
expect(runLifecycle).toHaveBeenCalledWith(pkg, "postpublish", aFiggyPudding);
87+
88+
runLifecycle.mock.calls.forEach(args => {
89+
const pud = args.slice().pop();
90+
91+
expect(pud.toJSON()).toMatchObject({
92+
projectScope: "test",
93+
tag: "lifecycles",
94+
});
12095
});
12196
});
97+
98+
it("omits opts.logstream", async () => {
99+
const opts = new Map([["logstream", "SKIPPED"]]);
100+
101+
await npmPublish(pkg, "canary", opts);
102+
103+
const pud = runLifecycle.mock.calls.pop().pop();
104+
105+
expect(pud.toJSON()).not.toHaveProperty("logstream");
106+
});
107+
108+
it("removes tarball after success", async () => {
109+
const opts = new Map();
110+
await npmPublish(pkg, "latest", opts);
111+
112+
expect(fs.remove).toHaveBeenLastCalledWith(path.join(pkg.location, pkg.tarball.filename));
113+
});
122114
});

‎utils/npm-publish/npm-publish.js

+37-22
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,53 @@
11
"use strict";
22

33
const fs = require("fs-extra");
4-
const log = require("libnpm/log");
54
const path = require("path");
6-
7-
const ChildProcessUtilities = require("@lerna/child-process");
8-
const getExecOpts = require("@lerna/get-npm-exec-opts");
5+
const log = require("libnpm/log");
6+
const publish = require("libnpm/publish");
7+
const figgyPudding = require("figgy-pudding");
8+
const runLifecycle = require("@lerna/run-lifecycle");
99

1010
module.exports = npmPublish;
1111

12-
function npmPublish(pkg, tag, { npmClient, registry }) {
12+
const PublishConfig = figgyPudding(
13+
{
14+
"dry-run": { default: false },
15+
dryRun: "dry-run",
16+
tag: { default: "latest" },
17+
},
18+
{
19+
other(key) {
20+
// allow any other keys _except_ circular objects
21+
return key !== "log" && key !== "logstream";
22+
},
23+
}
24+
);
25+
26+
function npmPublish(pkg, tag, _opts) {
1327
log.verbose("publish", pkg.name);
1428

15-
const distTag = tag && tag.trim();
16-
const opts = getExecOpts(pkg, registry);
17-
const args = ["publish", "--ignore-scripts"];
29+
const deets = { projectScope: pkg.name };
1830

19-
if (distTag) {
20-
args.push("--tag", distTag);
31+
if (tag) {
32+
deets.tag = tag;
2133
}
2234

23-
if (npmClient === "yarn") {
24-
// skip prompt for new version, use existing instead
25-
// https://yarnpkg.com/en/docs/cli/publish#toc-yarn-publish-new-version
26-
args.push("--new-version", pkg.version, "--non-interactive", "--no-git-tag-version");
27-
// yarn also needs to be told to stop creating git tags: https://git.io/fAr1P
35+
const opts = PublishConfig(_opts, deets);
36+
const tarFilePath = path.join(pkg.location, pkg.tarball.filename);
37+
38+
let chain = Promise.resolve();
39+
40+
if (!opts.dryRun) {
41+
chain = chain.then(() => fs.readFile(tarFilePath));
42+
chain = chain.then(tarData => publish(pkg.toJSON(), tarData, opts.toJSON()));
2843
}
2944

30-
// always add tarball file, created by npmPack()
31-
args.push(pkg.tarball.filename);
45+
chain = chain.then(() => runLifecycle(pkg, "publish", opts));
46+
chain = chain.then(() => runLifecycle(pkg, "postpublish", opts));
47+
48+
// don't leave the generated tarball hanging around after success
49+
chain = chain.then(() => fs.remove(tarFilePath));
3250

33-
log.silly("exec", npmClient, args);
34-
return ChildProcessUtilities.exec(npmClient, args, opts).then(() =>
35-
// don't leave the generated tarball hanging around after success
36-
fs.remove(path.join(pkg.location, pkg.tarball.filename)).then(() => pkg)
37-
);
51+
// pipelined Package instance
52+
return chain.then(() => pkg);
3853
}

‎utils/npm-publish/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
"test": "echo \"Run tests from root\" && exit 1"
3131
},
3232
"dependencies": {
33-
"@lerna/child-process": "file:../../core/child-process",
34-
"@lerna/get-npm-exec-opts": "file:../get-npm-exec-opts",
33+
"@lerna/run-lifecycle": "file:../run-lifecycle",
34+
"figgy-pudding": "^3.5.1",
3535
"fs-extra": "^7.0.0",
3636
"libnpm": "^2.0.1"
3737
}

0 commit comments

Comments
 (0)
Please sign in to comment.