Ghost 单仓库(Monorepo)架构导航:pnpm Workspace + Nx 驱动的全栈发布流水线
Ghost(README)是当前采用 pnpm workspace + Nx 组织的单仓库(Monorepo):pnpm 负责把 apps/、ghost/core/、koenig/、packages/ 等子项目链接成一份本地依赖图,Nx 则基于这份依赖图调度 build、lint、test、dev 等任务并缓存产物。阅读本指南后,你将掌握:在仓库中定位任一模块(前端应用 / 服务端 Core / 编辑器 / 共享库)的方法;workspace: 与 catalog: 依赖声明的区别与用法;以及"开发期直接跑 TypeScript 源码、生产期用编译产物"这套双轨构建机制是如何实现的。
Monorepo 概览:pnpm 提供依赖图,Nx 驱动任务
根 package.json 把仓库声明为名为 ghost-monorepo 的私有根包("private": true),并锁定包管理器与运行环境:
"packageManager": "pnpm@12.2.1+...,且通过preinstall钩子执行 scripts/enforce-package-manager.js 强制使用 pnpm;"engines": { "node": "^22.23.1 || ^24.20.0" }约束 Node 版本。
pnpm-workspace.yaml 是"哪些目录是工作区"的唯一事实来源,它通过 glob 声明了全部工作区:
packages:
- 'ghost/*'
- 'apps/*'
- 'e2e'
- 'koenig/*'
- 'packages/**'
- '!packages/_template' # 新包模板本身不参与工作区
- 'configs/*'
- 'scripts'
文件里还包含一批现代 pnpm 加固配置:strictDepBuilds: true、catalogMode: strict、通过 allowBuilds 对原生依赖构建做版本级白名单(如 better-sqlite3@12.11.1、sharp@0.35.3)、通过 overrides 统一收敛传递依赖版本(典型例子是把 knex-migrator>knex 钉回 2.4.2,与 ghost/core 使用的 knex 主版本对齐)。这些配置共同决定了依赖解析的安全边界。
而 nx.json 定义了 Nx 的运行行为:parallel: 4 控制并行度、cacheDirectory: ".nxcache" 存放缓存、namedInputs 中的 sharedGlobals 把根级配置文件(pnpm-workspace.yaml、pnpm-lock.yaml、ghost/tsconfig.json 等)纳入每个任务的输入指纹。修改某个应用或包之前,先阅读它旁边(同目录)的 README,这是仓库内约定俗成的守则。
顶层目录总览
| 目录 | 内容 |
|---|---|
| apps/ | Admin 应用、面向浏览器的公开应用与前端库 |
| ghost/core/ | Ghost 服务端、前端渲染、数据库迁移与服务端测试 |
| koenig/ | Koenig 编辑器,以及内容存取 / 转换 / 渲染相关的 kg-* 包 |
| packages/ | 共享库、schema、翻译、测试数据与适配器(adapter)契约 |
| configs/ | 共享的 ESLint、TypeScript、Vite 与 Vitest 配置包 |
| e2e/ | 覆盖完整 Admin 与公开站点旅程的 Playwright 测试 |
| docker/ | 本地开发与 CI 用容器及配套服务 |
| scripts/ | 仓库初始化、校验、构建与发布工具链 |
前端应用布局:apps/ 下的三类工程
apps/ 下并存着定位截然不同的前端工程,改动前必须分清它们属于哪一类:
- admin —— 新的 React Admin 应用,正逐步取代旧版;
- ember-admin —— 遗留的 Ember Admin,路由正随时间推移陆续从 Ember 迁到 React;
- activitypub —— 内嵌在 Admin 中的 React 应用;
- portal、comments-ui、signup-form、sodo-search、announcement-bar、admin-toolbar —— 发布到 npm、并通过 CDN 以
<script>标签加载的公开应用(public apps); - shade —— 当前 Admin 的设计系统;
- admin-x-framework —— 提供共享 Admin API hooks、路由与工具函数。
公开应用的运行模型与普通 SPA 不同:它们把浏览器产物打包成 UMD(例如 apps/portal/package.json 的 files 只包含 umd/、LICENSE、README.md),页面通过 script 标签加载,运行时配置来自 DOM 的 data-attributes。Ghost Core 则通过主题助手渲染这些集成点——主题里常见的 {{ghost_head}}、{{comments}} 就在服务端完成拼装(以 ghost_head.js 为例)。
Admin 的整合边界在 apps/admin/README.md 有专门说明:React 侧通过 admin-x-framework 提供 API hooks 与路由,Shade 提供应用外壳与设计系统;已迁移的路由渲染 React 组件,未迁移的路由回退到旧 Ember Admin,两者共享同一 UI 空间——这就是所谓的 Ember Bridge 渐进迁移策略。
Ghost Core:服务端与渲染核心
ghost/core/ 是名为 ghost 的主包,最常见的子路径如下:
| 路径 | 内容 |
|---|---|
ghost/core/core/server/ |
API、模型、服务、数据访问与服务端启动逻辑 |
ghost/core/core/frontend/ |
主题渲染、helpers、中间件与公共静态资源 |
ghost/core/core/shared/ |
跨服务端边界共享的配置与代码 |
ghost/core/content/ |
默认主题、适配器、设置、图片与运行时内容 |
ghost/core/test/ |
单元、集成与服务端 E2E 测试 |
新增 Core 服务时必须遵守的约定记录在 services README:服务的构建由 boot 流程负责——需要初始化逻辑的新服务必须暴露显式的 init(),并在 boot.js 的相应启动阶段被调用,不能让"第一个请求"去懒构造服务;持有资源的服务要把关闭/清理挂进 boot 生命周期。编写新服务默认使用 TypeScript + 具名导出,只有在 require() 边界确实需要时才保留薄薄的 CommonJS 包装。
Admin 构建产物在发布时会复制进 ghost/core/core/built/admin/ 供 Ghost Core 托管(源码目录中通常不存在该目录,属生成物)。因此请把 built/、build/、dist/、umd/ 一律视为生成输出,除非旁边的 README 明确说明例外。
Koenig:编辑器与内容转换管线的落位
koenig/ 是从原独立仓库整体迁入本 Monorepo(保留完整 git 历史)的内容编辑与渲染家族。其中 Lexical 编辑器 UI(koenig-lexical) 在构建期被打包进 Admin;而 kg-* 包被 Ghost Core 以本地工作区依赖的方式消费,用于服务端内容转换与渲染。整个目录下的依赖全部通过 workspace: 规格解析——dev、CI 与发布归档都不会从 npm 安装这些包。完整的包地图与开发命令见 koenig/README.md。
其中 kg-default-nodes 是所有卡片(card)的渲染唯一事实来源:编辑器与服务端都经由它渲染 HTML,因此它必须保持"浏览器安全"(会在浏览器和 Node 两端同时运行)。开发编辑器时推荐两种模式:在 koenig/koenig-lexical 内跑 pnpm dev 进入独立 demo(http://localhost:5173,最快反馈);或在仓库根跑 pnpm dev:lexical 启动完整 Ghost 开发环境 + 编辑器重建监听器,让 Admin 直接加载本地编辑器构建。
共享 packages 与 configs:内部包的"黄金路径"
packages/ 承载被 Ghost Core 与各应用共享的库。packages/_template 是新增内部包的起点模板,且已被 pnpm-workspace.yaml 显式排除在工作区之外(避免它被当作正式包参与构建)。新增或现代化一个内部包之前必读 packages/README.md,其中定义了"黄金路径"契约:
- 包名使用
@tryghost/<name>,"version": "0.0.0"且"private": true; - 声明
"ghostPackage": {"goldenPath": "compliant"},状态包括compliant(机械校验通过)、migration(历史导入过渡期)与exempt(长期豁免,如纯测试包),后两者必须写明reason; - 纯 TypeScript ESM,源码在
src/**/*.ts,测试在test/**/*.ts,编译产物输出到build/; - 每个入口导出按
source→types→default顺序声明(其作用见下文"源码与生产构建"); files只含build,不发布独立 npm 版本。
packages/adapters 下的 cache-base、jobs-base、redirects-base、scheduling-base、sso-base、storage-base 等基础包,定义了存储、调度、重定向、缓存等可替换服务的接口契约,是 Ghost 依赖注入/可插拔设计在 monorepo 层面的体现。
configs/ 则把共享配置做成按包名依赖的内部包,而不是让各工程各自复制一份。其中 configs/eslint/README.md 说明了两个核心工厂的用法:@internal/cfg-eslint 提供 nodeLibConfig() 给 Node 库,@internal/cfg-eslint-react 提供 reactAppConfig() 给 React 应用;Ghost Core、Ember Admin、Admin Toolbar 因规则集特殊而保留独立配置,但仍可直接 import 共享的规则原子(如 correctnessRules、nodeLibRules)。
工作区依赖:workspace: 与 catalog: 双轨管理
monorepo 内部包之间用 workspace: 版本号声明本地依赖(例如 portal 的 @tryghost/i18n: "workspace:*")。pnpm 会把这些包直接从当前 checkout 链接进来,开发与 CI 不会额外安装一份 npm 副本;发布时由发布脚本将 workspace: 范围改写为已发布的版本。
外部依赖的版本则统一收口在 pnpm-workspace.yaml 的 catalog: 目录中。各 workspace 的 manifest 里写 "react": "catalog:"(或 "catalog:react17" 指向命名目录)即可,避免同一个依赖在多处重复维护版本号。仓库还用命名 catalog(如 react17、tailwind3)支撑 Portal 这类仍跑 React 17 / Tailwind v3 的遗留应用。遇到目录中没有、又想统一管理的第三方依赖版本,改 catalog 而不是逐个改 package.json。
Nx 任务编排:依赖图驱动的构建、测试与开发
Nx 读取整个依赖图并按拓扑顺序执行任务:例如构建某个项目前,会先构建它依赖的项目;nx.json 里 targetDefaults 的 "build": {"dependsOn": ["^build"]} 正是这一语义的声明,dev 目标则声明 continuous: true 表示长驻进程。同时 Nx 会缓存声明过的任务输出(build 默认把 {projectRoot}/build、dist、es、types、umd 列为 outputs),命中缓存时直接跳过重跑。
从仓库根目录最常用的几条命令:
pnpm nx show projects # 列出所有可识别的工作区项目
pnpm nx show project <project-name> # 查看单个项目的 targets 与依赖
pnpm nx graph # 浏览器中打开依赖关系图
pnpm nx run <project-name>:<target> # 运行指定项目的某个 target,如 pnpm nx run @tryghost/admin:build
根 package.json 里的 pnpm build、pnpm lint、pnpm test 本质都是 pnpm nx run-many -t <target>——即对全仓各项目执行同名 target;例如 pnpm build = run-many -t build,而 pnpm lint 除了 run-many 还会追加 lint:boundaries(依赖巡航边界检查)与 lint:packages(校验内部包黄金路径)。日常开发可先执行一次 pnpm setup(安装 + 初始化 submodule),再按需 pnpm dev(基于 compose.dev.yaml 的 Docker 开发栈)。
源码与生产构建:source 导出条件的双轨机制
这是本仓库最具特色的构建设计。部分 TypeScript 包在导出中把 source 条件放在编译产物之前(见 packages/README.md 的示例 exports:"source": "./src/index.ts" → "types" → "default")。Ghost Core 的开发服务器与测试会启用 source 条件,从而直接加载 @tryghost/kg-default-nodes 这类包的原始 TypeScript——每次改动无需 tsc 重编译即可被正在运行的 dev server 和 core 测试命中。
生产环境不启用 source:Node 走 default 条件加载 build/ 里的编译产物,Ghost 发布归档包含的也是这些编译文件而非包源码;浏览器应用同样使用各自的常规构建输出。一个直接推论是:源码改动可能在开发环境立刻生效,但涉及产物内容的生产构建仍需要重新执行 pnpm build——当你改动包导出、构建配置或任何进入发布产物的代码时,务必补跑构建。
Koenig 已发布的 kg-* 包作为既有对外契约,保留了独立的 ESM 与 CommonJS 双输出:import 从 build/esm/ 解析,require 从 build/cjs/ 解析。它们的 files 列表包含 build/ 但不包含 src/,因此 source 条件所用的原始 TypeScript 永远不会被发布出去;而新建的内部包一律采用 ESM-only 契约。此外,由于 Ghost Core 本身是 CommonJS 却跑在支持 require(esm) 的 Node 上(仓库 engines 要求 Node ≥ 22),内部 ESM 包可同时服务 import 与 require() 两类消费者——前提是整个被引用的模块图不能出现顶层 await(ESLint 会强制这一限制)。
仓库之外的关联项目
Ghost 的若干组成部分仍然维护在独立仓库中:gscan 负责校验 Ghost 主题;Ghost-CLI 用于安装与管理生产环境站点;Source、Casper、Themes 存放官方主题;framework 承载 Ghost 使用的共享 Node.js 包;SDK 提供围绕 Ghost API 的工具链。本文只聚焦本仓库内部结构,这些外部仓库的具体用法以它们各自的文档为准。
对本仓库而言,最值得记住的实践结论是:改包之前先看对应目录的 README,改外部依赖版本先去 pnpm-workspace.yaml 的 catalog,改包导出/构建配置后记得跑 pnpm build——遵循这三条,你就能在 Ghost 这个庞大的 monorepo 里安全地穿梭于编辑器、服务端与共享库之间。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00