2021/11/05
CLIでブログ記事のテンプレートを出力できるようにした
きっかけ
このブログはGatsby.jsを利用して構築した。
特にHeadless CMSなどは使用しておらず、markdownで特定のディレクトリ配下に記事を配置すると記事が表示されるようにしている。
構成としてはこんな感じ。
プロジェクト/
└ src/
└ resources/
└ markdown/
├ 記事その1.md
├ 記事その2.md
├ 記事その3.md
...
各markdownにはヘッダーとして以下のようなものを記載する必要があり、ヘッダーの内容を基に記事のタイトルとか日付などを出力している。
---
title: "記事のタイトル"
date: '2021/11/05' <=記事の作成日付
slug: 記事のURL
tags: [記事、タグ]
description:
---
ここから記事本文......
ファイル名はなんでも良いのだが、編集を楽にするためにslugで設定したURLと同じにしている。
記事を書くたびに1ファイルずつ手作業で作成し、内容を記載していたが、割と面倒だったのでCLIで最低限のテンプレートを作ってもらえるようなものを作った。
作りたいもの
yarn run new-article
をプロジェクト配下で実行すると新規のmarkdownファイルが生成されるというもの。
プロジェクト/
└ src/
└ resources/
└ markdown/
├ 新しい記事.md
...
要件としては以下の通り。
- CLIは対話型で、ファイル名とtagsを入力。
- markdownファイルヘッダーは以下のように記載される
- title: ブランク
- date: CLI実行日のY/m/d
- tags: 入力されたtagsを出力
- description: ブランク
titleは対話形式で入力するには長くてしんどくなりそうなのであえてSKIPした。
CLIを作る言語はなんでも良いが、Gatsby.jsで作っているのでJavaScriptとする。
作り方
CLIの雛形作成
まずはコマンド打つとHello Worldを出力するようなCLIを作る。
package.json内で下記の記述を追加。
"bin": {
"new-article": "bin/newArticle/cli.js"
},
"scripts": {
......
"new-article": "node bin/new-article/cli"
},
プロジェクト配下にbinフォルダを作り、newArticle/cli.jsを追加。
プロジェクト/
└ bin/
└ newArticle/
└ cli.js
cli.jsの中身は以下の通り。
#!/usr/bin/env node
'use strict'
console.log('Hello World');
ここまでできたら一回叩いてみる。
$ yarn run new-article
Hello World
無事成功。
対話形式で作る
次は作ったCLIを対話型にするための準備をする。
コールバックの処理が面倒なので readline-syncを使用
readline.questionで質問ができ、入力値を受け取れる。
まずはシンプルに名前を聞いてみる。
const readline = require('readline-sync');
const question = () => {
return readline.question("What is your name? ");
}
const name = question();
console.log("Hello " + name);
実行
yarn run new-article
What is your name? makoto
Hello makoto
こんな感じで対話型のCLIが作れることを確認できた。
ファイルの出力
あとは入力値を基に、ファイルを出力せねばならない。 ライブラリはfsを使用。
連続でファイル書き込みを行うので、createWriteStreamを使用。
writeでファイル書き込みを行い、終わったらendを呼ぶだけでcreateWriteStream実行時に指定したファイルを出力できる。
const fs = require("fs");
const stream = fs.createWriteStream(__dirname + "test.txt");
stream.write("Hello\n");
stream.write("World\n");
stream.end("\n");
実行するとファイルが作成されている。
プロジェクト/
└ bin/
└ newArticle/
├ cli.js
└ test.txt
中身も予想通り。
Hello
World
色々試してみて、想像したものが作れそうということが分かった。
実際にできあがったもの
出来上がったものはこちら。
#!/usr/bin/env node
'use strict'
const readline = require('readline-sync');
const fs = require("fs");
const { exit } = require('process');
const questionFileName = () => {
return readline.question("fileName for your article(it will be your article's slug): ");
}
const questionTags = () => {
return readline.question("tags for your article(enter them separated by commas): ");
}
const getNowYMD = () => {
const dt = new Date();
const y = dt.getFullYear();
const m = ("00" + (dt.getMonth()+1)).slice(-2);
const d = ("00" + dt.getDate()).slice(-2);
return y + "/" + m + "/" + d;
}
const fileName = questionFileName();
const fileOutputPath = __dirname + "/../../src/resources/markdown/blog/" + fileName + ".md";
// ファイルが既に存在している場合はエラーを出す
if (fs.existsSync(fileOutputPath)) {
console.log("file already exists");
process.exit(1);
}
const tags = questionTags();
const stream = fs.createWriteStream(fileOutputPath);
stream.write("---\n");
stream.write("title: ''\n");
stream.write("date: '" + getNowYMD() + "'\n");
stream.write("slug: " + fileName + "\n");
stream.write("tags: [" + tags + "]\n");
stream.write("description: \n");
stream.write("---\n");
stream.end("\n");
// ファイル書き込みエラー処理
stream.on("error", (err)=>{
if(err)
console.log(err.message);
});
細かな修正を加えてはいるが、内容としてはそんなに難しくない。
現在日付けを出力するやり方についてはこの記事を参考にした。
実行結果
実行してみる。
yarn run new-article
fileName for your article(it will be your article's slug): test
tags for your article(enter them separated by commas): tag1, tag2
無事終了。
想定通りの場所にファイルが生成されている。
プロジェクト/
└ src/
└ resources/
└ markdown/
├ test.md
...
ファイルの中身も想定通り。
---
title: ''
date: '2021/11/06'
slug: test
tags: [tag1, tag2]
description:
---
今回早速完成したCLIを使用してテンプレートを出力したが、割と面倒な初期設定が簡単に済んだので、サクッと作れた割には効果が大きかったと思う。
まだまだ改善の余地は大きそうだが、一旦はここまでで。