generated from boneskull/boneskull-template
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
134 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--- | ||
title: Using Plugins | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
title: General Usage | ||
description: A general usage guide of reasonable depth | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,120 @@ | ||
--- | ||
title: Plugin Authoring | ||
title: Writing Plugins | ||
description: How to create your own plugins for `midnight-smoker` | ||
--- | ||
|
||
This guide describes how to create your own plugins for `midnight-smoker`. | ||
|
||
## Who This Guide is For | ||
|
||
This guide is for you if you want to: | ||
If you want to do any of these things: | ||
|
||
- Create custom rules (automated checks) to run against your packages | ||
- Create custom rules (automated lint checks) to run against your packages | ||
- Create a handler for an unsupported package manager | ||
- Create a custom reporter | ||
- Create a different strategy for invoking custom scripts (parallel, distributed, avoid package managers entirely, etc.) and emitting events for the results | ||
- Create middleware to control how automated checks are run (use-case here is iffy, but it's there) | ||
- Create middleware to control how rules get run (use-case here is iffy, but it's there) | ||
- Create a custom strategy for invoking package managers (alternative to `corepack`?) | ||
- (FUTURE) Create a custom configuration loader | ||
- (FUTURE) Custom CLI options & commands | ||
- (FUTURE) Custom configuration for all [components](#about-components) | ||
- (FUTURE) Custom configuration for all [components](#whats-a-component) | ||
|
||
...then this guide is for _you_. | ||
|
||
## What is a Plugin? | ||
|
||
A plugin is a module (it needn't be an entire package, but that's fine too) with a _named export_ `plugin` which conforms to the following interface: | ||
A plugin is a CJS or ESM module (it needn't be an entire package, but that's fine too) with a _named export_ `plugin` which conforms to the following interface: | ||
|
||
```typescript | ||
export type PluginFactory = (api: PluginAPI) => void | Promise<void>; | ||
``` | ||
|
||
:::note | ||
The implementation of a `PluginFactory` is expected to use the `PluginAPI` object to register one or more [_components_](#whats-a-component). | ||
|
||
### Plugin Exports | ||
|
||
Instead of a named `plugin` export, a _default_ export may be provided. In the case of CJS, this would correspond to `module.exports = ...`, and the equivalent to a named `plugin` export would be `exports.plugin = ...`. | ||
|
||
:::tip[Use Types] | ||
|
||
Alternatively, a default export may be provided. In the case of CJS, this would correspond to `module.exports = ...`; a named `plugin` export would be `exports.plugin = ...` or `module.exports.plugin = ...`. | ||
It's _strongly recommended_ to use TypeScript (either TS proper or JSDoc) when writing plugins, as it will help you navigate the `PluginAPI` object. | ||
|
||
::: | ||
|
||
The `PluginAPI` object will be your main interface into `midnight-smoker`. You _shouldn't_ need to pull anything other than types (if using TypeScript) from the `midnight-smoker` package itself. | ||
Both ESM and CJS plugins are supported. | ||
|
||
It's _strongly recommended_ to use TypeScript (or type-safe JS) when writing plugins, as it will help you navigate the `PluginAPI` object. | ||
### What's a Component? | ||
|
||
Next, we'll look at the `PluginAPI` object. | ||
`midnight-smoker` is ~~overengineered~~ modular, and most functionality is provided by _components_. A component is typically a function or object implementing one of a handful of interfaces defined by `midnight-smoker`. | ||
|
||
## The Plugin API | ||
A component may be one of: | ||
|
||
- A lint **rule** — analyzes the installed package artifact | ||
- A **reporter** — emits output | ||
- A **package manager** adapter — logic for packing, installing, and running custom scripts | ||
- A **script runner** — orchestrates custom script runs | ||
- A **rule runner** — invokes rules (dubious use-case) | ||
- An **executor** — spawns processes | ||
|
||
:::note[Mea Culpa] | ||
|
||
The `PluginAPI` object is the main interface into `midnight-smoker`. Its main purpose is to provide functions which a plugin will call to define _Components_. | ||
A "component" means something entirely different in the context of `midnight-smoker` than it does in a web context. Please [send suggestions](https://github.com/boneskull/midnight-smoker/issues/new) for a better word. | ||
|
||
::: | ||
|
||
### About Components | ||
Next, let's [take a closer look](#the-plugin-api) at the `PluginAPI` object by creating a custom rule. | ||
|
||
A plugin will contain one or more _Components_. A Component is an implementation of one of the following: | ||
## The Plugin API | ||
|
||
- A _package manager_ | ||
- A _rule_ | ||
- A custom _script runner_ | ||
- A custom _reporter_ (emits output for user) or _listener_ (listens for events, but doesn't necessarily output anything) | ||
- A custom _rule runner_ (runs automated checks) | ||
- A custom _executor_ (invokes package managers) | ||
The `PluginAPI` object is a plugin's main interface into `midnight-smoker`. It aims to provide _all_ the functionality that a plugin implementation needs to do its job. | ||
|
||
Since the most common use case will probably be a custom rule, let's take a look at that first. | ||
|
||
### Rules | ||
### Writing a Rule | ||
|
||
Much like [ESLint](https://eslint.org), a custom rule allows the implementor to define a "check". Instead of running against an AST, `midnight-smoker` runs against an _installed package artifact._ | ||
|
||
Again like ESLint, a rule can define its own options. These options are user-configurable via a [config file](/reference/config). | ||
|
||
Much like [ESLint](https://eslint.org), a custom rule allows the author to define a "check" to run against a package. The rule can have its own set of options, and is user-configurable via a [config file](./README.md#config-files). Rules can be considered "errors" or "warnings" per the user's preference. The user may also disable a rule entirely. | ||
To create a rule, we'll use the `api.defineRule()` function. Below is an example plugin. This plugin defines a trivial rule which asserts the `package.json` of the package under test always has a `private` property set to `true`. | ||
|
||
To define a rule, we'll use the `api.defineRule()` function. Below is an example of a trivial rule which asserts the `package.json` of a package always has a `private` property set to `true`: | ||
<!-- TODO: must make a new example --> | ||
|
||
<!-- prettier-ignore --> | ||
```ts file=../../../../../../example/plugin-rule/index.ts title="plugin-rule.ts" | ||
``` | ||
|
||
To load your plugin, you'll need to tell `midnight-smoker` it exists. To do so, provide the `--plugin plugin-rule.ts` option to the `smoker` CLI, or add this to your config: | ||
**`midnight-smoker` does not automatically detect plugins.** To use a plugin, it _must_ be referenced in the [config file](/reference/config) _or_ the [CLI](/reference/cli) via `--plugin ./plugin-rule.ts`. | ||
|
||
```json | ||
Assuming `plugin-rule.ts` is in the same directory as `smoker.config.json`, here's an example config file: | ||
|
||
```json title="smoker.config.json" | ||
{ | ||
"plugins": ["./path/to/plugin-rule.ts"] | ||
"plugin": ["./plugin-rule.ts"] | ||
} | ||
``` | ||
|
||
Now, when `midnight-smoker` runs its automated checks, it will run the `no-public-pkgs` rule along with the set of builtin rules. | ||
Now, when `midnight-smoker` lints, it will run the `no-public-pkgs` rule along with the set of builtin rules. | ||
|
||
:::note[Scoped Rule Names] | ||
|
||
The "real" name of the rule will be scoped to the plugin's name. The plugin's name is derived from the closest `package.json`. | ||
The "full" name of the rule (its "rule ID") will be _scoped_ to the plugin's name. For example, if the plugin is `foo` and the rule is `bar`, the rule's ID will be `foo/bar`. This is how it must be referenced in a config file. | ||
|
||
The plugin's name is derived from the _closest ancestor `package.json`_ of the plugin's entry point. | ||
|
||
Built-in rules' rule IDs do not have a scope. | ||
|
||
::: | ||
|
||
For more information about rule-related functions and types within the `PluginAPI` object, see the [reference documentation](./reference.md). | ||
Let's assume the name of our plugin is `plugin-rule`. We can test our options by adding some lines to our config: | ||
|
||
```diff lang=json title="smoker.config.json" | ||
{ | ||
"plugins": ["./path/to/plugin-rule.ts"], | ||
+ "rules": { | ||
+ "plugin-rule/no-public-pkgs": { | ||
+ "ignore": ["my-public-pkg"] | ||
+ } | ||
+ } | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters