wjs-converting-wp-to-hugo
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/<id>/ 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".
git clone --depth 1 https://github.com/jianshuo/claude-skills /tmp/wjs-converting-wp-to-hugo && cp -r /tmp/wjs-converting-wp-to-hugo/wjs-converting-wp-to-hugo ~/.claude/skills/wjs-converting-wp-to-hugoSKILL.md
# 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 从 `<link>` 原样保留。转换器是**纯函数 + 单元测试**,先测后写。 ## 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. **密码保护文章**(`<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()` 已按「空正文 / 单条短代码 / 默认 slug 黑名单」过滤。 转换器对这两类都已实现排除;用 `AskUserQuestion` 确认后再跑全量。 ## Steps ### 1. 放好输入,建工程 ```bash 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 ``` 拷入本 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 <site>.WordPress.*.xml ``` 打印报告:`posts / pages / images / uploads_copied / warnings`。核对文章数与 WP 后台一致。 warnings 会列出:空正文文章、被跳过的脚手架页、外链图片。 ### 4. 构建并断言所有老链接命中 ```bash hugo --gc --minify # 没装:brew install hugo(要 extended) python3 scripts/verify_build.py <site>.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 <site> --public --source=. --remote=origin git push -u origin main ``` ### 7. 开 Pages → Actions,**先开后跑** ```bash gh api -X POST repos/<owner>/<site>/pages -f build_type=workflow ``` **坑**:若 Pages 还没开就 push,首个 Action 会在 `configure-pages` 处 404 失败。开了 Pages 后**重跑**: ```bash gh workflow run "Deploy Hugo site to Pages" --repo <owner>/<site> gh run watch <run-id> --repo <owner>/<site> --exit-status ``` 验证临时地址 `https://<owner>.github.io/<site>/`: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 → `<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 把 `wp-block-gallery` 放 `<ul>`(无 `<figure>` 包裹),上一行的 figure 判定漏掉,每图前留孤立 `-` | `_ul_stack` 同样识别 `<ul class=wp-block-gallery>`,相册内 `<li>` 不输出 `- `(maggiacito.com 实战) | | CJK permalink 编码 | permalink 是 URL 编码的中文(`/sculpting-in-time/%e4%ba%8c…/`)。原样保留会让 Hugo 建字面 `%e4%..` 目录,服务器把请求里的 `%xx` 解码后对不上 → 老链接全断 | `_norm_url()` 用 `unquote()` 把路径解码成中文,Hugo 建 UTF-8 目录;静态主机对入站 `%xx` 解码即命中,**编码/解码两种老链接都活**。数字 `/archives/<id>/` 不受影响(maggiacito.com 实战) | | 图片从线上加载 | 正文图是绝对 `https://站点/wp-content/...` | `_root_relative()` 把自托管图改成 `/wp-content/...`;外链图保持绝对 | | 视频/嵌入丢失 | `<video>`/`<iframe>` 正文变空 | 原样透传为 HTML(`hugo.toml` 开 `goldmark unsafe=true`) | | lastmod 空 | 有的 WXR 无 `wp:post_modified` | 缺失时回退到 `wp:post_date` | | 经典编辑器软换行 | 正文裸 `\n` 被吃 | `handle_data` 保留裸文本换行 | | 实体没解码 | 标题里 `&` | `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 / 云命令)→ **重建
Repo-wide drift detector for the wjs-* Claude Code skills in this marketplace. Sweeps every SKILL.md, scores it against the repo's own conventions (V-ing naming, trigger-phrase density, companion files, description shape), and returns a grouped punch list ordered by severity. Read-only — never edits files. Use before pushing a batch of skill changes, or whenever you wonder "are these skills still internally consistent?
|
Use when the user asks to audit what's wrong with a project, "make it right", "看看项目出了什么问题", "为什么用户的需求还没上线", "为什么没提交App Store", "为什么没新build", or wants a holistic state-of-the-project check covering unmerged branches, stalled PRs, failed GitHub Actions, stale builds, plan drift (TODOS.md / ROADMAP), unreleased commits, and log errors. Runs read-only investigation, presents a grouped checklist, fixes only after explicit user confirmation. Aware of the Cathier iOS app workflow (Xcode + fastlane + auto-merge @claude PRs from in-app feedback).
Use when the user has a video + an SRT and wants the subtitles either burned into the pixels (libass, always-visible) or soft-muxed as a togglable track. Also handles the final composite step for the localization pipeline — burn subs, mix a dub track, and keep the original audio as a low-volume bed, all in ONE ffmpeg encode (no cascade). Verifies libass availability and auto-downloads a static evermeet ffmpeg build when Homebrew's stripped binary lacks it. Triggers — "烧字幕", "硬字幕", "burn subtitles", "burn-in subs", "embed subtitle", "soft mux SRT", "把字幕烧进视频", "做最终合成".
Use when the user complains about spam on his X/Twitter posts — 同城面付 / 寻固炮 / 线下上门 / 免费破处 这类引流号在他推文下刷的 emoji 垃圾回复 — and wants them removed. Covers the last 7 days (X recent-search window). Triggers — "把这些spam删掉", "清理X垃圾回复", "推文下面好多引流号", "clean spam replies", "/wjs-cleaning-spam".
Use when the user wants a 王建硕-style WeChat article (article.md) turned into a narrated short MP4 video — TTS voiceover via 火山引擎 Volcano TTS, HyperFrames CSS/GSAP animation per scene, subtle SFX, abstract watercolor background, full pipeline rendering to 1080×1920 portrait MP4 (30-90s). Triggers — "把这篇文章做成视频", "做一个解说视频", "讲解视频", "/wjs-converting-text-to-video".
Use when the user has a video + a target-language SRT and wants the video to actually speak that language — generates a time-aligned TTS voice dub. Routes by voice ID — Volcano (豆包) TTS for Chinese, edge-tts neural for any language. Defaults to one voice (single-speaker); opt-in multi-speaker via visual diarization. Outputs `*_<lang>_dub.mp4` with the dub audio in place of the original. Final mixing (audio bed + burn-in) is handed off to `/wjs-burning-subtitles`. Triggers — "配音", "中文配音", "Chinese dub", "voice over this", "dub the video", "TTS this SRT", "different voice for each speaker".
吃一堑长一智 — 走完 5 步交互式反思(堑 → 自动输出 → 旧权重 → 新参数 → 替代动作),从「情绪复盘」推进到「行为训练」,把第一反应这一层 L3 权重练新。Use when 王建硕 reflects on a personal setback, mistake, or recurring pattern (反思, 复盘, 回顾, 总结教训, 吃一堑, 长一智, "这次又栽了", "怎么又这样", "为什么我总是…", "想开点都做不到", "知道道理但做不到"). For the user as a human, not for Claude's task post-mortems.