Skip to content

Commit

Permalink
[Feature] Adds --scope and --ignore support for bootstrap, exec, run,…
Browse files Browse the repository at this point in the history
… clean and ls (#386)

* Refactoring to allow scope and ignore flags to work accross with bootstrap, exec, run, clean and ls commands

* fixed issue with bootstrapConfig and publishCOnfig ignore flags being ignored

* Adds tests for --ignore and --scope flag behaviour in ls, exec, run, clean and bootstrap commands

* Adds tests for PakcageUtilities.getFilteredPackages()

* Linting: removed unused imports

* fixes broken test in bootstrap due to non-normalized paths

* fixes broken tests in BootstrapCommand from windowns symlinks

* Removed the configFlags from PublishCommand

* Updated readme with --scoped and --ignore flags information

* fix(): Refactored Repository and PackageUtilities to remove filtering responsibility from Repository

* Fixes broken test for LSCommand
  • Loading branch information
lukebatchelor authored and gigabo committed Dec 13, 2016
1 parent 2beb13e commit d707386
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 108 deletions.
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ When run, this command will:
2. Symlink together all Lerna `packages` that are dependencies of each other.
3. `npm prepublish` all bootstrapped packages.

`lerna bootstrap` respects the `--ignore` flag (see [Flags](#flags)).
`lerna bootstrap` respects the `--ignore` and `--scope` flags (see [Flags](#flags)).

#### How `bootstrap` works

Expand Down Expand Up @@ -192,7 +192,7 @@ Let's use `babel` as an example.

**Note:** Circular dependencies result in circular symlinks which *may* impact your editor/IDE.

[Webstorm](https://www.jetbrains.com/webstorm/) locks up when circular symlinks are present. To prevent this, add `node_modules` to the list of ignored files and folders in `Preferences | Editor | File Types | Ignored files and folders`.
[Webstorm](https://www.jetbrains.com/webstorm/) locks up when circular symlinks are present. To prevent this, add `node_modules` to the list of ignored files and folders in `Preferences | Editor | File Types | Ignored files and folders`.

### publish

Expand Down Expand Up @@ -325,6 +325,8 @@ $ lerna clean

Remove the `node_modules` directory from all packages.

`lerna clean` respects the `--ignore` and `--scope` flags (see [Flags](#flags)).

### diff

```sh
Expand All @@ -347,6 +349,8 @@ $ lerna ls

List all of the public packages in the current Lerna repo.

`lerna ls` respects the `--ignore` and `--scope` flags (see [Flags](#flags)).

### run

```sh
Expand All @@ -357,9 +361,7 @@ $ lerna run build

Run an [npm script](https://docs.npmjs.com/misc/scripts) in each package that contains that script.

`lerna run` respects the `--concurrency` flag (see [Flags](#flags)).

`lerna run` respects the `--scope` flag (see [Flags](#flags)).
`lerna run` respects the `--concurrency`, `--scope` and `ignore` flags (see [Flags](#flags)).

```sh
$ lerna run --scope my-component test
Expand All @@ -375,9 +377,7 @@ $ lerna exec -- protractor conf.js

Run an arbitrary command in each package.

`lerna exec` respects the `--concurrency` flag (see [Flags](#flags)).

`lerna exec` respects the `--scope` flag (see [Flags](#flags)).
`lerna exec` respects the `--concurrency`, `--scope` and `--ignore` flags (see [Flags](#flags)).

```sh
$ lerna exec --scope my-component -- ls -la
Expand Down Expand Up @@ -425,13 +425,18 @@ Running `lerna` without arguments will show all commands/options.
"ignored-file",
"*.md"
]
},
"bootstrapConfig": {
"ignore": "component-*"
}
}
```

- `lerna`: the current version of Lerna being used.
- `version`: the current version of the repository.
- `publishConfig.ignore`: an array of globs that won't be included in `lerna updated/publish`. Use this to prevent publishing a new version unnecessarily for changes, such as fixing a `README.md` typo.
- `bootstrapConfig.ignore`: an glob that won't be bootstrapped when running the `lerna bootstrap` command.
- `bootstrapConfig.scope`: an glob that restricts which packages will be bootstrapped when running the `lerna bootstrap` command.

### Common `devDependencies`

Expand Down Expand Up @@ -487,15 +492,13 @@ $ lerna run --scope toolbar-* test

#### --ignore [glob]

Excludes a subset of packages when running the `bootstrap` command.
Excludes a subset of packages when running a command.

```sh
$ lerna bootstrap --ignore component-*
```

The `ignore` flag, when used with the `bootstrap` command, can also be set in `lerna.json` under the `bootstrapConfig` key. The command-line flag will take precendence over this option. This flag is supported in `bootstrap` and `exec` commands.

**Note**: If both `scope` and `ignore` are provided to `exec` command, `scope` takes precedence.
The `ignore` flag, when used with the `bootstrap` command, can also be set in `lerna.json` under the `bootstrapConfig` key. The command-line flag will take precendence over this option.

**Example**

Expand Down
4 changes: 2 additions & 2 deletions bin/lerna.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ var cli = meow([
" --skip-git Skip commiting, tagging, and pushing git changes (only affects publish)",
" --skip-npm Stop before actually publishing change to npm (only affects publish)",
" --npm-tag [tagname] Publish packages with the specified npm dist-tag",
" --scope [glob] Restricts the scope to package names matching the given glob (Works only in combination with the 'run' and the 'exec' command).",
" --ignore [glob] Ignores packages with names matching the given glob (Works only in combination with the 'exec' and 'bootstrap' commands).",
" --scope [glob] Restricts the scope to package names matching the given glob (Works only in combination with the 'run', 'exec', 'clean', 'ls' and 'bootstrap' commands).",
" --ignore [glob] Ignores packages with names matching the given glob (Works only in combination with the 'run', 'exec', 'clean', 'ls' and 'bootstrap' commands).",
" --force-publish Force publish for the specified packages (comma-separated) or all packages using * (skips the git diff check for changed packages)",
" --yes Skip all confirmation prompts",
" --repo-version Specify repo version to publish",
Expand Down
17 changes: 14 additions & 3 deletions src/Command.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ChildProcessUtilities from "./ChildProcessUtilities";
import FileSystemUtilities from "./FileSystemUtilities";
import PackageUtilities from "./PackageUtilities";
import ExitHandler from "./ExitHandler";
import progressBar from "./progressBar";
import Repository from "./Repository";
import PackageUtilities from "./PackageUtilities";
import logger from "./logger";

const DEFAULT_CONCURRENCY = 4;
Expand Down Expand Up @@ -100,9 +100,20 @@ export default class Command {
}

runPreparations() {
const scope = this.flags.scope || (this.configFlags && this.configFlags.scope);
const ignore = this.flags.ignore || (this.configFlags && this.configFlags.ignore);

if (scope) {
this.logger.info(`Scoping to packages that match '${scope}'`);
}
if (ignore) {
this.logger.info(`Ignoring packages that match '${ignore}'`);
}
try {
this.packages = PackageUtilities.getPackages(this.repository.packagesLocation);
this.packageGraph = PackageUtilities.getPackageGraph(this.packages);
this.repository.buildPackageGraph();
this.packages = this.repository.packages;
this.filteredPackages = PackageUtilities.filterPackages(this.packages, {scope, ignore});
this.packageGraph = this.repository.packageGraph;
} catch (err) {
this.logger.error("Errored while collecting packages and package graph", err);
this._complete(null, 1);
Expand Down
25 changes: 23 additions & 2 deletions src/PackageUtilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,36 @@ export default class PackageUtilities {
}

/**
* Filters a given set of packages and returns the one matching the given glob
* Filters a given set of packages and returns all packages that match the scope glob
* and do not match the ignore glob
*
* @param {!Array.<Package>} packages The packages to filter
* @param {Object} filters The scope and ignore filters.
* @param {String} filters.scope glob The glob to match the package name against
* @param {String} filters.ignore glob The glob to filter the package name against
* @return {Array.<Package>} The packages with a name matching the glob
*/
static filterPackages(packages, {scope, ignore}) {
packages = packages.slice();
if (scope) {
packages = PackageUtilities._filterPackages(packages, scope);
}
if (ignore) {
packages = PackageUtilities._filterPackages(packages, ignore, true);
}
return packages;
}

/**
* Filters a given set of packages and returns all packages matching the given glob
*
* @param {!Array.<Package>} packages The packages to filter
* @param {String} glob The glob to match the package name against
* @param {Boolean} negate Negate glob pattern matches
* @return {Array.<Package>} The packages with a name matching the glob
* @throws in case a given glob would produce an empty list of packages
*/
static filterPackages(packages, glob, negate = false) {
static _filterPackages(packages, glob, negate = false) {
if (typeof glob !== "undefined") {
packages = packages.filter((pkg) => {
if (negate) {
Expand Down
20 changes: 20 additions & 0 deletions src/Repository.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import GitUtilities from "./GitUtilities";
import FileSystemUtilities from "./FileSystemUtilities";
import PackageUtilities from "./PackageUtilities";
import path from "path";
import logger from "./logger";

Expand Down Expand Up @@ -43,7 +44,26 @@ export default class Repository {
return this.lernaJson && this.lernaJson.bootstrapConfig || {};
}

get packages() {
if (!this._packages) {
this.buildPackageGraph();
}
return this._packages;
}

get packageGraph() {
if (!this._packageGraph) {
this.buildPackageGraph();
}
return this._packageGraph;
}

isIndependent() {
return this.version === "independent";
}

buildPackageGraph() {
this._packages = PackageUtilities.getPackages(this.packagesLocation);
this._packageGraph = PackageUtilities.getPackageGraph(this._packages);
}
}
14 changes: 1 addition & 13 deletions src/commands/BootstrapCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import path from "path";

export default class BootstrapCommand extends Command {
initialize(callback) {
// Nothing to do...
this.configFlags = this.repository.bootstrapConfig;
callback(null, true);
}

Expand All @@ -28,7 +28,6 @@ export default class BootstrapCommand extends Command {
* @param {Function} callback
*/
bootstrapPackages(callback) {
this.filteredPackages = this.getPackages();
this.filteredGraph = PackageUtilities.getPackageGraph(this.filteredPackages);
this.logger.info(`Bootstrapping ${this.filteredPackages.length} packages`);
async.series([
Expand All @@ -45,17 +44,6 @@ export default class BootstrapCommand extends Command {
], callback);
}

/**
* Get packages to bootstrap
* @returns {Array.<Package>}
*/
getPackages() {
const ignore = this.flags.ignore || this.repository.bootstrapConfig.ignore;
if (ignore) {
this.logger.info(`Ignoring packages that match '${ignore}'`);
}
return PackageUtilities.filterPackages(this.packages, ignore, true);
}

runScriptInPackages(scriptName, callback) {
const packages = this.filteredPackages.slice();
Expand Down
6 changes: 3 additions & 3 deletions src/commands/CleanCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class CleanCommand extends Command {
callback(null, true);
} else {
this.logger.info(`About to remove the following directories:\n${
this.packages.map((pkg) => "- " + pkg.nodeModulesLocation).join("\n")
this.filteredPackages.map((pkg) => "- " + pkg.nodeModulesLocation).join("\n")
}`);
PromptUtilities.confirm("Proceed?", (confirmed) => {
if (confirmed) {
Expand All @@ -24,7 +24,7 @@ export default class CleanCommand extends Command {
}

execute(callback) {
progressBar.init(this.packages.length);
progressBar.init(this.filteredPackages.length);
this.rimrafNodeModulesInPackages((err) => {
progressBar.terminate();
if (err) {
Expand All @@ -37,7 +37,7 @@ export default class CleanCommand extends Command {
}

rimrafNodeModulesInPackages(callback) {
async.parallelLimit(this.packages.map((pkg) => (cb) => {
async.parallelLimit(this.filteredPackages.map((pkg) => (cb) => {
FileSystemUtilities.rimraf(pkg.nodeModulesLocation, (err) => {
progressBar.tick(pkg.name);
cb(err);
Expand Down
21 changes: 1 addition & 20 deletions src/commands/ExecCommand.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ChildProcessUtilities from "../ChildProcessUtilities";
import Command from "../Command";
import PackageUtilities from "../PackageUtilities";
import async from "async";

export default class ExecCommand extends Command {
Expand All @@ -13,29 +12,11 @@ export default class ExecCommand extends Command {
return;
}

if (this.flags.scope) {
this.logger.info(`Scoping to packages that match '${this.flags.scope}'`);
try {
this.packages = PackageUtilities.filterPackages(this.packages, this.flags.scope);
} catch (err) {
callback(err);
return;
}
} else if (this.flags.ignore) {
this.logger.info(`Ignoring packages that match ${this.flags.ignore}`);
try {
this.packages = PackageUtilities.filterPackages(this.packages, this.flags.ignore, true);
} catch (err) {
callback(err);
return;
}
}

callback(null, true);
}

execute(callback) {
async.parallelLimit(this.packages.map((pkg) => (cb) => {
async.parallelLimit(this.filteredPackages.map((pkg) => (cb) => {
this.runCommandInPackage(pkg, cb);
}), this.concurrency, callback);
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/LsCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class LsCommand extends Command {
}

execute(callback) {
const formattedPackages = this.packages
const formattedPackages = this.filteredPackages
.map((pkg) => `- ${pkg.name}${pkg.isPrivate() ? ` (${chalk.red("private")})` : ""}`)
.join("\n");

Expand Down
1 change: 1 addition & 0 deletions src/commands/PublishCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EOL } from "os";

export default class PublishCommand extends Command {
initialize(callback) {

if (this.flags.canary) {
this.logger.info("Publishing canary build");
}
Expand Down
13 changes: 1 addition & 12 deletions src/commands/RunCommand.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import NpmUtilities from "../NpmUtilities";
import Command from "../Command";
import PackageUtilities from "../PackageUtilities";
import async from "async";

export default class RunCommand extends Command {
Expand All @@ -13,24 +12,14 @@ export default class RunCommand extends Command {
return;
}

this.packagesWithScript = this.packages
this.packagesWithScript = this.filteredPackages
.filter((pkg) => pkg.scripts && pkg.scripts[this.script]);

if (!this.packagesWithScript.length) {
callback(new Error(`No packages found with the npm script '${this.script}'`));
return;
}

if (this.flags.scope) {
this.logger.info(`Scoping to packages that match '${this.flags.scope}'`);
try {
this.packagesWithScript = PackageUtilities.filterPackages(this.packagesWithScript, this.flags.scope);
} catch (err) {
callback(err);
return;
}
}

callback(null, true);
}

Expand Down
Loading

0 comments on commit d707386

Please sign in to comment.