From 1e98de4214ae598e7bbcb41c2cf6272c8a2f834d Mon Sep 17 00:00:00 2001 From: gustaff-weldon Date: Tue, 26 Sep 2017 16:28:36 +0200 Subject: [PATCH 1/4] Introduced support for package git dependency urls (coauthored with @synaptiko) --- README.md | 25 +++++- package.json | 1 + src/GitVersionParser.js | 22 +++++ src/Package.js | 18 +++- src/PackageGraph.js | 14 ++- src/PackageUtilities.js | 4 +- src/Repository.js | 20 ++++- src/VersionSerializer.js | 46 ++++++++++ src/commands/PublishCommand.js | 6 ++ test/GitVersionParser.js | 79 +++++++++++++++++ test/Package.js | 116 ++++++++++++++++++++++++- test/PackageGraph.js | 76 +++++++++++++++++ test/Repository.js | 2 +- test/VersionSerializer.js | 151 +++++++++++++++++++++++++++++++++ yarn.lock | 4 + 15 files changed, 569 insertions(+), 15 deletions(-) create mode 100644 src/GitVersionParser.js create mode 100644 src/VersionSerializer.js create mode 100644 test/GitVersionParser.js create mode 100644 test/PackageGraph.js create mode 100644 test/VersionSerializer.js diff --git a/README.md b/README.md index 2998eb9c5b..12eafd4239 100644 --- a/README.md +++ b/README.md @@ -663,6 +663,8 @@ Running `lerna` without arguments will show all commands/options. } }, "packages": ["packages/*"] + "useGitVersion": true, + "gitVersionPrefix": "v" } ``` @@ -672,6 +674,23 @@ Running `lerna` without arguments will show all commands/options. - `commands.bootstrap.ignore`: an array of globs that won't be bootstrapped when running the `lerna bootstrap` command. - `commands.bootstrap.scope`: an array of globs that restricts which packages will be bootstrapped when running the `lerna bootstrap` command. - `packages`: Array of globs to use as package locations. +- `useGitVersion`: a boolean (defaults to `false`) indicating if [git hosted urls](https://github.com/npm/hosted-git-info) should be allowed instead of plain version number. If enabled, Lerna will attempt to extract and save the interpackage dependency versions using git url-aware parser. + 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 `gitVersion` requires `publish` command to be used with `--exact` and is limited to urls with [`committish`](https://docs.npmjs.com/files/package.json#git-urls-as-dependencies) part present. + + Example assuming 2 packages where `my-package-1` depends on `my-package-2`, for which `package.json` of `my-package-1` could be: + ``` + { + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + dependencies: { "my-package-2": "github:example-user/my-package-2#1.0.0" }, + devDependencies: { "my-dev-dependency": "^1.0.0" }, + peerDependencies: { "my-peer-dependency": "^1.0.0" } + } + ``` +- `gitVersionPrefix`: version prefix string (defaults to 'v') ignored when extracting version number from commitish part of git url. eg. given `github:example-user/my-package-2#v1.0.0` + and `gitVersionPrefix: 'v'` version will be read as `1.0.0`. Ignored if `useGitVersion` is set to `false`. ### Common `devDependencies` @@ -896,9 +915,9 @@ May also be configured in `lerna.json`: #### --use-workspaces -Enables integration with [Yarn Workspaces](https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-workspaces-install-phase-1.md) (available since yarn@0.27+). -The values in the array are the commands in which Lerna will delegate operation to Yarn (currently only bootstrapping). -If `--use-workspaces` is true then `packages` will be overridden by the value from `package.json/workspaces.` +Enables integration with [Yarn Workspaces](https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-workspaces-install-phase-1.md) (available since yarn@0.27+). +The values in the array are the commands in which Lerna will delegate operation to Yarn (currently only bootstrapping). +If `--use-workspaces` is true then `packages` will be overridden by the value from `package.json/workspaces.` May also be configured in `lerna.json`: ```js diff --git a/package.json b/package.json index 344d2a6d9e..dbf34a42ff 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "glob-parent": "^3.1.0", "globby": "^6.1.0", "graceful-fs": "^4.1.11", + "hosted-git-info": "^2.5.0", "inquirer": "^3.2.2", "is-ci": "^1.0.10", "load-json-file": "^3.0.0", diff --git a/src/GitVersionParser.js b/src/GitVersionParser.js new file mode 100644 index 0000000000..8b6fdf4950 --- /dev/null +++ b/src/GitVersionParser.js @@ -0,0 +1,22 @@ +import { escapeRegExp } from "lodash"; +import hostedGitInfo from "hosted-git-info"; + +export default class GitVersionParser { + constructor(versionPrefix = "v") { + this._gitUrlPattern = new RegExp(`(.+?#${escapeRegExp(versionPrefix)})(.+)$`); + } + + parseVersion(version) { + const gitInfo = hostedGitInfo.fromUrl(version); + let targetMatches; + + if (gitInfo && gitInfo.committish) { + targetMatches = this._gitUrlPattern.exec(version); + } + + return { + prefix: targetMatches ? targetMatches[1] : null, + version: targetMatches ? targetMatches[2] : version + }; + } +} diff --git a/src/Package.js b/src/Package.js index 390fbb3745..74f3bba987 100644 --- a/src/Package.js +++ b/src/Package.js @@ -2,6 +2,7 @@ import dedent from "dedent"; import log from "npmlog"; import path from "path"; import semver from "semver"; +import _ from "lodash"; import dependencyIsSatisfied from "./utils/dependencyIsSatisfied"; import NpmUtilities from "./NpmUtilities"; @@ -60,12 +61,27 @@ export default class Package { return this._package.scripts || {}; } + set versionSerializer(versionSerializer) { + const previousSerializer = this._versionSerializer; + + this._versionSerializer = versionSerializer; + + if (previousSerializer) { + this._package = previousSerializer.serialize(this._package); + } + + if (versionSerializer) { + this._package = versionSerializer.deserialize(this._package); + } + } + isPrivate() { return !!this._package.private; } toJSON() { - return this._package; + const pkg = _.cloneDeep(this._package); + return this._versionSerializer ? this._versionSerializer.serialize(pkg) : pkg; } /** diff --git a/src/PackageGraph.js b/src/PackageGraph.js index c595f26a34..2d830df19e 100644 --- a/src/PackageGraph.js +++ b/src/PackageGraph.js @@ -20,7 +20,7 @@ export class PackageGraphNode { * devDependencies that would normally be included. */ export default class PackageGraph { - constructor(packages, depsOnly = false) { + constructor(packages, depsOnly = false, versionParser) { this.nodes = []; this.nodesByName = {}; @@ -38,11 +38,17 @@ export default class PackageGraph { for (let d = 0; d < depNames.length; d++) { const depName = depNames[d]; - const depVersion = dependencies[depName]; const packageNode = this.nodesByName[depName]; - if (packageNode && semver.satisfies(packageNode.package.version, depVersion)) { - node.dependencies.push(depName); + if (packageNode) { + const depVersion = (versionParser + ? versionParser.parseVersion(dependencies[depName]).version + : dependencies[depName] + ); + + if (semver.satisfies(packageNode.package.version, depVersion)) { + node.dependencies.push(depName); + } } } } diff --git a/src/PackageUtilities.js b/src/PackageUtilities.js index 29fccfc058..9b85689a94 100644 --- a/src/PackageUtilities.js +++ b/src/PackageUtilities.js @@ -87,8 +87,8 @@ export default class PackageUtilities { return packages; } - static getPackageGraph(packages, depsOnly) { - return new PackageGraph(packages, depsOnly); + static getPackageGraph(packages, depsOnly, versionParser) { + return new PackageGraph(packages, depsOnly, versionParser); } /** diff --git a/src/Repository.js b/src/Repository.js index 66903ce4dd..64b56de6ff 100644 --- a/src/Repository.js +++ b/src/Repository.js @@ -9,6 +9,8 @@ import semver from "semver"; import dependencyIsSatisfied from "./utils/dependencyIsSatisfied"; import Package from "./Package"; import PackageUtilities from "./PackageUtilities"; +import GitVersionParser from "./GitVersionParser"; +import VersionSerializer from "./VersionSerializer"; const DEFAULT_PACKAGE_GLOB = "packages/*"; @@ -118,8 +120,22 @@ export default class Repository { } buildPackageGraph() { - this._packages = PackageUtilities.getPackages(this); - this._packageGraph = PackageUtilities.getPackageGraph(this._packages); + const packages = PackageUtilities.getPackages(this); + const packageGraph = PackageUtilities.getPackageGraph(packages, false, this.lernaJson.useGitVersion + ? new GitVersionParser(this.lernaJson.gitVersionPrefix) + : null); + + if (this.lernaJson.useGitVersion) { + packages.forEach((pkg) => { + pkg.versionSerializer = new VersionSerializer({ + monorepoDependencies: packageGraph.get(pkg.name).dependencies, + versionParser: new GitVersionParser(this.lernaJson.gitVersionPrefix) + }); + }); + } + + this._packages = packages; + this._packageGraph = packageGraph; } hasDependencyInstalled(depName, version) { diff --git a/src/VersionSerializer.js b/src/VersionSerializer.js new file mode 100644 index 0000000000..c99e7c47ef --- /dev/null +++ b/src/VersionSerializer.js @@ -0,0 +1,46 @@ +export default class VersionSerializer { + constructor({ monorepoDependencies, versionParser }) { + this._monorepoDependencies = monorepoDependencies; + this._versionParser = versionParser; + this._dependenciesKeys = ["dependencies", "devDependencies", "peerDependencies"]; + this._strippedPrefixes = new Map(); + } + + serialize(pkg) { + this._dependenciesKeys.forEach((key) => { + this._prependPrefix(pkg[key] || {}); + }); + + return pkg; + } + + deserialize(pkg) { + this._dependenciesKeys.forEach((key) => { + this._stripPrefix(pkg[key] || {}); + }); + + return pkg; + } + + _prependPrefix(dependencies) { + this._strippedPrefixes.forEach((prefix, name) => { + const version = dependencies[name]; + if (version) { + dependencies[name] = `${prefix}${version}`; + } + }); + } + + _stripPrefix(dependencies) { + Object.keys(dependencies).forEach((name) => { + if (this._monorepoDependencies.includes(name)) { + const result = this._versionParser.parseVersion(dependencies[name]); + + if (result.prefix) { + dependencies[name] = result.version; + this._strippedPrefixes.set(name, result.prefix); + } + } + }); + } +} diff --git a/src/commands/PublishCommand.js b/src/commands/PublishCommand.js index ec9f28b5d2..d692e97e12 100644 --- a/src/commands/PublishCommand.js +++ b/src/commands/PublishCommand.js @@ -156,6 +156,11 @@ export default class PublishCommand extends Command { this.gitRemote = this.options.gitRemote || "origin"; this.gitEnabled = !(this.options.canary || this.options.skipGit); + if (this.repository.lernaJson.useGitVersion && !this.options.exact) { + this.logger.error("config", ```Using git version without 'exact' option is not recommended. + Please make sure you publish with --exact.```); + } + if (this.options.canary) { this.logger.info("canary", "enabled"); } @@ -604,6 +609,7 @@ export default class PublishCommand extends Command { }); } + commitAndTagUpdates() { if (this.repository.isIndependent()) { this.tags = this.gitCommitAndTagVersionForUpdates(); diff --git a/test/GitVersionParser.js b/test/GitVersionParser.js new file mode 100644 index 0000000000..0400a09ee8 --- /dev/null +++ b/test/GitVersionParser.js @@ -0,0 +1,79 @@ +import log from "npmlog"; + +// file under test +import GitVersionParser from "../src/GitVersionParser"; + +// silence logs +log.level = "silent"; + +describe("GitVersionParser", () => { + + describe("parseVersion - without prefix", () => { + let parser; + + beforeEach(() => { + parser = new GitVersionParser(""); + }); + + it("should work for semver version", () => { + expect(parser.parseVersion("0.0.2")).toEqual({ + prefix: null, + version: "0.0.2" + }); + + expect(parser.parseVersion("~0.0.2")).toEqual({ + prefix: null, + version: "~0.0.2" + }); + }); + + it("should work for git url", () => { + expect(parser.parseVersion("github:user-foo/project-foo#v0.0.1")).toEqual({ + prefix: "github:user-foo/project-foo#", + version: "v0.0.1" + }); + + expect(parser.parseVersion("git@github.com:user-foo/project-foo#0.0.5")).toEqual({ + prefix: "git@github.com:user-foo/project-foo#", + version: "0.0.5" + }); + }); + }); + + describe("parseVersion - with version prefix", () => { + let parser; + + beforeEach(() => { + parser = new GitVersionParser("v"); + }); + + it("should work for semver version", () => { + expect(parser.parseVersion("0.0.2")).toEqual({ + prefix: null, + version: "0.0.2" + }); + + expect(parser.parseVersion("~0.0.2")).toEqual({ + prefix: null, + version: "~0.0.2" + }); + }); + + it("should work for git url", () => { + expect(parser.parseVersion("github:user-foo/project-foo#v0.0.1")).toEqual({ + prefix: "github:user-foo/project-foo#v", + version: "0.0.1" + }); + + expect(parser.parseVersion("git@github.com:user-foo/project-foo#0.0.5")).toEqual({ + prefix: null, + version: "git@github.com:user-foo/project-foo#0.0.5" + }); + + expect(parser.parseVersion("git@github.com:user-foo/project-foo#v0.0.5")).toEqual({ + prefix: "git@github.com:user-foo/project-foo#v", + version: "0.0.5" + }); + }); + }); +}); diff --git a/test/Package.js b/test/Package.js index d04f7264a5..7ed7bb6142 100644 --- a/test/Package.js +++ b/test/Package.js @@ -102,15 +102,127 @@ describe("Package", () => { }); }); + describe(".set versionSerializer()", () => { + it("should call 'deserialize' method of serializer'", () => { + + const mockSerializer = { + serialize: jest.fn((pkg) => { + return pkg; + }), + deserialize: jest.fn((pkg) => { + return pkg; + }) + }; + + pkg.versionSerializer = mockSerializer; + + expect(mockSerializer.deserialize.mock.calls.length).toBe(1); + expect(mockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); + expect(mockSerializer.serialize.mock.calls.length).toBe(0); + }); + + it("should call 'serialize' on old and 'deserialize' on new serializer'", () => { + + const firstMockSerializer = { + serialize: jest.fn((pkg) => { + return pkg; + }), + deserialize: jest.fn((pkg) => { + return pkg; + }) + }; + + const secondMockSerializer = { + serialize: jest.fn((pkg) => { + return pkg; + }), + deserialize: jest.fn((pkg) => { + return pkg; + }) + }; + + pkg.versionSerializer = firstMockSerializer; + + expect(firstMockSerializer.deserialize.mock.calls.length).toBe(1); + expect(firstMockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); + expect(firstMockSerializer.serialize.mock.calls.length).toBe(0); + + pkg.versionSerializer = secondMockSerializer; + + expect(firstMockSerializer.deserialize.mock.calls.length).toBe(1); + expect(firstMockSerializer.serialize.mock.calls.length).toBe(1); + expect(firstMockSerializer.serialize.mock.calls[0][0]).toBe(pkg._package); + + expect(secondMockSerializer.deserialize.mock.calls.length).toBe(1); + expect(secondMockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); + expect(secondMockSerializer.serialize.mock.calls.length).toBe(0); + }); + }); + + describe(".toJSON()", () => { - it("should return internal package for serialization", () => { - expect(pkg.toJSON()).toBe(pkg._package); + it("should return internal package copy for serialization", () => { + expect(pkg.toJSON()).toEqual(pkg._package); const implicit = JSON.stringify(pkg, null, 2); const explicit = JSON.stringify(pkg._package, null, 2); expect(implicit).toBe(explicit); }); + + it("should not change internal package with versionSerializer", () => { + const mockSerializer = { + serialize: jest.fn((pkg) => { + return Object.assign({}, pkg, { foo: true }); + }), + deserialize: jest.fn((pkg) => { + const newPkg = Object.assign({}, pkg); + delete newPkg.foo; + return newPkg; + }) + }; + + pkg.versionSerializer = mockSerializer; + + let serializedPackage = pkg.toJSON(); + const pkgPackageClone = Object.assign({}, pkg._package, { foo: true }); + + expect(serializedPackage).not.toEqual(pkg._package); + expect(serializedPackage).toEqual(pkgPackageClone); + + expect(mockSerializer.deserialize.mock.calls.length).toBe(1); + expect(mockSerializer.serialize.mock.calls.length).toBe(1); + expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); + + serializedPackage = pkg.toJSON(); + + expect(serializedPackage).not.toEqual(pkg._package); + expect(serializedPackage).toEqual(pkgPackageClone); + + expect(mockSerializer.deserialize.mock.calls.length).toBe(1); + expect(mockSerializer.serialize.mock.calls.length).toBe(2); + expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); + expect(mockSerializer.serialize.mock.calls[1][0]).toEqual(pkg._package); + }); + + it("should use versionSerializer.serialize on internal package before return", () => { + const mockSerializer = { + serialize: jest.fn((pkg) => { + return pkg; + }), + deserialize: jest.fn((pkg) => { + return pkg; + }) + }; + + pkg.versionSerializer = mockSerializer; + + expect(pkg.toJSON()).toEqual(pkg._package); + + expect(mockSerializer.deserialize.mock.calls.length).toBe(1); + expect(mockSerializer.serialize.mock.calls.length).toBe(1); + expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); + }); }); describe(".runScript()", () => { diff --git a/test/PackageGraph.js b/test/PackageGraph.js new file mode 100644 index 0000000000..d1de7ce608 --- /dev/null +++ b/test/PackageGraph.js @@ -0,0 +1,76 @@ +import log from "npmlog"; + +// file under test +import Package from "../src/Package"; +import PackageGraph from "../src/PackageGraph"; + +// silence logs +log.level = "silent"; + +describe("PackageGraph", () => { + + const createPackages = function(version, dependencyVersion) { + dependencyVersion = dependencyVersion || version; + + return [ + new Package( + { + name: "my-package-1", + version: version, + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "^1.0.0" }, + devDependencies: { "my-dev-dependency": "^1.0.0" }, + peerDependencies: { "my-peer-dependency": "^1.0.0" } + }, + "/path/to/package1" + ), + new Package( + { + name: "my-package-2", + version: version, + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "^1.0.0" }, + devDependencies: { "my-package-1": dependencyVersion }, + peerDependencies: { "my-peer-dependency": "^1.0.0" } + }, + "/path/to/package2" + ) + ]; + }; + + describe("constructor", () => { + it(".get should return dependencies", () => { + const [ pkg1, pkg2 ] = createPackages("0.0.1"); + const graph = new PackageGraph([ pkg1, pkg2 ]); + + expect(graph.get(pkg1.name).dependencies).toEqual([]); + expect(graph.get(pkg2.name).dependencies).toEqual([pkg1.name]); + }); + + it(".get should not return the dependencies for unrecognized versions", () => { + const [ pkg1, pkg2 ] = createPackages("0.0.1", "github:user-foo/project-foo#v0.0.1"); + const graph = new PackageGraph([ pkg1, pkg2 ]); + + expect(graph.get(pkg1.name).dependencies).toEqual([]); + expect(graph.get(pkg2.name).dependencies).toEqual([]); + }); + + it(".get should not return the dependencies for unrecognized versions", () => { + const [ pkg1, pkg2 ] = createPackages("0.0.1", "github:user-foo/project-foo#v0.0.1"); + + const mockParser = { + parseVersion: jest.fn().mockReturnValue({ + prefix: "github:user-foo/project-foo#v", + version: "0.0.1" + }) + }; + + const graph = new PackageGraph([pkg1, pkg2], false, mockParser); + + expect(graph.get(pkg1.name).dependencies).toEqual([]); + expect(graph.get(pkg2.name).dependencies).toEqual([ pkg1.name ]); + }); + }); +}); diff --git a/test/Repository.js b/test/Repository.js index 65e7a3ea1b..e8cf07bda0 100644 --- a/test/Repository.js +++ b/test/Repository.js @@ -138,7 +138,7 @@ describe("Repository", () => { path.join(testDir, "packages"), path.join(testDir, "dir/nested"), path.join(testDir, "globstar"), - ]) + ]); }); }); diff --git a/test/VersionSerializer.js b/test/VersionSerializer.js new file mode 100644 index 0000000000..f5ecc7913e --- /dev/null +++ b/test/VersionSerializer.js @@ -0,0 +1,151 @@ +import log from "npmlog"; + +// file under test +import VersionSerializer from "../src/VersionSerializer"; + +// silence logs +log.level = "silent"; + +describe("VersionSerializer", () => { + + let serializer; + + beforeEach(() => { + const parser = { + parseVersion(version) { + const chunks = version.split("#"); + return { + prefix: chunks.length > 1 ? chunks[0] + "#" : null, + version: chunks.length > 1 ? chunks[1] : version, + }; + } + }; + serializer = new VersionSerializer({ + monorepoDependencies: [ + "my-package-1", "my-package-2", "my-package-3" + ], + versionParser: parser + }); + }); + + describe("deserialize", () => { + + it("should use version parser for inter-package dependencies only", () => { + const mockParser = { + parseVersion: jest.fn().mockReturnValue({ + prefix: null, + version: "0.0.1" + }) + }; + + serializer = new VersionSerializer({ + monorepoDependencies: [ + "my-package-1", "my-package-2", "my-package-3" + ], + versionParser: mockParser + }); + + const pkg = { + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "^1.0.0" }, + devDependencies: { "my-package-2": "^1.0.0" }, + peerDependencies: { "my-package-3": "^1.0.0" } + }; + + serializer.deserialize(pkg); + expect(mockParser.parseVersion.mock.calls.length).toBe(2); + }); + + it("should not touch versions parser does not recognize", () => { + const pkg = { + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "^1.0.0" }, + devDependencies: { "my-package-2": "^1.0.0" }, + peerDependencies: { "my-package-3": "^1.0.0" } + }; + + expect(serializer.deserialize(pkg)).toEqual(pkg); + }); + + it("should extract versions recognized by parser", () => { + expect(serializer.deserialize({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "bbb#1.0.0" }, + peerDependencies: { "my-package-3": "ccc#1.0.0" } + })).toEqual({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "1.0.0" }, + peerDependencies: { "my-package-3": "1.0.0" } + }); + }); + }); + + describe("serialize", () => { + + it("should not touch versions parser does not recognize", () => { + const pkg = { + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "^1.0.0" }, + devDependencies: { "my-package-2": "^1.0.0" }, + peerDependencies: { "my-package-3": "^1.0.0" } + }; + + expect(serializer.serialize(pkg)).toEqual(pkg); + }); + + it("should write back version strings transformed by deserialize", () => { + expect(serializer.deserialize({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "bbb#1.0.0" }, + peerDependencies: { "my-package-3": "ccc#1.0.0" } + })).toEqual({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "1.0.0" }, + peerDependencies: { "my-package-3": "1.0.0" } + }); + + expect(serializer.serialize({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "1.0.0" }, + peerDependencies: { "my-package-3": "1.0.0" } + })).toEqual({ + name: "my-package-1", + version: "1.0.0", + bin: "bin.js", + scripts: { "my-script": "echo 'hello world'" }, + dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, + devDependencies: { "my-package-2": "bbb#1.0.0" }, + peerDependencies: { "my-package-3": "ccc#1.0.0" } + }); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 8b41e83c7c..255c316bbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1926,6 +1926,10 @@ hosted-git-info@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.2.0.tgz#7a0d097863d886c0fabbdcd37bf1758d8becf8a5" +hosted-git-info@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + html-encoding-sniffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" From b98e06adadbbf23205fde5a616ce076479dc1879 Mon Sep 17 00:00:00 2001 From: gustaff-weldon Date: Wed, 4 Oct 2017 13:39:45 +0200 Subject: [PATCH 2/4] Review fixes --- src/Repository.js | 14 +++--- src/VersionSerializer.js | 6 +-- src/commands/PublishCommand.js | 9 ++-- test/GitVersionParser.js | 12 +----- test/Package.js | 79 ++++++++++++++-------------------- test/PackageGraph.js | 4 +- test/VersionSerializer.js | 25 +++++------ 7 files changed, 62 insertions(+), 87 deletions(-) diff --git a/src/Repository.js b/src/Repository.js index 64b56de6ff..c82b41c4cb 100644 --- a/src/Repository.js +++ b/src/Repository.js @@ -120,16 +120,18 @@ export default class Repository { } buildPackageGraph() { + // FIXME: should be destructured from command.options to support CLI flag overrides + const { useGitVersion, gitVersionPrefix } = this.lernaJson; + + const versionParser = useGitVersion && new GitVersionParser(gitVersionPrefix); const packages = PackageUtilities.getPackages(this); - const packageGraph = PackageUtilities.getPackageGraph(packages, false, this.lernaJson.useGitVersion - ? new GitVersionParser(this.lernaJson.gitVersionPrefix) - : null); + const packageGraph = PackageUtilities.getPackageGraph(packages, false, versionParser); - if (this.lernaJson.useGitVersion) { + if (useGitVersion) { packages.forEach((pkg) => { pkg.versionSerializer = new VersionSerializer({ - monorepoDependencies: packageGraph.get(pkg.name).dependencies, - versionParser: new GitVersionParser(this.lernaJson.gitVersionPrefix) + graphDependencies: packageGraph.get(pkg.name).dependencies, + versionParser }); }); } diff --git a/src/VersionSerializer.js b/src/VersionSerializer.js index c99e7c47ef..c4465a0eed 100644 --- a/src/VersionSerializer.js +++ b/src/VersionSerializer.js @@ -1,6 +1,6 @@ export default class VersionSerializer { - constructor({ monorepoDependencies, versionParser }) { - this._monorepoDependencies = monorepoDependencies; + constructor({ graphDependencies, versionParser }) { + this._graphDependencies = graphDependencies; this._versionParser = versionParser; this._dependenciesKeys = ["dependencies", "devDependencies", "peerDependencies"]; this._strippedPrefixes = new Map(); @@ -33,7 +33,7 @@ export default class VersionSerializer { _stripPrefix(dependencies) { Object.keys(dependencies).forEach((name) => { - if (this._monorepoDependencies.includes(name)) { + if (this._graphDependencies.includes(name)) { const result = this._versionParser.parseVersion(dependencies[name]); if (result.prefix) { diff --git a/src/commands/PublishCommand.js b/src/commands/PublishCommand.js index d692e97e12..caedecfda7 100644 --- a/src/commands/PublishCommand.js +++ b/src/commands/PublishCommand.js @@ -156,9 +156,11 @@ export default class PublishCommand extends Command { this.gitRemote = this.options.gitRemote || "origin"; this.gitEnabled = !(this.options.canary || this.options.skipGit); - if (this.repository.lernaJson.useGitVersion && !this.options.exact) { - this.logger.error("config", ```Using git version without 'exact' option is not recommended. - Please make sure you publish with --exact.```); + if (this.options.useGitVersion && !this.options.exact) { + throw new Error(dedent` + Using git version without 'exact' option is not recommended. + Please make sure you publish with --exact. + `); } if (this.options.canary) { @@ -609,7 +611,6 @@ export default class PublishCommand extends Command { }); } - commitAndTagUpdates() { if (this.repository.isIndependent()) { this.tags = this.gitCommitAndTagVersionForUpdates(); diff --git a/test/GitVersionParser.js b/test/GitVersionParser.js index 0400a09ee8..4fa1d1a988 100644 --- a/test/GitVersionParser.js +++ b/test/GitVersionParser.js @@ -9,11 +9,7 @@ log.level = "silent"; describe("GitVersionParser", () => { describe("parseVersion - without prefix", () => { - let parser; - - beforeEach(() => { - parser = new GitVersionParser(""); - }); + const parser = new GitVersionParser(""); it("should work for semver version", () => { expect(parser.parseVersion("0.0.2")).toEqual({ @@ -41,11 +37,7 @@ describe("GitVersionParser", () => { }); describe("parseVersion - with version prefix", () => { - let parser; - - beforeEach(() => { - parser = new GitVersionParser("v"); - }); + const parser = new GitVersionParser("v"); it("should work for semver version", () => { expect(parser.parseVersion("0.0.2")).toEqual({ diff --git a/test/Package.js b/test/Package.js index 7ed7bb6142..15fd91f1f6 100644 --- a/test/Package.js +++ b/test/Package.js @@ -102,66 +102,55 @@ describe("Package", () => { }); }); - describe(".set versionSerializer()", () => { + describe(".set versionSerializer", () => { it("should call 'deserialize' method of serializer'", () => { const mockSerializer = { - serialize: jest.fn((pkg) => { - return pkg; - }), - deserialize: jest.fn((pkg) => { - return pkg; - }) + serialize: jest.fn((pkg) => pkg), + deserialize: jest.fn((pkg) => pkg) }; pkg.versionSerializer = mockSerializer; - expect(mockSerializer.deserialize.mock.calls.length).toBe(1); - expect(mockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); - expect(mockSerializer.serialize.mock.calls.length).toBe(0); + expect(mockSerializer.deserialize).toBeCalled(); + expect(mockSerializer.deserialize).toBeCalledWith(pkg._package); + expect(mockSerializer.serialize).not.toBeCalled(); }); it("should call 'serialize' on old and 'deserialize' on new serializer'", () => { const firstMockSerializer = { - serialize: jest.fn((pkg) => { - return pkg; - }), - deserialize: jest.fn((pkg) => { - return pkg; - }) + serialize: jest.fn((pkg) => pkg), + deserialize: jest.fn((pkg) => pkg) }; const secondMockSerializer = { - serialize: jest.fn((pkg) => { - return pkg; - }), - deserialize: jest.fn((pkg) => { - return pkg; - }) + serialize: jest.fn((pkg) => pkg), + deserialize: jest.fn((pkg) => pkg) }; pkg.versionSerializer = firstMockSerializer; - expect(firstMockSerializer.deserialize.mock.calls.length).toBe(1); - expect(firstMockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); - expect(firstMockSerializer.serialize.mock.calls.length).toBe(0); + expect(firstMockSerializer.deserialize).toBeCalled(); + expect(firstMockSerializer.deserialize).toBeCalledWith(pkg._package); + expect(firstMockSerializer.serialize).not.toBeCalled(); pkg.versionSerializer = secondMockSerializer; - expect(firstMockSerializer.deserialize.mock.calls.length).toBe(1); - expect(firstMockSerializer.serialize.mock.calls.length).toBe(1); - expect(firstMockSerializer.serialize.mock.calls[0][0]).toBe(pkg._package); + expect(firstMockSerializer.deserialize).toBeCalled(); + expect(firstMockSerializer.serialize).toBeCalled(); + expect(firstMockSerializer.serialize).toBeCalledWith(pkg._package); - expect(secondMockSerializer.deserialize.mock.calls.length).toBe(1); - expect(secondMockSerializer.deserialize.mock.calls[0][0]).toBe(pkg._package); - expect(secondMockSerializer.serialize.mock.calls.length).toBe(0); + expect(secondMockSerializer.deserialize).toBeCalled(); + expect(secondMockSerializer.deserialize).toBeCalledWith(pkg._package); + expect(secondMockSerializer.serialize).not.toBeCalled(); }); }); describe(".toJSON()", () => { it("should return internal package copy for serialization", () => { + expect(pkg.toJSON()).not.toBe(pkg._package); expect(pkg.toJSON()).toEqual(pkg._package); const implicit = JSON.stringify(pkg, null, 2); @@ -190,38 +179,34 @@ describe("Package", () => { expect(serializedPackage).not.toEqual(pkg._package); expect(serializedPackage).toEqual(pkgPackageClone); - expect(mockSerializer.deserialize.mock.calls.length).toBe(1); - expect(mockSerializer.serialize.mock.calls.length).toBe(1); - expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); + expect(mockSerializer.deserialize).toBeCalled(); + expect(mockSerializer.serialize).toBeCalled(); + expect(mockSerializer.serialize).toBeCalledWith(pkg._package); serializedPackage = pkg.toJSON(); expect(serializedPackage).not.toEqual(pkg._package); expect(serializedPackage).toEqual(pkgPackageClone); - expect(mockSerializer.deserialize.mock.calls.length).toBe(1); - expect(mockSerializer.serialize.mock.calls.length).toBe(2); - expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); - expect(mockSerializer.serialize.mock.calls[1][0]).toEqual(pkg._package); + expect(mockSerializer.deserialize).toBeCalled(); + expect(mockSerializer.serialize).toHaveBeenCalledTimes(2); + expect(mockSerializer.serialize).toBeCalledWith(pkg._package); + expect(mockSerializer.serialize).lastCalledWith(pkg._package); }); it("should use versionSerializer.serialize on internal package before return", () => { const mockSerializer = { - serialize: jest.fn((pkg) => { - return pkg; - }), - deserialize: jest.fn((pkg) => { - return pkg; - }) + serialize: jest.fn((pkg) => pkg), + deserialize: jest.fn((pkg) => pkg) }; pkg.versionSerializer = mockSerializer; expect(pkg.toJSON()).toEqual(pkg._package); - expect(mockSerializer.deserialize.mock.calls.length).toBe(1); - expect(mockSerializer.serialize.mock.calls.length).toBe(1); - expect(mockSerializer.serialize.mock.calls[0][0]).toEqual(pkg._package); + expect(mockSerializer.deserialize).toBeCalled(); + expect(mockSerializer.serialize).toBeCalled(); + expect(mockSerializer.serialize).toBeCalledWith(pkg._package); }); }); diff --git a/test/PackageGraph.js b/test/PackageGraph.js index d1de7ce608..b7246191e7 100644 --- a/test/PackageGraph.js +++ b/test/PackageGraph.js @@ -9,7 +9,7 @@ log.level = "silent"; describe("PackageGraph", () => { - const createPackages = function(version, dependencyVersion) { + function createPackages(version, dependencyVersion = version) { dependencyVersion = dependencyVersion || version; return [ @@ -38,7 +38,7 @@ describe("PackageGraph", () => { "/path/to/package2" ) ]; - }; + } describe("constructor", () => { it(".get should return dependencies", () => { diff --git a/test/VersionSerializer.js b/test/VersionSerializer.js index f5ecc7913e..32f1db321c 100644 --- a/test/VersionSerializer.js +++ b/test/VersionSerializer.js @@ -21,7 +21,7 @@ describe("VersionSerializer", () => { } }; serializer = new VersionSerializer({ - monorepoDependencies: [ + graphDependencies: [ "my-package-1", "my-package-2", "my-package-3" ], versionParser: parser @@ -39,7 +39,7 @@ describe("VersionSerializer", () => { }; serializer = new VersionSerializer({ - monorepoDependencies: [ + graphDependencies: [ "my-package-1", "my-package-2", "my-package-3" ], versionParser: mockParser @@ -56,7 +56,7 @@ describe("VersionSerializer", () => { }; serializer.deserialize(pkg); - expect(mockParser.parseVersion.mock.calls.length).toBe(2); + expect(mockParser.parseVersion).toHaveBeenCalledTimes(2); }); it("should not touch versions parser does not recognize", () => { @@ -111,24 +111,19 @@ describe("VersionSerializer", () => { }); it("should write back version strings transformed by deserialize", () => { - expect(serializer.deserialize({ - name: "my-package-1", - version: "1.0.0", - bin: "bin.js", - scripts: { "my-script": "echo 'hello world'" }, - dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, - devDependencies: { "my-package-2": "bbb#1.0.0" }, - peerDependencies: { "my-package-3": "ccc#1.0.0" } - })).toEqual({ + + // since serializer is stateful, version prefixes will be stored in its state + serializer.deserialize({ name: "my-package-1", version: "1.0.0", bin: "bin.js", scripts: { "my-script": "echo 'hello world'" }, dependencies: { "my-dependency": "dont-touch-this#1.0.0" }, - devDependencies: { "my-package-2": "1.0.0" }, - peerDependencies: { "my-package-3": "1.0.0" } - }); + devDependencies: { "my-package-2": "bbb#1.0.0" }, + peerDependencies: { "my-package-3": "ccc#1.0.0" } + }) + // the preserved prefixes should be written back expect(serializer.serialize({ name: "my-package-1", version: "1.0.0", From 03dca0d8f5fdfe0edb720ed17c6ee88fbcd8b7b7 Mon Sep 17 00:00:00 2001 From: gustaff-weldon Date: Thu, 5 Oct 2017 14:17:55 +0200 Subject: [PATCH 3/4] Package graph resolution moved to command; Package cleanup --- src/Command.js | 25 ++++++++++--- src/Package.js | 6 --- src/Repository.js | 40 -------------------- src/UpdatedPackagesCollector.js | 2 +- test/Command.js | 41 +++++++++++++++----- test/Package.js | 66 ++++++++------------------------- test/Repository.js | 26 ------------- 7 files changed, 67 insertions(+), 139 deletions(-) diff --git a/src/Command.js b/src/Command.js index 2e810d027d..23f4fb53d2 100644 --- a/src/Command.js +++ b/src/Command.js @@ -5,11 +5,13 @@ import log from "npmlog"; import ChildProcessUtilities from "./ChildProcessUtilities"; import FileSystemUtilities from "./FileSystemUtilities"; import GitUtilities from "./GitUtilities"; +import GitVersionParser from "./GitVersionParser"; import PackageUtilities from "./PackageUtilities"; import Repository from "./Repository"; import filterFlags from "./utils/filterFlags"; import writeLogFile from "./utils/writeLogFile"; import UpdatedPackagesCollector from "./UpdatedPackagesCollector"; +import VersionSerializer from "./VersionSerializer"; // handle log.success() log.addLevel("success", 3001, { fg: "green", bold: true }); @@ -294,7 +296,8 @@ export default class Command { } runPreparations() { - const { scope, ignore, registry, since } = this.options; + const { rootPath, packageConfigs } = this.repository; + const { scope, ignore, registry, since, useGitVersion, gitVersionPrefix } = this.options; if (scope) { log.info("scope", scope); @@ -309,10 +312,22 @@ export default class Command { } try { - this.repository.buildPackageGraph(); - this.packages = this.repository.packages; - this.packageGraph = this.repository.packageGraph; - this.filteredPackages = PackageUtilities.filterPackages(this.packages, { scope, ignore }); + const versionParser = useGitVersion && new GitVersionParser(gitVersionPrefix); + const packages = PackageUtilities.getPackages({ rootPath, packageConfigs }); + const packageGraph = PackageUtilities.getPackageGraph(packages, false, versionParser); + + if (useGitVersion) { + packages.forEach((pkg) => { + pkg.versionSerializer = new VersionSerializer({ + graphDependencies: packageGraph.get(pkg.name).dependencies, + versionParser + }); + }); + } + + this.packages = packages; + this.packageGraph = packageGraph; + this.filteredPackages = PackageUtilities.filterPackages(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. diff --git a/src/Package.js b/src/Package.js index 74f3bba987..872a2293e8 100644 --- a/src/Package.js +++ b/src/Package.js @@ -62,14 +62,8 @@ export default class Package { } set versionSerializer(versionSerializer) { - const previousSerializer = this._versionSerializer; - this._versionSerializer = versionSerializer; - if (previousSerializer) { - this._package = previousSerializer.serialize(this._package); - } - if (versionSerializer) { this._package = versionSerializer.deserialize(this._package); } diff --git a/src/Repository.js b/src/Repository.js index c82b41c4cb..8be3c4b87c 100644 --- a/src/Repository.js +++ b/src/Repository.js @@ -8,9 +8,6 @@ import semver from "semver"; import dependencyIsSatisfied from "./utils/dependencyIsSatisfied"; import Package from "./Package"; -import PackageUtilities from "./PackageUtilities"; -import GitVersionParser from "./GitVersionParser"; -import VersionSerializer from "./VersionSerializer"; const DEFAULT_PACKAGE_GLOB = "packages/*"; @@ -69,22 +66,6 @@ export default class Repository { .map(parentDir => path.resolve(this.rootPath, parentDir)); } - get packages() { - if (!this._packages) { - this.buildPackageGraph(); - } - - return this._packages; - } - - get packageGraph() { - if (!this._packageGraph) { - this.buildPackageGraph(); - } - - return this._packageGraph; - } - get packageJson() { if (!this._packageJson) { try { @@ -119,27 +100,6 @@ export default class Repository { return this.version === "independent"; } - buildPackageGraph() { - // FIXME: should be destructured from command.options to support CLI flag overrides - const { useGitVersion, gitVersionPrefix } = this.lernaJson; - - const versionParser = useGitVersion && new GitVersionParser(gitVersionPrefix); - const packages = PackageUtilities.getPackages(this); - const packageGraph = PackageUtilities.getPackageGraph(packages, false, versionParser); - - if (useGitVersion) { - packages.forEach((pkg) => { - pkg.versionSerializer = new VersionSerializer({ - graphDependencies: packageGraph.get(pkg.name).dependencies, - versionParser - }); - }); - } - - this._packages = packages; - this._packageGraph = packageGraph; - } - hasDependencyInstalled(depName, version) { log.silly("hasDependencyInstalled", "ROOT", depName, version); diff --git a/src/UpdatedPackagesCollector.js b/src/UpdatedPackagesCollector.js index 53d8f02e8f..08c2c1f2d4 100644 --- a/src/UpdatedPackagesCollector.js +++ b/src/UpdatedPackagesCollector.js @@ -37,7 +37,7 @@ export default class UpdatedPackagesCollector { this.logger = command.logger; this.repository = command.repository; this.packages = command.filteredPackages; - this.packageGraph = command.repository.packageGraph; + this.packageGraph = command.packageGraph; this.options = command.options; } diff --git a/test/Command.js b/test/Command.js index 1e612f8e61..28f09a5a91 100644 --- a/test/Command.js +++ b/test/Command.js @@ -236,22 +236,43 @@ describe("Command", () => { }); describe(".runPreparations()", () => { + let testDir; + + function cli(cmd, ...args) { + return execa(cmd, args, { cwd: testDir }); + } + + function run(opts) { + const cmd = new OkCommand([], opts, testDir); + return cmd.run().then(() => cmd); + } + + describe("get .packages", () => { + + it("returns the list of packages", async () => { + testDir = await initFixture("Command/basic") + const { packages } = await run(); + expect(packages).toEqual([]); + }); + }); + + describe("get .packageGraph", () => { + + it("returns the graph of packages", async () => { + testDir = await initFixture("Command/basic") + const { packageGraph } = await run(); + expect(packageGraph).toBeDefined(); + expect(packageGraph).toHaveProperty("nodes", []); + expect(packageGraph).toHaveProperty("nodesByName", {}); + }); + }); + describe(".filteredPackages", () => { - let testDir; beforeEach(() => initFixture("UpdatedCommand/basic").then((dir) => { testDir = dir; })); - function cli(cmd, ...args) { - return execa(cmd, args, { cwd: testDir }); - } - - function run(opts) { - const cmd = new OkCommand([], opts, testDir); - return cmd.run().then(() => cmd); - } - it("--scope should filter packages", async () => { const { filteredPackages } = await run({ scope: ["package-2", "package-4"] }); expect(filteredPackages.length).toEqual(2); diff --git a/test/Package.js b/test/Package.js index 15fd91f1f6..ed7b12ca30 100644 --- a/test/Package.js +++ b/test/Package.js @@ -116,40 +116,11 @@ describe("Package", () => { expect(mockSerializer.deserialize).toBeCalledWith(pkg._package); expect(mockSerializer.serialize).not.toBeCalled(); }); - - it("should call 'serialize' on old and 'deserialize' on new serializer'", () => { - - const firstMockSerializer = { - serialize: jest.fn((pkg) => pkg), - deserialize: jest.fn((pkg) => pkg) - }; - - const secondMockSerializer = { - serialize: jest.fn((pkg) => pkg), - deserialize: jest.fn((pkg) => pkg) - }; - - pkg.versionSerializer = firstMockSerializer; - - expect(firstMockSerializer.deserialize).toBeCalled(); - expect(firstMockSerializer.deserialize).toBeCalledWith(pkg._package); - expect(firstMockSerializer.serialize).not.toBeCalled(); - - pkg.versionSerializer = secondMockSerializer; - - expect(firstMockSerializer.deserialize).toBeCalled(); - expect(firstMockSerializer.serialize).toBeCalled(); - expect(firstMockSerializer.serialize).toBeCalledWith(pkg._package); - - expect(secondMockSerializer.deserialize).toBeCalled(); - expect(secondMockSerializer.deserialize).toBeCalledWith(pkg._package); - expect(secondMockSerializer.serialize).not.toBeCalled(); - }); }); describe(".toJSON()", () => { - it("should return internal package copy for serialization", () => { + it("should return clone of internal package for serialization", () => { expect(pkg.toJSON()).not.toBe(pkg._package); expect(pkg.toJSON()).toEqual(pkg._package); @@ -160,38 +131,31 @@ describe("Package", () => { }); it("should not change internal package with versionSerializer", () => { + pkg._package.state = "serialized" + const mockSerializer = { serialize: jest.fn((pkg) => { - return Object.assign({}, pkg, { foo: true }); + pkg.state = "serialized" + return pkg; }), deserialize: jest.fn((pkg) => { - const newPkg = Object.assign({}, pkg); - delete newPkg.foo; - return newPkg; + pkg.state = "deserialized" + return pkg }) }; - pkg.versionSerializer = mockSerializer; - - let serializedPackage = pkg.toJSON(); - const pkgPackageClone = Object.assign({}, pkg._package, { foo: true }); - - expect(serializedPackage).not.toEqual(pkg._package); - expect(serializedPackage).toEqual(pkgPackageClone); + const serializedPkg = Object.assign({}, pkg._package, { state: "serialized" }) + const deserializedPkg = Object.assign({}, pkg._package, { state: "deserialized" }) + pkg.versionSerializer = mockSerializer; expect(mockSerializer.deserialize).toBeCalled(); - expect(mockSerializer.serialize).toBeCalled(); - expect(mockSerializer.serialize).toBeCalledWith(pkg._package); - - serializedPackage = pkg.toJSON(); + expect(pkg._package).toEqual(deserializedPkg); - expect(serializedPackage).not.toEqual(pkg._package); - expect(serializedPackage).toEqual(pkgPackageClone); + const serializedResult = pkg.toJSON(); + expect(pkg._package).toEqual(deserializedPkg); + expect(serializedResult).toEqual(serializedPkg); - expect(mockSerializer.deserialize).toBeCalled(); - expect(mockSerializer.serialize).toHaveBeenCalledTimes(2); - expect(mockSerializer.serialize).toBeCalledWith(pkg._package); - expect(mockSerializer.serialize).lastCalledWith(pkg._package); + expect(mockSerializer.serialize).toBeCalled(); }); it("should use versionSerializer.serialize on internal package before return", () => { diff --git a/test/Repository.js b/test/Repository.js index e8cf07bda0..20dd653bd0 100644 --- a/test/Repository.js +++ b/test/Repository.js @@ -142,32 +142,6 @@ describe("Repository", () => { }); }); - describe("get .packages", () => { - it("returns the list of packages", () => { - const repo = new Repository(testDir); - expect(repo.packages).toEqual([]); - }); - - it("caches the initial value", () => { - const repo = new Repository(testDir); - expect(repo.packages).toBe(repo.packages); - }); - }); - - describe("get .packageGraph", () => { - it("returns the graph of packages", () => { - const repo = new Repository(testDir); - expect(repo.packageGraph).toBeDefined(); - expect(repo.packageGraph).toHaveProperty("nodes", []); - expect(repo.packageGraph).toHaveProperty("nodesByName", {}); - }); - - it("caches the initial value", () => { - const repo = new Repository(testDir); - expect(repo.packageGraph).toBe(repo.packageGraph); - }); - }); - describe("get .packageJson", () => { afterEach(() => { readPkg.sync = readPkgSync; From 1aad956953e8e016daeeb8152d501cd932545724 Mon Sep 17 00:00:00 2001 From: gustaff-weldon Date: Thu, 5 Oct 2017 14:54:12 +0200 Subject: [PATCH 4/4] Readme updates --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 12eafd4239..35f1b4342e 100644 --- a/README.md +++ b/README.md @@ -663,8 +663,6 @@ Running `lerna` without arguments will show all commands/options. } }, "packages": ["packages/*"] - "useGitVersion": true, - "gitVersionPrefix": "v" } ``` @@ -674,24 +672,6 @@ Running `lerna` without arguments will show all commands/options. - `commands.bootstrap.ignore`: an array of globs that won't be bootstrapped when running the `lerna bootstrap` command. - `commands.bootstrap.scope`: an array of globs that restricts which packages will be bootstrapped when running the `lerna bootstrap` command. - `packages`: Array of globs to use as package locations. -- `useGitVersion`: a boolean (defaults to `false`) indicating if [git hosted urls](https://github.com/npm/hosted-git-info) should be allowed instead of plain version number. If enabled, Lerna will attempt to extract and save the interpackage dependency versions using git url-aware parser. - 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 `gitVersion` requires `publish` command to be used with `--exact` and is limited to urls with [`committish`](https://docs.npmjs.com/files/package.json#git-urls-as-dependencies) part present. - - Example assuming 2 packages where `my-package-1` depends on `my-package-2`, for which `package.json` of `my-package-1` could be: - ``` - { - name: "my-package-1", - version: "1.0.0", - bin: "bin.js", - dependencies: { "my-package-2": "github:example-user/my-package-2#1.0.0" }, - devDependencies: { "my-dev-dependency": "^1.0.0" }, - peerDependencies: { "my-peer-dependency": "^1.0.0" } - } - ``` -- `gitVersionPrefix`: version prefix string (defaults to 'v') ignored when extracting version number from commitish part of git url. eg. given `github:example-user/my-package-2#v1.0.0` - and `gitVersionPrefix: 'v'` version will be read as `1.0.0`. Ignored if `useGitVersion` is set to `false`. - ### Common `devDependencies` @@ -943,6 +923,63 @@ 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