Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Blog Post Script & Workflow #2707

Merged
merged 4 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/issue_opened.new-blog-post.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: New Issue (Create Blog Post)

on:
issues:
types: [opened]

jobs:
open-pr:
runs-on: ubuntu-latest
steps:
- name: 🎬 Update status
id: update_status
uses: actions/github-script@v7
with:
script: |
const response = await github.rest.reactions.createForIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
content: 'eyes'
});

return response.data.id;
- name: 🔬 Check actor
uses: tspascoal/get-user-teams-membership@v3
id: checkUserMember
with:
username: ${{ github.actor }}
team: committers
GITHUB_TOKEN: ${{ secrets.ORG_READ_PAT }}

- name: 🛑 Stop if not member
if: ${{ steps.checkUserMember.outputs.isTeamMember == false }}
run: |
echo "You cannot run this job."
exit 86

- name: ⬇️ Checkout Repository
uses: actions/checkout@v4

- name: ⎔ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm

- name: 📦 Install script dependencies
working-directory: scripts
run: npm install

- name: 📝 Create Blog Post
working-directory: scripts
run: node new-blog.mjs ${{ github.event.issue.id }}

- name: 📌 Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'feat: create new blog post'
title: 'feat: create new blog post'
body: |
This PR was automatically created by the "Issue opened (new blog)" workflow.

closes #${{ github.event.issue.number}}
branch: feat/new-blog-post-${{ github.event.issue.number }}
draft: true

- name: 🗑️ Remove reaction
uses: actions/github-script@v7
with:
script: |
github.rest.reactions.deleteForIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
reaction_id: ${{ steps.update_status.outputs.result }}
})

- name: ✅ Set complete reaction
uses: actions/github-script@v7
if: ${{ steps.cpr.outputs.pull-request-number }}
with:
script: |
github.rest.reactions.createForIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
content: 'rocket'
});
92 changes: 68 additions & 24 deletions scripts/new-blog.mjs
Original file line number Diff line number Diff line change
@@ -1,33 +1,77 @@
import fs from 'fs';

// e.g. node scripts/new-blog.mjs "Blog Title"

const title = process.argv[2];
const date = new Date().toISOString().split('T')[0];
const slug = `${date}-${title.toLowerCase().replace(/\s+/g, '-')}`;
const filePath = `../src/content/blog/${slug}.mdx`;
const folderPath = `../src/images/pillar-blog/${slug}`;

const data = `---
title: ${title}
author: Steve Gourley
description: 100-160 character description of the blog post
date: ${new Date().toISOString()}
category: UGRC
tags:
- website
cover_image: /src/images/pillar-blog/default-social-card.png
cover_image_alt: ugrc social card
---
import { Octokit } from '@octokit/rest';
import matter from 'gray-matter';
import capitalize from 'lodash.capitalize';
import fs from 'node:fs';

const defaultText = (slug) => `
{/* remove if not using images */}
import { Image } from 'astro:assets';

import myImage from \`@images/blog/${slug}/image.png\`;
{/* remove if not using an image */}
import myImage from '@images/blog/${slug}/image.png';

My blog post content starts here.

{/* remove if not using an image */}
<Image src={myImage} loading="eager" alt="A sample image" />
`;

fs.writeFileSync(filePath, data, 'utf-8');
fs.mkdirSync(folderPath);
export const getDataFromIssue = (body) => {
const parts = body.split('### ');

return {
author: parts[1].substring(parts[1].indexOf('\n') + 1).trim(),
title: parts[2].substring(parts[2].indexOf('\n') + 1).trim(),
description: parts[3].substring(parts[3].indexOf('\n') + 1).trim(),
category: parts[4].substring(parts[4].indexOf('\n') + 1).trim(),
};
};

export const createNewBlogPost = (slug, blog, date) => {
const frontmatter = matter.stringify(defaultText(slug), {
title: capitalize(blog.title),
author: blog.author,
description: blog.description,
date,
category: blog.category,
cover_image: '/src/images/pillar-blog/default-social-card.png',
cover_image_alt: 'ugrc social card',
});

const filePath = `../src/content/blog/${slug}.mdx`;
const folderPath = `../src/images/pillar-blog/${slug}`;

fs.writeFileSync(filePath, frontmatter, 'utf-8');
fs.mkdirSync(folderPath);
fs.writeFileSync(`${folderPath}/.placeholder`, 'delete this file if you are not adding images', 'utf-8');
};

if (process.env.NODE_ENV !== 'test') {
const issueNumber = process.argv[2];

if (!issueNumber) {
console.error('Missing an issue number');
process.exit(1);
}

const octokit = new Octokit();
const issue = await octokit.rest.issues.get({
owner: 'agrc',
repo: 'gis.utah.gov',
issue_number: issueNumber,
});

if (!issue.data.body.includes('<!-- bot = {"type":"blog-post"} -->')) {
console.debug('This issue does not contain the correct metadata');
process.exit(0);
}

const blog = getDataFromIssue(issue.data.body);

const date = new Date().toISOString();
const noTime = date.split('T')[0];

const slug = `${noTime}-${blog.title.toLowerCase().replace(/\s+/g, '-')}`;

createNewBlogPost(slug, blog, date);
}
46 changes: 46 additions & 0 deletions scripts/new-blog.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import mock from 'mock-fs';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { describe, it } from 'node:test';

import { createNewBlogPost, getDataFromIssue } from './new-blog.mjs';

describe('new-blog', () => {
it('should parse the body into parts', () => {
const body = `### Blog author\nSean Fernandez\n### Blog title\nGPS Network Highlights\n### Blog description\nUGRC is excited to share TURN GPS network updates including the network expansion of a full GNSS solution, and improvements to the servers.\n### Blog category\nTURN\n### <!---->\n<!-- bot = {"type":"blog-post"} -->`;
const blog = getDataFromIssue(body);

assert.equal(blog.title, 'GPS Network Highlights');
assert.equal(
blog.description,
'UGRC is excited to share TURN GPS network updates including the network expansion of a full GNSS solution, and improvements to the servers.',
);
assert.equal(blog.category, 'TURN');
assert.equal(blog.author, 'Sean Fernandez');
});

it('create a new blog post', () => {
const blog = {
title: 'GPS Network Highlights',
description: '120-160 characters.',
category: ['TURN'],
author: 'Sean Fernandez',
};

const filePath = '../src/content/blog/2024-07-15-gps-network-highlights.mdx';
const slug = '2024-07-15-gps-network-highlights';
createNewBlogPost(slug, blog, '2024-07-15T09:24:53.000Z');

const file = fs.readFileSync(filePath, 'utf-8');

assert.ok(file.includes('---'));
assert.ok(file.includes('title: Gps network highlights'));
assert.ok(file.includes('description: 120-160 characters.'));
assert.ok(file.includes(`date: '2024-07-15T09:24:53.000Z'`));
assert.ok(file.includes('author: Sean Fernandez'));
assert.ok(file.includes('category:'));
assert.ok(file.includes(' - TURN'));

mock.restore();
});
});
Loading