diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml new file mode 100644 index 00000000..507aacd1 --- /dev/null +++ b/.github/workflows/markdownlint.yml @@ -0,0 +1,154 @@ +name: Markdownチェック(reviewdog) + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "docs/**/*.md" + - ".github/**/*.md" + push: + paths: + - "docs/**/*.md" + - ".github/**/*.md" + +permissions: + contents: read + +concurrency: + group: markdownlint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + markdownlint-pr: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Install tools + run: npm install -g markdownlint-cli@0.48.0 + + - name: Log goコマンド確認開始 + run: echo "goコマンドの存在を確認します" >&2 + + - name: Check Go availability + run: | + if ! command -v go >/dev/null 2>&1; then + echo "goコマンドが見つからないためジョブを失敗扱いにします" >&2 + exit 1 + fi + + - name: Install reviewdog + run: | + go install github.com/reviewdog/reviewdog/cmd/reviewdog@v0.21.0 + echo "$HOME/go/bin" >> "$GITHUB_PATH" + + - name: Log markdownlint開始 + run: echo "markdownlintとreviewdogを実行します" >&2 + + - name: Markdown lint + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + # 複数コマンドをパイプで繋いでいないため pipefail は省略 + exit_code=0 + markdownlint "docs/**/*.md" ".github/**/*.md" > markdownlint.txt || exit_code=$? + reviewdog_exit=0 + reviewdog < markdownlint.txt \ + -f=markdownlint \ + -name="Markdownチェック" \ + -reporter="github-pr-review" \ + -level=warning || reviewdog_exit=$? + # markdownlint: 1=lint違反, 2以上=実行エラー + # reviewdog: 0=成功(PRコメント/チェックの投稿が成功) + if [ "$reviewdog_exit" -ne 0 ]; then + echo "reviewdogが失敗しました: exit code ${reviewdog_exit}" >&2 + exit "$reviewdog_exit" + fi + # exit_code=1 は reviewdog で通知済みのため成功扱い + if [ "$exit_code" -gt 1 ]; then + echo "markdownlintが失敗しました: exit code ${exit_code}" >&2 + exit "$exit_code" + fi + + - name: Log markdownlint完了 + run: echo "markdownlintとreviewdogの処理が完了しました" >&2 + + markdownlint-push: + if: github.event_name == 'push' + runs-on: ubuntu-latest + + permissions: + contents: read + checks: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Install tools + run: npm install -g markdownlint-cli@0.48.0 + + - name: Log goコマンド確認開始 + run: echo "goコマンドの存在を確認します" >&2 + + - name: Check Go availability + run: | + if ! command -v go >/dev/null 2>&1; then + echo "goコマンドが見つからないためジョブを失敗扱いにします" >&2 + exit 1 + fi + + - name: Install reviewdog + run: | + go install github.com/reviewdog/reviewdog/cmd/reviewdog@v0.21.0 + echo "$HOME/go/bin" >> "$GITHUB_PATH" + + - name: Log markdownlint開始 + run: echo "markdownlintとreviewdogを実行します" >&2 + + - name: Markdown lint + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + # 複数コマンドをパイプで繋いでいないため pipefail は省略 + exit_code=0 + markdownlint "docs/**/*.md" ".github/**/*.md" > markdownlint.txt || exit_code=$? + reviewdog_exit=0 + reviewdog < markdownlint.txt \ + -f=markdownlint \ + -name="Markdownチェック" \ + -reporter="github-check" \ + -level=warning || reviewdog_exit=$? + # markdownlint: 1=lint違反, 2以上=実行エラー + # reviewdog: 0=成功(PRコメント/チェックの投稿が成功) + if [ "$reviewdog_exit" -ne 0 ]; then + echo "reviewdogが失敗しました: exit code ${reviewdog_exit}" >&2 + exit "$reviewdog_exit" + fi + # exit_code=1 は reviewdog で通知済みのため成功扱い + if [ "$exit_code" -gt 1 ]; then + echo "markdownlintが失敗しました: exit code ${exit_code}" >&2 + exit "$exit_code" + fi + + - name: Log markdownlint完了 + run: echo "markdownlintとreviewdogの処理が完了しました" >&2 diff --git a/.github/workflows/mermaid-lint.yml b/.github/workflows/mermaid-lint.yml new file mode 100644 index 00000000..6b041cc3 --- /dev/null +++ b/.github/workflows/mermaid-lint.yml @@ -0,0 +1,221 @@ +name: Mermaid図チェック(reviewdog) + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "docs/**/*.md" + - ".github/**/*.md" + +permissions: + contents: read + +concurrency: + group: mermaid-lint-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + mermaid: + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Log Mermaid CLIインストール開始 + run: echo "Mermaid CLIをインストールします" >&2 + + - name: Install Mermaid CLI + run: | + set -e + npm install -g @mermaid-js/mermaid-cli@11.12.0 + + - name: Log Mermaid CLIインストール完了 + run: echo "Mermaid CLIのインストールが完了しました" >&2 + + - name: Log mmdcコマンド確認開始 + run: echo "mmdcコマンドの存在を確認します" >&2 + + - name: Check mmdc availability + run: | + if ! command -v mmdc >/dev/null 2>&1; then + echo "mmdcコマンドが見つからないためジョブを失敗扱いにします" >&2 + exit 1 + fi + + - name: Log goコマンド確認開始 + run: echo "goコマンドの存在を確認します" >&2 + + - name: Check Go availability + run: | + if ! command -v go >/dev/null 2>&1; then + echo "goコマンドが見つからないためジョブを失敗扱いにします" >&2 + exit 1 + fi + + - name: Install reviewdog + run: | + go install github.com/reviewdog/reviewdog/cmd/reviewdog@v0.21.0 + echo "$HOME/go/bin" >> "$GITHUB_PATH" + + - name: Log Mermaidブロック抽出開始 + run: echo "Mermaidブロック抽出を開始します" >&2 + + - name: Extract Mermaid blocks + run: | + mkdir -p tmp + node <<'EOF' + const fs = require('fs') + const path = require('path') + + // Mermaid抽出ファイルと元Markdownの対応を「抽出ファイル:元ファイル:開始行」形式で記録 + // 例: tmp/diagram-1.mmd:docs/example.md:42 + const mapPath = path.join('tmp', 'map.txt') + fs.writeFileSync(mapPath, '') + + // Mermaidブロックの件数 + let index = 0 + + // 指定ディレクトリ以下のMarkdownを探索 + function walk(dir) { + if (!fs.existsSync(dir)) return + fs.readdirSync(dir).forEach((entry) => { + const full = path.join(dir, entry) + const stat = fs.statSync(full) + if (stat.isDirectory()) { + walk(full) + return + } + if (full.endsWith('.md')) { + processFile(full) + } + }) + } + + function processFile(file) { + const lines = fs.readFileSync(file, 'utf8').split('\n') + let inside = false + let buffer = [] + let startLine = 0 + + lines.forEach((line, idx) => { + const trimmed = line.trim() + // Mermaidブロックの開始 + if (!inside && trimmed.startsWith('```mermaid')) { + inside = true + buffer = [] + startLine = idx + 1 + return + } + + // Mermaidブロックの終了 + if (inside && trimmed.startsWith('```')) { + inside = false + const name = `tmp/diagram-${++index}.mmd` + fs.writeFileSync(name, buffer.join('\n')) + fs.appendFileSync(mapPath, `${name}:${file}:${startLine}\n`) + return + } + + if (inside) buffer.push(line) + }) + + // ファイル末尾まで到達しても終了フェンスが無い場合(閉じ忘れ)もブロックを検証対象に含める + if (inside) { + const name = `tmp/diagram-${++index}.mmd` + fs.writeFileSync(name, buffer.join('\n')) + fs.appendFileSync(mapPath, `${name}:${file}:${startLine}\n`) + } + } + + walk('docs') + walk('.github') + console.error(`抽出したMermaid図: ${index}件`) + EOF + + - name: Log Mermaidブロック抽出完了 + run: echo "Mermaidブロック抽出が完了しました" >&2 + + - name: Log Mermaid図検証開始 + run: echo "Mermaid図の検証を開始します" >&2 + + - name: Validate Mermaid diagrams + run: | + : > tmp/result.txt + MAX_ERROR_LENGTH=500 + # Mermaid CLIの実行環境起因エラー(Puppeteer/ブラウザ起動失敗等)を判定するための正規表現パターン + # 各要素は正規表現として評価するため、必要に応じてメタ文字を使用/エスケープする + # 出力メッセージの大文字小文字揺れを吸収するため、grepは -i を使用する + MMD_CLI_EXEC_ERROR_PATTERNS=( + # Puppeteer関連のエラー文言(例: "Error: Puppeteer failed to connect") + 'error.*puppeteer' + # 実行ファイル未検出(例: "Executable doesn't exist at /path/to/chrome") + "executable doesn't exist" + # browser executable.*exist は実行ファイル探索時の存在エラー検知を意図(例: "browser executable doesn't exist") + 'browser executable.*exist' + # 起動失敗の共通文言(例: "Failed to launch the browser process!") + 'failed to launch' + # browsertype.launch のドットはリテラルとして扱う(例: "browserType.launch: Executable doesn't exist ...") + 'browsertype\.launch' + ) + # 配列要素を | で連結して正規表現文字列を生成(例: error.*puppeteer|...) + MMD_CLI_EXEC_ERROR_REGEX=$(IFS='|'; printf '%s' "${MMD_CLI_EXEC_ERROR_PATTERNS[*]}") + cat <<'JSON' > tmp/puppeteer-config.json + { + "args": ["--no-sandbox", "--disable-setuid-sandbox"] + } + JSON + if [ -s tmp/map.txt ]; then + mmdc_exec_error=0 + mmdc_exec_summary="" + while IFS=":" read -r mmd src line; do + if [ -z "${mmd}" ]; then + continue + fi + if ! output=$(mmdc -p tmp/puppeteer-config.json -i "${mmd}" -o "${mmd}.svg" 2>&1); then + summary=$(printf "%s" "${output}" | tr '\n' ' ' | cut -c1-${MAX_ERROR_LENGTH}) + if printf "%s" "${output}" | grep -qiE "${MMD_CLI_EXEC_ERROR_REGEX}"; then + mmdc_exec_error=1 + mmdc_exec_summary="${summary}" + echo "mmdcの実行に失敗しました: ${summary}" >&2 + # 実行環境エラーは全図に影響するため、以降の検証を中断してジョブを失敗扱いにする + break + fi + echo "${src}:${line}: Mermaid図の構文検証に失敗しました: ${summary}" >> tmp/result.txt + fi + done < tmp/map.txt + if [ "${mmdc_exec_error}" -ne 0 ]; then + echo "Mermaid CLIの実行エラーのためジョブを失敗扱いにします: ${mmdc_exec_summary}" >&2 + exit 1 + fi + else + echo "Mermaidブロックが見つからなかったため検証をスキップします" >&2 + fi + + - name: Log Mermaid図検証完了 + run: echo "Mermaid図の検証が完了しました" >&2 + + - name: Log reviewdog通知開始 + run: echo "reviewdogで結果を通知します" >&2 + + - name: Report via reviewdog + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + reviewdog < tmp/result.txt \ + -efm="%f:%l: %m" \ + -name="Mermaid図チェック" \ + -reporter=github-pr-review \ + -level=error + + - name: Log reviewdog通知完了 + run: echo "reviewdogの通知処理が完了しました" >&2 diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..26593a22 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,4 @@ +{ + "MD013": false, + "MD033": false +}