Skip to content

Commit

Permalink
Add universal hosted git URL support (#1278)
Browse files Browse the repository at this point in the history
* Support gitRange (#semver:^1.2.3) as well as gitCommittish
* Move dependency update logic into Package#updateDependency()
* Test PublishCommand/normal-exact with meaningful fixtures

BREAKING CHANGE:

* Remove --use-git-version and --git-version-prefix options
  The former is now implied, and the latter is always "" or "v".

  Updating git hosted versions no longer throws when --exact is missing.

* Remove VersionSerializer, GitVersionParser
  We now simply recognize any local package name with a matching git url.

refs #1033
  • Loading branch information
evocateur committed Feb 22, 2018
1 parent 42dc70c commit 027d061
Show file tree
Hide file tree
Showing 42 changed files with 463 additions and 495 deletions.
90 changes: 29 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,35 @@ For example the `nsp` dependency is necessary in this case for `lerna run nsp`
}
```

### Git Hosted Dependencies

Lerna allows target versions of local dependent packages to be written as a [git remote url](https://docs.npmjs.com/cli/install) with a `committish` (e.g., `#v1.0.0` or `#semver:^1.0.0`) instead of the normal numeric version range.
This allows packages to be distributed via git repositories when packages must be private and a [private npm registry is not desired](https://www.dotconferences.com/2016/05/fabien-potencier-monolithic-repositories-vs-many-repositories).

Please note that lerna does _not_ perform the actual splitting of git history into the separate read-only repositories. This is the responsibility of the user. (See [this comment](https://github.com/lerna/lerna/pull/1033#issuecomment-335894690) for implementation details)

```
// packages/pkg-1/package.json
{
name: "pkg-1",
version: "1.0.0",
dependencies: {
"pkg-2": "github:example-user/pkg-2#v1.0.0"
}
}
// packages/pkg-2/package.json
{
name: "pkg-2",
version: "1.0.0"
}
```

In the example above,

* `lerna bootstrap` will properly symlink `pkg-2` into `pkg-1`.
* `lerna publish` will update the committish (`#v1.0.0`) in `pkg-1` when `pkg-2` changes.

### Flags

Options to Lerna can come from configuration (`lerna.json`) or on the command
Expand Down Expand Up @@ -1011,67 +1040,6 @@ The root-level package.json must also include a `workspaces` array:
This list is broadly similar to lerna's `packages` config (a list of globs matching directories with a package.json),
except it does not support recursive globs (`"**"`, a.k.a. "globstars").

#### --use-git-version

Allow target versions of dependent packages to be written as [git hosted urls](https://github.com/npm/hosted-git-info) instead of a plain version number.
If enabled, Lerna will attempt to extract and save the interpackage dependency versions from `package.json` files using git url-aware parser.

Eg. assuming monorepo with 2 packages where `my-package-1` depends on `my-package-2`, `package.json` of `my-package-1` could be:

```
// packages/my-package-1/package.json
{
name: "my-package-1",
version: "1.0.0",
bin: "bin.js",
dependencies: {
"my-package-2": "github:example-user/my-package-2#v1.0.0"
},
devDependencies: {
"my-dev-dependency": "^1.0.0"
},
peerDependencies: {
"my-peer-dependency": "^1.0.0"
}
}
```

For the case above Lerna will read the version of `my-package-2` dependency as `1.0.0`.

This allows packages to be distributed via git repos if eg. packages are private and [private npm repo is not an option](https://www.dotconferences.com/2016/05/fabien-potencier-monolithic-repositories-vs-many-repositories).

Please note that using `--use-git-version`

* is limited to urls with [`committish`](https://docs.npmjs.com/files/package.json#git-urls-as-dependencies) part present (ie. `github:example-user/my-package-2` is invalid)
* requires `publish` command to be used with `--exact`

May also be configured in `lerna.json`:

```js
{
...
"useGitVersion": true
}
```

#### --git-version-prefix

Defines version prefix string (defaults to 'v') ignored when extracting version number from a commitish part of git url.
Everything after the prefix will be considered a version.

Eg. given `github:example-user/my-package-2#v1.0.0` and `gitVersionPrefix: 'v'` version will be read as `1.0.0`.

Only used if `--use-git-version` is set to `true`.

May also be configured in `lerna.json`:

```js
{
...
"gitVersionPrefix": "v"
}
```

#### --stream

Stream output from child processes immediately, prefixed with the originating
Expand Down
21 changes: 4 additions & 17 deletions src/Command.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const PackageGraph = require("./PackageGraph");
const Repository = require("./Repository");
const writeLogFile = require("./utils/write-log-file");
const UpdatedPackagesCollector = require("./UpdatedPackagesCollector");
const VersionSerializer = require("./VersionSerializer");
const collectPackages = require("./utils/collect-packages");
const filterPackages = require("./utils/filter-packages");
const ValidationError = require("./utils/validation-error");
Expand Down Expand Up @@ -255,7 +254,7 @@ class Command {
}

const { rootPath, packageConfigs } = this.repository;
const { scope, ignore, registry, since, useGitVersion, gitVersionPrefix } = this.options;
const { scope, ignore, registry, since } = this.options;

if (scope) {
log.info("scope", scope);
Expand All @@ -270,21 +269,9 @@ class Command {
}

try {
const packages = collectPackages({ rootPath, packageConfigs });
const packageGraph = new PackageGraph(packages, { graphType: "allDependencies" });

if (useGitVersion) {
packages.forEach(pkg => {
pkg.versionSerializer = new VersionSerializer({
localDependencies: packageGraph.get(pkg.name).localDependencies,
tagVersionPrefix: gitVersionPrefix,
});
});
}

this.packages = packages;
this.packageGraph = packageGraph;
this.filteredPackages = filterPackages(packages, { scope, ignore });
this.packages = collectPackages({ rootPath, packageConfigs });
this.packageGraph = new PackageGraph(this.packages, { graphType: "allDependencies" });
this.filteredPackages = filterPackages(this.packages, { scope, ignore });

// The UpdatedPackagesCollector requires that filteredPackages be present prior to checking for
// updates. That's okay because it further filters based on what's already been filtered.
Expand Down
55 changes: 38 additions & 17 deletions src/Package.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function shallowCopy(json) {
return Object.keys(json).reduce((obj, key) => {
const val = json[key];

/* istanbul ignore if */
if (Array.isArray(val)) {
obj[key] = val.slice();
} else if (val && typeof val === "object") {
Expand All @@ -25,10 +26,7 @@ function shallowCopy(json) {
}

class Package {
constructor(json, location, rootPath = location) {
let pkg = json;
// TODO: less mutation by reference

constructor(pkg, location, rootPath = location) {
Object.defineProperties(this, {
// read-only
name: {
Expand Down Expand Up @@ -97,28 +95,51 @@ class Package {
binLocation: {
value: path.join(location, "node_modules", ".bin"),
},
// side-effects
versionSerializer: {
set(impl) {
this.serialize = impl.serialize;
pkg = impl.deserialize(pkg);
},
},
serialize: {
value: K => K,
writable: true,
},
// "private"
json: {
get() {
return shallowCopy(pkg);
return pkg;
},
},
});
}

updateDependency(resolved, depVersion, savePrefix) {
const depName = resolved.name;

// first, try runtime dependencies
let depCollection = this.json.dependencies;

// fall back to devDependencies (it will always be one of these two)
if (!depCollection || !depCollection[depName]) {
depCollection = this.json.devDependencies;
}

if (resolved.registry || resolved.type === "directory") {
// a version (1.2.3) OR range (^1.2.3) OR directory (file:../foo-pkg)
depCollection[depName] = `${savePrefix}${depVersion}`;
} else if (resolved.gitCommittish) {
// a git url with matching committish (#v1.2.3 or #1.2.3)
const [tagPrefix] = /^\D*/.exec(resolved.gitCommittish);

// update committish
const { hosted } = resolved; // take that, lint!
hosted.committish = `${tagPrefix}${depVersion}`;

// always serialize the full git+ssh url (identical to previous resolved.saveSpec)
depCollection[depName] = hosted.sshurl({ noGitPlus: false, noCommittish: false });
} else if (resolved.gitRange) {
// a git url with matching gitRange (#semver:^1.2.3)
const { hosted } = resolved; // take that, lint!
hosted.committish = `semver:${savePrefix}${depVersion}`;

// always serialize the full git+ssh url (identical to previous resolved.saveSpec)
depCollection[depName] = hosted.sshurl({ noGitPlus: false, noCommittish: false });
}
}

toJSON() {
return this.serialize(this.json);
return shallowCopy(this.json);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/PackageGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ class PackageGraph extends Map {

const satisfies = forceLocal
? () => true
: (version, resolved) => semver.satisfies(version, resolved.fetchSpec || resolved.gitCommittish);
: (version, resolved) =>
semver.satisfies(version, resolved.gitCommittish || resolved.gitRange || resolved.fetchSpec);

this.forEach((currentNode, currentName) => {
const { graphDependencies } = currentNode;
Expand Down
68 changes: 0 additions & 68 deletions src/VersionSerializer.js

This file was deleted.

0 comments on commit 027d061

Please sign in to comment.