From b6cab3557c342f501453e0da9220b4826d2ec30d Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Sun, 31 Dec 2023 23:08:02 -0800 Subject: [PATCH] docs: wip --- packages/docs/astro.config.ts | 19 ++-- .../content/docs/guides/getting-started.md | 2 - .../docs/src/content/docs/guides/plugins.md | 3 + .../docs/src/content/docs/guides/usage.md | 4 + .../content/docs/guides/writing-plugins.md | 97 +++++++++++++------ .../docs/src/content/docs/reference/cli.md | 52 +++++----- .../src/content/docs/reference/config.mdx | 50 ++++------ 7 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 packages/docs/src/content/docs/guides/plugins.md create mode 100644 packages/docs/src/content/docs/guides/usage.md diff --git a/packages/docs/astro.config.ts b/packages/docs/astro.config.ts index 1d830b02d..f668948b8 100644 --- a/packages/docs/astro.config.ts +++ b/packages/docs/astro.config.ts @@ -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: { diff --git a/packages/docs/src/content/docs/guides/getting-started.md b/packages/docs/src/content/docs/guides/getting-started.md index 3d25944db..93193b9d8 100644 --- a/packages/docs/src/content/docs/guides/getting-started.md +++ b/packages/docs/src/content/docs/guides/getting-started.md @@ -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). diff --git a/packages/docs/src/content/docs/guides/plugins.md b/packages/docs/src/content/docs/guides/plugins.md new file mode 100644 index 000000000..2c36415c3 --- /dev/null +++ b/packages/docs/src/content/docs/guides/plugins.md @@ -0,0 +1,3 @@ +--- +title: Using Plugins +--- diff --git a/packages/docs/src/content/docs/guides/usage.md b/packages/docs/src/content/docs/guides/usage.md new file mode 100644 index 000000000..bfc2e7d83 --- /dev/null +++ b/packages/docs/src/content/docs/guides/usage.md @@ -0,0 +1,4 @@ +--- +title: General Usage +description: A general usage guide of reasonable depth +--- diff --git a/packages/docs/src/content/docs/guides/writing-plugins.md b/packages/docs/src/content/docs/guides/writing-plugins.md index 9f5f84d65..2ccc7dc8d 100644 --- a/packages/docs/src/content/docs/guides/writing-plugins.md +++ b/packages/docs/src/content/docs/guides/writing-plugins.md @@ -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; ``` -:::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`: + ```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"] ++ } ++ } +} +``` diff --git a/packages/docs/src/content/docs/reference/cli.md b/packages/docs/src/content/docs/reference/cli.md index 66135b875..48f2246c6 100644 --- a/packages/docs/src/content/docs/reference/cli.md +++ b/packages/docs/src/content/docs/reference/cli.md @@ -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). @@ -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. diff --git a/packages/docs/src/content/docs/reference/config.mdx b/packages/docs/src/content/docs/reference/config.mdx index 02d76c719..b54226ab4 100644 --- a/packages/docs/src/content/docs/reference/config.mdx +++ b/packages/docs/src/content/docs/reference/config.mdx @@ -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: @@ -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 > 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`