publish-technical.mjs 脚本改造:支持 blog-writer 双来源扫描与全量 Git 提交
从单目录扫描到全仓库匹配,从仅提文章到图片一并提交

publish-technical.mjs 脚本改造:支持 blog-writer 双来源扫描与全量 Git 提交
背景:发布脚本的局限
publish-technical.mjs 是 OpenClaw 博客发布流程中的关键脚本——它扫描知识库中的草稿文章,列出可发布列表,然后将选中的文章推送到 Hugo 站点。
最初版本的脚本有两个致命缺陷:
- 只扫描单一目录:仅查看
/AI/OpenClaw/08-运维体系/,其他目录(如01-核心概念/)里的文章完全看不到 - 只匹配
content_type: blog-post:用create_note.sh创建的笔记如果没有手动加这个标记,就被忽略
这导致了一个尴尬的场景:今天刚归档的 2026-07-03-1124-openclaw-memory-search-complete-guide.md 明明已经写好、有完整的 frontmatter 和 featuredImage,但因为放在 01-核心概念/ 目录且缺少 content_type: blog-post 标记,脚本完全扫不到。
改造方案概览
本次改造涉及三个独立改动:
| 改动 | 解决的问题 | 影响范围 |
|---|---|---|
| 全仓库扫描 + 双来源匹配 | 漏扫文章、单一标记依赖 | 脚本逻辑 |
| 修复 YAML 多行列表解析 bug | 脚本崩溃 | 健壮性 |
| git add 改为全量提交 | 图片未上传导致文章无封面 | 发布流程 |
改动一:全仓库扫描 + 双来源匹配
从单目录到全仓库
原来的脚本用 grep -rl 在固定目录下查找 content_type: blog-post,现在改为扫描整个 Obsidian 仓库:
// 原逻辑:只扫一个目录
const dir = '/path/to/08-运维体系/'
const results = execSync(`grep -rl 'content_type: blog-post' ${dir}`).toString().split('\n')
// 新逻辑:全仓库递归扫描
const results = execSync('grep -rl "content_type: blog-post" /home/oklife/Obsidian-oklife-ub/').toString().split('\n')双来源匹配策略
仅靠 content_type: blog-post 还不够——有些文章是手动编辑或旧脚本生成的,没有这个标记。新的匹配逻辑采用双来源:
- 来源 A:
content_type: blog-post标记存在(会话归档产出) - 来源 B:
slug和featuredImage两个字段同时存在(已有文件改写)
function isBlogPost(file) {
const fm = parseFrontMatter(file)
// 来源 A:有 content_type 标记
if (fm.content_type === 'blog-post') return true
// 来源 B:slug + featuredImage 同时存在
if (fm.slug && fm.featuredImage) return true
return false
}智能过滤
脚本还增加了智能过滤,避免把聊天记录、普通笔记混入发布列表:
const SKIP_PATTERNS = ['chat-', '_chat_', 'journal']
const VALID_TYPES = ['blog-post', undefined] // undefined 表示来源 B 文章
function shouldSkip(file) {
// 跳过聊天文件
if (SKIP_PATTERNS.some(p => file.includes(p))) return true
// 跳过非博客类型
const fm = parseFrontMatter(file)
if (fm.type && !VALID_TYPES.includes(fm.type)) return true
return false
}已发布状态标注 + 列表上限
为了让发布流程更直观,脚本现在会:
- 扫描 Hugo 的
content/目录,收集所有已发布文章的 slug - 在列表中每行标注 ✅ 已发布 / 📝 草稿
- 已发布文章不可选,只有草稿可以发布
- 列表按日期降序排列,最多显示 10 篇
📋 待发布文章列表(最多 10 篇)
───────────────────────────────
📝 2026-07-03-1239-publish-technical-script-dual-source-upgrade.md
📝 2026-07-03-1124-openclaw-memory-search-complete-guide.md
✅ 2026-07-02-hugo-auto-deploy-pipeline.md ← 已发布,跳过
✅ 2026-07-01-openclaw-agent-creation-guide.md ← 已发布,跳过
───────────────────────────────
请选择要发布的文章编号(逗号分隔,回车确认):改动二:修复 parseFrontMatter 的 YAML 多行列表解析 Bug
问题现象
当 YAML frontmatter 中包含多行列表格式时,脚本会崩溃:
keywords:
- "OpenClaw Memory Search"
- "OpenClaw 语义记忆"
- "LM Studio 嵌入模型"报错信息:push onto string——JavaScript 的 Array.prototype.push() 被调用在了一个字符串变量上。
根因分析
parseFrontMatter 函数在解析 YAML 时,对多行列表的处理有缺陷:
// 有问题的代码
let value = result[key] // 可能是字符串
value.push(item) // 如果 value 是字符串就会报错!
当 result[key] 已经被赋值为字符串(比如 type: "" 的空字符串),后续再尝试 .push() 就会崩溃。
修复方案
function parseFrontMatter(file) {
const result = {}
const yamlBlock = file.match(/---\n([\s\S]*?)\n---/)[1]
yamlBlock.split('\n').forEach(line => {
const match = line.match(/^(\w[\w-]*):\s*(.*)$/)
if (!match) return
const key = match[1]
let value = match[2].trim()
// 处理空字符串
if (value === '""' || value === "''") {
result[key] = ''
return
}
// 处理多行列表
if (value.startsWith('-')) {
if (!Array.isArray(result[key])) {
result[key] = [] // 确保是数组
}
result[key].push(value.replace(/^\s*-\s*/, '').replace(/^["']|["']$/g, ''))
return
}
// 处理普通值
if (typeof value === 'string' && value.startsWith('"')) {
value = value.slice(1, -1)
}
result[key] = value
})
return result
}关键修复点:
Array.isArray检查:在 push 之前确认值是数组- 空值初始化:遇到列表格式时,如果之前不是数组就初始化为
[] type: ""特殊处理:空字符串解析后跳过,避免误判为非博客类型
改动三:git add 改为全量提交
问题
原来的脚本只 git add 文章文件:
git add /path/to/article.md
git commit -m "Publish: article.md"
git push origin main但 Hugo 博客的图片存放在 static/images/ 目录下,单独提交文章会导致:
- 文章引用了
featuredImage - 但图片文件没有被 git 跟踪
- 部署到服务器后图片 404
修复
改为全量提交:
git add .
git commit -m "Publish: article.md + related assets"
git push origin main虽然这不是最精细的做法(理论上应该只 add 相关图片),但对于当前单作者、低频发布的场景,全量提交是最简单可靠的方案。
经验总结
这次改造的核心教训:
| 教训 | 说明 |
|---|---|
| 双来源比单标记更可靠 | 不要只依赖 content_type 标记,slug + featuredImage 组合也能有效识别博客文章 |
| YAML 解析要防御性编程 | 多行列表、空字符串、混合类型都要提前处理 |
| git add 粒度 vs 可靠性 | 精细 add 看起来优雅,但全量提交更不容易出错 |
| 已发布状态要可视化 | 在列表中标注 ✅/📝,避免重复发布和混淆 |
下一步
- 考虑将
publish-journal.mjs和journal-templates.mjs也纳入统一扫描逻辑 - 探索
git add的精细化方案:扫描文章中引用的图片路径,只 add 相关文件 - 为
create_blog_note.sh增加自动 slug 冲突检测,避免同名覆盖
–全文完–

梦行志
