remark-contributors

remark plugin to inject a given list of contributors into a table

Usage no npm install needed!

<script type="module">
  import remarkContributors from 'https://cdn.skypack.dev/remark-contributors';
</script>

README

remark-contributors

Build Coverage Downloads Size Sponsors Backers Chat

remark plugin to generate a list of contributors.

Contents

What is this?

This package is a unified (remark) plugin that takes a list of contributors and adds them in a table to a ## Contributors heading.

unified is a project that transforms content with abstract syntax trees (ASTs). remark adds support for markdown to unified. mdast is the markdown AST that remark uses. This is a remark plugin that transforms mdast.

When should I use this?

This project is useful when you’re writing documentation for a project, typically a Node.js package, that has one or more readmes and maybe some other markdown files as well. You want to show who helped build the project by adding their names, websites, and perhaps some more info. This package is useful because it’s automated: you can customize who is added and how that’s formatted. But it won’t be as expressive as writing such sections manually.

This plugin is used in remark-git-contributors. The difference is that that plugin takes the Git history into account, which isn’t always needed or correct.

Install

This package is ESM only. In Node.js (version 12.20+, 14.14+, or 16.0+), install with npm:

npm install remark-contributors

In Deno with Skypack:

import remarkContributors from 'https://cdn.skypack.dev/remark-contributors@6?dts'

In browsers with Skypack:

<script type="module">
  import remarkContributors from 'https://cdn.skypack.dev/remark-contributors@6?min'
</script>

Use

Say we have the following file example.md in this project:

# Example

Some text.

## Contributors

## License

MIT

And our module example.js looks as follows:

import {read} from 'to-vfile'
import {remark} from 'remark'
import remarkGfm from 'remark-gfm'
import remarkContributors from 'remark-contributors'

main()

async function main() {
  const file = await remark()
    .use(remarkGfm) // Required: add support for tables (a GFM feature).
    .use(remarkContributors)
    .process(await read('example.md'))

  console.log(String(file))
}

Now running node example.js yields:

# Example

Some text.

## Contributors

| Name                | Website                     |
| ------------------- | --------------------------- |
| **Hugh Kennedy**    | <https://hughsk.io>         |
| **Titus Wormer**    | <https://wooorm.com>        |
| **Vincent Weevers** | <https://vincentweevers.nl> |
| **Nick Baugh**      | <https://niftylettuce.com>  |

## License

MIT

👉 Note: These contributors are inferred from this project’s package.json. Running this example in a different package will yield different results.

API

This package exports no identifiers. The default export is remarkContributors.

unified().use(remarkContributors[, options])

Generate a list of contributors. In short, this plugin:

  • looks for the first heading matching /^contributors$/i or options.heading
  • if no heading is found and appendIfMissing is set, injects such a heading
  • if there is a heading, replaces everything in that section with a new table
options

Configuration (optional in Node.js, required in browsers).

options.contributors

List of contributors to inject (Array<Object>). In Node.js, defaults to the contributors field in the closest package.json upwards from the processed file, if there is one. Supports the string form (name <email> (url)) as well. Fails if no contributors are found or given.

options.align

Alignment to use for all cells in the table ('left' | 'right' | 'center' | null, default: null).

options.appendIfMissing

Whether to add a ## Contributors section if none exists (boolean, default: false). The default does nothing when the section doesn’t exist so that you have to choose where and if the section is added.

options.heading

Heading to look for (string (case-insensitive) or RegExp, default: 'contributors').

options.formatters

Map of fields found in contributors to formatters (Record<string, Formatter>). These given formatters extend the default formatters.

The keys in formatters should correspond directly (case-sensitive) to keys in contributors.

The values can be:

  • null or undefined — does nothing
  • false — shortcut for {label: key, exclude: true}, can be used to exclude default formatters
  • true — shortcut for {label: key}, can be used to include default formatters (like email)
  • string — shortcut for {label: value}
  • Formatter — …or a proper formatter object

Formatters have the following properties:

  • label — text in the header row that labels the column for this field
  • exclude — whether to ignore these fields (default: false)
  • format — function called with value, key, contributor to format the value. Expected to return PhrasingContent. Can return null or undefined (ignored), string (wrapped in a text node), string that looks like a URL (wrapped in a link), one Node, or Array<Node>
Notes
  • define fields other than name, url, github, or twitter in formatters to label them properly
  • by default, fields named url will be labelled Website (so that package.json contributors field is displayed nicely)
  • by default, fields named email are ignored
  • Name fields are displayed as strong
  • GitHub and Twitter URLs are automatically stripped and displayed with @mentions wrapped in an https:// link
  • if a field is undefined for a given contributor, then the value will be an empty table cell
  • columns are sorted in the order they are defined (first defined => first displayed)

Examples

Example: passing contributors

The following example shows how contributors can be passed:

import {remark} from 'remark'
import remarkGfm from 'remark-gfm'
import remarkContributors from './index.js'

main()

async function main() {
  const file = await remark()
    .use(remarkGfm)
    .use(remarkContributors, {
      contributors: [
        // String form:
        'Jane Doe <jane@doe.com> (https://example.com/jane)',
        // Object form, with just a name:
        {name: 'John Doe'},
        // Some more info:
        {name: 'Mona Lisa', url: 'https://github.com/monatheoctocat'}
      ]
    })
    .process('## Contributors')

  console.log(String(file))
}

Yields:

## Contributors

| Name          | Website                             |
| ------------- | ----------------------------------- |
| **Jane Doe**  | <https://example.com/jane>          |
| **John Doe**  |                                     |
| **Mona Lisa** | <https://github.com/monatheoctocat> |

Example: formatters

By default, unknown fields in contributors will be added to the table:

import {remark} from 'remark'
import remarkGfm from 'remark-gfm'
import remarkContributors from 'remark-contributors'

main()

async function main() {
  const file = await remark()
    .use(remarkGfm)
    .use(remarkContributors, {
      contributors: [
        {name: 'Jane Doe', age: 31, topping: 'Mozzarella'},
        {name: 'John Doe', age: 29, topping: 'Olive'},
        {name: 'Mona Lisa', age: 3, topping: 'Pineapple'}
      ]
    })
    .process('## Contributors')

  console.log(String(file))
}

Yields:

## Contributors

| Name          | age | topping    |
| ------------- | --- | ---------- |
| **Jane Doe**  | 31  | Mozzarella |
| **John Doe**  | 29  | Olive      |
| **Mona Lisa** | 3   | Pineapple  |

It’s possible to customize how these new fields are formatted:

@@ -12,7 +12,16 @@ async function main() {
         {name: 'Jane Doe', age: 31, topping: 'Mozzarella'},
         {name: 'John Doe', age: 29, topping: 'Olive'},
         {name: 'Mona Lisa', age: 3, topping: 'Pineapple'}
-      ]
+      ],
+      formatters: {
+        age: {
+          label: 'Age',
+          format(d) {
+            return {type: 'emphasis', children: [{type: 'text', value: String(d)}]}
+          }
+        },
+        topping: 'Topping'
+      }
     })
     .process('## Contributors')

Yields:

| Name          | Age  | Topping    |
| ------------- | ---- | ---------- |
| **Jane Doe**  | *31* | Mozzarella |
| **John Doe**  | *29* | Olive      |
| **Mona Lisa** | *3*  | Pineapple  |

👉 Note: Observe that the labels of Age and Topping are now cased, and that the age values are now wrapped in emphasis.

Types

This package is fully typed with TypeScript. It exports additional Format, FormatterObject, Formatter, Formatters, ContributorObject, Contributor, and Options types, which model these respective interfaces.

Compatibility

Projects maintained by the unified collective are compatible with all maintained versions of Node.js. As of now, that is Node.js 12.20+, 14.14+, and 16.0+. Our projects sometimes work with older versions, but this is not guaranteed.

This plugin works with unified version 6+ and remark version 7+.

Security

options.contributors (or contributors in package.json) is used and injected into the tree when given or found. Data in those lists is formatted by options.formatters. If a user has access to either, this could open you up to a cross-site scripting (XSS) attack.

This may become a problem if the markdown is later transformed to rehype (hast) or opened in an unsafe markdown viewer.

Related

Contribute

See contributing.md in remarkjs/.github for ways to get started. See support.md for ways to get help.

This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.

Contributors

Name Website
Hugh Kennedy https://hughsk.io
Titus Wormer https://wooorm.com
Vincent Weevers https://vincentweevers.nl
Nick Baugh https://niftylettuce.com

License

MIT © Hugh Kennedy