DEV Community

Cover image for Writing a script to cross-post to DEV.to
Emma Goto 🍙
Emma Goto 🍙

Posted on • Originally published at emgoto.com on

Writing a script to cross-post to DEV.to

After publishing a post to my site, I usually cross-post it to DEV.to. Originally I would use their RSS feature and cross-posting was quite a painless process, but over time I've added new features to my blog like:

Which meant that I had to keep making manual changes to all my posts to make them ready for publishing on DEV. To save me some time, I wrote a script to automate this process.

If you've never written a script before, I've written a post on renaming files with Node.js scripts which should give you a good overview.

The cross-posting workflow I follow now is this:

  1. Publish a new post on my personal blog.
  2. Go to DEV.to and refresh my RSS feed (explained below).
  3. Run my devto.js script.
  4. Double-check the draft on DEV.to, and then hit publish.

Hook up your RSS feed to DEV.to

I cross-post my posts to DEV.to via my site’s RSS feed. This way, I get the “Originally published at” message to show up underneath the title of my posts:

"DEV.to post showing originally published at emgoto.com"

If you go to your DEV.to settings page, and click the Extensions option, you’ll have the opportunity to add an RSS feed:

"RSS feed options in DEV.to settings page"

Once you have hooked up your RSS feed, DEV.to will periodically check it to see if there are any new posts, and add the post in DEV as a draft.

After I publish a post on my own site, I go into DEV.to and hit the “Fetch feed now” button to make it show up straightaway. Unfortunately DEV doesn't have an API to do this step from within my script.

Run the script to update the draft post in DEV.to

To run this script, you’ll need your own DEV API key. I store mine in a .env file in my site’s repository:

// .env
DEV_API_KEY=<key_goes_here>
Enter fullscreen mode Exit fullscreen mode

The script makes use of two of the DEV API’s endpoints:

My posts are stored on my repository with Markdown and frontmatter, in a format like this:

--------
title: "Hello! This is the markdown file"
date: 2021-09-25
tags: ["react"]
--------

Content of the post goes here.

![Image with alt text](./image.png)
Enter fullscreen mode Exit fullscreen mode

The script will transform it into this on DEV:

--------
title: "Hello! This is the markdown file"
published: false
tags: ["react"]
--------

Content of the post goes here.

![Image with alt textt](https://emgoto.com/slug/image.png)
Enter fullscreen mode Exit fullscreen mode

There’s three things to point out here:

  • I make sure the frontmatter has published: false so it stays in draft mode
  • I remove the date field. If you leave this value, DEV will set it as having been published at midnight of the date you specified. This can reduce the chance of your post actually getting views on DEV’s home page since it’s considered an “old” post.
  • There’s no DEV image API so you'll need to host the image yourself

The full version of the script is available on my site’s Github repository, and I’ve got a shortened version below that you can copy-paste.

#!/usr/bin/env node

const { readFile } = require('fs');
const { join } = require('path');
const glob = require('glob');
const fetch = require('node-fetch');

// I store my API key in a .env file
require('dotenv').config(); 

const updateArticle = (devArticleId, content) => {
    fetch(`https://dev.to/api/articles/${devArticleId}`, {
        method: 'PUT',
        headers: {
            'api-key': process.env.DEV_API_KEY,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            article: {
                body_markdown: content,
            },
        }),
    })
};

const updateFile = (content, slug) => {
    // Update frontmatter
    content = content.replace(/date: .*\n/, 'published: false\n');

    // Update images
    content = content.replace(
        /!\[(.+?)\]\(\.\/(.+?)\)/g,
        `![$1](https://emgoto.com/${slug}/$2)`,
    );

    // TODO: Any other changes to your content

    return content;
}

const devto = () => {
    // Looks for folders inside of "posts" folder that matches the given slug.
    const slug = process.argv[1];
    const file = [
        ...glob.sync(join(process.cwd(), 'posts', slug, 'index.mdx')),
    ][0];

    readFile(file, 'utf8', (err, content) => {
        if (err) reject(err);

        const title = content.match(/title: "(.*)"\n/)[1];
        content = updateFile(content, slug);

        fetch('https://dev.to/api/articles/me/unpublished', {
            headers: { 'api-key': process.env.DEV_API_KEY },
        })
            .then((response) => response.json())
            .then((response) => {
                if (response.length > 0) {
                    const draftTitle = response[0].title;
                    if (draftTitle === title) {
                        const devArticleId = response[0].id;
                        updateArticle(devArticleId, content);
                    }
                }
            })
    });
};

devto();
Enter fullscreen mode Exit fullscreen mode

Discussion (3)

Collapse
vonheikemen profile image
Heiker

There is something that you might find useful: the fs module has promise-based functions under the promises object.

So code like this is perfectly valid.

const { readFile } = require('fs').promises;

const somefunction = async () => { 
  const content = await readFile('file.txt', 'utf8');
   // moar code...
};
Enter fullscreen mode Exit fullscreen mode
Collapse
emma profile image
Emma Goto 🍙 Author

Thanks for the tip!

Collapse
ful1e5 profile image
Kaiz Khatri

Way better than my TheActionDev.

Thanks, Emma for sharing ❤️