Skip to content

Commit

Permalink
fix(publish): Allow per-leaf subdirectory publishing
Browse files Browse the repository at this point in the history
Thanks @heavypennies for the original implementation.

Closes #2109
  • Loading branch information
evocateur committed Jun 9, 2019
1 parent c2fb639 commit ea861d9
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 3 deletions.
13 changes: 13 additions & 0 deletions commands/publish/README.md
Expand Up @@ -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 <tag>`

```sh
Expand Down Expand Up @@ -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:
Expand Down
25 changes: 25 additions & 0 deletions commands/publish/__tests__/publish-command.test.js
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
12 changes: 9 additions & 3 deletions commands/publish/index.js
Expand Up @@ -595,16 +595,22 @@ 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(
[
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()
Expand Down
35 changes: 35 additions & 0 deletions core/package/__tests__/core-package.test.js
Expand Up @@ -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({
Expand Down
21 changes: 21 additions & 0 deletions core/package/index.js
Expand Up @@ -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;
Expand Down

0 comments on commit ea861d9

Please sign in to comment.