Skip to content

Commit

Permalink
feat(conventional-commits): Add conventional prerelease/graduation (l…
Browse files Browse the repository at this point in the history
  • Loading branch information
Shawn Erquhart authored and evocateur committed May 11, 2019
1 parent c23452e commit 73fe232
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 101 deletions.
6 changes: 4 additions & 2 deletions __helpers__/build-graph.js
Expand Up @@ -6,7 +6,7 @@ const PackageGraph = require("@lerna/package-graph");

module.exports = buildGraph;

function buildGraph() {
function buildGraph(mapPackages = pkg => pkg) {
// cat __fixtures__/toposort/packages/*/package.json
const packages = [
{
Expand Down Expand Up @@ -61,7 +61,9 @@ function buildGraph() {
name: "package-standalone",
version: "1.0.0",
},
].map(json => new Package(json, `/test/packages/${json.name}`, "/test"));
]
.map(mapPackages)
.map(json => new Package(json, `/test/packages/${json.name}`, "/test"));

return new PackageGraph(packages);
// require("console").dir(graph, { compact: false })
Expand Down
91 changes: 91 additions & 0 deletions __tests__/collect-updates.test.js
@@ -1,5 +1,7 @@
"use strict";

const dedent = require("dedent");

jest.mock("@lerna/describe-ref");
jest.mock("../lib/has-tags");
jest.mock("../lib/make-diff-predicate");
Expand Down Expand Up @@ -46,6 +48,10 @@ const ALL_NODES = Object.freeze([
expect.objectContaining({ name: "package-standalone" }),
]);

const toPrereleaseMapper = names => pkg => {
return !names || names.includes(pkg.name) ? Object.assign(pkg, { version: `${pkg.version}-alpha.0` }) : pkg;
};

describe("collectUpdates()", () => {
beforeEach(() => {
// isolate each test
Expand Down Expand Up @@ -227,6 +233,91 @@ describe("collectUpdates()", () => {
]);
});

it("returns all prereleased nodes with --conventional-graduate", () => {
const graph = buildGraph(toPrereleaseMapper());
const pkgs = graph.rawPackageList;
const execOpts = { cwd: "/test" };

const updates = collectUpdates(pkgs, graph, execOpts, {
conventionalCommits: true,
conventionalGraduate: true,
});

expect(updates).toEqual(ALL_NODES);
});

it("returns all prereleased nodes with --conventional-graduate *", () => {
const graph = buildGraph(toPrereleaseMapper());
const pkgs = graph.rawPackageList;
const execOpts = { cwd: "/test" };

const updates = collectUpdates(pkgs, graph, execOpts, {
conventionalCommits: true,
conventionalGraduate: "*",
});

expect(updates).toEqual(ALL_NODES);
});

it("always includes prereleased nodes targeted by --conventional-graduate <pkg>", () => {
changedPackages.add("package-dag-3");

const graph = buildGraph(toPrereleaseMapper(["package-dag-3", "package-standalone"]));
const pkgs = graph.rawPackageList;
const execOpts = { cwd: "/test" };

const updates = collectUpdates(pkgs, graph, execOpts, {
conventionalCommits: true,
conventionalGraduate: "package-standalone",
});

expect(updates).toEqual([
expect.objectContaining({ name: "package-dag-3" }),
expect.objectContaining({ name: "package-standalone" }),
]);
});

it("always includes prereleased nodes targeted by --conventional-graduate <pkg>,<pkg>", () => {
changedPackages.add("package-dag-3");

const graph = buildGraph(toPrereleaseMapper(["package-dag-3", "package-standalone", "package-dag-2b"]));
const pkgs = graph.rawPackageList;
const execOpts = { cwd: "/test" };

const updates = collectUpdates(pkgs, graph, execOpts, {
forcePublish: "package-standalone,package-dag-2b",
});

expect(updates).toEqual([
expect.objectContaining({ name: "package-dag-2b" }),
expect.objectContaining({ name: "package-dag-3" }),
expect.objectContaining({ name: "package-standalone" }),
]);
});

it(
dedent`
always includes prereleased nodes targeted by --conventional-graduate <pkg> --conventional-graduate <pkg>
`,
() => {
changedPackages.add("package-dag-3");

const graph = buildGraph(toPrereleaseMapper(["package-dag-3", "package-standalone", "package-dag-2b"]));
const pkgs = graph.rawPackageList;
const execOpts = { cwd: "/test" };

const updates = collectUpdates(pkgs, graph, execOpts, {
forcePublish: ["package-standalone", "package-dag-2b"],
});

expect(updates).toEqual([
expect.objectContaining({ name: "package-dag-2b" }),
expect.objectContaining({ name: "package-dag-3" }),
expect.objectContaining({ name: "package-standalone" }),
]);
}
);

it("uses revision range with --canary", () => {
changedPackages.add("package-dag-2a");

Expand Down
54 changes: 54 additions & 0 deletions __tests__/lib-collect-packages.test.js
@@ -0,0 +1,54 @@
"use strict";

// helpers
const buildGraph = require("../__helpers__/build-graph");

// file under test
const collectPackages = require("../lib/collect-packages");

const toNamesList = collection => Array.from(collection).map(pkg => pkg.name);

test("returns all packages", () => {
const graph = buildGraph();
const result = collectPackages(graph);

expect(toNamesList(result)).toMatchInlineSnapshot(`
Array [
"package-cycle-1",
"package-cycle-2",
"package-cycle-extraneous",
"package-dag-1",
"package-dag-2a",
"package-dag-2b",
"package-dag-3",
"package-standalone",
]
`);
});

test("filters packages through isCandidate, passing node and name", () => {
const graph = buildGraph();
const packagesToInclude = ["package-cycle-1"];
const isCandidate = (node, name) => {
return packagesToInclude.includes(node.name) && node.name === name;
};
const result = collectPackages(graph, { isCandidate });

expect(toNamesList(result)).toMatchInlineSnapshot(`
Array [
"package-cycle-1",
"package-cycle-2",
"package-cycle-extraneous",
]
`);
});

test("calls onInclude with included package name", () => {
const graph = buildGraph();
const packagesToInclude = ["package-standalone"];
const isCandidate = (node, name) => packagesToInclude.includes(name);
const onInclude = jest.fn();
collectPackages(graph, { isCandidate, onInclude });

expect(onInclude).toHaveBeenCalledWith(packagesToInclude[0]);
});
40 changes: 0 additions & 40 deletions __tests__/lib-get-forced-packages.test.js

This file was deleted.

40 changes: 40 additions & 0 deletions __tests__/lib-get-packages-for-option.test.js
@@ -0,0 +1,40 @@
"use strict";

// file under test
const getPackagesForOption = require("../lib/get-packages-for-option");

test("no argument", () => {
const result = getPackagesForOption();

expect(Array.from(result)).toEqual([]);
});

test("--config-option", () => {
const result = getPackagesForOption(true);

expect(Array.from(result)).toEqual(["*"]);
});

test("--config-option *", () => {
const result = getPackagesForOption("*");

expect(Array.from(result)).toEqual(["*"]);
});

test("--config-option foo", () => {
const result = getPackagesForOption("foo");

expect(Array.from(result)).toEqual(["foo"]);
});

test("--config-option foo,bar", () => {
const result = getPackagesForOption("foo,bar");

expect(Array.from(result)).toEqual(["foo", "bar"]);
});

test("--config-option foo --config-option bar", () => {
const result = getPackagesForOption(["foo", "bar"]);

expect(Array.from(result)).toEqual(["foo", "bar"]);
});
73 changes: 38 additions & 35 deletions collect-updates.js
Expand Up @@ -4,14 +4,21 @@ const log = require("npmlog");
const describeRef = require("@lerna/describe-ref");

const hasTags = require("./lib/has-tags");
const collectDependents = require("./lib/collect-dependents");
const getForcedPackages = require("./lib/get-forced-packages");
const collectPackages = require("./lib/collect-packages");
const getPackagesForOption = require("./lib/get-packages-for-option");
const makeDiffPredicate = require("./lib/make-diff-predicate");

module.exports = collectUpdates;
module.exports.collectPackages = collectPackages;
module.exports.getPackagesForOption = getPackagesForOption;

function collectUpdates(filteredPackages, packageGraph, execOpts, commandOptions) {
const forced = getForcedPackages(commandOptions.forcePublish);
const { forcePublish, conventionalCommits, conventionalGraduate } = commandOptions;

// If --conventional-commits and --conventional-graduate are both set, ignore --force-publish
const useConventionalGraduate = conventionalCommits && conventionalGraduate;
const forced = getPackagesForOption(useConventionalGraduate ? conventionalGraduate : forcePublish);

const packages =
filteredPackages.length === packageGraph.size
? packageGraph
Expand Down Expand Up @@ -43,45 +50,41 @@ function collectUpdates(filteredPackages, packageGraph, execOpts, commandOptions

if (forced.size) {
// "warn" might seem a bit loud, but it is appropriate for logging anything _forced_
log.warn("force-publish", forced.has("*") ? "all packages" : Array.from(forced.values()).join("\n"));
log.warn(
useConventionalGraduate ? "conventional-graduate" : "force-publish",
forced.has("*") ? "all packages" : Array.from(forced.values()).join("\n")
);
}

let candidates;

if (!committish || forced.has("*")) {
if (useConventionalGraduate) {
// --conventional-commits --conventional-graduate
if (forced.has("*")) {
log.info("", "Graduating all prereleased packages");
} else {
log.info("", "Graduating prereleased packages");
}
} else if (!committish || forced.has("*")) {
// --force-publish or no tag
log.info("", "Assuming all packages changed");
candidates = new Set(packages.values());
} else {
log.info("", `Looking for changed packages since ${committish}`);
candidates = new Set();

const hasDiff = makeDiffPredicate(committish, execOpts, commandOptions.ignoreChanges);
const needsBump =
!commandOptions.bump || commandOptions.bump.startsWith("pre")
? () => false
: /* skip packages that have not been previously prereleased */
node => node.prereleaseId;

packages.forEach((node, name) => {
if (forced.has(name) || needsBump(node) || hasDiff(node)) {
candidates.add(node);
}

return collectPackages(packages, {
onInclude: name => log.verbose("updated", name),
});
}

const dependents = collectDependents(candidates);
dependents.forEach(node => candidates.add(node));

// The result should always be in the same order as the input
const updates = [];
log.info("", `Looking for changed packages since ${committish}`);

packages.forEach((node, name) => {
if (candidates.has(node)) {
log.verbose("updated", name);
const hasDiff = makeDiffPredicate(committish, execOpts, commandOptions.ignoreChanges);
const needsBump =
!commandOptions.bump || commandOptions.bump.startsWith("pre")
? () => false
: /* skip packages that have not been previously prereleased */
node => node.prereleaseId;
const isForced = (node, name) =>
(forced.has("*") || forced.has(name)) && (useConventionalGraduate ? node.prereleaseId : true);

updates.push(node);
}
return collectPackages(packages, {
isCandidate: (node, name) => isForced(node, name) || needsBump(node) || hasDiff(node),
onInclude: name => log.verbose("updated", name),
});

return updates;
}
32 changes: 32 additions & 0 deletions lib/collect-packages.js
@@ -0,0 +1,32 @@
"use strict";

const collectDependents = require("./collect-dependents");

module.exports = collectPackages;

function collectPackages(packages, { isCandidate = () => true, onInclude } = {}) {
const candidates = new Set();

packages.forEach((node, name) => {
if (isCandidate(node, name)) {
candidates.add(node);
}
});

const dependents = collectDependents(candidates);
dependents.forEach(node => candidates.add(node));

// The result should always be in the same order as the input
const updates = [];

packages.forEach((node, name) => {
if (candidates.has(node)) {
if (onInclude) {
onInclude(name);
}
updates.push(node);
}
});

return updates;
}

0 comments on commit 73fe232

Please sign in to comment.