Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/pages-deploy.yml
Original file line number Diff line number Diff line change
@@ -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
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)だけは埋め込みません。完全オフラインではシステムフォントにフォールバックします(レイアウトは崩れません)。

Expand Down
27 changes: 27 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
正本は各章の index.html(shared/ 参照版)のまま。dist/ はビルド成果物で
git 管理外。内容や shared/ を変更したら `python build.py` で再生成する。

SEO / 重複コンテンツ対策:
完全版 HTML には `<link rel="canonical" href="編集用URL">` と
`<meta name="robots" content="noindex, follow">` を自動挿入する。
Pages 上で 2 系統の URL(章フォルダ / dist/)が同一コンテンツを返すため、
検索エンジンには編集用 URL を正本として扱わせる。

既知の制約:
theme.css は Google Fonts を @import している。完全オフラインでは
Web フォントが読めず、システムフォントにフォールバックする
Expand All @@ -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:", "//")

Expand Down Expand Up @@ -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 を </head> 直前に注入する。

編集用 URL(章フォルダ)が正本であることを検索エンジンに伝え、
完全版 URL(dist/<章名>.html)の重複 indexing を避ける。
"""
canonical = f"{CANONICAL_BASE}/{chapter_name}/"
tags = (
f'<link rel="canonical" href="{canonical}">\n'
f'<meta name="robots" content="noindex, follow">\n'
)
if "</head>" in html:
return html.replace("</head>", tags + "</head>", 1)
# </head> が無いケースは想定外。head 解析より、検出して失敗させる方が安全
raise RuntimeError(f"{chapter_name}: </head> が見つからず canonical を挿入できません")


def main() -> int:
# Windows の既定コンソール(cp932)でも日本語を出せるようにする
try:
Expand All @@ -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(
Expand Down
Loading