mcpField
W
wjs-converting-wp-to-hugo

wjs-converting-wp-to-hugo

github.com/jianshuo開発137103 回インストール今日更新
リポジトリ

wjs-converting-wp-to-hugo について

wjs-converting-wp-to-hugoは、WordPressのエクスポートデータをHugoの静的サイトへ移行するスキルです。MarkdownとGitでの管理を目指す人向けに、WXRとuploadsフォルダーをオフラインで変換し、既存の記事URLを維持します。

主な機能

  1. 01WXRからMarkdownへの変換
  2. 02ローカル画像と添付ファイルのコピー
  3. 03既存記事URLの維持
  4. 04Hugoテーマとビルド手順の生成
  5. 05変換テストとビルド後リンクの検証
  6. 06機密入力とコンテンツ外ページの除外

活用シーン

  1. 01WordPressサイトの静的サイトへの移行
  2. 02GitとMarkdownによる記事管理
  3. 03検索や外部参照で使われる既存リンクの維持
  4. 04GitHub Pagesを通じたHugoサイトの公開

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jianshuo/claude-skills --skill wjs-converting-wp-to-hugo

インストール後、Claude Code で/wjs-converting-wp-to-hugoを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
gitignore
hugo.toml
workflow-hugo.yml
assets/layouts/
index.html
assets/layouts/_default/
archives.html
baseof.html
list.html
single.html
terms.html
assets/layouts/partials/
style.html
scripts/
verify_build.py
wxr_to_hugo.py
tests/
test_wxr.py

詳細

wjs-converting-wp-to-hugo

任意の WordPress サイトを Hugo + Markdown + git の静的サイトに移行し、GitHub Pages にデプロイします。 必要な入力は 2 つだけで、全工程をオフラインで実行でき、サードパーティへの依存はありません。

  1. WXR エクスポート — WordPress 管理画面の ツール → エクスポート → すべてのコンテンツ から取得した *.xml(すべての投稿/ページ/カテゴリー/タグの HTML 本文を含みます)。
  2. uploads/ フォルダー — サイトの wp-content/uploads/(年/月 単位でディレクトリ分けされた画像と添付ファイル)。

生成物:content/*.md + static/wp-content/uploads/ + 手書きのシンプルなテーマ。Hugo でビルドし、GitHub Actions で公開します。 サイト全体の URL は /archives/<数字>/ のまま維持され、古いリンクは 100%そのまま使えます。

使用する場合

  • WordPress サイトの動的機能/コメント/データベースをなくし、git + Markdown で管理したい場合。
  • WXR .xml と uploads/ を提供できる(または入手できる)場合。
  • 古いリンクを維持する必要がある場合(SEO/外部からの参照)。

使用しない場合

  • WXR がなく、オンラインのサイトしかない → まず WP 管理画面でエクスポートするか、REST API で JSON を取得してください(このスキルでは、より移植性の高い WXR を使います)。
  • コメント/会員機能/検索などの動的機能を維持したい場合 → 静的サイトでは実現できないため、適していません。
  • サイトがごく小規模(数件の記事)→ 手作業のほうが速いです。

基本原則

WXR を唯一の信頼できる情報源とし、uploads/ はそのまま静的リソースとして扱います。 画像はダウンロードも名前変更もしません。uploads/ を static/wp-content/uploads/ にコピーし、本文中の画像 URL をルート相対の /wp-content/uploads/... に変更するだけで、そのまま参照できます。記事の URL は <link> の内容をそのまま維持します。変換器は純粋関数 + 単体テストで構成し、テストを先に書きます。

パイプライン

WXR .xml + uploads/  →  wxr_to_hugo.py  →  content/*.md + static/wp-content/uploads/  →  hugo build  →  GitHub Actions  →  Pages

必ずユーザーに確認する 2 つの判断(黙って決めないこと)

WordPress には、静的サイトでは扱えないコンテンツが 2 種類あります。必ずユーザーに確認し、勝手に公開しないでください。

  1. パスワード保護された投稿(<wp:post_password> が空でないもの)。静的サイトにはパスワード保護機能がないため、公開すると誰でも閲覧できます。 選択肢:除外(デフォルト。最も安全ですが、URL は 404 になります)/公開する/draft にする。 件数の確認には必ず ElementTree(つまり parse_items)を使い、grep をそのまま使わないでください:<wp:post_password> の値は CDATA で囲まれているため(<![CDATA[secret]]>)、grep '<wp:post_password>[^<]*</...>' を使うとすべて空として扱われ、「パスワード保護された投稿は 0 件」と誤判定し、実際にパスワードが設定された投稿を見落とします(maggiacito.com での実例。1 件を公開してしまうところでした)。
  2. WordPress のひな形ページ(sample-page、プラグインのショートコードを含む login/register/findpassword などのページ、空ページ、ログインデザイナーのプレビュー用ページ)。 デフォルトでは除外します。これらはコンテンツではありません。is_real_page() は「本文が空/ショートコードが 1 つだけ/デフォルトの slug ブラックリスト」に基づいてフィルタリングします。

この 2 種類はいずれも変換器で除外するよう実装済みです。AskUserQuestion で確認してから全件を変換してください。

手順

1. 入力を配置し、プロジェクトを作成する
mkdir -p ~/code/<site> && cd ~/code/<site> && git init
# 把 WXR 拷进来(注意:WXR 含密码文章正文 + 作者邮箱,勿提交!见「安全」)
cp /path/to/<site>.WordPress.*.xml .
# uploads/ 放到工程根(含子目录 年/月)。注意它可能含 wordpress_db.sql —— 勿提交!
cp -R /path/to/uploads ./uploads
mkdir -p scripts tests content/posts layouts/_default layouts/partials static

このスキルのアセットをコピーします(ディレクトリ構成を維持してください)。

SK="$HOME/.claude/skills/wjs-converting-wp-to-hugo"
cp "$SK"/scripts/*.py scripts/                       # wxr_to_hugo.py, verify_build.py
cp "$SK"/tests/test_wxr.py tests/                     # 单元测试(须放 tests/,与 scripts/ 同级)
cp -R "$SK"/assets/layouts/. layouts/                 # 手写主题
cp "$SK"/assets/hugo.toml .                           # 改 title / baseURL / 菜单
mkdir -p .github/workflows && cp "$SK"/assets/workflow-hugo.yml .github/workflows/hugo.yml
cp "$SK"/assets/gitignore .gitignore
printf '%s' '<你的域名,如 huixianju.cn>' > static/CNAME   # 自定义域名
2. まずテストを実行する(変換器は TDD で開発します)
python3 tests/test_wxr.py     # 期望 ALL PASS;改任何转换逻辑都先加失败测试
3. 件数を確認してから、全件を変換する
python3 scripts/wxr_to_hugo.py <site>.WordPress.*.xml

レポートに posts / pages / images / uploads_copied / warnings が出力されます。記事数が WP 管理画面の件数と一致することを確認してください。 warnings には、本文が空の記事、スキップされたひな形ページ、外部画像が表示されます。

4. ビルドし、古いリンクがすべてヒットすることを検証する
hugo --gc --minify              # 没装:brew install hugo(要 extended)
python3 scripts/verify_build.py <site>.WordPress.*.xml   # checked N posts, missing 0
5. ローカルで目視確認する
hugo server -p 1313

オンラインのサイトと照らし合わせて 5 件を確認します(画像付き記事 1 件、リンクが複数ある記事 1 件を含めます)。タイトル、リスト、リンク、画像、動画が正しく表示されるか確認してください。 重要:リンクはページ相対(../../...)であること、画像はオンラインからではなくローカルの /wp-content/uploads/ から読み込まれることを確認してください。

6. GitHub にプッシュする(公開リポジトリについては「セキュリティ」を参照)
gh repo create <site> --public --source=. --remote=origin
git push -u origin main
7. Pages → Actions を有効にする。先に有効にしてから実行する
gh api -X POST repos/<owner>/<site>/pages -f build_type=workflow

注意点:Pages を有効にする前に push すると、最初の Action は configure-pages で 404 エラーになり失敗します。Pages を有効にしたら、再実行してください。

gh workflow run "Deploy Hugo site to Pages" --repo <owner>/<site>
gh run watch <run-id> --repo <owner>/<site> --exit-status

仮 URL https://<owner>.github.io/<site>/ を検証します。ホーム、一つの記事、カテゴリー、index.xml、画像 1 枚がすべて 200 を返すことを確認してください。 (デプロイ直後、画像が一時的に 301 を返す場合があります。CDN のウォームアップによるもので、リダイレクトをたどれば最終的に 200 になります。)

8. DNS を切り替える(操作者が手動で実施。検証に合格してから行う)

まず仮 URL でサイト全体に問題がないことを確認します。WP は引き続きオンラインのままなので、リスクはありません。 その後、DNS プロバイダー(Cloudflare など)でドメインを Pages に向けます。

  • apex:A レコード → 185.199.108.153 / 109.153 / 110.153 / 111.153、または CNAME → <owner>.github.io。
  • Cloudflare のオレンジ雲プロキシを使う場合、SSL/TLS を Full に設定します。初回の証明書発行時は、一時的に灰色の雲(DNS のみ)にできます。
  • DNS の反映後、Pages で Enforce HTTPS をオンにします。
  • 数日間オンラインで安定してから、古い WP を停止します(まず停止してバックアップを残し、ロールバックが不要だと確認してから完全に削除します)。

変換器で遭遇した問題(wxr_to_hugo.py で修正済み。元に戻さないこと)

問題現象修正方法
ハイパーリンクの href が失われる<a href> がテキストだけになり、URL が失われる<a> 内のテキストを蓄積し、閉じタグで [テキスト](href) を出力する
ギャラリーに余分な - が付く(figure 版)画像付き記事で各画像の前に空のリスト記号が付くfigure スタックで wp-block-gallery を判定し、ギャラリー内の <li> では - を出力しない
ギャラリーに余分な - が付く(ul 版)初期の Gutenberg は <figure> で囲まず、<ul> に wp-block-gallery を指定するため、前の行の figure 判定で検出できず、画像ごとに孤立した - が残る_ul_stack でも <ul class=wp-block-gallery> を判定し、ギャラリー内の <li> では - を出力しない(maggiacito.com での実例)
CJK パーマリンクのエンコードパーマリンクが URL エンコードされた中国語になっている(/sculpting-in-time/%e4%ba%8c…/)。そのまま維持すると、Hugo は文字どおり %e4%.. のディレクトリを作成します。サーバーはリクエスト内の %xx をデコードするため一致せず、古いリンクがすべて切れます_norm_url() で unquote() を使ってパスをデコードし、中国語のディレクトリを Hugo に作成させます。静的ホストは受信した %xx をデコードして一致させるため、エンコード版とデコード版の両方の古いリンクが機能します。数字の /archives/<id>/ には影響しません(maggiacito.com での実例)
画像がオンラインから読み込まれる本文中の画像が絶対 URL https://サイト/wp-content/... になっている_root_relative() で自サイト上の画像を /wp-content/... に変更し、外部画像は絶対 URL のまま維持する
動画/埋め込みが失われる本文中の <video>/<iframe> が空になるHTML のまま透過し、hugo.toml で goldmark unsafe=true を有効にする
lastmod が空になるWXR に wp:post_modified がない場合がある存在しない場合は wp:post_date にフォールバックする
クラシックエディターのソフト改行本文中の裸の \n が無視されるhandle_data で裸のテキストの改行を保持する
エンティティがデコードされないタイトルに &#038; が残るhtml.unescape() を使用し、タイトル内の引用符をシングルクォートに置き換える
新しい記事の投稿番号が飛ぶURL 番号を手動で付けると重複する可能性があるnext_archive_id() で既存の最大番号を調べ、+1 する

リンクの移植性

hugo.toml で relativeURLs = true + canonifyURLs = false を設定すると、Hugo はすべてのリンクをページ相対 (../../archives/123/)で出力します。そのため、public/ 内のコンテンツは file://、サブパス(github.io/<repo>/)、カスタムドメインのいずれでもリンクをクリックできます。 画像 URL は変換器でルート相対に変更済みです。Hugo がさらに相対パスに変換するため、ローカルでもオンラインでも参照できます。

安全(公开仓库必读)

WXR 和原始 uploads/ 含敏感数据,绝不进 git:

  • WXR .xml 含密码保护文章的正文(正是你从站点排除的内容)和作者邮箱。
  • uploads/wordpress_db.sql 是整库 dump(用户、密码哈希)。转换器拷贝时已跳过 .sql/.DS_Store。
  • assets/gitignore 用根锚定 /uploads/(不能写 uploads/,否则会连 static/wp-content/uploads/ 一起忽略,图片就传不上去)。它还忽略 *.WordPress.*.xml、public/、resources/。
  • 若 WXR 之前已被 commit:gitignore 对已跟踪文件无效,要先 git rm --cached <xml>。
  • 推公开仓库前,若历史里有 WXR、含基础设施细节的设计文档(实例 ID / IP / 云命令)→ 重建干净历史:
    git checkout --orphan _clean && git add -A && git commit -m "..." && git branch -D main && git branch -m main
    
    确认 git diff --cached --name-only | grep -iE 'WordPress.*xml|wordpress_db|\.sql$' 为空再推。

主题

assets/layouts/ 是手写极简主题,零外部依赖(不用 submodule / PaperMod,省掉外来构建代码与权限麻烦)。 CJK 友好、首页文章列表、分类页、按年归档、文章页上下篇、/feed/ → /index.xml 老 RSS 兼容。 改 static/CNAME 为目标域名。

以后写新文章

ID=$(python3 -c "import sys;sys.path.insert(0,'scripts');import wxr_to_hugo;print(wxr_to_hugo.next_archive_id('content'))")
# 建 content/posts/$ID.md,front matter 写 url: /archives/$ID/,与老文章同一号段
git push   # Action 自动构建上线

验收

  • 文章数与 WP 后台一致;每个 /archives/<id>/ 在 public/ 命中(verify_build.py)
  • 图片全本地化,无外链 wp-content;视频/iframe 透传可播
  • 密码文章、脚手架页已按用户决定处理
  • 链接页面相对,file:///子路径/域名都可点
  • WXR / db.sql / 原始 uploads 未进 git;公开仓库历史无敏感数据
  • 临时 Pages 地址全站 200,DNS 切换后老链接 200

コメント