目录

publish-technical.mjs 脚本改造:支持 blog-writer 双来源扫描与全量 Git 提交

从单目录扫描到全仓库匹配,从仅提文章到图片一并提交

publish-technical.mjs 脚本改造:支持 blog-writer 双来源扫描与全量 Git 提交

背景:发布脚本的局限

publish-technical.mjs 是 OpenClaw 博客发布流程中的关键脚本——它扫描知识库中的草稿文章,列出可发布列表,然后将选中的文章推送到 Hugo 站点。

最初版本的脚本有两个致命缺陷:

  1. 只扫描单一目录:仅查看 /AI/OpenClaw/08-运维体系/,其他目录(如 01-核心概念/)里的文章完全看不到
  2. 只匹配 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 还不够——有些文章是手动编辑或旧脚本生成的,没有这个标记。新的匹配逻辑采用双来源

  • 来源 Acontent_type: blog-post 标记存在(会话归档产出)
  • 来源 BslugfeaturedImage 两个字段同时存在(已有文件改写)
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
}

已发布状态标注 + 列表上限

为了让发布流程更直观,脚本现在会:

  1. 扫描 Hugo 的 content/ 目录,收集所有已发布文章的 slug
  2. 在列表中每行标注 ✅ 已发布 / 📝 草稿
  3. 已发布文章不可选,只有草稿可以发布
  4. 列表按日期降序排列,最多显示 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
}

关键修复点:

  1. Array.isArray 检查:在 push 之前确认值是数组
  2. 空值初始化:遇到列表格式时,如果之前不是数组就初始化为 []
  3. 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 看起来优雅,但全量提交更不容易出错
已发布状态要可视化在列表中标注 ✅/📝,避免重复发布和混淆

下一步

  1. 考虑将 publish-journal.mjsjournal-templates.mjs 也纳入统一扫描逻辑
  2. 探索 git add 的精细化方案:扫描文章中引用的图片路径,只 add 相关文件
  3. create_blog_note.sh 增加自动 slug 冲突检测,避免同名覆盖

–全文完–

感谢阅读
若你有故事想讲、有困惑想聊、或是想找个人说说心里话,甚至只是吐槽发泄一下情绪,都欢迎来找我聊聊:   《内容已折叠,点击展开》

希望我写的每一个字,成为我自己和某个人活下去、拼下去的力量。                     《内容已折叠,点击展开》

“技术终归是工具,而我们一次次认真把问题理顺,守住的其实不只是页面样式和代码输出,还有那一点不愿被混乱打败的心气,是每一个深夜仍愿点灯前行的人。”

转载请注明来自https://oklife.me。

文尾配图水墨画图片