Skip to content

Commit

Permalink
feat(publish): Ensure published packages contain a LICENSE file (#1465)
Browse files Browse the repository at this point in the history
Fixes #1213
  • Loading branch information
honzajavorek authored and evocateur committed Jul 16, 2018
1 parent 346d156 commit 5863564
Show file tree
Hide file tree
Showing 43 changed files with 486 additions and 21 deletions.
19 changes: 17 additions & 2 deletions commands/__mocks__/@lerna/npm-publish.js
@@ -1,12 +1,27 @@
"use strict";

const fs = require("fs-extra");
const path = require("path");

const registry = new Map();

// by default, act like a spy that populates registry
const mockNpmPublish = jest.fn((pkg, tag) => {
registry.set(pkg.name, tag);
const entry = { tag };
registry.set(pkg.name, entry);

return Promise.resolve()
.then(() => fs.readdir(pkg.location))
.then(filenames => {
entry.licenseBasename = filenames.find(f => f.match(/^licen[cs]e/i));

return Promise.resolve();
if (entry.licenseBasename) {
return fs.readFile(path.join(pkg.location, entry.licenseBasename), { encoding: "utf8" });
}
})
.then(licenseText => {
entry.licenseText = licenseText;
});
});

// keep test data isolated
Expand Down
3 changes: 2 additions & 1 deletion commands/publish/README.md
Expand Up @@ -21,7 +21,8 @@ More specifically, this command will:
3. Update the `package.json` of all updated packages to their new versions.
4. Update all dependencies of the updated packages with the new versions, specified with a [caret (^)](https://docs.npmjs.com/files/package.json#dependencies).
5. Create a new git commit and tag for the new version.
6. Publish updated packages to npm.
6. Make sure the updated packages contain license files before publishing. Use root license file if the packages do not contain their own.
7. Publish updated packages to npm.

> Lerna won't publish packages which are marked as private (`"private": true` in the `package.json`).
Expand Down
@@ -0,0 +1,3 @@
{
"version": "1.0.0"
}
@@ -0,0 +1,4 @@
{
"name": "licenses-missing",
"version": "0.0.0-monorepo"
}
@@ -0,0 +1,4 @@
{
"name": "package-1",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

XYZ License
@@ -0,0 +1,4 @@
{
"name": "package-2",
"version": "1.0.0"
}
@@ -0,0 +1,4 @@
{
"name": "package-3",
"version": "1.0.0"
}
@@ -0,0 +1,4 @@
{
"name": "licenses-names",
"version": "0.0.0-monorepo"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,4 @@
{
"name": "package-1",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,4 @@
{
"name": "package-2",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,4 @@
{
"name": "package-3",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,4 @@
{
"name": "package-4",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
@@ -0,0 +1,4 @@
{
"name": "package-5",
"version": "1.0.0"
}
3 changes: 3 additions & 0 deletions commands/publish/__tests__/__fixtures__/licenses/LICENSE
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

ABC License
3 changes: 3 additions & 0 deletions commands/publish/__tests__/__fixtures__/licenses/lerna.json
@@ -0,0 +1,3 @@
{
"version": "1.0.0"
}
4 changes: 4 additions & 0 deletions commands/publish/__tests__/__fixtures__/licenses/package.json
@@ -0,0 +1,4 @@
{
"name": "licenses",
"version": "0.0.0-monorepo"
}
@@ -0,0 +1,4 @@
{
"name": "package-1",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
Copyright (c) 2018 Tester McPerson <test@example.com>

XYZ License
@@ -0,0 +1,7 @@
{
"name": "package-2",
"version": "1.0.0",
"dependencies": {
"package-1": "^1.0.0"
}
}
Expand Up @@ -50,10 +50,26 @@ Map {

exports[`publish --canary --independent: npm published 1`] = `
Map {
"package-1" => "canary",
"package-3" => "canary",
"package-4" => "canary",
"package-2" => "canary",
"package-1" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-3" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-4" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-2" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
}
`;

Expand Down Expand Up @@ -194,10 +210,26 @@ Map {

exports[`publish --canary: npm published 1`] = `
Map {
"package-1" => "canary",
"package-3" => "canary",
"package-4" => "canary",
"package-2" => "canary",
"package-1" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-3" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-4" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
"package-2" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": "canary",
},
}
`;

Expand Down
Expand Up @@ -75,10 +75,26 @@ Successfully published:

exports[`PublishCommand independent mode publishes changed packages: npm published 1`] = `
Map {
"package-1" => undefined,
"package-3" => undefined,
"package-4" => undefined,
"package-2" => undefined,
"package-1" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-3" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-4" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-2" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
}
`;

Expand Down Expand Up @@ -243,10 +259,26 @@ Successfully published:

exports[`PublishCommand normal mode publishes changed packages: npm published 1`] = `
Map {
"package-1" => undefined,
"package-3" => undefined,
"package-4" => undefined,
"package-2" => undefined,
"package-1" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-3" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-4" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
"package-2" => Object {
"licenseBasename": undefined,
"licenseText": undefined,
"tag": undefined,
},
}
`;

Expand Down
33 changes: 33 additions & 0 deletions commands/publish/__tests__/create-temp-licenses.test.js
@@ -0,0 +1,33 @@
"use strict";

const fs = require("fs-extra");
const path = require("path");

const initFixture = require("@lerna-test/init-fixture")(__dirname);
const createTempLicenses = require("../lib/create-temp-licenses");

test("creates temporary copy of the source license", async () => {
const cwd = await initFixture("licenses");

const pkg = { name: "package-1", location: path.join(cwd, "packages", "package-1") };
await createTempLicenses(path.join(cwd, "LICENSE"), [pkg]);

expect(fs.exists(path.join(pkg.location, "LICENSE"))).resolves.toBe(true);
});

test("resolves when source license path is missing", async () => {
const cwd = await initFixture("licenses");

const pkg = { name: "package-1", location: path.join(cwd, "packages", "package-1") };
await createTempLicenses(null, [pkg]);

expect(fs.exists(path.join(pkg.location, "LICENSE"))).resolves.toBe(false);
});

test("resolves when there are no packages", async () => {
const cwd = await initFixture("licenses");

await createTempLicenses(path.join(cwd, "LICENSE"), []);

expect(fs.exists(path.join(cwd, "packages", "package-1", "LICENSE"))).resolves.toBe(false);
});
36 changes: 36 additions & 0 deletions commands/publish/__tests__/get-license-path.test.js
@@ -0,0 +1,36 @@
"use strict";

const path = require("path");

const initFixture = require("@lerna-test/init-fixture")(__dirname);
const getLicensePath = require("../lib/get-license-path");

test("recognizes American spelling", async () => {
const cwd = await initFixture("licenses-names");

expect(getLicensePath(path.join(cwd, "packages", "package-1"))).resolves.toBe("LICENSE");
});

test("recognizes British spelling", async () => {
const cwd = await initFixture("licenses-names");

expect(getLicensePath(path.join(cwd, "packages", "package-2"))).resolves.toBe("licence");
});

test("doesn't mind upper or lower case", async () => {
const cwd = await initFixture("licenses-names");

expect(getLicensePath(path.join(cwd, "packages", "package-3"))).resolves.toBe("LiCeNSe");
});

test("resolves to null if there is no license file", async () => {
const cwd = await initFixture("licenses-names");

expect(getLicensePath(path.join(cwd, "packages", "package-4"))).resolves.toBeNull();
});

test("resolves to the first license file, alphabetically", async () => {
const cwd = await initFixture("licenses-names");

expect(getLicensePath(path.join(cwd, "packages", "package-5"))).resolves.toBe("LICENCE");
});
15 changes: 15 additions & 0 deletions commands/publish/__tests__/get-packages-without-license.test.js
@@ -0,0 +1,15 @@
"use strict";

const path = require("path");

const initFixture = require("@lerna-test/init-fixture")(__dirname);
const getPackagesWithoutLicense = require("../lib/get-packages-without-license");

test("getPackagesWithoutLicense", async () => {
const cwd = await initFixture("licenses");

const pkg1 = { name: "package-1", location: path.join(cwd, "packages", "package-1") };
const pkg2 = { name: "package-2", location: path.join(cwd, "packages", "package-2") };

expect(getPackagesWithoutLicense([pkg1, pkg2])).resolves.toEqual(expect.arrayContaining([pkg1]));
});

0 comments on commit 5863564

Please sign in to comment.