Publish ReasonML API docs automatically
Tags: bucklescript, ocaml, reason
Published: 2020-06-24 04:01:16

---
title: Publish ReasonML API docs automatically
published: true
description: How to set up a fast build pipeline that publishes API docs on every git push
tags: bucklescript,ocaml,reason
//cover_image: https://direct_url_to_image.jpg
---
API docs are a much-needed and often missing piece of the documentation story of every good library. For BuckleScript/ReasonML libraries, the information on how to generate them has been a bit obscure. Fortunately, it's now possible to set up an automatic API doc pipeline with fairly low effort.

In this post I will show how to:

- Set up a separate git branch in your repo for documentation
- Write some basic API docs in the source code
- Add Azure Pipelines CI/CD pipeline files to the repo that describe how to build and publish API docs
- Navigate the Azure Pipelines developer portal and finalize the pipeline setup

## Set up git branch

The first thing to do is to set up a new orphan branch to hold the actual documentation files. We will use GitHub Pages to host the docs and serve them from the repo. Start from a clean repo state and create the branch:

```
$ git checkout --orphan gh-pages
```

Now delete all project files except the `.gitignore`, and add this [index.html](https://raw.githubusercontent.com/yawaramin/prometo/gh-pages/index.html) file to the project root. Once added, edit the file and replace `./Yawaramin__Prometo/Yawaramin__Prometo` with `./YourProject/YourProject`, where `YourProject` is your project name.

Commit this file to the branch and push it:

```
$ git add .
$ git commit -m"Add index"
$ git push -u origin gh-pages
```

Now checkout your main branch and get ready to write some API docs.

## Write some API docs

This is a very basic overview of how to write doc comments. See the OCamldoc link above for more.

Say you have a [standard](/yawaramin/a-modular-ocaml-project-structure-1ikd) ReasonML project structure with source files under `src/`, e.g. `src/YourProject.re`. Let's write some API docs:

```ocaml
[@text {|YourProject - a nice project.|}];

/** [coolFunction(x)] is [x + 1]. */
let coolFunction = x => x + 1;
```

The `[@text ...];` block (the semicolon is important) at the top of the file becomes the overall module's doc comment. We need to use this special `[@text ...]` attribute because of a [bug](https://github.com/facebook/reason/issues/2419) in Reason that doesn't understand the normal doc comment, `/** ... */`, at the module level.

However as shown next, Reason works OK for doc comments that are attached to specific items in the module, like the `coolFunction` function.

Note that this bug only affects Reason syntax; if you're writing source code in OCaml syntax, then the regular doc comment `(** ... *)` will work anywhere in a module.

**Quick tip:** it's important to _not_ start each line with `*` as the doc generator doesn't strip out the leading asterisk. Ensure that multi-line comments look something like this:

```ocaml
/** This
    is a
    multi-line
    comment. */
```

Next, install the `bsdoc` package as a development dependency:

```
$ npm install -D bsdoc # or yarn etc.
```

Commit and push these changes.

## Add Azure Pipelines capability to the repo

This involves adding several files to the repo and customizing them for your project. They are:

### `azure-pipelines.yml`

This goes in the project root. You can copy over [this file](https://github.com/yawaramin/prometo/blob/74d633ab94f567e4ba73f1ff0d0b487006b3547a/azure-pipelines.yml) and customize to your needs. Specifically:

- L1: you may want to customize the build name
- L6: set the name of the branch that should trigger the build
- L13: this doesn't need to change; just a note that you specifically want to use a MacOS VM to run the main build because the bsdoc binary is only published for MacOS now. In the future if it's available for Linux you may use a Linux VM.
- L26: set the same branch name as in L6

### `.ci/`

Copy over the [entire `.ci/` directory](https://github.com/yawaramin/prometo/tree/74d633ab94f567e4ba73f1ff0d0b487006b3547a/.ci) and its contents to your repo. The needed changes are:

- `.ci/build-docs.yml`:
  - L2: change the `Yawaramin__Prometo` to `YourProject` (remember, assuming `YourProject` is the name of your project, see above)

**Special note:** the way this works is, the `bsdoc` tool generates docs and puts them in the project's `docs/` directory. It's important that this directory be free of any other files. But many projects put documentation in `docs/`, so this can be a problem. If this is the case for you, add a step to this file, before the `npx bsdoc build ...` step, to [delete](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/delete-files?view=azure-devops) the existing `docs` directory, so the `bsdoc` command's output is not mixed up with any other files.

- `.ci/publish-api-docs.yml`:
  - L3: change the repo URL to your repo
  - L9: change `Yawaramin__Prometo` to `YourProject`
  - L31: change the repo URL to your repo
  - L32: change the name of the docs publisher bot
- `.ci/utils/cache.yml`:
  - L2: change the value of the `cache_key` parameter to your project lockfile name. I'm using pnpm so mine is `pnpm-lock.yaml`, but you will likely have `package-lock.json` or `yarn.lock`. This is important for caching to work properly. This pipeline will cache all build artifacts so it never needs to download and rebuild dependencies over and over again. Builds should take a couple of minutes, max.

Now commit and push all of the above.

## Allow the pipeline to update and push docs

Now you will want to grant the pipeline permission to push generated docs to your repo's `gh-pages` branch which you created earlier. You can do this by generating a deploy key and telling GitHub about it.

First, generate an SSH key:

```
$ ssh-keygen -t rsa -b 4096 -C "your@email"
```

When prompted for a location, enter `/Users/YourName/.ssh/deploy_key` (or the equivalent absolute path for your operating system). Note, it's important that `deploy_key` be exactly the name used. Press Enter both times when prompted for a passphrase and wait for the key to be generated (should take a second). Copy the contents of `/Users/YourName/.ssh/deploy_key.pub` to the clipboard.

Now go to `https://github.com/YourName/YourProject/settings/keys/new` and set the title 'docs', and for the key value, paste in the contents you copied above.

Check the 'Allow write access' checkbox and click 'Add key'.

## Set up Azure Pipelines

Now, you are ready to tell Azure Pipelines about the project, so it can start running builds on every push. Do the following:

Go to https://dev.azure.com/ and log in with your GitHub account.

Click the '+ New project' button (blue, top-right corner) and enter your project name and description (this will probably be the same as in the GitHub repo). Select visibility 'Public' and click 'Create'.

In the new project, go to your 'Library' page's 'Secure files' section: `https://dev.azure.com/YourName/YourProject/_library?itemType=SecureFiles`

Click the '+ Secure file' button and choose the SSH private key you created earlier, called `deploy_key`. Ensure in the 'Pipeline permissions' section of this file that the 'Authorize for use in all pipelines' setting is turned on.

Now, go to the 'Pipelines' page in the left panel, and click 'Create Pipeline'. Choose GitHub for the 'Where is your code?' question, and select your repository. It should find and show your repo's `azure-pipelines.yml` file. Click the 'Run' button (top-right corner) to create and run it for the first time.

If all goes well, the pipeline runs and publishes the generated API docs to your repo's `gh-pages` branch, which is then served by GitHub under `https://YourName.github.io/YourProject`. If everything looks good, add that URL prominently to the repo's URL/README, and you now have automatic API docs! See [this repo](https://github.com/yawaramin/prometo/) for an example of this setup and the generated documentation.

Of course, it may not be as simple as that, especially if I've forgotten something in the steps above. Any feedback about that is appreciated!