From ea861d9ca3d5e9cbef5c12668044b074da67aa94 Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Sun, 9 Jun 2019 15:13:35 -0700 Subject: [PATCH] fix(publish): Allow per-leaf subdirectory publishing Thanks @heavypennies for the original implementation. Closes #2109 --- commands/publish/README.md | 13 +++++++ .../publish/__tests__/publish-command.test.js | 25 +++++++++++++ commands/publish/index.js | 12 +++++-- core/package/__tests__/core-package.test.js | 35 +++++++++++++++++++ core/package/index.js | 21 +++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) diff --git a/commands/publish/README.md b/commands/publish/README.md index 7d4b23a538..ac9ce0a10e 100644 --- a/commands/publish/README.md +++ b/commands/publish/README.md @@ -98,6 +98,9 @@ lerna publish --contents dist # publish the "dist" subfolder of every Lerna-managed leaf package ``` +**NOTE:** You should wait until the `postpublish` lifecycle phase (root or leaf) to clean up this generated subdirectory, +as the generated package.json is used during package upload (_after_ `postpack`). + ### `--dist-tag ` ```sh @@ -275,6 +278,16 @@ You can customize the dist-tag on a per-package basis by setting [`tag`](https:/ - Passing [`--dist-tag`](#--dist-tag-tag) will _overwrite_ this value. - This value is _always_ ignored when [`--canary`](#--canary) is passed. +### `publishConfig.directory` + +This _non-standard_ field allows you to customize the published subdirectory just like [`--contents`](#--contents-dir), but on a per-package basis. All other caveats of `--contents` still apply. + +```json + "publishConfig": { + "directory": "dist" + } +``` + ## LifeCycle Events Lerna will run [npm lifecycle scripts](https://docs.npmjs.com/misc/scripts#description) during `lerna publish` in the following order: diff --git a/commands/publish/__tests__/publish-command.test.js b/commands/publish/__tests__/publish-command.test.js index 4ab7db6fe9..12b235af1b 100644 --- a/commands/publish/__tests__/publish-command.test.js +++ b/commands/publish/__tests__/publish-command.test.js @@ -21,6 +21,7 @@ const getNpmUsername = require("../lib/get-npm-username"); const verifyNpmPackageAccess = require("../lib/verify-npm-package-access"); // helpers +const commitChangeToPackage = require("@lerna-test/commit-change-to-package"); const loggingOutput = require("@lerna-test/logging-output"); const initFixture = require("@lerna-test/init-fixture")(__dirname); @@ -262,6 +263,30 @@ Map { }); }); + describe("publishConfig.directory", () => { + it("mimics effect of --contents, but per-package", async () => { + const cwd = await initFixture("lifecycle"); + + await commitChangeToPackage(cwd, "package-1", "chore: setup", { + publishConfig: { + directory: "dist", + }, + }); + + await lernaPublish(cwd)(); + + expect(packDirectory).toHaveBeenCalledWith( + expect.objectContaining({ name: "package-1" }), + expect.stringMatching(/packages\/package-1\/dist$/), + expect.any(Object) + ); + expect(packDirectory).toHaveBeenCalledWith( + expect.objectContaining({ name: "package-2" }), + expect.stringMatching(/packages\/package-2$/), + expect.any(Object) + ); + }); + }); describe("in a cyclical repo", () => { it("should throw an error with --reject-cycles", async () => { expect.assertions(1); diff --git a/commands/publish/index.js b/commands/publish/index.js index e475c1b0ca..624a764d67 100644 --- a/commands/publish/index.js +++ b/commands/publish/index.js @@ -595,7 +595,13 @@ class PublishCommand extends Command { } const { contents } = this.options; - const getLocation = contents ? pkg => path.resolve(pkg.location, contents) : pkg => pkg.location; + + if (contents) { + // globally override directory to publish + for (const pkg of this.packagesToPublish) { + pkg.contents = contents; + } + } const opts = this.conf.snapshot; const mapper = pPipe( @@ -603,8 +609,8 @@ class PublishCommand extends Command { this.options.requireScripts && (pkg => this.execScript(pkg, "prepublish")), pkg => - pulseTillDone(packDirectory(pkg, getLocation(pkg), opts)).then(packed => { - tracker.verbose("packed", pkg.name, path.relative(this.project.rootPath, getLocation(pkg))); + pulseTillDone(packDirectory(pkg, pkg.contents, opts)).then(packed => { + tracker.verbose("packed", pkg.name, path.relative(this.project.rootPath, pkg.contents)); tracker.completeWork(1); // store metadata for use in this.publishPacked() diff --git a/core/package/__tests__/core-package.test.js b/core/package/__tests__/core-package.test.js index a6d1f28301..266174f137 100644 --- a/core/package/__tests__/core-package.test.js +++ b/core/package/__tests__/core-package.test.js @@ -64,6 +64,41 @@ describe("Package", () => { }); }); + describe("get .contents", () => { + it("returns pkg.location by default", () => { + const pkg = factory({ version: "1.0.0" }); + expect(pkg.contents).toBe(path.normalize("/root/path/to/package")); + }); + + it("returns pkg.publishConfig.directory when present", () => { + const pkg = factory({ + version: "1.0.0", + publishConfig: { + directory: "dist", + }, + }); + expect(pkg.contents).toBe(path.normalize("/root/path/to/package/dist")); + }); + + it("returns pkg.location when pkg.publishConfig.directory is not present", () => { + const pkg = factory({ + version: "1.0.0", + publishConfig: { + tag: "next", + }, + }); + expect(pkg.contents).toBe(path.normalize("/root/path/to/package")); + }); + }); + + describe("set .contents", () => { + it("sets pkg.contents to joined value", () => { + const pkg = factory({ version: "1.0.0" }); + pkg.contents = "dist"; + expect(pkg.contents).toBe(path.normalize("/root/path/to/package/dist")); + }); + }); + describe("get .bin", () => { it("should return the bin object", () => { const pkg = factory({ diff --git a/core/package/index.js b/core/package/index.js index f918d64d2a..67b9970737 100644 --- a/core/package/index.js +++ b/core/package/index.js @@ -95,6 +95,27 @@ class Package { this[PKG].version = version; } + get contents() { + // if modified with setter, use that value + if (this._contents) { + return this._contents; + } + + // if provided by pkg.publishConfig.directory value + if (this[PKG].publishConfig && this[PKG].publishConfig.directory) { + return path.join(this.location, this[PKG].publishConfig.directory); + } + + // default to package root + return this.location; + } + + set contents(subDirectory) { + Object.defineProperty(this, "_contents", { + value: path.join(this.location, subDirectory), + }); + } + // "live" collections get dependencies() { return this[PKG].dependencies;