Crosspost to dev.to #74
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Crosspost to dev.to | |
| # Zenn の英語自動翻訳が付いた記事を dev.to へ転載する。 | |
| # Zenn の翻訳は日本語公開から数日後に非同期で進むため、push ではなく | |
| # 毎日のポーリングで「翻訳済みか」を確認する。 | |
| # | |
| # dev.to へ出るのは 1 run で最大 1 本。下書きへ戻した記事があればその再公開を | |
| # 優先し、その日は新規転載を行わない。 | |
| on: | |
| schedule: | |
| - cron: "0 0 * * *" # 毎日 00:00 UTC = 09:00 JST | |
| workflow_dispatch: # 手動実行用 | |
| inputs: | |
| hide_slugs: | |
| description: "下書きへ戻すZenn slug(カンマ区切り)。空なら通常の転載処理" | |
| required: false | |
| default: "" | |
| refresh_canonical: | |
| description: "既存dev.to記事のcanonical_urlをブログ正本へ一括更新する" | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write # crossposted-devto.json のコミットに必要 | |
| concurrency: | |
| group: crosspost-devto | |
| cancel-in-progress: false | |
| jobs: | |
| crosspost: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: "20" | |
| - name: Install dependencies | |
| run: npm ci | |
| working-directory: .github/scripts | |
| - name: Test and validate local sources | |
| run: | | |
| npm --prefix .github/scripts test | |
| node tools/validate-content.mjs | |
| node tools/zenn-sync/sync.mjs --check | |
| - name: Check canonical refresh targets | |
| if: inputs.refresh_canonical == true | |
| run: node .github/scripts/crosspost-devto.mjs --check-canonical | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Refresh existing canonical URLs | |
| if: inputs.refresh_canonical == true | |
| run: node .github/scripts/crosspost-devto.mjs --refresh-canonical | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Persist refreshed canonical state | |
| if: inputs.refresh_canonical == true | |
| run: | | |
| if [ -n "$(git status --porcelain crossposted-devto.json)" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add crossposted-devto.json | |
| git commit -m "chore: refresh dev.to canonical URLs" | |
| git push | |
| else | |
| echo "No state change." | |
| fi | |
| - name: Hide over-posted articles | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs != '' | |
| run: node .github/scripts/crosspost-devto.mjs --hide | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| HIDE_SLUGS: ${{ github.event.inputs.hide_slugs }} | |
| - name: Persist hidden state | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs != '' | |
| run: | | |
| if [ -n "$(git status --porcelain crossposted-devto.json)" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add crossposted-devto.json | |
| git commit -m "chore: hide over-posted dev.to articles" | |
| git push | |
| else | |
| echo "No state change." | |
| fi | |
| - name: Republish one hidden article | |
| id: republish | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs == '' | |
| run: node .github/scripts/crosspost-devto.mjs --republish | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Persist republished state | |
| if: inputs.refresh_canonical != true && steps.republish.outputs.republished == 'true' | |
| run: | | |
| if [ -n "$(git status --porcelain crossposted-devto.json)" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add crossposted-devto.json | |
| git commit -m "chore: republish one dev.to article" | |
| git push | |
| else | |
| echo "No state change." | |
| fi | |
| - name: Prepare reservation or recover previous posting | |
| id: prepare | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs == '' && steps.republish.outputs.republished != 'true' | |
| run: node .github/scripts/crosspost-devto.mjs --prepare | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Persist reservation before external POST | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs == '' && steps.republish.outputs.republished != 'true' | |
| run: | | |
| if [ -n "$(git status --porcelain crossposted-devto.json)" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add crossposted-devto.json | |
| git commit -m "chore: reserve dev.to crosspost" | |
| git push | |
| else | |
| echo "No state change." | |
| fi | |
| - name: Send newly reserved article | |
| if: inputs.refresh_canonical != true && steps.prepare.outputs.created == 'true' | |
| run: node .github/scripts/crosspost-devto.mjs --send-new | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Fix one resolvable internal link set | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs == '' | |
| run: node .github/scripts/crosspost-devto.mjs --fix-links | |
| env: | |
| DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} | |
| - name: Persist finalized crosspost state | |
| if: inputs.refresh_canonical != true && github.event.inputs.hide_slugs == '' | |
| run: | | |
| if [ -n "$(git status --porcelain crossposted-devto.json)" ]; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add crossposted-devto.json | |
| git commit -m "chore: finalize dev.to crosspost state" | |
| git push | |
| else | |
| echo "No state change." | |
| fi |