Skip to content

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.

Read more about using hooks

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",
      ]);
    });
  }
}

Read more about creating publishing plugins.