diff --git a/core/filter-options/__tests__/get-filtered-packages.test.js b/core/filter-options/__tests__/get-filtered-packages.test.js new file mode 100644 index 0000000000..a9215f6801 --- /dev/null +++ b/core/filter-options/__tests__/get-filtered-packages.test.js @@ -0,0 +1,137 @@ +"use strict"; + +// we're actually testing integration with git +jest.unmock("@lerna/collect-updates"); + +const path = require("path"); +const execa = require("execa"); +const touch = require("touch"); +const yargs = require("yargs/yargs"); + +const initFixture = require("@lerna-test/init-fixture")(path.resolve(__dirname, "../../command")); +const gitAdd = require("@lerna-test/git-add"); +const gitCommit = require("@lerna-test/git-commit"); +const gitTag = require("@lerna-test/git-tag"); +const PackageGraph = require("@lerna/package-graph"); +const { getPackages } = require("@lerna/project"); + +const getFilteredPackages = require("../lib/get-filtered-packages"); +const filterOptions = require(".."); + +async function buildGraph(cwd) { + const packages = await getPackages(cwd); + return new PackageGraph(packages); +} + +function parseOptions(...args) { + return filterOptions(yargs()).parse(args); +} + +test("--scope filters packages by glob", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--scope", "package-2", "--scope", "*-4"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe("package-2"); + expect(result[1].name).toBe("package-4"); +}); + +test("--since returns all packages if no tag is found", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--since"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result).toHaveLength(5); +}); + +test("--since returns packages updated since the last tag", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--since"); + + await gitTag(cwd, "v1.0.0"); + await touch(path.join(cwd, "packages/package-2/random-file")); + await gitAdd(cwd, "-A"); + await gitCommit(cwd, "test"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe("package-2"); + expect(result[1].name).toBe("package-3"); +}); + +test("--since should return packages updated since ", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--since", "master"); + + // We first tag, then modify master to ensure that specifying --since will override checking against + // the latest tag. + await gitTag(cwd, "v1.0.0"); + + await touch(path.join(cwd, "packages/package-1/random-file")); + await gitAdd(cwd, "-A"); + await gitCommit(cwd, "test"); + + // Then we can checkout a new branch, update and commit. + await execa("git", ["checkout", "-b", "test"], execOpts); + + await touch(path.join(cwd, "packages/package-2/random-file")); + await gitAdd(cwd, "-A"); + await gitCommit(cwd, "test"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result).toHaveLength(2); + expect(result[0].name).toBe("package-2"); + expect(result[1].name).toBe("package-3"); +}); + +test("--scope package-{2,3,4} --since master", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--scope", "package-{2,3,4}", "--since", "master"); + + await execa("git", ["checkout", "-b", "test"], execOpts); + await touch(path.join(cwd, "packages/package-4/random-file")); + await gitAdd(cwd, "-A"); + await gitCommit(cwd, "test"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result).toHaveLength(1); + expect(result[0].name).toBe("package-4"); +}); + +test("--include-filtered-dependents", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--scope", "package-1", "--include-filtered-dependents"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result.map(pkg => pkg.name)).toEqual(["package-1", "package-2", "package-5", "package-3"]); +}); + +test("--include-filtered-dependencies", async () => { + const cwd = await initFixture("filtering"); + const packageGraph = await buildGraph(cwd); + const execOpts = { cwd }; + const options = parseOptions("--scope", "package-3", "--include-filtered-dependencies"); + + const result = await getFilteredPackages(packageGraph, execOpts, options); + + expect(result.map(pkg => pkg.name)).toEqual(["package-3", "package-2", "package-1"]); +}); diff --git a/core/filter-options/index.js b/core/filter-options/index.js index 046b6014f6..63bc257169 100644 --- a/core/filter-options/index.js +++ b/core/filter-options/index.js @@ -1,8 +1,10 @@ "use strict"; const dedent = require("dedent"); +const getFilteredPackages = require("./lib/get-filtered-packages"); module.exports = filterOptions; +module.exports.getFilteredPackages = getFilteredPackages; function filterOptions(yargs) { // Only for 'run', 'exec', 'clean', 'ls', and 'bootstrap' commands diff --git a/core/filter-options/lib/get-filtered-packages.js b/core/filter-options/lib/get-filtered-packages.js new file mode 100644 index 0000000000..79bd3c0579 --- /dev/null +++ b/core/filter-options/lib/get-filtered-packages.js @@ -0,0 +1,34 @@ +"use strict"; + +const collectUpdates = require("@lerna/collect-updates"); +const filterPackages = require("@lerna/filter-packages"); + +module.exports = getFilteredPackages; + +function getFilteredPackages(packageGraph, execOpts, options) { + let chain = Promise.resolve(); + + chain = chain.then(() => + filterPackages(packageGraph.rawPackageList, options.scope, options.ignore, options.private) + ); + + if (options.since !== undefined) { + chain = chain.then(filteredPackages => + Promise.resolve(collectUpdates(filteredPackages, packageGraph, execOpts, options)).then(updates => { + const updated = new Set(updates.map(({ pkg }) => pkg.name)); + + return filteredPackages.filter(pkg => updated.has(pkg.name)); + }) + ); + } + + if (options.includeFilteredDependents) { + chain = chain.then(filteredPackages => packageGraph.addDependents(filteredPackages)); + } + + if (options.includeFilteredDependencies) { + chain = chain.then(filteredPackages => packageGraph.addDependencies(filteredPackages)); + } + + return chain; +} diff --git a/core/filter-options/package.json b/core/filter-options/package.json index d2e05c8d1d..a83b5b0300 100644 --- a/core/filter-options/package.json +++ b/core/filter-options/package.json @@ -13,6 +13,7 @@ "url": "https://github.com/evocateur" }, "files": [ + "lib", "index.js" ], "main": "index.js", @@ -30,6 +31,8 @@ "test": "echo \"Run tests from root\" && exit 1" }, "dependencies": { + "@lerna/collect-updates": "file:../../utils/collect-updates", + "@lerna/filter-packages": "file:../../utils/filter-packages", "dedent": "^0.7.0" } } diff --git a/package-lock.json b/package-lock.json index 18b271ea55..32628cda36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -451,6 +451,8 @@ "@lerna/filter-options": { "version": "file:core/filter-options", "requires": { + "@lerna/collect-updates": "file:utils/collect-updates", + "@lerna/filter-packages": "file:utils/filter-packages", "dedent": "^0.7.0" } },