Skip to content

Commit 54008c6

Browse files
committedDec 7, 2018
feat(npm-dist-tag): Use fetch API instead of CLI to make changes
1 parent b387881 commit 54008c6

File tree

8 files changed

+222
-151
lines changed

8 files changed

+222
-151
lines changed
 
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"use strict";
22

33
const mockAdd = jest.fn(() => Promise.resolve());
4-
const mockCheck = jest.fn(() => true);
4+
const mockList = jest.fn(() => Promise.resolve({}));
55
const mockRemove = jest.fn(() => Promise.resolve());
66

77
exports.add = mockAdd;
8-
exports.check = mockCheck;
8+
exports.list = mockList;
99
exports.remove = mockRemove;

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

-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ Set {
108108
"package-2",
109109
// package-5 is private
110110
]);
111-
expect(npmDistTag.check).not.toHaveBeenCalled();
112111
expect(npmDistTag.remove).not.toHaveBeenCalled();
113112
expect(npmDistTag.add).not.toHaveBeenCalled();
114113

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

+12-11
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test("publish --npm-tag", async () => {
2929
await lernaPublish(cwd)("--npm-tag", "custom");
3030

3131
expect(npmPublish.registry.get("package-3")).toBe("custom");
32-
expect(npmDistTag.check).not.toHaveBeenCalled();
32+
expect(npmDistTag.remove).not.toHaveBeenCalled();
3333
});
3434

3535
test("publish --temp-tag", async () => {
@@ -41,14 +41,15 @@ test("publish --temp-tag", async () => {
4141

4242
expect(npmPublish.registry.get("package-4")).toBe("lerna-temp");
4343

44-
expect(npmDistTag.remove).toHaveBeenLastCalledWith(
45-
expect.objectContaining({ name: "package-4" }),
46-
"lerna-temp",
47-
expect.objectContaining({ registry: "test-registry" })
48-
);
49-
expect(npmDistTag.add).toHaveBeenLastCalledWith(
50-
expect.objectContaining({ name: "package-4", version: "1.0.1" }),
51-
"latest",
52-
expect.objectContaining({ registry: "test-registry" })
53-
);
44+
const conf = expect.objectContaining({
45+
sources: expect.objectContaining({
46+
cli: {
47+
data: expect.objectContaining({
48+
registry: "test-registry",
49+
}),
50+
},
51+
}),
52+
});
53+
expect(npmDistTag.remove).toHaveBeenLastCalledWith("package-4@1.0.1", "lerna-temp", conf);
54+
expect(npmDistTag.add).toHaveBeenLastCalledWith("package-4@1.0.1", "latest", conf);
5455
});

‎commands/publish/index.js

+8-10
Original file line numberDiff line numberDiff line change
@@ -576,16 +576,14 @@ class PublishCommand extends Command {
576576
let chain = Promise.resolve();
577577

578578
const actions = [
579-
pkg =>
580-
Promise.resolve()
581-
.then(() => npmDistTag.check(pkg, "lerna-temp", this.npmConfig))
582-
.then(exists => {
583-
if (exists) {
584-
return npmDistTag.remove(pkg, "lerna-temp", this.npmConfig);
585-
}
586-
})
587-
.then(() => npmDistTag.add(pkg, distTag, this.npmConfig))
588-
.then(() => pkg),
579+
pkg => {
580+
const spec = `${pkg.name}@${pkg.version}`;
581+
582+
return Promise.resolve()
583+
.then(() => npmDistTag.remove(spec, "lerna-temp", this.conf))
584+
.then(() => npmDistTag.add(spec, distTag, this.conf))
585+
.then(() => pkg);
586+
},
589587
pkg => {
590588
tracker.info("dist-tag", "%s@%s => %j", pkg.name, pkg.version, distTag);
591589
tracker.completeWork(1);

‎package-lock.json

+2-3
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,122 +1,138 @@
11
"use strict";
22

3-
jest.mock("@lerna/child-process");
4-
5-
const os = require("os");
3+
jest.mock("libnpm/fetch");
64

75
// mocked modules
8-
const ChildProcessUtilities = require("@lerna/child-process");
6+
const fetch = require("libnpm/fetch");
97

108
// file under test
119
const npmDistTag = require("..");
1210

13-
describe("dist-tag", () => {
14-
ChildProcessUtilities.exec.mockResolvedValue();
15-
16-
describe("npmDistTag.add()", () => {
17-
const pkg = {
18-
name: "foo-pkg",
19-
version: "1.0.0",
20-
location: "/test/npm/dist-tag/add",
21-
};
22-
const tag = "added-tag";
23-
const registry = "https://custom-registry/add";
24-
25-
it("adds a dist-tag for a given package@version", async () => {
26-
await npmDistTag.add(pkg, tag, {});
27-
28-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
29-
"npm",
30-
["dist-tag", "add", "foo-pkg@1.0.0", tag],
31-
{
32-
cwd: pkg.location,
33-
env: {},
34-
pkg,
35-
}
36-
);
37-
});
11+
const stubLog = {
12+
verbose: jest.fn(),
13+
info: jest.fn(),
14+
warn: jest.fn(),
15+
};
16+
const baseOptions = new Map([["log", stubLog], ["tag", "latest"]]);
17+
18+
fetch.mockImplementation(() => Promise.resolve());
19+
fetch.json.mockImplementation(() => Promise.resolve({}));
20+
21+
describe("npmDistTag.add()", () => {
22+
it("adds a dist-tag for a given package@version", async () => {
23+
const opts = new Map(baseOptions);
24+
const tags = await npmDistTag.add("@scope/some-pkg@1.0.1", "added-tag", opts);
3825

39-
it("supports custom registry", async () => {
40-
await npmDistTag.add(pkg, tag, { registry });
41-
42-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith(
43-
"npm",
44-
["dist-tag", "add", "foo-pkg@1.0.0", tag],
45-
{
46-
cwd: pkg.location,
47-
env: {
48-
npm_config_registry: registry,
49-
},
50-
pkg,
51-
}
52-
);
26+
expect(tags).toEqual({
27+
"added-tag": "1.0.1",
5328
});
29+
expect(fetch).toHaveBeenLastCalledWith(
30+
"-/package/@scope%2fsome-pkg/dist-tags/added-tag",
31+
expect.objectContaining({
32+
method: "PUT",
33+
body: JSON.stringify("1.0.1"),
34+
headers: {
35+
"content-type": "application/json",
36+
},
37+
})
38+
);
5439
});
5540

56-
describe("npmDistTag.remove()", () => {
57-
const pkg = {
58-
name: "bar-pkg",
59-
location: "/test/npm/dist-tag/remove",
60-
};
61-
const tag = "removed-tag";
62-
const registry = "https://custom-registry/remove";
63-
64-
it("removes a dist-tag for a given package", async () => {
65-
await npmDistTag.remove(pkg, tag, {});
66-
67-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith("npm", ["dist-tag", "rm", pkg.name, tag], {
68-
cwd: pkg.location,
69-
env: {},
70-
pkg,
71-
});
72-
});
41+
it("does not attempt to add duplicate of existing tag", async () => {
42+
fetch.json.mockImplementationOnce(() =>
43+
Promise.resolve({
44+
latest: "1.0.0",
45+
"dupe-tag": "1.0.1",
46+
})
47+
);
7348

74-
it("supports custom registry", async () => {
75-
await npmDistTag.remove(pkg, tag, { registry });
49+
const opts = new Map(baseOptions);
50+
const tags = await npmDistTag.add("@scope/some-pkg@1.0.1", "dupe-tag", opts);
7651

77-
expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith("npm", ["dist-tag", "rm", pkg.name, tag], {
78-
cwd: pkg.location,
79-
env: {
80-
npm_config_registry: registry,
81-
},
82-
pkg,
83-
});
52+
expect(tags).toEqual({
53+
latest: "1.0.0",
54+
"dupe-tag": "1.0.1",
8455
});
56+
expect(fetch).not.toHaveBeenCalled();
57+
expect(stubLog.warn).toHaveBeenLastCalledWith(
58+
"dist-tag",
59+
"@scope/some-pkg@dupe-tag already set to 1.0.1"
60+
);
8561
});
8662

87-
describe("npmDistTag.check()", () => {
88-
const pkg = {
89-
name: "baz-pkg",
90-
location: "/test/npm/dist-tag/check",
91-
};
92-
const registry = "https://custom-registry/check";
93-
94-
it("tests if a dist-tag for a given package exists", () => {
95-
ChildProcessUtilities.execSync.mockReturnValue(["latest", "target-tag"].join(os.EOL));
96-
97-
expect(npmDistTag.check(pkg, "target-tag", {})).toBe(true);
98-
expect(npmDistTag.check(pkg, "latest", {})).toBe(true);
99-
expect(npmDistTag.check(pkg, "missing", {})).toBe(false);
100-
101-
expect(ChildProcessUtilities.execSync).toHaveBeenLastCalledWith("npm", ["dist-tag", "ls", pkg.name], {
102-
cwd: pkg.location,
103-
env: {},
104-
pkg,
105-
});
63+
it("defaults tag argument to opts.tag", async () => {
64+
fetch.json.mockImplementationOnce(() =>
65+
Promise.resolve({
66+
latest: "1.0.0",
67+
})
68+
);
69+
70+
const opts = new Map(baseOptions);
71+
const tags = await npmDistTag.add("@scope/some-pkg@1.0.1", undefined, opts);
72+
73+
expect(tags).toEqual({
74+
latest: "1.0.1",
10675
});
76+
});
77+
});
78+
79+
describe("npmDistTag.remove()", () => {
80+
it("removes an existing dist-tag for a given package", async () => {
81+
fetch.json.mockImplementationOnce(() =>
82+
Promise.resolve({
83+
latest: "1.0.0",
84+
"removed-tag": "1.0.1",
85+
})
86+
);
87+
88+
const opts = new Map(baseOptions);
89+
const tags = await npmDistTag.remove("@scope/some-pkg@1.0.1", "removed-tag", opts);
90+
91+
expect(tags).not.toHaveProperty("removed-tag");
92+
expect(fetch).toHaveBeenLastCalledWith(
93+
"-/package/@scope%2fsome-pkg/dist-tags/removed-tag",
94+
expect.objectContaining({
95+
method: "DELETE",
96+
})
97+
);
98+
});
10799

108-
it("supports custom registry", () => {
109-
ChildProcessUtilities.execSync.mockReturnValue("target-tag");
100+
it("does not attempt removal of nonexistent tag", async () => {
101+
const opts = new Map(baseOptions);
102+
const tags = await npmDistTag.remove("@scope/some-pkg@1.0.1", "missing-tag", opts);
110103

111-
expect(npmDistTag.check(pkg, "target-tag", { registry })).toBe(true);
104+
expect(tags).toEqual({});
105+
expect(fetch).not.toHaveBeenCalled();
106+
expect(stubLog.info).toHaveBeenLastCalledWith(
107+
"dist-tag",
108+
'"missing-tag" is not a dist-tag on @scope/some-pkg'
109+
);
110+
});
111+
});
112112

113-
expect(ChildProcessUtilities.execSync).toHaveBeenLastCalledWith("npm", ["dist-tag", "ls", pkg.name], {
114-
cwd: pkg.location,
115-
env: {
116-
npm_config_registry: registry,
117-
},
118-
pkg,
119-
});
113+
describe("npmDistTag.list()", () => {
114+
it("returns dictionary of dist-tags", async () => {
115+
fetch.json.mockImplementationOnce(() =>
116+
Promise.resolve({
117+
latest: "1.0.0",
118+
"other-tag": "1.0.1",
119+
})
120+
);
121+
122+
const opts = new Map(baseOptions);
123+
const tags = await npmDistTag.list("@scope/some-pkg", opts);
124+
125+
expect(tags).toEqual({
126+
latest: "1.0.0",
127+
"other-tag": "1.0.1",
120128
});
129+
expect(fetch.json).toHaveBeenLastCalledWith(
130+
"-/package/@scope%2fsome-pkg/dist-tags",
131+
expect.objectContaining({
132+
spec: expect.objectContaining({
133+
name: "@scope/some-pkg",
134+
}),
135+
})
136+
);
121137
});
122138
});

‎utils/npm-dist-tag/npm-dist-tag.js

+81-23
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,96 @@
11
"use strict";
22

3-
const log = require("libnpm/log");
4-
5-
const ChildProcessUtilities = require("@lerna/child-process");
6-
const getExecOpts = require("@lerna/get-npm-exec-opts");
3+
const npa = require("libnpm/parse-arg");
4+
const fetch = require("libnpm/fetch");
5+
const RegistryConfig = require("npm-registry-fetch/config");
76

87
exports.add = add;
9-
exports.check = check;
108
exports.remove = remove;
9+
exports.list = list;
10+
11+
function add(spec, tag, _opts) {
12+
// tag is not in the pudding spec, handle separately
13+
const cleanTag = (tag || _opts.get("tag")).trim();
14+
15+
const opts = RegistryConfig(_opts, {
16+
spec: npa(spec),
17+
});
18+
19+
const { name, rawSpec: version } = opts.spec;
20+
21+
opts.log.verbose("dist-tag", `adding "${cleanTag}" to ${name}@${version}`);
22+
23+
return fetchTags(opts).then(tags => {
24+
if (tags[cleanTag] === version) {
25+
opts.log.warn("dist-tag", `${name}@${cleanTag} already set to ${version}`);
26+
return tags;
27+
}
28+
29+
const uri = `-/package/${opts.spec.escapedName}/dist-tags/${cleanTag}`;
30+
const payload = opts.concat({
31+
method: "PUT",
32+
body: JSON.stringify(version),
33+
headers: {
34+
// cannot use fetch.json() due to HTTP 204 response,
35+
// so we manually set the required content-type
36+
"content-type": "application/json",
37+
},
38+
});
39+
40+
// success returns HTTP 204, thus no JSON to parse
41+
return fetch(uri, payload.toJSON()).then(() => {
42+
opts.log.verbose("dist-tag", `added "${cleanTag}" to ${name}@${version}`);
1143

12-
function add(pkg, tag, { registry }) {
13-
log.silly("npmDistTag.add", tag, pkg.version, pkg.name);
44+
// eslint-disable-next-line no-param-reassign
45+
tags[cleanTag] = version;
1446

15-
return ChildProcessUtilities.exec(
16-
"npm",
17-
["dist-tag", "add", `${pkg.name}@${pkg.version}`, tag],
18-
getExecOpts(pkg, registry)
19-
);
47+
return tags;
48+
});
49+
});
2050
}
2151

22-
function check(pkg, tag, { registry }) {
23-
log.silly("npmDistTag.check", tag, pkg.name);
52+
function remove(spec, tag, _opts) {
53+
const opts = RegistryConfig(_opts, {
54+
spec: npa(spec),
55+
});
56+
57+
opts.log.verbose("dist-tag", `removing "${tag}" from ${opts.spec.name}`);
58+
59+
return fetchTags(opts).then(tags => {
60+
const version = tags[tag];
61+
62+
if (!version) {
63+
opts.log.info("dist-tag", `"${tag}" is not a dist-tag on ${opts.spec.name}`);
64+
return tags;
65+
}
66+
67+
const uri = `-/package/${opts.spec.escapedName}/dist-tags/${tag}`;
68+
const payload = opts.concat({
69+
method: "DELETE",
70+
});
71+
72+
// the delete properly returns a 204, so no json to parse
73+
return fetch(uri, payload.toJSON()).then(() => {
74+
opts.log.verbose("dist-tag", `removed "${tag}" from ${opts.spec.name}@${version}`);
75+
76+
// eslint-disable-next-line no-param-reassign
77+
delete tags[tag];
78+
79+
return tags;
80+
});
81+
});
82+
}
2483

25-
const result = ChildProcessUtilities.execSync(
26-
"npm",
27-
["dist-tag", "ls", pkg.name],
28-
getExecOpts(pkg, registry)
29-
);
84+
function list(spec, _opts) {
85+
const opts = RegistryConfig(_opts, {
86+
spec: npa(spec),
87+
});
3088

31-
return result.indexOf(tag) >= 0;
89+
return fetchTags(opts);
3290
}
3391

34-
function remove(pkg, tag, { registry }) {
35-
log.silly("npmDistTag.remove", tag, pkg.name);
92+
function fetchTags(opts) {
93+
const uri = `-/package/${opts.spec.escapedName}/dist-tags`;
3694

37-
return ChildProcessUtilities.exec("npm", ["dist-tag", "rm", pkg.name, tag], getExecOpts(pkg, registry));
95+
return fetch.json(uri, opts.toJSON());
3896
}

‎utils/npm-dist-tag/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"license": "MIT",
1111
"author": {
1212
"name": "Daniel Stockman",
13+
"email": "daniel.stockman@gmail.com",
1314
"url": "https://github.com/evocateur"
1415
},
1516
"files": [
@@ -30,8 +31,7 @@
3031
"test": "echo \"Run tests from root\" && exit 1"
3132
},
3233
"dependencies": {
33-
"@lerna/child-process": "file:../../core/child-process",
34-
"@lerna/get-npm-exec-opts": "file:../get-npm-exec-opts",
35-
"libnpm": "^2.0.1"
34+
"libnpm": "^2.0.1",
35+
"npm-registry-fetch": "^3.8.0"
3636
}
3737
}

0 commit comments

Comments
 (0)
Please sign in to comment.