diff --git a/commands/publish/README.md b/commands/publish/README.md index a15875cce99..10ae41d6c85 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` @@ -120,11 +121,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 @@ -305,6 +305,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 5a4f73d5f10..372185edba6 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 2d2273dadcb..586c774f717 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 9ca6b16d404..6ad07ac12ef 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"); @@ -261,10 +262,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 1a6416a9c8c..1b378010565 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" } } }, @@ -1478,6 +1481,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": {