diff --git a/commands/publish/README.md b/commands/publish/README.md index 059416d808..ceb7b9fd35 100644 --- a/commands/publish/README.md +++ b/commands/publish/README.md @@ -65,6 +65,7 @@ This is useful when a previous `lerna publish` failed to publish all packages to - [`--tag-version-prefix`](#--tag-version-prefix) - [`--temp-tag`](#--temp-tag) - [`--yes`](#--yes) +- [`--summary-file `](#--summary-file) ### `--canary` @@ -118,11 +119,10 @@ This option can be used to publish a [`prerelease`](http://carrot.is/coding/npm_ > Note: the `latest` tag is the one that is used when a user runs `npm install my-package`. > To install a different tag, a user can run `npm install my-package@prerelease`. -> ### `--force-publish` -To be used with [`--canary`](#--canary) to publish a canary version of all packages in your monorepo. This flag can be helpful when you need to make canary releases of packages beyond what was changed in the most recent commit. +To be used with [`--canary`](#--canary) to publish a canary version of all packages in your monorepo. This flag can be helpful when you need to make canary releases of packages beyond what was changed in the most recent commit. ``` lerna publish --canary --force-publish @@ -303,6 +303,31 @@ lerna publish --canary --yes When run with this flag, `lerna publish` will skip all confirmation prompts. Useful in [Continuous integration (CI)](https://en.wikipedia.org/wiki/Continuous_integration) to automatically answer the publish confirmation prompt. +### `--summary-file` + +```sh +# Will create a summary file in the root directory, i.e. `./lerna-publish-summary.json` +lerna publish --canary --yes --summary-file +# Will create a summary file in the provided directory, i.e. `./some/other/dir/lerna-publish-summary.json` +lerna publish --canary --yes --summary-file ./some/other/dir + +``` + +When run with this flag, a json summary report will be generated after all packages have been successfully published (see below for an example). + +```json +[ + { + "packageName": "package1", + "version": "v1.0.1-alpha" + }, + { + "packageName": "package2", + "version": "v2.0.1-alpha" + } +] +``` + ## Deprecated Options ### `--no-verify-access` diff --git a/commands/publish/__tests__/publish-command.test.js b/commands/publish/__tests__/publish-command.test.js index 5a4f73d5f1..372185edba 100644 --- a/commands/publish/__tests__/publish-command.test.js +++ b/commands/publish/__tests__/publish-command.test.js @@ -34,6 +34,8 @@ const initFixture = require("@lerna-test/helpers").initFixtureFactory(__dirname) const path = require("path"); const fs = require("fs-extra"); +const fsmain = require("fs"); + // file under test const lernaPublish = require("@lerna-test/helpers").commandRunner(require("../command")); @@ -360,6 +362,51 @@ Map { }); }); + describe("--summary-file", () => { + it("skips creating the summary file", async () => { + const cwd = await initFixture("normal"); + const fsSpy = jest.spyOn(fs, "writeFileSync"); + await lernaPublish(cwd); + + expect(fsSpy).not.toHaveBeenCalled(); + }); + + it("creates the summary file within the provided directory", async () => { + const cwd = await initFixture("normal"); + const fsSpy = jest.spyOn(fsmain, "writeFileSync"); + await lernaPublish(cwd)("--summary-file", "./outputs"); + + const expectedJsonResponse = [ + { packageName: "package-1", version: "1.0.1" }, + { packageName: "package-2", version: "1.0.1" }, + { packageName: "package-3", version: "1.0.1" }, + { packageName: "package-4", version: "1.0.1" }, + ]; + expect(fsSpy).toHaveBeenCalled(); + expect(fsSpy).toHaveBeenCalledWith( + "./outputs/lerna-publish-summary.json", + JSON.stringify(expectedJsonResponse) + ); + }); + + it("creates the summary file at the root when no custom directory is provided", async () => { + const cwd = await initFixture("normal"); + const fsSpy = jest.spyOn(fsmain, "writeFileSync"); + await lernaPublish(cwd)("--summary-file"); + + const expectedJsonResponse = [ + { packageName: "package-1", version: "1.0.1" }, + { packageName: "package-2", version: "1.0.1" }, + { packageName: "package-3", version: "1.0.1" }, + { packageName: "package-4", version: "1.0.1" }, + ]; + expect(fsSpy).toHaveBeenCalled(); + expect(fsSpy).toHaveBeenCalledWith( + "./lerna-publish-summary.json", + JSON.stringify(expectedJsonResponse) + ); + }); + }); describe("--verify-access", () => { it("publishes packages after verifying the user's access to each package", async () => { const testDir = await initFixture("normal"); diff --git a/commands/publish/command.js b/commands/publish/command.js index 2d2273dadc..586c774f71 100644 --- a/commands/publish/command.js +++ b/commands/publish/command.js @@ -110,6 +110,12 @@ exports.builder = (yargs) => { describe: "Verify package read-write access for current npm user.", type: "boolean", }, + "summary-file": { + // generate lerna publish json output. + describe: + "Generate a json summary report after all packages have been successfully published, you can pass an optional path for where to save the file.", + type: "string", + }, }; composeVersionOptions(yargs); diff --git a/commands/publish/index.js b/commands/publish/index.js index 2892169dd9..c36fff5531 100644 --- a/commands/publish/index.js +++ b/commands/publish/index.js @@ -1,6 +1,7 @@ "use strict"; const os = require("os"); +const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const pMap = require("p-map"); @@ -256,10 +257,31 @@ class PublishCommand extends Command { return chain.then(() => { const count = this.packagesToPublish.length; - const message = this.packagesToPublish.map((pkg) => ` - ${pkg.name}@${pkg.version}`); output("Successfully published:"); - output(message.join(os.EOL)); + + if (this.options.summaryFile !== undefined) { + // create a json object and output it to a file location. + const filePath = this.options.summaryFile + ? `${this.options.summaryFile}/lerna-publish-summary.json` + : "./lerna-publish-summary.json"; + const jsonObject = this.packagesToPublish.map((pkg) => { + return { + packageName: pkg.name, + version: pkg.version, + }; + }); + output(jsonObject); + try { + fs.writeFileSync(filePath, JSON.stringify(jsonObject)); + output("Publish summary created: ", filePath); + } catch (error) { + output("Failed to create the summary report", error); + } + } else { + const message = this.packagesToPublish.map((pkg) => ` - ${pkg.name}@${pkg.version}`); + output(message.join(os.EOL)); + } this.logger.success("published", "%d %s", count, count === 1 ? "package" : "packages"); }); diff --git a/core/lerna/schemas/lerna-schema.json b/core/lerna/schemas/lerna-schema.json index cdcd57e8fc..2fd7e24cce 100644 --- a/core/lerna/schemas/lerna-schema.json +++ b/core/lerna/schemas/lerna-schema.json @@ -874,6 +874,9 @@ }, "continueIfNoMatch": { "$ref": "#/$defs/filters/continueIfNoMatch" + }, + "summaryFile": { + "$ref": "#/$defs/filters/summaryFile" } } }, @@ -1487,6 +1490,10 @@ "continueIfNoMatch": { "type": "boolean", "description": "Don't fail if no package is matched." + }, + "summaryFile": { + "type": "string", + "description": "Generate a json summary report after all packages have been successfully published, you can pass an optional path for where to save the file." } }, "commandOptions": {