Skip to content

Commit

Permalink
Update plugin docs
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed May 24, 2019
1 parent 2433ef7 commit a6f9996
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 59 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down
240 changes: 181 additions & 59 deletions docs/plugins/README.md
Expand Up @@ -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, ...).
Expand Down Expand Up @@ -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"
}
}
}
Expand All @@ -61,7 +70,7 @@ Alternatively, here's a `foo` plugin as a local module:
{
"plugins": {
"./scripts/foo.js": {
"option": "value"
"key": "value"
}
}
}
Expand All @@ -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();
Expand All @@ -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`.
Expand Down

0 comments on commit a6f9996

Please sign in to comment.