From cc62942bb950b477714dc8f3a712f11f8b512ef6 Mon Sep 17 00:00:00 2001 From: watanabe-kohei-jp <283722319+watanabe-kohei-jp@users.noreply.github.com> Date: Fri, 22 May 2026 10:56:55 +0900 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=85=A8=E7=89=88=20HTML=20=E3=82=92?= =?UTF-8?q?=20GitHub=20Pages=20=E3=81=AE=20/dist/=20=E3=81=A7=E9=85=8D?= =?UTF-8?q?=E4=BF=A1=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 何をしているか main への push で `python build.py` を CI で実行し、生成された 完全版 HTML (`dist/*.html`) を既存リポと一緒に Pages にデプロイ。 これまで「ローカルでビルドして手動配布」だった完全版が、Pages 上の URL(`co-lect.github.io/lectures/dist/<章名>.html`)として常に 最新版が公開され、右クリック → 保存でダウンロードできるようになる。 ## 変更 - `.github/workflows/pages-deploy.yml` (新規) - main push でビルド + デプロイ。PR では build-only で破損検出 - `_site/` に編集用(リポ全体)+ 完全版(`dist/*.html`)を rsync で詰める - 既存の編集用 URL(章フォルダ、`shared/`、`sitemap.xml` 等)は維持 - `build.py` - 完全版 HTML の `` 直前に `` と `` を自動挿入 - 重複コンテンツ対策。検索エンジンには編集用 URL を正本として扱わせる - `README.md` - 「完全版を入手する」セクションを Pages 経由のダウンロード手順に拡張 - 取り扱い注意(dist/ を直接編集しない・force-add しない)を明文化 ## 設計判断 - **`_site` パターン採用**: Pages 設定を「Actions」に切り替えると、 artifact に含めなかった既存 URL は消える。rsync で `.git` `.github` `dist` `_site` のみ除外して残りを全部 `_site/` に入れることで、 編集用 URL の互換性を保つ - **`dist/` は引き続き git 管理外**: CI が生成・配信するため、リポに 入れる必要なし。PR #12 で確立した不変条件を維持 - **canonical/noindex を build.py 側で注入**: ワークフローではなく ビルドスクリプトで仕込むことで、ローカルビルドした完全版にも同じ メタが入る(手元配布版でも検索エンジン的に安全) - **PR ジョブは build-only**: 外部 PR で deploy 権限を持たせない ## デプロイ前に必要な手動作業 Settings → Pages → Source を「GitHub Actions」に切り替える必要あり (現状は「Deploy from a branch」想定)。切替後は本ワークフローが 唯一のデプロイ経路になる。 ## 動作確認(ローカル) - `python build.py` で 4 章すべて [OK]、size 妥当 - 全章に canonical / robots=noindex が `` 直前に挿入されることを確認 Closes #33 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/pages-deploy.yml | 73 ++++++++++++++++++++++++++++++ README.md | 27 +++++++++-- build.py | 27 +++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/pages-deploy.yml diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml new file mode 100644 index 0000000..eaee50f --- /dev/null +++ b/.github/workflows/pages-deploy.yml @@ -0,0 +1,73 @@ +name: Deploy GitHub Pages + +# main への push で Pages を自動デプロイする。 +# +# 配信物は 2 系統: +# - 編集用: 章フォルダ・shared/・sitemap.xml 等(リポをそのまま) +# - 完全版: `python build.py` で生成した dist/<章名>.html +# +# 「Deploy from a branch」から「GitHub Actions」へ切り替える必要がある +# (Settings → Pages → Source)。切替後は本ワークフローが唯一のデプロイ経路。 +# +# PR では build-only ジョブだけ走らせ、build.py の破損を merge 前に検出する。 +# 詳細: Issue #33 + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# 同時デプロイは 1 件まで。in-progress を中断しないことで、進行中の deploy を守る。 +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build standalone HTML + run: python3 build.py + + - name: Stage site + # _site/ に編集用(リポ全体)+ 完全版(dist/)の両方を詰める。 + # .git/.github/dist/_site は site 配信から除外。 + run: | + mkdir -p _site + rsync -a --delete \ + --exclude '.git' \ + --exclude '.github' \ + --exclude 'dist' \ + --exclude '_site' \ + ./ _site/ + mkdir -p _site/dist + cp dist/*.html _site/dist/ + + - name: Upload Pages artifact + # PR では artifact をアップロードしない(deploy しないため)。 + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + # PR では deploy しない。main への push / workflow_dispatch のみ。 + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 51008ad..98f41fb 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,36 @@ cd lectures --- -## 完全版(1 ファイル)をビルドする +## 完全版(1 ファイル)を入手する -各章を 1 つの HTML にまとめた「完全版」を生成できます。`shared/` の CSS・JS と画像をすべて埋め込むので、**リポジトリもネット接続もなしで開けます**。メール添付・USB 配布・オフライン閲覧に。 +各章を 1 つの HTML にまとめた「完全版」が用意されています。`shared/` の CSS・JS と画像をすべて埋め込むので、**リポジトリもネット接続もなしで開けます**。メール添付・USB 配布・オフライン閲覧に。 + +### 方法 1: ダウンロードする(推奨) + +CI が自動更新する常に最新版を Pages から取得できます。 + +``` +https://co-lect.github.io/lectures/dist/00-about.html +https://co-lect.github.io/lectures/dist/01-claude-code-intro.html +https://co-lect.github.io/lectures/dist/02-setup.html +https://co-lect.github.io/lectures/dist/03-claude-md.html +``` + +ブラウザで開いて右クリック →「名前を付けて保存」で `.html` 1 ファイルが手元に残ります。 + +### 方法 2: 自分でビルドする ```bash python build.py ``` -`dist/00-about.html` などが生成されます(`dist/` は git 管理外のビルド成果物)。正本は各章の `index.html`(`shared/` 参照版)のままなので、`shared/` や本文を変更したら `python build.py` で作り直します。依存は Python 3 標準ライブラリのみ。 +`dist/00-about.html` などが生成されます(`dist/` は git 管理外のビルド成果物)。依存は Python 3 標準ライブラリのみ。 + +### 注意 + +- `dist/` は **ビルドの度に上書きされる**。直接編集しないこと +- 配布前は **必ず `python build.py` を実行**するか、Pages の最新版を取得する +- `dist/` は git 管理外(`.gitignore` 設定済み)。`git add -f` で強制追加しないこと > Web フォント(Google Fonts)だけは埋め込みません。完全オフラインではシステムフォントにフォールバックします(レイアウトは崩れません)。 diff --git a/build.py b/build.py index fe2d1f8..ebf0a2e 100644 --- a/build.py +++ b/build.py @@ -8,6 +8,12 @@ 正本は各章の index.html(shared/ 参照版)のまま。dist/ はビルド成果物で git 管理外。内容や shared/ を変更したら `python build.py` で再生成する。 +SEO / 重複コンテンツ対策: + 完全版 HTML には `` と + `` を自動挿入する。 + Pages 上で 2 系統の URL(章フォルダ / dist/)が同一コンテンツを返すため、 + 検索エンジンには編集用 URL を正本として扱わせる。 + 既知の制約: theme.css は Google Fonts を @import している。完全オフラインでは Web フォントが読めず、システムフォントにフォールバックする @@ -27,6 +33,9 @@ ROOT = Path(__file__).resolve().parent DIST = ROOT / "dist" +# Pages 上の編集用 URL のベース。完全版 HTML の canonical に使う。 +CANONICAL_BASE = "https://co-lect.github.io/lectures" + # 外部 / data URI は埋め込み対象外(そのまま残す) _SKIP_PREFIXES = ("http://", "https://", "data:", "//") @@ -97,6 +106,23 @@ def img_repl(m: re.Match) -> str: return html +def inject_seo_meta(html: str, chapter_name: str) -> str: + """完全版 HTML に canonical と robots=noindex を 直前に注入する。 + + 編集用 URL(章フォルダ)が正本であることを検索エンジンに伝え、 + 完全版 URL(dist/<章名>.html)の重複 indexing を避ける。 + """ + canonical = f"{CANONICAL_BASE}/{chapter_name}/" + tags = ( + f'\n' + f'\n' + ) + if "" in html: + return html.replace("", tags + "", 1) + # が無いケースは想定外。head 解析より、検出して失敗させる方が安全 + raise RuntimeError(f"{chapter_name}: が見つからず canonical を挿入できません") + + def main() -> int: # Windows の既定コンソール(cp932)でも日本語を出せるようにする try: @@ -116,6 +142,7 @@ def main() -> int: for ch in chapters: html = (ch / "index.html").read_text(encoding="utf-8-sig") built = inline(html, ch) + built = inject_seo_meta(built, ch.name) # 未解決のローカル参照(../ や _assets/)が残っていないか検証 leftover = sorted(set(re.findall(