emdaer

📓 Create and Maintain Better READMEs

Get Started

What is emdaer?

emdaer lets you to use plugins and transforms within markdown files because READMEs (and other documentation) are crucial files that are often lackluster and/or incomplete and have a tendency to become stale

A couple use cases that illustrate the power of emdaer:

  • 🤝 Keep it in sync Create templates for use across all of your organizations projects to promote synchronicity and reduce doing the same work over and over
  • 🗃 Keep it organized Keep your documentation DRY and organized by importing content from your codebase, splitting large documents into chunks, and formatting it with Prettier.
  • 🍋 Keep it fresh Ensure your documents stay up to date by pulling in new data from various sources with every build

How emdaer works

emdaer processes template files and writes the resulting files to your project.

We match .emdaer/(**/*).emdaer(.md) and use the captured part of each matched file to determine the path for the output.

Plugins & Transforms

# <!--emdaer-p
  - '@emdaer/plugin-value-from-package'
  - value: name
-->

Hello, World!

<!--emdaer-p
  - '@emdaer/plugin-import'
  - path: './.emdaer/printThrice.js'
    args:
      - 'Hey'
-->

<!--emdaer-t
  - '@emdaer/transform-prettier'
  - options:
      proseWrap: preserve
      singleQuote: true
      trailingComma: es5
-->

This example, once processed, will look something like this:

<h1>mypackage-name</h1>

<p>Hello, World!</p>

<p>Hey<br>Hey<br>Hey</p>

This example includes two plugin calls (emdaer-p) and one transform call (emdaer-t).

❓ Help
The first plugin call is to [@emdaer/plugin-value-from-package](/emdaer/emdaer/blob/master/packages/plugin-value-from-package). It is used to get the value of `name` from `package.json`. That way if your project name change, so does your README.
The second plugin call is to [@emdaer/plugin-import](/emdaer/emdaer/blob/master/packages/plugin-import). It is used to import a function called `printThrice` and executing it with the argument `Hey`, printing it three times. The `path` parameter can be any node modules that exports a string, exports a function that returns a string, or exports a funciton that returns a promise that resolves to a string.
The third emdaer call is to [@emdaer/transform-prettier](/emdaer/emdaer/blob/master/packages/transform-prettier). It will format your README with the given options so you don't have to.

Emdaer plugin/transform calls are just html comments. These calls take the form of yaml tuples where the first item is the name of the function to call and the second item is an options object that is passed to that function.

For plugins, the result of the call replaces the corresponding comment block.

For transforms, the function acts on the entire document and rewrites the entire file. In some cases, like @emdaer/transform-table-of-contents, transforms can inline their content, replacing the corresponding comment block.

Code Fences

Platforms vary in how they provide syntax highlighted code to users READMEs, rendering code fences with language specificers as HTML/CSS, each in their own way. Emdaer also transforms your README in HTML via marked to make it more portable.

Instead of trying to guess how platforms expect the HTML/CSS of a code fence to be output, when emdaer encounters a code fence with a language specified, it will ignore it. This means that while the rest of your README will be transformed to HTML, code fences will remain in Markdown. This sacrifices a bit of portability for the sake of readability and UX.

NOTE: If it's important that your README be pure HTML, do not use language specifiers in your code fences.

Adding emdaer to your project

We recommend using emdaer with lint-staged and husky.

Install dependencies:

npm install --save-dev @emdaer/cli @emdaer/plugin-value-from-package lint-staged husky

Follow the lint-staged setup instructions.

{
  "scripts": {
+   "emdaer": "emdaer && git add *.md",
+   "precommit": "lint-staged"
  }
}

In your lint-staged config file add an entry for emdaer:

module.exports = {
  '*.js': ['eslint --fix', 'prettier --write', 'git add'],
+ '*.emdaer.md': ['emdaer --yes', 'git add'],
};

NOTE: In the case of a precommit hook (or CI/other automation), we don't want to be prompted about anything. The --yes flag will automatically answer "yes" to any prompts. For example, it will make emdaer write your READMEs without prompting about overwritting direct changes to a destination README file.

Add a .emdaer/README.emdaer.md file:

# <!--emdaer-p
  - '@emdaer/plugin-value-from-package'
  - value: name
-->

And give it a whirl:

npm run emdaer

When you commit your changes, lint-staged will run emdaer on any *.emdaer.md files you may have changed.

Manual Usage

emdaer can be run manually against files by providing space separated file paths:

npm run emdaer -- .emdaer/README.emdaer.md .emdaer/CONTRIBUTING.emdaer.md

If emdaer is not provided a path, the default glob .emdaer/**/*.emdaer.md is searched:

npm run emdaer

NOTE: By default, emdaer checks for existing changes to your READMEs before writing. If it detects changes, it will provide a prompt asking if you would like to overwrite the README with the newly generated content. If you accidentally edited the README directly, you will want to answer n to the prompt, move any changes to the respective .emdaer/*.emdaer.md file, and rerun emdaer. If you would like to discard those changes, answer Y to the prompt or use the --yes flag to skip the prompt all together. In both cases, emdaer will overwrite the README with the newly generated content.