Skip to content

Commit

Permalink
fix(add): Support explicit & implicit relative file: specifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
evocateur committed Jul 30, 2018
1 parent acfd48b commit 41f231f
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 7 deletions.
23 changes: 22 additions & 1 deletion commands/add/__tests__/add-command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const bootstrap = require("@lerna/bootstrap");
// helpers
const initFixture = require("@lerna-test/init-fixture")(__dirname);
const pkgMatchers = require("@lerna-test/pkg-matchers");
const { getPackages } = require("@lerna/project");

// file under test
const lernaAdd = require("@lerna-test/command-runner")(require("../command"));
Expand Down Expand Up @@ -79,7 +80,7 @@ describe("AddCommand", () => {
expect(await readPkg(testDir, "packages/package-2")).toDependOn("@test/package-1", "^1.0.0");
});

it("should reference specfied range", async () => {
it("should reference specified range", async () => {
const testDir = await initFixture("basic");

await lernaAdd(testDir)("@test/package-1@~1");
Expand All @@ -97,6 +98,26 @@ describe("AddCommand", () => {
});
});

it("adds explicit local file: specifier as file: specifier", async () => {
const testDir = await initFixture("basic");

await lernaAdd(testDir)("@test/package-1@file:packages/package-1");

expect(await readPkg(testDir, "packages/package-2")).toDependOn("@test/package-1", "file:../package-1");
});

it("adds local dep as file: specifier when existing relationships are file: specifiers", async () => {
const testDir = await initFixture("existing");
const [, , pkg3] = await getPackages(testDir);

pkg3.updateLocalDependency({ name: "@test/package-2", type: "directory" }, "file:../package-2", "");
await pkg3.serialize();

await lernaAdd(testDir)("@test/package-1");

expect(await readPkg(testDir, "packages/package-2")).toDependOn("@test/package-1", "file:../package-1");
});

it("should add target package to devDependencies", async () => {
const testDir = await initFixture("basic");

Expand Down
24 changes: 20 additions & 4 deletions commands/add/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class AddCommand extends Command {
return true;
}

return getRangeToReference(this.spec, deps, this.savePrefix) !== deps[targetName];
return getRangeToReference(this.spec, deps, pkg.location, this.savePrefix) !== deps[targetName];
});

return result;
Expand All @@ -121,9 +121,9 @@ class AddCommand extends Command {

return pMap(this.packagesToChange, pkg => {
const deps = this.getPackageDeps(pkg);
const range = getRangeToReference(this.spec, deps, this.savePrefix);
const range = getRangeToReference(this.spec, deps, pkg.location, this.savePrefix);

this.logger.verbose("add", `${targetName}@${range} as ${this.dependencyType} in ${pkg.name}`);
this.logger.verbose("add", `${targetName}@${range} to ${this.dependencyType} in ${pkg.name}`);
deps[targetName] = range;

return pkg.serialize();
Expand All @@ -145,7 +145,9 @@ class AddCommand extends Command {
const { name, fetchSpec } = this.spec;

if (this.selfSatisfied) {
return Promise.resolve(this.packageGraph.get(name).version);
const node = this.packageGraph.get(name);

return Promise.resolve(this.spec.saveRelativeFileSpec ? node.location : node.version);
}

return packageJson(name, { version: fetchSpec }).then(pkg => pkg.version);
Expand All @@ -159,6 +161,20 @@ class AddCommand extends Command {
return false;
}

// an explicit "file:packages/foo" always saves as a relative "file:../foo"
if (this.spec.type === "directory" && fetchSpec === pkg.location) {
this.spec.saveRelativeFileSpec = true;

return true;
}

// existing relative file spec means local dep should be added the same way
this.spec.saveRelativeFileSpec = Array.from(this.packageGraph.values()).some(
node =>
node.localDependencies.size &&
Array.from(node.localDependencies.values()).some(resolved => resolved.type === "directory")
);

if (fetchSpec === "latest") {
return true;
}
Expand Down
9 changes: 8 additions & 1 deletion commands/add/lib/get-range-to-reference.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
"use strict";

const npa = require("npm-package-arg");
const path = require("path");
const semver = require("semver");

module.exports = getRangeToReference;

function getRangeToReference(spec, deps, prefix) {
function getRangeToReference(spec, deps, loc, prefix) {
const current = deps[spec.name];
const resolved = spec.type === "tag" ? `${prefix}${spec.version}` : spec.fetchSpec;

if (spec.saveRelativeFileSpec) {
// "version" has been resolved to pkg.location in getPackageVersion()
return npa.resolve(spec.name, path.relative(loc, spec.version), loc).saveSpec;
}

if (prefix && current && semver.intersects(current, resolved)) {
return current;
}
Expand Down
3 changes: 2 additions & 1 deletion helpers/pkg-matchers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ const matchDependency = dependencyType => (manifest, pkg, range, options) => {
}

const version = manifest[dependencyType][pkg];
const mismatchedDep = range ? !semver.intersects(version, range) : false;
// we don't care about semver intersection, it's not always a semver range
const mismatchedDep = range && version !== range;

if (mismatchedDep) {
return {
Expand Down

0 comments on commit 41f231f

Please sign in to comment.