Writing Plugins
If you've ever written a webpack
plugin it's a lot like that.
A plugin definition is:
- a name for the plugin, should match the name of the package (REQUIRED)
- a class the has an
apply
function where a plugin hooks into various functions in auto (REQUIRED)
You can write a plugin directly in TypeScript with no build step:
import { Auto, IPlugin } from "auto"; export default class TestPlugin implements IPlugin { name = "test"; /** Tap into auto plugin points. */ apply(auto: Auto) {} }
import { Auto, IPlugin } from "auto"; export default class TestPlugin implements IPlugin { name = "test"; /** Tap into auto plugin points. */ apply(auto: Auto) {} }
Or in JavaScript:
module.exports = class TestPlugin { constructor() { this.name = "test"; } /** * Tap into auto plugin points. * @param {import('@auto-it/core').default} auto */ apply(auto) {} };
module.exports = class TestPlugin { constructor() { this.name = "test"; } /** * Tap into auto plugin points. * @param {import('@auto-it/core').default} auto */ apply(auto) {} };
Hooks
Plugins work by hooking into various actions that auto
has to do in order to facilitate a release or interact with your GitHub repo.
Adding Options
Most plugins will find the need to some some options from the user.
The constructor of the plugin gets access to the options passed in the .autorc
.
import { Auto, IPlugin } from "@auto-it/core"; interface TestPluginOptions { someOption?: boolean; } export default class TestPlugin implements IPlugin { /** The name of the plugin */ name = "test"; /** The options of the plugin */ readonly options: TestPluginOptions; /** Initialize the plugin with it's options */ constructor(options: TestPluginOptions) { this.options = options; } }
import { Auto, IPlugin } from "@auto-it/core"; interface TestPluginOptions { someOption?: boolean; } export default class TestPlugin implements IPlugin { /** The name of the plugin */ name = "test"; /** The options of the plugin */ readonly options: TestPluginOptions; /** Initialize the plugin with it's options */ constructor(options: TestPluginOptions) { this.options = options; } }
Validation
To get validate of the options passed to plugins, auto
uses io-ts and exports a utility function to validate the structured produced by io-ts
.
It lets you defined your interfaces in JavaScript and easily convert them to TypeScript.
This means it's super simple to have type-safe code and runtime validation checking!
First install the following:
npm i --save io-ts fp-ts # or yarn add io-ts fp-ts
npm i --save io-ts fp-ts # or yarn add io-ts fp-ts
Then convert your options interface to the equivalent io-ts
structure.
import * as t from "io-ts"; const pluginOptions = t.partial({ someOption: t.boolean, }); export type TestPluginOptions = t.TypeOf<typeof pluginOptions>;
import * as t from "io-ts"; const pluginOptions = t.partial({ someOption: t.boolean, }); export type TestPluginOptions = t.TypeOf<typeof pluginOptions>;
Then tap into the validateConfig
hook and use the validatePluginConfiguration
utility.
import { validatePluginConfiguration } from "@auto-it/core"; export default class MyPlugin implements IPlugin { // ... apply(auto: Auto) { auto.hooks.validateConfig.tapPromise(this.name, async (name, options) => { if (name === this.name || name === `auto-plugin-${this.name}`) { return validatePluginConfiguration(this.name, pluginOptions, options); } }); } }
import { validatePluginConfiguration } from "@auto-it/core"; export default class MyPlugin implements IPlugin { // ... apply(auto: Auto) { auto.hooks.validateConfig.tapPromise(this.name, async (name, options) => { if (name === this.name || name === `auto-plugin-${this.name}`) { return validatePluginConfiguration(this.name, pluginOptions, options); } }); } }
And that's it!
Now auto
will validate your plugins configuration before running.
Example Plugin - NPM (simple)
To create a plugin simply make a class with an apply
method and tap into the hooks you need.
import * as fs from "fs"; import { promisify } from "util"; import { IAutoHooks, Auto, SEMVER, execPromise } from "auto"; import getConfigFromPackageJson from "./package-config"; const readFile = promisify(fs.readFile); export default class NPMPlugin { public apply(auto: Auto) { auto.hooks.getAuthor.tapPromise("NPM", async () => { const { author } = JSON.parse(await readFile("package.json", "utf-8")); if (author) { auto.logger.log.info("NPM: Got author information from package.json"); return author; } }); auto.hooks.getPreviousVersion.tapPromise("NPM", async () => { const { version } = JSON.parse(await readFile("package.json", "utf-8")); auto.logger.log.info( "NPM: Got previous version from package.json - ", version ); if (version) { return auto.prefixRelease( JSON.parse(await readFile("package.json", "utf-8")).version ); } }); auto.hooks.getRepository.tapPromise("NPM", async () => { auto.logger.log.info("NPM: getting repo information from package.json"); return getConfigFromPackageJson(); }); auto.hooks.publish.tapPromise("NPM", async (version: SEMVER) => { await execPromise("npm", [ "version", version, "-m", "Bump version to: %s [skip ci]", ]); await execPromise("npm", ["publish"]); await execPromise("git", [ "push", "--follow-tags", "--set-upstream", auto.remote, "$branch", ]); }); } }
import * as fs from "fs"; import { promisify } from "util"; import { IAutoHooks, Auto, SEMVER, execPromise } from "auto"; import getConfigFromPackageJson from "./package-config"; const readFile = promisify(fs.readFile); export default class NPMPlugin { public apply(auto: Auto) { auto.hooks.getAuthor.tapPromise("NPM", async () => { const { author } = JSON.parse(await readFile("package.json", "utf-8")); if (author) { auto.logger.log.info("NPM: Got author information from package.json"); return author; } }); auto.hooks.getPreviousVersion.tapPromise("NPM", async () => { const { version } = JSON.parse(await readFile("package.json", "utf-8")); auto.logger.log.info( "NPM: Got previous version from package.json - ", version ); if (version) { return auto.prefixRelease( JSON.parse(await readFile("package.json", "utf-8")).version ); } }); auto.hooks.getRepository.tapPromise("NPM", async () => { auto.logger.log.info("NPM: getting repo information from package.json"); return getConfigFromPackageJson(); }); auto.hooks.publish.tapPromise("NPM", async (version: SEMVER) => { await execPromise("npm", [ "version", version, "-m", "Bump version to: %s [skip ci]", ]); await execPromise("npm", ["publish"]); await execPromise("git", [ "push", "--follow-tags", "--set-upstream", auto.remote, "$branch", ]); }); } }