Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add support for configurable package locations #365

Merged
merged 4 commits into from
Dec 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading