{"id":17865798,"url":"https://github.com/ttskch/esa2github","last_synced_at":"2026-02-02T19:34:22.286Z","repository":{"id":66183914,"uuid":"264862817","full_name":"ttskch/esa2github","owner":"ttskch","description":"esaのコンテンツをGitHubにpushするための高機能Webhookハンドラー","archived":false,"fork":false,"pushed_at":"2023-06-14T16:48:16.000Z","size":30,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T22:41:28.196Z","etag":null,"topics":["esa","github","heroku","webhook"],"latest_commit_sha":null,"homepage":"https://zenn.dev/ttskch/articles/7423c0ab5104fd","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ttskch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-05-18T07:32:33.000Z","updated_at":"2023-08-04T20:53:56.000Z","dependencies_parsed_at":"2023-09-26T11:14:59.019Z","dependency_job_id":null,"html_url":"https://github.com/ttskch/esa2github","commit_stats":{"total_commits":23,"total_committers":2,"mean_commits":11.5,"dds":0.04347826086956519,"last_synced_commit":"0ab85322a45c9d7cf8061e4d4cb44a73fb7efe61"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ttskch/esa2github","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fesa2github","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fesa2github/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fesa2github/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fesa2github/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ttskch","download_url":"https://codeload.github.com/ttskch/esa2github/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ttskch%2Fesa2github/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260735334,"owners_count":23054657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esa","github","heroku","webhook"],"created_at":"2024-10-28T09:25:01.845Z","updated_at":"2026-02-02T19:34:17.266Z","avatar_url":"https://github.com/ttskch.png","language":"JavaScript","readme":"# esa2github\n\n## esa2githubとは\n\n[esa](https://esa.io/) で書いたMarkdownファイルをGitHubに自動でpushするための [Generic Webhook](https://docs.esa.io/posts/37) ハンドラーアプリケーションです。\n\n以下のような特長があります。\n\n* Frontmatterを付加しないようにできる\n* Frontmatterの内容を記事ごとに自由に書ける\n  * これにより、Gatsby、Hugo、VuePressなど任意の静的サイトジェネレータと併用できる\n* 指定した日時にGitHubにコミットがpushされるように予約できる\n* ワンクリックでHerokuにデプロイしてすぐに使い始められる\n\n## インストール方法\n\n### Herokuへのデプロイ\n\n[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/ttskch/esa2github)\n\nこのボタンを押すと、下図のような画面が開いて、あなたのHerokuアカウントにesa2githubをデプロイできます。\n\n\u003cimg src=\"https://user-images.githubusercontent.com/4360663/182005488-381e5ffe-367a-435a-a7c6-e274c562c2a4.png\" width=\"50%\"\u003e\n\n\u003e 無料枠で利用可能ですが、Heroku Addonを使用するためクレジットカード情報の登録が必須となります。\n\n環境変数の設定内容については [#環境変数](#環境変数) をご参照ください。\n\nデプロイが完了すると、`https://{アプリ名}.herokuapp.com` にてWebhookハンドラーが利用できるようになります👍\n\n### セルフホスティング\n\n```bash\n$ git clone git@github.com:ttskch/esa2github.git\n$ cd esa2github\n$ npm i\n$ npm start\n$ npm run ngrok # 開発環境向けです。本番環境では適宜Webサーバーを用意してください。\n```\n\n\u003e [#予約投稿機能](#予約投稿機能) を使用する場合は、これに加えて以下のようにワーカーを起動し、\n\u003e \n\u003e ```bash\n\u003e $ npm run worker\n\u003e ```\n\u003e \n\u003e `REDIS_URL` 環境変数にRedisサーバーのURLを設定する必要があります。\n\nこれで、`https://xxxxxxxx.ngrok.io` にてWebhookハンドラーが利用できるようになります👍\n\nただし、[ngrok](https://ngrok.com/) は本番環境で利用すべきではないので、本番環境では適宜Webサーバーを用意してください。\n\n## 環境変数\n\n各種設定は環境変数にて行います。環境変数の意味は以下のとおりです。\n\n| 環境変数 | 意味 | 必須/任意 | 設定値の例 |\n| --- | --- | --- | --- |\n| `GITHUB_OWNER` | 保存先GitHubリポジトリのオーナー名 | 必須 | `ttskch` |\n| `GITHUB_REPO` | 保存先GitHubリポジトリのリポジトリ名 | 必須 | `zenn-content` |\n| `GITHUB_BRANCH` | 保存先GitHubリポジトリのブランチ名 |  | （空欄にするとデフォルトブランチが使われます） |\n| `GITHUB_BASE_PATH` | 保存先ディレクトリへのパス |  | `articles` （空欄にするとルートに保存されます） |\n| `GITHUB_FILENAME_BY_TITLE` | 保存時のファイル名に投稿タイトルを使うか |  | `yes` （空欄にすると投稿IDが使われます） |\n| `GITHUB_ACCESS_TOKEN` | GitHubのPersonal Access Token（[ここで作成](https://github.com/settings/tokens)） | 必須 |  |\n| `ESA_DISABLE_DEFAULT_FRONTMATTER` | 作成されるファイルにデフォルトのFrontmatterを付与しなくする |  | `yes` |\n| `ESA_SECRET` | esaのGeneric Webhookにsecretが設定されている場合はその値 |  |  |\n| `REDIS_URL` | 予約投稿機能を使う場合はRedisサーバーのURL |  | `redis://127.0.0.1:6379`\u003cbr\u003e`rediss://user:password@host:port?tls=true`  |\n\n## 使い方\n\n`https://{チーム名}.esa.io/team/webhooks` を開いて、`Webhookを追加` ボタンから `Generic` を選択して設定を作成します。\n\n設定内容は例えば以下のような感じです。\n\n\u003cimg src=\"https://user-images.githubusercontent.com/4360663/182005923-a39e5deb-1f10-4955-a278-0c4c8d55250f.png\" width=\"50%\"\u003e\n\nあとは、Generic Webhookの対象としたカテゴリ配下に記事を作成または更新すれば、Webhook経由でGitHubにMarkdownファイルがpushされます👍\n\n### Frontmatterを自由に記述する\n\n[標準のGitHub Webhook](https://docs.esa.io/posts/176) では、作成されるMarkdownファイルの先頭に自動で一定のFrontmatterが付加されます。\n\n\u003e 具体的には、[記事のURLに `.md` を付けたとき](https://docs.esa.io/posts/176.md) と同じ内容でファイルが作成されます。\n\nesa2githubでもこれに合わせて、デフォルトでは以下のようなFrontmatterが付加されます。\n\n```\n---\ntitle: \"タイトル\"\ncategory: path/to/category\ntags:\n  - タグ1\n  - タグ2\npublished: true\nnumber: 123\n---\n```\n\n\u003e `created_at` `updated_at` の2つはGeneric Webhookでは取得されませんでした。\n\nこれに加えて、以下のように記事本文の先頭のコードブロックにFrontmatterを書けば、それがFrontmatterとしてマージされます。\n\n````\n```\n---\ntitle: \"上書きタイトル\"\ndate: 2020-05-20\n---\n```\n\n本文\n````\n\nこの場合、上記2つのFrontmatterはマージされて最終的なファイル内容は以下のようになります。\n\n```\n---\ntitle: \"上書きタイトル\"\ncategory: path/to/category\ntags:\n  - タグ1\n  - タグ2\npublished: true\nnumber: 123\ndate: 2020-05-20\n---\n\n本文\n```\n\nまた、`ESA_DISABLE_DEFAULT_FRONTMATTER` 環境変数に [Truthy](https://developer.mozilla.org/ja/docs/Glossary/Truthy) な値をセットしておくことで、デフォルトのFrontmatterを完全に無効にすることもできます。\n\n先ほどの例で言うと、`ESA_DISABLE_DEFAULT_FRONTMATTER` が `true` だった場合は、最終的なファイル内容はシンプルに以下のとおりになります。\n\n```\n---\ntitle: \"上書きタイトル\"\ndate: 2020-05-20\n---\n\n本文\n```\n\n`ESA_DISABLE_DEFAULT_FRONTMATTER` が `true` で、かつ本文先頭にFrontmatterの記載がない場合は、最終的なファイル内容は以下のように本文のみになります。\n\n```\n本文\n```\n\n### 予約投稿機能\n\n本文先頭のFrontmatterの `commitAt` という項目で日時を指定することで、予約投稿を行うことができます。\n\n````\n```\n---\ncommitAt: 2020-05-02 18:00:00 +0900\n---\n```\n````\n\nこれが書かれた記事のWebhookを受け取った場合、即座にcommit\u0026pushはされず、指定された日時（誤差最大1分）にスケジューリングされます。\n\n\u003e ちなみに、指定された日時がすでに過去だった場合は結果的に即座にコミットされます。\n\n日時を表す文字列のパースには [dayjs](https://github.com/iamkun/dayjs) を使っているので、dayjsが正常に解釈できる表記なら何でも指定可能です。\n\n予約投稿機能を使用する場合は、[Heroku Data for Redis](https://elements.heroku.com/addons/heroku-redis) や [Upstash](https://upstash.com/) などRedisサーバーを別途用意した上で `REDIS_URL` 環境変数にRedisサーバーのURLを設定する必要があります。\n\nなお、TLS経由での接続が必要なRedisサーバーを使用する場合、URLは `rediss://xxxxx?tls=true` といった形式にする必要があります。\n\n\u003e 参考：\u003chttps://github.com/OptimalBits/bull/issues/2325\u003e\n\n### Herokuの無料プランで予約投稿機能を使う場合の注意点\n\nesa2githubでは、予約投稿機能の実現のため、Herokuの [Worker Dyno](https://devcenter.heroku.com/articles/background-jobs-queueing) を [ONにしています](https://github.com/ttskch/esa2github/blob/master/app.json#L12-L15)。\n\n2020/05/21時点で [こちらのドキュメント](https://devcenter.heroku.com/articles/free-dyno-hours#dyno-sleeping) に\n\n\u003e Worker dynos do not sleep, because they do not respond to web requests. Be mindful of this as they may run 24/7 and consume from your pool of hours.\n\nという記述があり、Worker DynoはWeb Dynoと違って30分放置してもスリープしないっぽく見えるのですが、Herokuの中の人にTwitterで質問してみたところ、**Web Dynoがスリープするときは他のDynoも巻き込む仕様** [とのことでした](https://twitter.com/herokujp/status/1263379997294657536)。\n\nなので無料プランだと、最後にWebhookにアクセスがあってから30分で予約投稿のキューを処理するWorker Dynoがスリープしてしまって、予約時刻になってもキューが処理されず期待どおりに動作しません。\n\n\u003e 次にWebhookにアクセスがあったタイミング（esaで何かしら記事をShipItしたタイミング）でWeb Dynoと一緒にWorker Dynoも再起動して、そこで溜まっていたキューが一気に処理されるような動作になります。\n\nこれを防ぐため、esa2githubはデフォルトで [Heroku Scheduler](https://devcenter.heroku.com/articles/scheduler) をインストールします。\n\n例えばブログ記事のように投稿時刻が毎日朝9:00や夕方18:00などとと決まっているような場合であれば、Heroku Schedulerを使って毎日その直前の時刻にWeb Dyno（とWorker Dyno）を起こしてあげるようにしておくと、一応解決することができます。\n\n毎日夕方17:30（JST）にWeb Dynoを起こすには、以下のようなジョブを登録しておけばよいです。\n\n\u003cimg src=\"https://tva1.sinaimg.cn/large/007S8ZIlgy1gf060e3xpsj30ky0io40a.jpg\" width=\"50%\"\u003e\n\n\u003e Heroku Schedulerからcurlで定期的にアクセスすることでWeb DynoをスリープさせないというハックはHerokuユーザーの間ではよく知られています。\n\nなお、Web DynoもWorker Dynoもスリープしていない時間は当然に [Free Dyno Hours](https://devcenter.heroku.com/articles/free-dyno-hours) を消費するので、このように定期的に起こすのは無料枠の消費を早めます。\n\nなので、もし予約投稿機能を使わないなら、Heroku Schedulerも使わず、Worker Dyno自体もOFFにしておくとよいでしょう。\n\n![](https://tva1.sinaimg.cn/large/007S8ZIlgy1gez5gmzb5nj31wc0dg40a.jpg)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fesa2github","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fttskch%2Fesa2github","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fttskch%2Fesa2github/lists"}