Skip to content

Commit

Permalink
docs: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Jan 1, 2024
1 parent a7b2ede commit b6cab35
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 93 deletions.
19 changes: 13 additions & 6 deletions packages/docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,22 @@ export default defineConfig({
sidebar: [
{
label: 'Guides',
autogenerate: {
directory: 'guides',
},
items: [
{label: 'Getting Started', link: '/guides/getting-started'},
{label: 'General Usage', link: '/guides/usage'},
{label: 'Configuration', link: '/guides/config'},
{label: 'Using Custom Scripts', link: '/guides/custom-scripts'},
{label: 'Using Plugins', link: '/guides/plugins'},
{label: 'Writing Plugins', link: '/guides/writing-plugins'},
],
},
{
label: 'Reference',
autogenerate: {
directory: 'reference',
},
items: [
{label: 'CLI', link: '/reference/cli'},
{label: 'Configuration', link: '/reference/config'},
{label: 'Rules', link: '/reference/rules'},
],
},
],
components: {
Expand Down
2 changes: 0 additions & 2 deletions packages/docs/src/content/docs/guides/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,3 @@ Assuming your package isn't all jacked up, you should see output akin to:
✔ Successfully ran 1 script
✔ Lovey-dovey! 💖
```

Next, read about the [command-line interface](/guides/cli).
3 changes: 3 additions & 0 deletions packages/docs/src/content/docs/guides/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
title: Using Plugins
---
4 changes: 4 additions & 0 deletions packages/docs/src/content/docs/guides/usage.md
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
---
97 changes: 67 additions & 30 deletions packages/docs/src/content/docs/guides/writing-plugins.md
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"]
+ }
+ }
}
```
52 changes: 26 additions & 26 deletions packages/docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@ description: midnight-smoker CLI reference

_Not_ `midnight-smoker`. That is too long.

## Conventions

### Array-Type Options

Options which accept multiple values (e.g., [`--workspace`](-w---workspace)) may be provided multiple times. **They cannot be provided as comma-delimited values**.

:::tip[Use Short Aliases for Multiple Values]

It's convenient to use short options (`-w` instead of `--workspace`) when providing multiple values for options. Example:

```shell
smoker run -w=foo -w=bar -w=baz quux
```

:::

### Object-Type Options

Any option more complicated than an array of strings is **unsupported on the CLI** and must be provided via a [config file](/reference/config).

### Exit Codes

`smoker` will exit with code `0` if linting passes without an "error" severity issue and if all custom scripts exit with code `0`, and `1` otherwise.

`smoker` will exit with code `1` if packing or installation of any package fails.

### Default Behavior

When executed without a command, `smoker`'s default behavior is to [`lint`](#command-lint).
Expand Down Expand Up @@ -245,32 +271,6 @@ Do not fail if a workspace's`package.json` does not contain the script(s) provid

Only applicable when used with [`--all`](#--all) or [`--workspace`](#-w---workspace).

## Conventions

### Array-Type Options

Options which accept multiple values (e.g., [`--workspace`](-w---workspace)) may be provided multiple times. **They cannot be provided as comma-delimited values**.

:::tip[Use Short Aliases for Multiple Values]

It's convenient to use short options (`-w` instead of `--workspace`) when providing multiple values for options. Example:

```shell
smoker run -w=foo -w=bar -w=baz quux
```

:::

### Object-Type Options

Any option more complicated than an array of strings is **unsupported on the CLI** and must be provided via a [config file](/reference/config).

### Exit Codes

`smoker` will exit with code `0` if linting passes without an "error" severity issue and if all custom scripts exit with code `0`, and `1` otherwise.

`smoker` will exit with code `1` if packing or installation of any package fails.

## Further Reading

See the [CLI Guide](/guides/cli) for more in-depth recipes and examples.
50 changes: 21 additions & 29 deletions packages/docs/src/content/docs/reference/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import {Tabs, TabItem} from '@astrojs/starlight/components';

I know what you're thinking: _"I just don't have enough config files!"_ **midnight-smoker** solves this problem by providing you _the opportunity to add another one_.

## Conventions

For the purposes of this documentation, we'll use the following conventions:

- `kebab-case` will be used as the "canonical" name.
- JSON examples will use `kebab-case`.
- JavaScript examples will use `camelCase`.

Some config file options are only used when run with certain CLI commands. This is denoted in the documentation below with the **Command** label.

## Configuration Files

Configuration may be specified in the `smoker` field of your `package.json`, or one of:
Expand All @@ -18,47 +28,29 @@ Configuration may be specified in the `smoker` field of your `package.json`, or

`smoker` will look for the closest of these files from the current working directory.

## About Configuration Options
## Compared to the CLI

This section explains some arguably-important things about the configuration options and how they relate to the [`smoker` CLI](/reference/cli).
This section explains some arguably-important things about how config options relate to CLI options.

### CLI Equivalence
### Equivalent Options

_Most_ config options have equivalent [command-line options](/reference/cli).

Generally, _if_ the type of a configuration option _is_ more complex than an array of strings (which can be expressed by repeating the option on the CLI), the option _will not_ be available on the CLI. This is because there's _no good way to express it_. Fight me.

### Precedence

If an option is provided on the command-line, it will _override_ the equivalent configuration option. Which is probably what you expect to happen.

### Option Names
Generally, _if_ the type of a configuration option is more complex than an array of strings (which can be expressed by repeating the option on the CLI), the option _will not_ be available on the CLI. This is because there's _no good way to express it_. Fight me.

In a config file, option names may be specified in `camelCase` or `kebab-case`. For example, `includeRoot` and `include-root` are equivalent. _This is not true for command-line options._
### CLI &gt; Config

### Command-Specific Options
The CLI takes precedence over any options set in a config file.

Some config file options are only used when run with certain CLI commands. This is denoted in the documentation below with the **Command** label.

### Boolean Options

Any boolean option can be negated by prefixing its _the kebab-case name_ it with `no-`. For example:

-`no-lint: true` is equivalent to `lint: false`.
-`no-includeRoot: true` is invalid
-`no-include-root: true` is equivalent to `include-root: false`.
In other words, if an option is provided on the command-line, it will _override_ the equivalent configuration option. Which is probably what you expect to happen.

This is usually more helpful on the command-line. Maybe you should just not worry about it.
## Configuration Options

## Conventions
:::note[Option Names]

For the purposes of this documentation, we'll use the following conventions:
In a config file, option names may be specified in `camelCase` or `kebab-case`. For example, `includeRoot` and `include-root` are equivalent.

- `kebab-case` will be used as the "canonical" name.
- JSON examples will use `kebab-case`.
- JavaScript examples will use `camelCase`.

## Configuration Options
:::

### `add`

Expand Down

0 comments on commit b6cab35

Please sign in to comment.