DEV Community

Ankur Sheel
Ankur Sheel

Posted on • Originally published at ankursheel.com on

Automating the creation of a new blog post

MDX and gatsby are great for building a blog. But, its a pain to create all the skeleton stuff every time I want to create a new blog post.

Steps to create a new blog post

  1. Create a new folder under content/posts/{year}/{date-slug}/
  2. Create a new index.mdx file in the above folder.
  3. Copy the frontmatter from a template file or an existing post.
  4. Modify most of the properties for the new post.
  5. If the post has an image gallery, then
    1. create a subfolder images.
    2. copy a data.json file from an existing folder and update it.

Heres an example of the frontmatter for this post

---
author: 'Ankur Sheel'
date: '2019-09-12'
slug: 'automating-creation-of-new-blog-post'
title: 'Automating the creation of a new blog post'
excerpt: 'Creating a new blogpost in markdown and gatsby has quite a bit of skeleton stuff. Lets see how to automate it.'
tags:
    - 'tutorial'
    - 'gatsby'
    - 'typescript'
featuredImage: ''
featuredImagePosition: ''
imageFacebook: './image-facebook.png'
imageTwitter: './image-twitter.png'
---

Wouldn't it be nice if it could all be generated by a single command?

Now you might ask, whether it’s worth spending the few extra hours to automate something that takes just a few minutes of my time.

You might be correct in saying that it doesn’t save me much time and there is not much of a context switch when I am starting a new post. But its a pain creating this manually every time and it’s also error-prone.

Let's Automate all the things.

Libraries we will be using

Install the following packages using yarn or npm.

  • slugify: Slugify the title automatically
  • inquirer: CLI get user input for title, excerpt, tags etc
  • jsToYaml: Convert json to YAML
  • mkdirp: helper to create directories
  • prettier: Optional. Code formatter to format the generated files using a prettier config.

Let’s build the script

Helper functions

const padLeft0 = (n: number) => n.toString().padStart(2, '0');

const fromRoot = (...p: string[]) => path.join(__dirname, '..', ...p);

const formatDate = (d: Date) => `${d.getFullYear()}-${padLeft0(d.getMonth() + 1)}-${padLeft0(d.getDate())}`;

const listify = (a: string) => (a && a.trim().length ? a.split(',').map(s => s.trim()) : '');

Use Inquirer to get details from the user

In my case, I am getting the title, description, tags and whether the post has images.

const prompt = await inquirer.prompt([
    {
        type: 'input',
        name: 'title',
        message: 'Title',
    },
    {
        type: 'input',
        name: 'description',
        message: 'Excerpt/Description',
    },
    {
        type: 'input',
        name: 'tags',
        message: 'Tags/Keywords (comma separated)',
    },
    {
        type: 'list',
        name: 'images',
        message: 'Post has image gallery (yes/no)',
        choices: [{ name: 'Yes', value: 'Y' }, { name: 'No', value: 'N' }],
    },
]);

const { title, description, tags, images } = prompt;

Create the folder structure

const date = new Date();
const slug = slugify(title);

const destination = fromRoot('content/posts', `${date.getFullYear().toString()}`, `${formatDate(date)}-${slug}`);

mkdirp.sync(destination);

The date is set to the current date and the slug is automatically created from the title.

The folder is created with a consistent structure.

Create the MDX file with the frontmatter

const yaml = jsToYaml.stringify({
    author: 'Ankur Sheel',
    date: formatDate(new Date()),
    slug,
    title,
    excerpt: description,
    tags: listify(tags),
    featuredImage: '',
    featuredImagePosition: '',
    imageFacebook: './image-facebook.png',
    imageTwitter: './image-twitter.png',
});
const markdown = prettier.format(`---\r\n${yaml}\r\n---\r\n`, {
    ...require('../.prettierrc'), // eslint-disable-line global-require
    trailingComma: 'es5',
    endOfLine: 'crlf',
    parser: 'mdx',
});

fs.writeFileSync(path.join(destination, 'index.mdx'), markdown);

So what’s happening in the above code snippet

  • Lines 1-12 : Create a JSON object and use jsToYaml to convert it to a YAML object.
  • Lines 13-18 : Use prettier to get a formatted string of the yaml object and wrap it with --- so that it’s in a format that the MDX expects.

Some of the properties are hardcoded such as author, imageFacebook, imageTwitter and the rest are auto-generated.

The file name is also always named index.mdx and lives in the created folder.

Create the images folder and data.json (if required)

if (images === 'Y') {
    const data = {
        gallery: [{ image: `./${slug}-1.jpg`, title: '' }],
    };

    const json = prettier.format(JSON.stringify(data, null, 4), {
        ...require('../.prettierrc'), // eslint-disable-line global-require
        trailingComma: 'es5',
        endOfLine: 'crlf',
        parser: 'json',
    });

    const imagesDestination = path.join(destination, 'images');
    mkdirp.sync(imagesDestination);

    fs.writeFileSync(path.join(imagesDestination, 'data.json'), json);
}

Let’s see what’s happening here

  • Line 1 : Create the images only folder only if the user indicated that this post will have an image gallery.
  • Lines 2-4 : Create the basic JSON object with a single entry for image.
  • Lines 6-11 : Use prettier to get a formatted string of the json object.
  • Lines 13-14 : Create the images folder.
  • Line 16 : Create the data.json file.

Other Notes

  • It might be better/faster to write the generator using plop so that I have a single template file that can be used. Plop also supports modifying a file which might be helpful if I have a lot of blogposts and want to change the frontmatter structure.

Conclusion

Setting up a new blog post can take a few minutes of my time and can be prone to errors but with this script, I just need to call the script, enter a few values which are specific to the post and I am ready to start writing.

You can see the whole script here.

Have you automated something in your projects? What modifications would you make to this script? Let me know in the comments.

Top comments (0)