--- name: wjs-converting-wp-to-hugo description: Use when migrating a WordPress site to a Hugo static site on GitHub Pages from a WXR export (.xml) plus the wp-content/uploads folder — preserving /archives// URLs, localizing images, and deploying via GitHub Actions. Triggers — "把 WordPress 迁成 Hugo", "wordpress 转静态站", "migrate WordPress to Hugo", "WXR to Hugo", "publish WordPress to GitHub Pages", "/wjs-converting-wp-to-hugo". --- # wjs-converting-wp-to-hugo 把任意 WordPress 站迁成 **Hugo + Markdown + git** 静态站,部署到 **GitHub Pages**。 输入只需两样,**全程离线、零第三方依赖**: 1. **WXR 导出** — WordPress 后台 `工具 → 导出 → 所有内容` 得到的 `*.xml`(包含全部文章/页面/分类/标签的 HTML 正文)。 2. **`uploads/` 文件夹** — 站点的 `wp-content/uploads/`(按 `年/月` 分目录的图片与附件)。 产出:`content/*.md` + `static/wp-content/uploads/` + 手写极简主题,Hugo 构建,GitHub Actions 发布。 **全站 URL 保持 `/archives/<数字>/` 不变,老链接 100% 不断。** ## When to use - 用户有一个 WordPress 站,想去掉动态/评论/数据库,改成 git + Markdown 维护。 - 用户提供了 WXR `.xml` 和 `uploads/`(或能拿到)。 - 老链接必须保留(SEO / 外部引用)。 ## When NOT to use - 没有 WXR,只有线上站 → 先在 WP 后台导出,或用 REST API 拉 JSON(本 skill 走 WXR,更可移植)。 - 要保留评论/会员/搜索等动态功能 → 静态站做不了,不适用。 - 站点极小(几篇)→ 手抄更快。 ## Core principle **WXR 是唯一真相源,`uploads/` 直接当静态资源。** 图片不下载、不改名:把 `uploads/` 拷进 `static/wp-content/uploads/`,正文里的图片 URL 改成 **根相对** `/wp-content/uploads/...` 即可原地解析。文章 URL 从 `` 原样保留。转换器是**纯函数 + 单元测试**,先测后写。 ## Pipeline ``` WXR .xml + uploads/ → wxr_to_hugo.py → content/*.md + static/wp-content/uploads/ → hugo build → GitHub Actions → Pages ``` ## Two decisions you MUST ask the user (do not silently decide) WordPress 里有两类内容静态站处理不了,**必须问用户**,别擅自发布: 1. **密码保护文章**(`` 非空)。静态站无密码门 → 发布就是公开。 选项:**排除**(默认,最安全,URL 会 404)/ 公开发布 / 转成 `draft`。 **核对计数务必用 ElementTree(即 `parse_items`),别用裸 grep**:`` 的值是 CDATA 包裹的(``),`grep '[^<]*'` 会把每条都当空 → 误报「0 篇密码文章」漏掉真有密码的文章(maggiacito.com 实战,差点漏发 1 篇)。 2. **WordPress 脚手架页**(`sample-page`、`login`/`register`/`findpassword` 等插件短代码页、空页、登录设计器预览页)。 默认**排除**——它们不是内容。`is_real_page()` 已按「空正文 / 单条短代码 / 默认 slug 黑名单」过滤。 转换器对这两类都已实现排除;用 `AskUserQuestion` 确认后再跑全量。 ## Steps ### 1. 放好输入,建工程 ```bash mkdir -p ~/code/ && cd ~/code/ && git init # 把 WXR 拷进来(注意:WXR 含密码文章正文 + 作者邮箱,勿提交!见「安全」) cp /path/to/.WordPress.*.xml . # uploads/ 放到工程根(含子目录 年/月)。注意它可能含 wordpress_db.sql —— 勿提交! cp -R /path/to/uploads ./uploads mkdir -p scripts tests content/posts layouts/_default layouts/partials static ``` 拷入本 skill 的资产(保持目录对应): ```bash 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 的) ```bash python3 tests/test_wxr.py # 期望 ALL PASS;改任何转换逻辑都先加失败测试 ``` ### 3. 确认计数 + 跑全量转换 ```bash python3 scripts/wxr_to_hugo.py .WordPress.*.xml ``` 打印报告:`posts / pages / images / uploads_copied / warnings`。核对文章数与 WP 后台一致。 warnings 会列出:空正文文章、被跳过的脚手架页、外链图片。 ### 4. 构建并断言所有老链接命中 ```bash hugo --gc --minify # 没装:brew install hugo(要 extended) python3 scripts/verify_build.py .WordPress.*.xml # checked N posts, missing 0 ``` ### 5. 本地肉眼核对 ```bash hugo server -p 1313 ``` 对照线上抽查 5 篇(含 1 篇图片帖、1 篇多链接帖):标题、列表、链接、图片、视频是否正常。 **关键**:链接应是页面相对(`../../...`),图片从本地 `/wp-content/uploads/` 加载,不是从线上拉。 ### 6. 推到 GitHub(公开仓库见「安全」) ```bash gh repo create --public --source=. --remote=origin git push -u origin main ``` ### 7. 开 Pages → Actions,**先开后跑** ```bash gh api -X POST repos///pages -f build_type=workflow ``` **坑**:若 Pages 还没开就 push,首个 Action 会在 `configure-pages` 处 404 失败。开了 Pages 后**重跑**: ```bash gh workflow run "Deploy Hugo site to Pages" --repo / gh run watch --repo / --exit-status ``` 验证临时地址 `https://.github.io//`:home / 一篇 post / categories / index.xml / 一张图都 200。 (刚部署时图片可能短暂 301,是 CDN 预热,跟随重定向最终 200。) ### 8. DNS 切换(操作者手动,验证通过后再做) 先确认临时地址全站无误,**WP 仍在线**,零风险。然后在 DNS 商(如 **Cloudflare**)把域名指向 Pages: - apex:A 记录 → `185.199.108.153 / 109.153 / 110.153 / 111.153`,或 CNAME → `.github.io`。 - 用 Cloudflare 橙云代理时,SSL/TLS 设 **Full**;首次签证书可临时灰云(仅 DNS)。 - DNS 生效后 Pages 勾 **Enforce HTTPS**。 - 线上稳定数日后再下线老 WP(先停机留备份,确认无需回退再彻底删)。 ## 转换器踩过的坑(已在 wxr_to_hugo.py 修好,勿回退) | 坑 | 现象 | 修法 | |---|---|---| | 超链接丢 href | `` 只剩文字,URL 丢了 | `` 内攒文字,闭合时输出 `[文字](href)` | | 相册多余 `-`(figure 版) | 图片帖每张图前一个空列表符 | `figure` 栈识别 `wp-block-gallery`,相册内 `
  • ` 不输出 `- ` | | 相册多余 `-`(ul 版) | 早期 Gutenberg 把 `wp-block-gallery` 放 `
      `(无 `
      ` 包裹),上一行的 figure 判定漏掉,每图前留孤立 `-` | `_ul_stack` 同样识别 `