Links
💫

Automatically Generating Content on GitBook with GitHub Actions

Sometimes, it can be useful to automatically generate some of your documentation. Whether you're generating Markdown based on Docstrings, or you're adding a footer with a link to a feedback form at the bottom of every page, you can achieve this effect using GitBook, with help from the GitHub Sync feature.
For the purposes of this example, let's consider a company, CoolProductIncorporated who need to document their cool developer focused product. The team decides that at the end of each page of documentation, they want to add a footer that links to a video demo of their product. How might they achieve that?
We have a demo repository that implements the steps in this blog post. Check it out to see it in action!​

Setup and prep

  1. 1.
    ​Create a GitBook space in the appropriate org
  2. 2.
    Create a new empty GitHub repo to sync content to
  3. 3.
    ​Set up sync with GitHub repo, making sure to choose initial import from GitBook
  4. 4.
    Add a .gitbook.yaml file in the root to tell GitBook we're storing our documentation in the docs folder. It should contain just the line root: ./docs/. Create a folder docs in the root of your repository.
At this stage, the building blocks are laid to start making modifications to files using scripts.
At this point, it's time to start thinking about how the generated content will be generated. This will vary greatly depending on your use case. If you're generating content from docstrings, you may be able to find a program that outputs markdown in the format you're looking for. For other types of content generation, you might need to write a script yourself. For the purposes of this step, you need to run a program (in a format that can be run on GitHub Actions) that modifies your documentation and outputs Markdown.
Here's an example program written in Javascript, that adds a footer to every documentation page that doesn't already have it.
scripts/append-footer.js
scripts/footer.md
var glob = require("glob");
var fsPromises = require("fs/promises");
var path = require("path");
const { spawn } = require("child_process");
​
// read environmental variables
const ghActor = process.env["GITHUB_ACTOR"];
const isGitHubActions = Boolean(process.env["GITHUB_ACTIONS"]);
​
const GITBOOK_ACTOR = "gitbook-bot";
console.log(`GitHub actions ${isGitHubActions}`);
console.log(`GitHub actor ${ghActor}`);
​
if (isGitHubActions) {
// If we're in GitHub Actions, only run this for pushes from GitBook
if (ghActor !== GITBOOK_ACTOR) {
process.exit(0);
}
}
​
// Load the footer from the footer.md file
const footer = require("fs").readFileSync(path.join(__dirname, "./footer.md"));
​
// find each markdown document in the docs directory
glob(path.join(__dirname, "../docs/**/*.md"), async (error, fileNames) => {
console.log(fileNames);
// for each document
const editPromises = fileNames.map(async (fileName) => {
// load the contents of the document
const fileContents = await (await fsPromises.readFile(fileName)).toString();
// if the document doesn't end with the footer text
if (!fileContents.endsWith(footer)) {
// add the footer text to the end of the document
const newFileContents = fileContents + footer;
await fsPromises.writeFile(fileName, newFileContents);
}
});
​
await Promise.all(editPromises);
​
spawn("git", ["add", "."]);
});
​
### Check out this awesome video about our content!
This example code depends on the glob npm package, so you'll need to add it to your package.json. If you don't already have a package.json, you can create one in your repository root using npm init -y, and install glob with npm install --save glob. You can store both append-footer.js and footer.md in the scripts folder in your repository.
Remember - you don't have to use Javascript to modify or generate your documentation! This is just an example. Feel free to use any program that can run in GitHub actions.

Automating generation with GitHub Actions

Now that we have our script, we need to run it automatically each time our content is changed. To do that, we'll configure a GitHub Actions workflow. In our repository, we'll create a directory at .github/workflows, to store our workflows. In there, we'll create a file called append-footer.yml.
name: append-footer
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
​
steps:
- uses: actions/setup-[email protected]
with:
node-version: v16.x
- uses: actions/[email protected]
with:
fetch-depth: 1
- name: npm install
run: npm ci
- name: Enforce docs footer
run: node ./scripts/append-footer.js
- name: Git Prep
run: |
git config --local user.email "[email protected]"
git config --local user.name "footer-bot[bot]"
git diff
git diff-index HEAD
git diff-index --quiet HEAD || git commit -am "docs(footer): ensure footer exists on all docs pages"
- name: Git Push
run: "git push origin $(git branch --show-current) || true"
This workflow does a lot - lets step through it.
We first name the workflow and configure it to run each time there's a push to the repository's main branch. The script itself ensures that we only run when GitBook pushes to main - no need to configure that here.
Using the ubuntu-latest base image, we use the setup-node action to ensure we're using the correct version of node.js, check out the contents of the repository, and install dependencies.
The next few steps are more interesting - first we run the script above, which will append the footer to all documentation pages that don't already have it, and stages the results in git.
At this point, we configure git. We set up a dummy email and name to be attached to the commit, and only if there was staged changes, commits the code. In the case that each documentation file already has the correct footer, there are no changes to commit, so we ignore them.
Finally, we push the new commit to GitHub. GitBook will read the changes and update its copy of the documentation.
CoolProductIncorporated no longer have to think about footers - they can trust that the robots will handle it, and focus on writing the very best documentation they can for their Cool Product!

Thanks!

Thanks for reading. We hope this article was helpful for you. If you have any questions, please feel free to get in touch with us.​
Do I have to use GitHub actions, or can I use my own CI system like Travis CI/Circle CI, or Jenkins?
Can I reuse the script for other purposes?
Do I have to use the Javascript ecosystem?