Skip to content

Commit

Permalink
Merge pull request #365 from lerna/config-locations
Browse files Browse the repository at this point in the history
[Feature] Add support for configurable package locations
  • Loading branch information
gigabo committed Dec 14, 2016
2 parents f325820 + af4a738 commit 1d14f9f
Show file tree
Hide file tree
Showing 30 changed files with 329 additions and 75 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ Your repository should now look like this:

```
lerna-repo/
packages/
package.json
lerna.json
```
Expand Down Expand Up @@ -125,7 +124,6 @@ When run, this command will:

1. Add `lerna` as a [`devDependency`](https://docs.npmjs.com/files/package.json#devdependencies) in `package.json` if it doesn't already exist.
2. Create a `lerna.json` config file to store the `version` number.
3. Create a `packages` directory if it hasn't been created already.

Example output on a new git repo:

Expand Down Expand Up @@ -428,7 +426,8 @@ Running `lerna` without arguments will show all commands/options.
},
"bootstrapConfig": {
"ignore": "component-*"
}
},
"packages": ["packages/*"]
}
```

Expand All @@ -437,6 +436,7 @@ Running `lerna` without arguments will show all commands/options.
- `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.
- `packages`: Array of globs to use as package locations.

### Common `devDependencies`

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"cmd-shim": "^2.0.2",
"command-join": "^1.1.1",
"cross-spawn": "^4.0.0",
"glob": "^7.0.6",
"inquirer": "^0.12.0",
"lodash.find": "^4.3.0",
"lodash.unionwith": "^4.2.0",
Expand Down
6 changes: 0 additions & 6 deletions src/Command.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ export default class Command {
return;
}

if (!FileSystemUtilities.existsSync(this.repository.packagesLocation)) {
this.logger.warn("`packages/` directory does not exist, have you run `lerna init`?");
this._complete(null, 1);
return;
}

if (!FileSystemUtilities.existsSync(this.repository.packageJsonLocation)) {
this.logger.warn("`package.json` does not exist, have you run `lerna init`?");
this._complete(null, 1);
Expand Down
27 changes: 14 additions & 13 deletions src/PackageUtilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import FileSystemUtilities from "./FileSystemUtilities";
import PackageGraph from "./PackageGraph";
import Package from "./Package";
import path from "path";
import {sync as globSync} from "glob";
import minimatch from "minimatch";

export default class PackageUtilities {
Expand All @@ -27,25 +28,25 @@ export default class PackageUtilities {
return require(PackageUtilities.getPackageConfigPath(packagesPath, name));
}

static getPackages(packagesPath) {
static getPackages(repository) {
const packages = [];

FileSystemUtilities.readdirSync(packagesPath).forEach((packageDirectory) => {
if (packageDirectory[0] === ".") {
return;
}
repository.packageConfigs.forEach((globPath) => {

const packagePath = PackageUtilities.getPackagePath(packagesPath, packageDirectory);
const packageConfigPath = PackageUtilities.getPackageConfigPath(packagesPath, packageDirectory);
globSync(path.join(repository.rootPath, globPath, "package.json"))
.map((fn) => path.resolve(fn))
.forEach((packageConfigPath) => {
const packagePath = path.dirname(packageConfigPath);

if (!FileSystemUtilities.existsSync(packageConfigPath)) {
return;
}
if (!FileSystemUtilities.existsSync(packageConfigPath)) {
return;
}

const packageJson = require(packageConfigPath);
const pkg = new Package(packageJson, packagePath);
const packageJson = require(packageConfigPath);
const pkg = new Package(packageJson, packagePath);

packages.push(pkg);
packages.push(pkg);
});
});

return packages;
Expand Down
10 changes: 8 additions & 2 deletions src/Repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import PackageUtilities from "./PackageUtilities";
import path from "path";
import logger from "./logger";

const DEFAULT_PACKAGE_GLOB = "packages/*";

export default class Repository {
constructor() {
if (!GitUtilities.isInitialized()) {
Expand All @@ -14,7 +16,7 @@ export default class Repository {
this.rootPath = path.resolve(GitUtilities.getTopLevelDirectory());
this.lernaJsonLocation = path.join(this.rootPath, "lerna.json");
this.packageJsonLocation = path.join(this.rootPath, "package.json");
this.packagesLocation = path.join(this.rootPath, "packages");
this.packagesLocation = path.join(this.rootPath, "packages"); // TODO: Kill this.

// Legacy
this.versionLocation = path.join(this.rootPath, "VERSION");
Expand Down Expand Up @@ -44,6 +46,10 @@ export default class Repository {
return this.lernaJson && this.lernaJson.bootstrapConfig || {};
}

get packageConfigs() {
return (this.lernaJson || {}).packages || [DEFAULT_PACKAGE_GLOB];
}

get packages() {
if (!this._packages) {
this.buildPackageGraph();
Expand All @@ -63,7 +69,7 @@ export default class Repository {
}

buildPackageGraph() {
this._packages = PackageUtilities.getPackages(this.packagesLocation);
this._packages = PackageUtilities.getPackages(this);
this._packageGraph = PackageUtilities.getPackageGraph(this._packages);
}
}
10 changes: 5 additions & 5 deletions src/UpdatedPackagesCollector.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PackageUtilities from "./PackageUtilities";
import GitUtilities from "./GitUtilities";
import progressBar from "./progressBar";
import minimatch from "minimatch";
Expand All @@ -13,9 +12,10 @@ class Update {
}

export default class UpdatedPackagesCollector {
constructor(packages, packageGraph, flags, publishConfig) {
this.packages = packages;
this.packageGraph = packageGraph;
constructor(repository, flags, publishConfig) {
this.repository = repository;
this.packages = repository.packages;
this.packageGraph = repository.packageGraph;
this.flags = flags;
this.publishConfig = publishConfig;
}
Expand Down Expand Up @@ -140,7 +140,7 @@ export default class UpdatedPackagesCollector {
}

hasDiffSinceThatIsntIgnored(pkg, commits) {
const folder = PackageUtilities.getPackagePath(PackageUtilities.getPackagesPath(""), pkg.name);
const folder = path.relative(this.repository.rootPath, pkg.location);
const diff = GitUtilities.diffSinceIn(commits, pkg.location);

if (diff === "") {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/DiffCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class DiffCommand extends Command {

this.filePath = this.package
? this.package.location
: this.repository.packagesLocation;
: this.repository.rootPath;

this.lastCommit = GitUtilities.hasTags()
? GitUtilities.getLastTaggedCommit()
Expand Down
17 changes: 7 additions & 10 deletions src/commands/InitCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,13 @@ export default class InitCommand extends Command {
}

execute(callback) {
this.ensurePackagesDirectory();
this.ensurePackageJSON();
this.ensureLernaJson();
this.ensureNoVersionFile();
this.logger.success("Successfully created Lerna files");
callback(null, true);
}

ensurePackagesDirectory() {
const packagesLocation = this.repository.packagesLocation;
if (!FileSystemUtilities.existsSync(packagesLocation)) {
this.logger.info("Creating packages folder.");
FileSystemUtilities.mkdirSync(packagesLocation);
}
}

ensurePackageJSON() {
let {packageJsonLocation, packageJson} = this.repository;

Expand Down Expand Up @@ -59,7 +50,12 @@ export default class InitCommand extends Command {
}

ensureLernaJson() {
let {versionLocation, lernaJsonLocation, lernaJson} = this.repository;
let {
versionLocation,
lernaJsonLocation,
lernaJson,
packageConfigs
} = this.repository;

let version;

Expand All @@ -82,6 +78,7 @@ export default class InitCommand extends Command {

objectAssign(lernaJson, {
lerna: this.lernaVersion,
packages: packageConfigs,
version: version
});

Expand Down
3 changes: 1 addition & 2 deletions src/commands/PublishCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export default class PublishCommand extends Command {
}

const updatedPackagesCollector = new UpdatedPackagesCollector(
this.packages,
this.packageGraph,
this.repository,
this.flags,
this.repository.publishConfig
);
Expand Down
3 changes: 1 addition & 2 deletions src/commands/UpdatedCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import chalk from "chalk";
export default class UpdatedCommand extends Command {
initialize(callback) {
const updatedPackagesCollector = new UpdatedPackagesCollector(
this.packages,
this.packageGraph,
this.repository,
this.flags,
this.repository.publishConfig
);
Expand Down
121 changes: 121 additions & 0 deletions test/BootstrapCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,127 @@ describe("BootstrapCommand", () => {
});
});

describe("a repo with packages outside of packages/", () => {
let testDir;

beforeEach((done) => {
testDir = initFixture("BootstrapCommand/extra", done);
});

it("should bootstrap packages", (done) => {
const bootstrapCommand = new BootstrapCommand([], {});

bootstrapCommand.runValidations();
bootstrapCommand.runPreparations();

assertStubbedCalls([
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT }] }
]],
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-2"), stdio: STDIO_OPT }] }
]],
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "@test/package-1@^0.0.0"], { cwd: path.join(testDir, "packages", "package-4"), stdio: STDIO_OPT }] }
]],
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "foo@0.1.12"], { cwd: path.join(testDir, "package-3"), stdio: STDIO_OPT }] }
]],
]);

bootstrapCommand.runCommand(exitWithCode(0, (err) => {
if (err) return done(err);

try {
assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
// Make sure the `prepublish` script got run (index.js got created).
assert.ok(pathExists.sync(path.join(testDir, "packages", "package-1", "index.js")));
// package-1 should not have any packages symlinked
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-2")));
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-3")));
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-4")));
// package-2 package dependencies are symlinked
assert.equal(
path.resolve(fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "@test", "package-1"))),
path.resolve(path.join(testDir, "packages", "package-1")),
"package-1 should be symlinked to package-2"
);
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-3")));
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-4")));
// package-3 package dependencies are symlinked
assert.equal(
normalize(fs.readlinkSync(path.join(testDir, "package-3", "node_modules", "@test", "package-1"))),
normalize(path.join(testDir, "packages", "package-1")),
"package-1 should be symlinked to package-3"
);
assert.equal(
normalize(fs.readlinkSync(path.join(testDir, "package-3", "node_modules", "package-2"))),
normalize(path.join(testDir, "packages", "package-2")),
"package-2 should be symlinked to package-3"
);
assert.throws(() => fs.readlinkSync(path.join(testDir, "package-3", "node_modules", "package-4")));
// package-4 package dependencies are symlinked
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-1")));
assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-2")));
assert.equal(
normalize(fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-3"))),
normalize(path.join(testDir, "package-3")),
"package-3 should be symlinked to package-4"
);
// package binaries are symlinked
assert.equal(
normalize(FileSystemUtilities.isSymlink(path.join(testDir, "package-3", "node_modules", ".bin", "package-2"))),
normalize(path.join(testDir, "packages", "package-2", "cli.js")),
"package-2 binary should be symlinked in package-3"
);
assert.equal(
normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli1"))),
normalize(path.join(testDir, "package-3", "cli1.js")),
"package-3 binary should be symlinked in package-4"
);
assert.equal(
normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli2"))),
normalize(path.join(testDir, "package-3", "cli2.js")),
"package-3 binary should be symlinked in package-4"
);
done();
} catch (err) {
done(err);
}
}));
});

it("should not bootstrap ignored packages", (done) => {
const bootstrapCommand = new BootstrapCommand([], {
ignore: "package-@(3|4)"
});

bootstrapCommand.runValidations();
bootstrapCommand.runPreparations();

assertStubbedCalls([
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT }] }
]],
[ChildProcessUtilities, "spawn", { nodeCallback: true }, [
{ args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-2"), stdio: STDIO_OPT }] }
]]
]);

bootstrapCommand.runCommand(exitWithCode(0, (err) => {
if (err) return done(err);

try {
assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");

done();
} catch (err) {
done(err);
}
}));
});
});

describe("external dependencies that haven't been installed", () => {
let testDir;

Expand Down
4 changes: 2 additions & 2 deletions test/Command.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ describe("Command", () => {
});

describe(".run()", () => {
it.skip("should exist", (done) => {
it("should exist", (done) => {
class TestCommand extends Command {
initialize(callback) { callback(); }
initialize(callback) { callback(null, true); }
execute() {

done();
Expand Down
2 changes: 1 addition & 1 deletion test/DiffCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("DiffCommand", () => {
assert.equal(args[0], "diff");
assert.equal(args[1].length, 40); // commit
assert.equal(args[2], "--color=auto");
assert.equal(args[3], path.join(testDir, "packages"));
assert.equal(args[3], testDir);
callback(0);
});

Expand Down

0 comments on commit 1d14f9f

Please sign in to comment.