活过 10 年的 awesome-list,awesome-mac 凭什么还没死
- 用 AST 解析器将 Markdown 转化为结构化 JSON 数据,实现可编程数据源
- 增量 RSS 生成器自动追踪新增软件,将清单变成持续更新的内容渠道
- 集成 AI 维护 skill 实现多语言同步,降低人工维护成本
- 构建自动化 CI 管线,从编辑 Markdown 到多渠道分发全自动
2016 年 7 月,jaywcjlove 在 GitHub 创建了 awesome-mac。那时候 awesome 系列正火,sindresorhus 的 awesome 仓库已经成了程序员的「导航站」,各种 awesome-xxx 如雨后春笋。awesome-mac 只是其中之一,收集 macOS 上的好软件,按类别整理。
十年过去了。同时期的 awesome-list 死了一大片,活下来的也在吃老本。但 awesome-mac 不一样,今天打开它的 GitHub 主页,107K Star,8052 Fork,最近一次提交就在今天,README 里塞了超过 600 个软件条目。更有意思的是,它的 README 文件 243KB,比很多正式开源项目的文档都长。
一个 Markdown 清单凭什么活十年?我把它拆开看了看,发现这东西远不止「一个列表」那么简单。
表面是清单,底下是引擎
大部分人看 awesome-mac,看到的就是一个分类齐全的软件列表。Reading and Writing Tools、Developer Tools、Design and Product、AI Tools,从编辑器到虚拟化,从截图到密码管理,覆盖了 macOS 使用的方方面面。每个条目格式统一,软件名加链接加一句话描述,配上开源/免费/App Store 图标标记。
但如果你只看到这层,就低估了它。
翻到仓库根目录的文件树,你会发现这根本不是一个纯 Markdown 项目。它有 build/ast.mjs,一个基于 remark 的 AST 解析器,专门把 README 解析成结构化 JSON 数据。它有 build/feed.mjs,一个增量 RSS 生成器,自动追踪最近 50 次提交里新增的软件,生成四种语言的 RSS feed。它有 .github/workflows/ci.yml,一个完整的 CI 管线,每次 push 到 master 都会自动构建、生成 AST、更新 RSS、部署到 GitHub Pages、打 Tag 发 Release。它甚至有 package.json,通过 npm 发布了多语言版本的 JSON 数据包,其他项目可以直接 npm install awesome-mac 拿到结构化数据。
说白了,awesome-mac 的 README 只是一层数据展示。真正的核心是一个内容生产管线,README 是数据源,AST 解析器是编译器,RSS feed 是分发渠道,CI 是自动化引擎。
从下往上看,维护者只需要编辑 Markdown(数据源层),AST 解析器自动编译成 JSON(解析编译层),RSS 和 npm 包多渠道分发(分发输出层),CI 全自动跑流水线(自动化层),AI Skill 和贡献规范保障社区维护质量(维护治理层)。每一层都有对应的源码文件和配置支撑,不是空架子。
这个设计思路值得拆开看。
AST 解析器,把 Markdown 当数据库用
build/ast.mjs 是整个项目的「编译器」。它用 remark 把 README.md 解析成 AST(抽象语法树),然后遍历树节点,提取每个软件条目的名称、URL、描述和图标信息。
核心逻辑在 getMarkIcons 函数里。它识别每个列表项的图标标记,判断这个软件是开源的、免费的、还是在 App Store 上架的。具体做法是检查 imageReference 类型的节点,用正则匹配 identifier 字段。这段代码有意思的地方在于,它把 Markdown 的图标标记当成了结构化标签来用。README 里写的 ![Open-Source Software][OSS Icon] 不只是给人看的视觉标记,更是机器可读的元数据。
解析结果输出到 dist/awesome-mac.json,还按语言拆分了 awesome-mac.zh.json、awesome-mac.ja.json、awesome-mac.ko.json。这些 JSON 文件通过 package.json 的 exports 字段对外发布,第三方项目可以按需引入。
{
"exports": {
".": "./dist/awesome-mac.json",
"./ko": "./dist/awesome-mac.ko.json",
"./ja": "./dist/awesome-mac.ja.json",
"./zh": "./dist/awesome-mac.zh.json"
}
}
你想想看,这个设计意味着什么。awesome-mac 不只是一个给人读的列表,它还是一个可编程的数据源。你想做一个 macOS 软件推荐网站?npm install awesome-mac 就拿到全部数据。你想做一个软件分类器?JSON 数据已经结构化好了。你想做一个 macOS 软件搜索引擎?数据现成,还自动更新。
增量 RSS,让清单变成内容渠道
build/feed.mjs 是另一个精巧的设计。它不是简单地生成一个 RSS feed,而是实现了增量更新。
脚本通过 execFileSync 调用 git 命令,扫描最近 50 次提交(--commit-limit 参数控制,默认 50),找出哪些软件条目是新增的。然后为四种语言的 README 分别生成 RSS feed,输出到 feed/feed.xml、feed/feed-zh.xml、feed/feed-ko.xml、feed/feed-ja.xml。
关键在于增量逻辑的实现方式。脚本没有维护额外的状态文件,而是从已有的 RSS XML 文件本身推导增量状态。也就是说,上一次生成的 RSS 既是输出也是缓存,下次运行时先读旧的 XML,再对比新提交,只输出增量部分。这种「以输出为状态」的设计很巧妙,避免了引入额外的缓存文件或数据库。
CI 管线里还有一段逻辑专门处理 RSS 提交。ci.yml 在构建完成后,会检查 feed/*.xml 是否有变化。如果有,就用 github-actions[bot] 自动提交一个 chore: update RSS feeds [skip ci] 的 commit。这就是为什么你在 awesome-mac 的提交历史里会看到大量 bot 提交,它们都是 CI 自动生成的 RSS 更新。
这意味着 awesome-mac 的用户不只可以通过 GitHub 主页看软件列表,还可以订阅 RSS feed。每当有人提交 PR 新增一个软件,CI 自动更新 RSS,订阅者立刻就能收到通知。一个 awesome-list 就这样变成了一个持续更新的内容渠道。
四语同步与 AI 维护 Skill
awesome-mac 维护着四个语言的 README,英文、中文、日文、韩文。这对人工维护来说是巨大的工作量。项目在 .codex/skills/awesome-mac-maintainer/ 下放了一个 Codex Skill,专门用于自动化多语言维护。
这个 Skill 的 SKILL.md 写得很规范,核心工作流是,识别最合适的分类,读各语言 README 的本地上下文,添加或更新条目到所有需要的语言文件,描述保持一句话,最后用 rg 和 git diff 验证。它的 curation rules 强调了几点,匹配每个文档实际使用的本地 section(不假设四个文件结构完全相同),保持已有的字母排序,不重写邻近条目,编辑范围最小化。
描述风格也定了规矩,一句话讲清这个 app 是什么,简洁具体,优先描述产品身份而不是功能列表,不强调「macOS app」(除非必要),避免营销措辞。还给了参考句式,「Open-source HTTP(S) debugging proxy for intercepting, inspecting, modifying, and replaying requests.」
更有意思的是,CONTRIBUTING.md 里专门写了 AI 辅助贡献的指引。它明确欢迎 AI 辅助的 PR,但要求遵循同样的仓库规则。还建议贡献者使用 $awesome-mac-maintainer Skill 来做多语言 curation 任务,并给了示例 prompt。这大概是 awesome-list 生态里最早把 AI 维护 Skill 直接集成进仓库的项目之一。
分类体系的演进
从 README 的目录结构可以看出,awesome-mac 的分类体系经过了很多轮演进。现在有几十个大类,从 Reading and Writing Tools 到 Gaming Software,覆盖了 macOS 使用的几乎每个场景。
有几个分类特别值得注意。
AI Tools 是相对较新的分类,收录了大量 AI 相关的 macOS 应用。从 ChatGPT、Claude 的官方桌面端,到 BoltAI、Cherry Studio 这种第三方客户端,再到 Claude Usage Monitor、TokenMeter 这种 usage 监控工具。这个分类的存在说明项目在积极跟进 AI 时代的工具生态。光 AI Tools 一个分类就有 40 多个条目,比很多独立 awesome-list 的全部内容都多。
Pirated software download site blocklist 是个很有意思的分类。它不是收录软件,而是收录盗版软件下载站的黑名单。README 里写着「Refuse piracy from me. Software vendors can go to these places rights.」,列了三个被划掉的盗版站点链接。这个黑名单的存在说明项目不只是在做软件推荐,还在主动维护一个正向的软件生态。
Mac App Download Sites 分类分成了 Genuine Sites 和 Pirated blocklist 两部分,一边推荐正版渠道,一边拉黑盗版站点。这种「白名单 + 黑名单」的双轨设计,在 awesome-list 里很少见。
诚实的几个发现
拆到这个深度,有些 README 里不会写的限制和问题值得说说。
协议标识三处不一致。 GitHub API 返回的 licenseInfo 是 CC0-1.0(Creative Commons Zero,完全放弃版权),但 package.json 里写的是 CC-BY-SA-4.0(署名+相同方式共享),README 底部的 License 章节贴的是 Creative Commons Attribution 4.0 International License(CC-BY-4.0,署名)。三个地方三个协议,而且 CC0 和 CC-BY-SA 是性质完全不同的协议,CC0 是放弃所有权利,CC-BY-SA 是保留权利但允许使用。这对于想引用 awesome-mac 数据的第三方项目来说是个法律风险,你得自己判断到底适用哪个协议。
AST 解析器的图标识别有大小写陷阱。 getIconDetail 函数在匹配图标时用了 toLocaleLowerCase() 统一转小写再匹配,这个设计本身是对的。但正则 ^(freeware\s+icon|oss\s+icon|app-store\s+icon|awesome-list\s+icon) 只匹配特定写法。README 里有不少条目写的是 ![OSS][OSS Icon] 而不是 ![Open-Source Software][OSS Icon],alt 文本不同但 identifier 相同,解析没问题。但如果有人写了 ![Open Source][OSS Icon] 或 ![AppStore][app-store Icon],alt 文本变了但 identifier 没变,也能正常匹配。可如果 identifier 写错了,比如 [app store icon] 少了连字符,就会被静默忽略。这种静默失败在数据层面不容易发现。
Issue #245 揭示了链接有效性的长期痛点。 这个 issue 标题就是「Validate Links」,7 条评论,至今未关闭。awesome-mac 收录了 600+ 软件条目,每个都有外链。软件会改官网、会下架、会换域名。维护链接有效性是这个项目最头疼的问题之一,但目前没有自动化的链接检查机制。ISSUE_TEMPLATE 里虽然有 bug_report.yml,但没有专门的死链报告流程。
bus factor 约等于 1。 30 个贡献者里,jaywcjlove 一个人贡献了 1282 次,第二名 github-actions[bot] 136 次(CI 自动提交),第三名 alichtman 92 次。排除 bot 后,第二贡献者的提交量不到作者的 7%。这个项目高度依赖一个人。如果 jaywcjlove 哪天不维护了,接手者面临的不仅是内容更新,还有 AST 解析器、RSS 管线、CI 配置这一整套基础设施的理解成本。
README 顶部的引流矩阵。 打开 awesome-mac 的 README,第一屏不是软件列表,是赞助商广告和 jaywcjlove 自己的 30+ 个 macOS App 的推广链接。从 Zipora 解压工具到 Scap 截图工具,从 Vidwall 壁纸到 Mousio 鼠标提示,每个都配了图标和 App Store 链接。这个量级的推广位置,商业价值不低。一个 107K Star 的仓库,每天有大量开发者访问,顶部 30+ 个 App 的曝光量相当于一个中型科技博客。这是开源项目「内容变现」的一种模式,虽然 README 里没有明确标注这些是商业推广。
可复用的模式
拆完 awesome-mac,我最大的收获不是发现了一个好用的软件列表,而是看到了一种**「Markdown 即数据库」**的工程模式。
这个模式的核心思路是,用 Markdown 作为数据的原始格式,人可读写,版本可控。然后在 Markdown 之上构建工具链,AST 解析器把 Markdown 转成结构化数据,CI 自动化处理构建和分发,RSS/JSON/npm 多渠道输出。维护者只需要编辑 Markdown,其他全是自动的。
这个模式的迁移性很强。任何需要维护结构化清单的场景都可以用,软件推荐、学习资源、API 文档、配置项索引。你不需要建数据库,不需要写后端,一个 Markdown 文件加一套构建脚本就能跑起来。awesome-mac 用十年证明了这条路走得通,而且能走很远。
另一个值得带走的是**「以输出为状态」**的增量设计。不维护额外的缓存文件,用已有的输出作为下次运行的输入状态。这种设计减少了状态管理的复杂度,特别适合 CI 场景下的自动化任务。
至于 awesome-mac 本身,它已经从十年前的一个 awesome-list,演变成了一个集内容生产、数据分发、社区维护于一体的平台。107K Star 不是因为它收集的软件有多全,而是因为它把一个看似简单的清单做成了基础设施。能活十年的开源项目,靠的不是最初的想法,而是持续演化的工程能力。
评论互动