From a6f99960c93cb7a39f6c80934a801cb2ae0930f7 Mon Sep 17 00:00:00 2001 From: Lars Kappert Date: Wed, 15 May 2019 19:58:37 +0200 Subject: [PATCH] Update plugin docs --- README.md | 3 + docs/plugins/README.md | 240 +++++++++++++++++++++++++++++++---------- 2 files changed, 184 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index f6ae0f43..2b124a7c 100644 --- a/README.md +++ b/README.md @@ -551,6 +551,9 @@ name repo.remote, repo.protocol, repo.host, repo.owner, repo.repository, repo.project ``` +The `version` variable is available from `beforeBump` (it's only available in `beforeStart` if there is no need to +prompt the user for the next version). + ## Plugins Since v11, release-it can be extended in many, many ways. Please head over to [plugins](docs/plugins/README.md) for more diff --git a/docs/plugins/README.md b/docs/plugins/README.md index 176565ad..d12c4a27 100644 --- a/docs/plugins/README.md +++ b/docs/plugins/README.md @@ -4,6 +4,15 @@ Since v11, release-it has evolved into a pluggable task runner. It was previousl GitHub/GitLab releases, and npm packages, but this is no longer the case. As long as can either be written in Node.js, or executed from the shell, it can be integrated in the release-it process. +## Contents + +- [Overview](#overview) +- [Using a plugin](#using-a-plugin) +- [Creating a plugin](#creating-a-plugin) +- [Available & example plugins](#available--example-plugins) + +### Overview + Plugins allow additional and custom actions in the release process, such as: - Publish the package to any registry (this is language-agnostic, e.g. Ruby, Python, ...). @@ -48,7 +57,7 @@ This example uses the `release-it-bar` module and is configured in `package.json }, "plugins": { "release-it-bar": { - "option": "value" + "key": "value" } } } @@ -61,7 +70,7 @@ Alternatively, here's a `foo` plugin as a local module: { "plugins": { "./scripts/foo.js": { - "option": "value" + "key": "value" } } } @@ -71,36 +80,17 @@ Alternatively, here's a `foo` plugin as a local module: To create a plugin, extend the `Plugin` class, and implement one or more release-cycle methods. See the "interface" below (where none of the methods is required). Any of these methods can be `async` (except for -`getIncrementedVersionSync`). If you're interested in writing a plugin, please take a look at +`getIncrementedVersionCI`). If you're interested in writing a plugin, please take a look at [the `runTasks` test helper](https://github.com/release-it/release-it/blob/master/test/util/index.js#L33-L54), to see how a plugin is integrated in the release process. Also see the [base `Plugin` class](https://github.com/release-it/release-it/blob/master/lib/plugin/Plugin.js) where the plugin should be extended from. -## Interface - -```javascript -class Plugin { - static isEnabled() {} - static disablePlugin() {} - init() {} - getName() {} - getLatestVersion() {} - getIncrementedVersionSync() {} - getIncrementedVersion() {} - beforeBump() {} - bump() {} - beforeRelease() {} - release() {} - afterRelease(); -} -``` - ### Example This minimal example reads the current version from a `VERSION` file, and bumps it once the new version is known. -```javascript +```js class MyPlugin extends Plugin { getLatestVersion() { return fs.readFileSync('./VERSION').trim(); @@ -124,112 +114,244 @@ In the context of the whole release process, this may also be relevant for other Since order matters here, the release-cycle methods of internal plugins are executed _after_ other plugins. Except for the `release` and `afterRelease` methods at the end. -## Conditionally enable the plugin +## API + +- [Interface overview](#interface-overview) +- [Static methods](#static-methods) +- [Release-cycle methods](#release-cycle-methods) +- [Getter methods](#getter-methods) +- [Helper methods](#helper-methods) +- [Execution Order](#execution-order) + +### Interface overview + +```js +class Plugin { + static isEnabled() {} + static disablePlugin() {} + getInitialOptions() {} + init() {} + getName() {} + getLatestVersion() {} + getIncrementedVersionCI() {} + getIncrementedVersion() {} + beforeBump() {} + bump() {} + beforeRelease() {} + release() {} + afterRelease(); +} +``` + +Note that any of the methods in the plugin can be `async` except for `disablePlugin()`. In the method signatures below +this is implied everywhere (e.g. `→ Boolean` means it should return a boolean, or a promise resolving to a boolean). + +### Static methods + +#### isEnabled() → Boolean By default, a plugin is always enabled. Override the static `isEnabled` method to enable the plugin based on specific conditions, such as plugin configuration or the presence of a file or directory. -## Disable core plugins +#### disablePlugin() → String In case a plugin replaces a core plugin, it should be disabled by returning the name of the core plugin. Return a string (or array of strings) containing the plugin name (one or more of `version`, `git`, `github`, `gitlab`, `npm`). -## Release-cycle methods +### Release-cycle methods -Implement release-cycle methods to execute logic during the release process. +Implement release-cycle methods to execute logic during the release process. All methods are run async, so `async/await` +can be used freely. -### `init` +#### init() Implement `init` to validate prequisites and gather application or package details such as the current version. The `init` method for all plugins are run async in parallel. -### `beforeBump` +#### beforeBump() Implement `beforeBump` to prepare things, gather and/or output interesting information for the user, such as a changelog or other details to help the user confirm the release will be executed properly. -### `bump` +#### bump(version) Implement `bump` to increment the version in manifests or other files containing the version of the application or -package (e.g. `package.json` for Node.js modules). This method receives the incremented `version` as the first and only -argument. +package (e.g. `package.json` for Node.js modules). -### `beforeRelease` +#### beforeRelease() -Implement `beforeRelease` to perform tasks that should happen after the bump, and to stage things before the `release`. +Implement `beforeRelease` to perform tasks that should happen after the bump, and stage things before the `release`. -### `release` +#### release() Implement `release` for the main flow of the plugin. This is where the "steps" should be declared (see [step](#step) in class API), resulting in prompts (interactive) or spinners (non-interactive) that will execute tasks for confirmed steps. -### `afterRelease` +#### afterRelease() Implement `afterRelease` to provide details about a successful release, e.g. a link to the release page. ## Getter methods -### `getName` +Implement any of the following methods to be ahead of any core plugin and use that during the release process instead. + +#### getName() → String Provide the name of the package being released. -### `getLatestVersion` +#### getLatestVersion() → SemVer Implement `getLatestVersion` and return the latest version prior to the current release, so release-it can determine the next version. -### `getIncrementedVersionSync` +#### getInitialOptions(options, pluginName) → Object + +By default, every plugin receives the options configured in `options[pluginName]`. For instance, the core `npm` plugin +receives the options under the `npm` property in the configuration. Other plugins receive the options as they are +configured in the `plugins` section. However, if a plugin requires additional options from other plugins, the +`getInitialOptions` is useful: + +```js +getInitialOptions(options, pluginName) { + return Object.assign({}, options[pluginName], { + tagName: options.git.tagName, + }); +} +``` + +#### Internal getter methods + +The following methods are mostly internal methods that normally should not be implemented in any plugin, but in rare +cases this might be useful. + +##### getIncrementedVersionCI({ latestVersion, increment, isPreRelease, preReleaseId }) → SemVer -Implement `getIncrementedVersionSync` to provide the next version (must be synchronous). This should normally not be -implemented in a custom plugin, but left to the internal `version` plugin. +Implement `getIncrementedVersionCI` to provide the next version without prompting the user. I.e. determine the next +version based on the provided values. This method exists to provide the next `version` to other elements of the release +process early on, such as `scripts.beforeStart` and the introduction text. -### `getIncrementedVersion` +##### getIncrementedVersion({ latestVersion, increment, isPreRelease, preReleaseId }) → SemVer -Implement `getIncrementedVersion` to provide the next version (can be async). This should normally not be implemented in -a custom plugin, but left to the internal `version` plugin. +Implement `getIncrementedVersion` to provide the next version, and prompt the user if this can't be determined +automatically. -## Class API +### Helper methods The `Plugin` class exposes helper methods, here's an overview: -### `setContext` +#### this.setContext(context) → void -Set additional data during runtime. +Set additional data local to the plugin during runtime. -### `getContext` +#### this.getContext() → Object -Get the plugin options, plus additional runtime data set with `setContext`. +Get the plugin options extended with additional runtime data set with `setContext`. -### `registerPrompts` +#### this.registerPrompts(...prompts) → void Register one or more prompts and allow the user to confirm actions or provide details. -### `exec` +A prompt object looks like this: + +```js +{ + type: 'confirm', + name: 'my-prompt', + message: 'Are you sure?' +} +``` + +Under the hood, [Inquirer.js](https://github.com/SBoudrias/Inquirer.js) is used. See +[Inquirer.js/#objects](https://github.com/SBoudrias/Inquirer.js/#objects) for more details. + +#### this.step() → Promise + +Display a prompt or a spinner during the `release` release-cycle method. This automatically shows a prompt if +interactive, or a spinner in CI (non-interactive) mode. + +```js +await this.step({ + enabled: true, + task: () => this.doTask(), + label: 'Doing task', + prompt: 'my-prompt' +}); +``` + +If the prompt receives a "No" from the user, the `task` callback is not executed. + +#### this.exec() → Promise Execute commands in the child process (i.e. the shell). This is used extensively by release-it to execute `git` and `npm` commands. Be aware of cross-OS compatibility. Use template variables to render replacements. For instance, the command `git log ${latestTag}...HEAD` becomes -`git log v1.2.3...HEAD` before being executed. +`git log v1.2.3...HEAD` before being executed. The replacements are all configuration options (with the default values +in [conf/release-it.json](conf/release-it.json)), plus the following variables: -### `step` +``` +version +latestVersion +latestTag +changelog +name +repo.remote, repo.protocol, repo.host, repo.owner, repo.repository, repo.project +``` + +All variables are available from `beforeBump` (i.e. not in `init`). -Display a prompt or a spinner during the `run` release-cycle method. A prompt if interactive, or a spinner in -non-interactive mode. +Note that in dry runs, commands are **not** executed as they may contain write operations. Read-only operations should +add the `write: false` option to run in dry mode: + +```js +this.exec('git log', { options: { write: false } }); +``` -### `debug` +#### this.debug() → void Insert `this.debug(...)` statements to log interesting details when `DEBUG=release-it:* release-it ...` is used. The output is namespaced automatically (e.g. `release-it:foo My log output`). -### `log` +#### this.log() → void + +Use `this.log.[verbose|warn|error|log|info]` to log and inform the user about what's going on in the release process. + +### Execution order + +Assuming there are two plugins configured, "PluginA" and "PluginB": + +```json +{ + "plugins": { + "PluginA": {}, + "PluginB": {} + } +} +``` + +First, the `init` method is executed for `PluginA`, then `PluginB`, and then the core plugins: `npm` → `gitlab` → +`github` → `git` → `version`. + +Then the same for `getName` and `getLatestVersion`. For these getter methods, the value of the first plugin that returns +something is used throughout the release process. This allows a plugin to be ahead of core plugins. + +After this, the `beforeBump`, `bump` and `beforeRelease` methods are executed for each plugin in the same order. + +And finally, for `release` and `afterRelease` the order is reversed, so that tasks can be executed after release-it core +plugins are done. Examples include to trigger deployment hooks, or send a notification to indicate a successfull release +or deployment. -Use `this.log.[verbose|warn|error]` to log and inform the user about what's going on in the release process. +Here's an example. If the `npm` plugin is enabled, `npm.getName()` is the first plugin/method that returns something +(the `name` from `package.json` is used in this case). If this plugin is not enabled, `getName` of the next plugin is +invoked (e.g. the `git` plugin will infer the name from the remote Git url), etcetera. However, the methods of custom +plugins are invoked first, so they can override the `name`, `latestVersion`, `repo`, and `changelog` values that would +otherwise be taken from the core plugins. -### Examples +## Available & example plugins +- All packages tagged with [`"release-it-plugin"` on npm](https://www.npmjs.com/search?q=keywords:release-it-plugin). - [@release-it/conventional-changelog](https://github.com/release-it/conventional-changelog) - uses `conventional-recommended-bump` in `getIncrementedVersion()` and `conventional-changelog` in `beforeRelease` to generate the changelog. Optionally updates `CHANGELOG.md`.