Alexito's World

A world of coding 💻, by Alejandro Martinez

Making a script to automate creating posts for GatsbyJS

One small inconvenience of a static blog generator running without a CMS is that to create a new post in markdown you need to add manually the front matter in the top of the file. That's why the first thing you should do after setting up your static site generator is to create a small script that automates the new post creation.

The main purpose of the script is to ask you a series of questions about the post you want to create and then generate the files and folders needed for new the post to show up in your site.

Post structure

Commonly static site generators use Markdown files to facilitate the creation of the content. Markdown allows you to write plain text with rich format just by surrounding words by simple delimiters. Markdown has become the lengua franca of rich text: GitHub, Stackoverflow, any text editor... all of them support Markdown (or a variation of it) for rich text. You can read about it and how it was designed in John Gruber's website.

For your site generator to find the posts, it's usually as simple as defining the folder that contains them and the generator will just render any markdown file that it finds.

content/
├── posts
│   ├── one-post.md
│   └── another-post.md

If your posts just contain text that is more than fine. But if you want to add images that you have locally and want the generator to detect them, you will need an easy way to define where to find the images. You could always add the images in your assets folder and configure your generator to look for them there, but that won't help if you use any text editor with previews.

The standard way for text editors to display images in markdown files is to have them in the same folder.

For example, the markdown that shows the GIF at the top of this post is this: ![](create_post.gif). With this Typora, the editor that I use, can preview the image because it's just a relative path to the same file location.

Typora example

That's what is usually called a bundle. The name of the post and slug are defined by a folder name and inside that folder you include an index.md for the post and whatever resource you want to link.

content/
├── posts
│   ├── one-post
│   │   ├── image1.jpg
│   │   ├── image2.png
│   │   └── index.md
│   └── another-post
│       └── index.md

I personally recommend to use always bundles as it will help to keep your content together. Because remember, the important thing is that the content is yours.

Front matter

Front matter defines the metadata for your posts in the same text file. This makes your posts portable as it contains all the information of itself.

Front matter is defined by a delimiter at the top of the markdown file that separates it from the rest of the content. It defines the metadata with a key-value structure, usually in YAML but can also be done in JSON or TOML.

The information that defines depends on your own setup but usually it will be the title, date, authors, categories... any data that your site generator needs to know about the post itself should be here.

In my case the metadata of my posts looks like this:

title: Making a script to automate creating posts for GatsbyJS
date: 2018-08-08
draft: false
author: Alejandro Martinez
tags: ['GatsbyJS','Javascript','Scripting']
source_url: ''

You can see the common title, date and author. In my case I also include which tags relate to the post and a source URL if I want the post to be linked to another website for reference.

Asking the questions

Now that we understand what a new posts needs lets take a look at what our scripts needs to do.

First it should ask a series of questions that would help define the metadata of the post.

For that I used the inquirer package. It's a super simple API that defines questions to make to the user.

inquirer.prompt([
    {
        type: 'input',
        message: `What's your post title?`,
        name: 'title',
        validate: input => Boolean(input),
    },
    ...
]).then(answers => {
    ...    
})

When all the questions are answered the callback receives the answers. It's an object with the keys we defined as name in each question.

So it's really easy to get the data that the user input:

const { title, source_url } = answers

The nice thing about this package is that is supports different types of questions, from the simple input to a list of options. I even used a separate plugin called search-checkbox that let's me search trough the available tags of my blog to pick which ones I want to use.

This is crucial for me because I don't want to create new tags by accident or that because a small typo a whole new page is created.

Generating the structure

Once we have all the data for the post we can move on an generate the folder and file for the bundle.

To create the file with the front matter I use gray-matter. I just need to create an object with the data I want to export and call the stringify function.

const data = {
    title,
    ...
}
var matter = gray.stringify('', data)

You can also pass the content of your post as the first parameter. In my case I just pass an empty string but you can use it if you want to write the post in the terminal or create the excerpt manually.

With the string of the markdown file generated we just need to use fs and path modules to write the files to disk.

createPostBundle = (slug, matter) => {
    const folder = path.join(__dirname, '..', 'content', 'posts', `${slug}`)

    try {
        fs.mkdirSync(folder)
    } catch (e) {
        console.log(c.red`Cannot create directory ${e}`)
        return null
    }

    const file = path.join(folder, `index.md`)
    try {
        fs.writeFileSync(file, matter)
    } catch (e) {
        console.log(c.red`Cannot write file ${e}`)
        return null
    }

    return file
}

Conclusion

Reducing the friction to start writing is really important for me. Creating a new file is not that hard, but when you need to give it a proper file name, put it in the correct folder and then copy paste the front matter you endup writing less and less.

Automatic the creation of the posts is a simple task that will increase your productivity massively.

I've given some code snippets in this post that you can use, but because everybody has different necessities and preferences for this I don't think there is a need to share the entire file. Instead use the lego pieces I've given to create your own ^^

Extra: Just a file

If you don't want to use bundles and prefer to just have the markdown file as the post you can still use the rest of information of this post! You just need to change the creation of the files, instead of making a folder with a file inside, just make the file directly.

createPostFile = (slug, matter) => {
    const file = path.join(__dirname, '..', 'content', 'posts', `${slug}.md`)
    console.log(file)

    try {
        fs.writeFileSync(file, matter)
    } catch (e) {
        console.log("Cannot write file ", e)
    }
}

My script actually has a parameter that let's me control which kind of structure to use

Extra: Date

Some people likes to put the date at the time of creation. That comes really handy if you have a fix schedule, if that's your case just ask for it with inquirer.

But in my case I prefer to use the date of creation automatically. For that I just use moment to get the current date in the appropriate format.

const date = moment().format('YYYY-MM-DD')

Extra: slug

I've seen some examples where people defines the slug of the post in the front matter. If that's your case, again, just ask for it with inquirer.

In my case I prefer to deduce the slug from the file name, which includes the data. To do that I use the slug package that is included with GatsbyJS.

Extra: npm run

I'm a really big fan of npm run. Being able to have all the scripts for your project in the single place reduces the cognitive load while switching projects.

That's why in my package.json I included a command to run my script:

"scripts": {
	"post": "scripts/createpost.js"
},

Extra: open the article

Automating everything give you productivity, but if you want to go the extra mile I recommend you to automatically open the text file after creating it.

You can use the child_process to execute the open command so the file opens with your favourite text editor.

const { exec } = require('child_process');
...
exec(`open ${file}`)

Productivity x10!

If you liked this article please consider supporting me