首页
/ Serverless Framework 贡献者工程手册:Monorepo 架构、测试矩阵与发布流水线全解

Serverless Framework 贡献者工程手册:Monorepo 架构、测试矩阵与发布流水线全解

2026-09-07 16:45:48作者:咎岭娴Homer

本文基于 Serverless Framework 仓库的 AGENTS.md 编写,系统拆解该 CLI 框架 monorepo 的包结构与 Runner 路由架构、本地开发规范、依赖与锁定文件约束、分层测试策略、esbuild 发布打包链路,以及 CI 流水线与版本发布机制。读完后,你将知道如何在本地跑通这个以 Node.js 24 + npm 12(ES Modules)开发、面向 Node.js >= 18 的 CLI,以及一次改动从 lint、测试到 canary/正式发布的完整旅程。

一、Monorepo 结构总览

Serverless Framework 是一个用于将应用部署到 AWS Lambda 及其他托管云服务的命令行工具,由 YAML 配置(serverless.yml)驱动。仓库采用 npm workspaces 组织 monorepo,顶层结构如下:

├── packages/
│   ├── sf-core/                # CLI shell: entry point, command router, runners
│   ├── serverless/             # Traditional framework: AWS provider, plugins, config schema
│   ├── engine/                 # Shared AWS client wrappers used across the CLI
│   ├── mcp/                    # MCP server for AI IDEs
│   ├── util/                   # Shared utilities
│   ├── standards/              # ESLint and Prettier configs
│   ├── framework-dist/         # Bundled distribution package (excluded from npm workspaces)
│   └── sf-core-installer/      # Published to npm as "serverless" (excluded from npm workspaces)
├── binary-installer/           # Go-based binary installer
├── docs/sf/                    # User-facing documentation (published to serverless.com)
├── skills/                     # Agent Skills shipped inside the CLI (CI-linted)
└── release-scripts/            # Release automation

根目录 package.json 中的 workspaces 配置印证了这一点:packages/* 全部纳入,但明确排除了 packages/framework-distpackages/sf-core-installer(见 workspaces 字段中的 !packages/framework-dist!packages/sf-core-installer),并额外纳入 release-scripts。两个被排除的包之所以独立,是因为:

  • packages/sf-core-installer 才是 npm 用户实际 npm install 到的 serverless 包,它拥有自己独立的 overrides、自己发布时npm-shrinkwrap.json 和独立的 .npmrc —— 根目录层面的依赖修复永远传不到它那里;
  • packages/framework-dist 在 git 中是一个空壳,内容在构建时才生成,其发布产物直接决定终端用户安装到的内容。

架构要点:CLI Shell 与 Runner 路由

从源码结构看,packages/sf-core 是 CLI 外壳:

  • 入口 bin/sf-core.js 用 yargs 解析命令行参数(scriptName('serverless'),强制 --param 解析为数组),随后调用 ServerlessCore.run()
  • 核心调度器 src/lib/router.js 维护 const runners = [ComposeRunner, CfnRunner, TraditionalRunner],按配置文件的名称前缀与 shouldRun 判定把命令分发给对应 Runner,并在执行前后完成状态保存、遥测事件与元数据落盘;
  • 该包同时承载认证(src/lib/auth/)、变量解析器(src/lib/resolvers/)、可观测性与 Agent Skills 逻辑(src/lib/agent-skills/)。

packages/serverless 是绝大多数改动落地的地方:AWS provider 实现、全部插件(lib/plugins/aws/lib/plugins/esbuild/ 等),以及 serverless.yml 配置模式。两个关键文件的分工值得注意:

  • lib/config-schema.js 定义 serverless.yml 的基础 JSON Schema(含 providerfunctionsbuildframeworkVersion 等顶层属性),各插件在此基础上扩展自己的配置面;
  • lib/classes/plugin-manager.js原生/内置插件的权威注册表 —— 该文件开头连续 import 了 pluginDeploypluginAwsProviderpluginAwsPackageCompileEventsApiGateway 等数十个插件并聚合为 internalPlugins 数组;注意 lib/plugins/index.js 并不是完整列表,判断“框架内置了哪些插件”应以 plugin-manager 为准。

二、本地开发环境搭建

开发统一使用 Node.js 24 + npm 12(ES Modules),但发布的 CLI 支持 Node.js >= 18。根 package.json 通过 packageManager: "npm@12.0.1"devEngines(node ^24.15.0)声明了这一基线:

# 安装依赖(npm ci 永不改写 lockfile;优先于 npm install)
npm ci

# 在测试项目上以本地源码方式运行框架
cd /path/to/your/test-project
node /path/to/serverless/packages/sf-core/bin/sf-core.js deploy

直接 node packages/sf-core/bin/sf-core.js <command> 跑本地源码的方式,正是入口文件与 Runner 路由机制的直接消费场景:命令会在 serverless.yml 所在目录被 findRunner 扫描匹配后进入 TraditionalRunner

三、代码风格与 Lint 流程

格式规则

仓库通过 Prettier 强制执行以下风格(配置见 prettier.config.js 与共享标准 packages/standards/src/prettier.js):

  • 不使用分号(Prettier 会移除)
  • 字符串使用单引号
  • 2 空格缩进
  • LF 换行符
  • 一律使用 ES Modulesimport/export),不用 require()
  • 优先原生 JavaScript 而非 lodash;异步代码使用 async/await
  • 新增的示例、fixture 与代码片段应使用当前厂商推荐的现代写法(ESM .mjs handler、最新 runtime、AWS SDK v3)——与仓库中旧内容的风格不一致不构成保留遗留写法的理由

Lint 命令

npm run prettier        # 检查格式
npm run prettier:fix    # 修复格式
npm run lint            # 运行 ESLint
npm run lint:fix        # 修复 lint 问题

容易踩坑的细节(Gotchas)

这些细节都有对应的仓库证据:

  1. ESLint 只检查显式路径 glob —— 配置集中在根目录 eslint.config.js。新建包或顶层源码目录后,如果不把路径加进该文件的 glob 列表,它会被静默地完全不 lint
  2. 共享 ESLint 配置禁用了 no-unused-vars 等规则 —— packages/standards/src/eslint.js 中关闭了未使用变量检查,因此 lint 抓不到未使用的变量或 import,需要靠代码评审自行把关。
  3. pre-commit 钩子只做了格式化的半自动化 —— .husky/pre-commit 仅一行 npx lint-staged --concurrent true,根 package.jsonlint-staged 配置对 *.{js,ts,tsx,jsx} 执行 prettier -w。也就是说提交时只对暂存的 JS/TS 文件跑 Prettier,push 前仍须手动跑 lint。
  4. ES Modules 规则的一个例外packages/sf-core-installer 是 CommonJS(因为它是发布到 npm 的安装器壳)。
  5. .env 文件刻意不加入 gitignore —— 测试 fixture 依赖它们(.gitignore 中有一条被注释掉的 **/.env 及说明),所以绝不要把真实凭据写进任何 .env 文件

四、依赖管理约束

Node 18 运行时兼容是硬约束

packages/serverless/package.json 声明 engines.node: ">=18.0",即发布产物必须继续支持 Node 18,即便开发环境已用 Node 24。为此,放弃 Node 18 支持的主版本升级会被 .github/dependabot.yml 的 ignore 列表拦截 —— 该文件以注释明确列出每条拦截原因(如 ora v9 drops support for Node.js 18open v11 drops support for Node.js 18joi v18 drops support for Node.js 18 等)。升级依赖前应先查看该文件;仅开发用的依赖(devDependencies)则不受此约束。

lockfile 与版本解析规则

  • package-lock.json 只能用 npm 12 写:npm <= 11 会在 workspaces 仓库中静默丢弃根级 overrides(npm/cli#4834 问题);普通安装一律用 npm ci
  • .npmrc 仅一行 min-release-age=3发布不到 3 天的 npm 版本不会被解析,除非显式传 --min-release-age=0。这是一道针对上游“刚发布就有问题”版本的缓冲闸。

五、测试矩阵:单元测试、集成测试与无头 CLI 测试

单元测试(本地运行)

npm run test:unit -w @serverlessinc/sf-core     # jest 跑 packages/sf-core/tests/unit/
npm run test:unit -w @serverless/framework      # jest 跑 packages/serverless/test/unit/
npm test                                        # 同时跑两个 unit 套件

两个易错点:

  • 目录命名不一致:sf-core 用 tests/,serverless 用 test/,新增测试时容易放错位置;
  • 必须通过 npm script 调用 Jest,不能裸跑 jest:脚本中显式设置了 --experimental-vm-modules(见 packages/sf-core/package.jsontest:unit 脚本:NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" ... jest "tests/unit/.*"),这是 ESM 支持的前提。

集成测试(真实 AWS)

集成测试会部署真实的 AWS 栈。它们在 CI 中对非 draft PR 运行,本地运行需要 AWS 凭据及 TESTING.md 中描述的前置资源:

npm test -w @serverlessinc/sf-core              # 集成套件(不含 domains 与 mcp)
npm run test:<suite> -w @serverlessinc/sf-core  # 指定套件

可用的定向套件(对应 packages/sf-core/package.json 中的 test:* 脚本)包括:simple:nodejssimple:pythonsimple:composesimple:dashboardsimple:resolversresolversesbuildsamsandboxesstatedeployment-bucketlicense-keydomainsmcpcompose:devcompose:subset优先选择覆盖你所改动区域的定向套件,而不是全量集成套件。

两个被排除在 npm test 之外的套件及原因:

  • domains:没有任何 CI workflow 跑它,仅在显式调用时执行;
  • mcp:由路径过滤的 CI: MCP Servers workflow(.github/workflows/ci-mcp.yml)负责;其中只有 mcp-auth.test.js 需要 TESTING.md 中的 Cognito 前置资源——缺失时该套件跳过、其余照常运行。

注意:tests/integration/ 下的任何新目录会自动加入 npm test,因此昂贵的新套件必须像上面两个一样显式排除。

集成测试的约定(conventions):

  1. 每个套件为 <name>.test.js 配一个同级 fixture/ 目录承载被测服务 —— 一个测试文件独占一个 fixture 目录,因为 jest 并行跑测试文件且不设 worker 上限,两个文件共用一个目录会在 .serverless/node_modules/ 与暂存产物上相互竞争;
  2. 复用 packages/sf-core/tests/utils/ 中的共享工具(runSfCore.jstestUtils.js,例如 fetchWithRetry 用于处理最终一致性端点),不要自己手写 CLI 调用;
  3. fixture 不得声明遗留打包插件(serverless-esbuildserverless-webpackserverless-plugin-typescriptserverless-bundle)——它们会抛 PLUGIN_TYPESCRIPT_CONFLICT,除非设置了 build.esbuild: false
  4. 新集成测试必须自清理(部署 → 执行 → 拆除,失败时也要拆除)、使用唯一栈名保证并行安全、fixture 与断言中不得出现密钥或账号 ID。

Dev 模式测试需要预先构建被 gitignore 的 shim:

npm run build:devmode:shim -w @serverless/framework

对应 packages/serverless/package.json 中的脚本:esbuild lib/plugins/aws/dev/shim.js --bundle --platform=node --minify --outfile=lib/plugins/aws/dev/shim.min.js(CI 会作为独立步骤执行)。

其他套件

npm test -w @serverless/mcp                          # mcp 测试(任何 CI workflow 都不跑)
npm test -w @serverless/engine                       # engine 单元测试
npm run test:python -w @serverlessinc/sf-core        # python 插件测试
npm run test:build -w @serverlessinc/sf-core         # 打包冒烟 + skills 打包校验(不在 CI 中)
cd binary-installer && go test ./... && make build-prod   # Go 安装器

两个覆盖盲区需要注意:

  • python CI job 是路径过滤的(仅当 python 插件路径变更时运行,见 ci-python.yml)——失败可能长期潜伏在 main 上,直到某个 PR 触碰这些路径;
  • packages/util 完全没有测试:它的改动只能通过消费方的套件被间接验证。

无头(Headless)方式测试 CLI 行为

永远不要用 pty 驱动 CLIscriptpty.spawn):pty 与真实终端无法区分,会导致 spinner 动画和交互式提示弹出。请使用纯管道(pipe)——交互判定门槛通常是 stdin.isTTY && stdout.isTTY && !CI,例如:

echo "your-input" | node packages/sf-core/bin/sf-core.js some-command

六、发布与打包链路:esbuild 单文件、非 JS 资产与 Go 安装器

esbuild 打包与非 JS 资产注册

发布的 CLI 用 esbuild 打包成单文件(构建配置:packages/sf-core/esbuild.js,入口 ./bin/sf-core.js,产物 ../framework-dist/dist/sf-core.js)。标准 import/export 模块会被自动打包,但非 JS 资产以及一切通过 __dirname 相对路径加载的资源(JSON、.py 文件、模板、被 spawn 的脚本)必须在 packages/sf-core/scripts/prepareDistributionTarballs.js显式注册 —— 否则代码在源码下运行正常、发布后却会断。

另外,构建时还会把 skills/ 目录的 Agent Skills 清单读入并 define__SF_SKILLS_MANIFEST__ 常量内嵌进 bundle(源码运行则实时读取 skills/ 目录),版本号 __SF_CORE_VERSION__ 同理,IS_CANARY=true 时以 git 短 SHA 作为版本。

一个明确的反模式警告来自 esbuild.js 源码注释:esbuild 必须保留在 external 列表中。如果把它一并打包,其内部 transformSync 所 spawn 的 Worker 中 __filename 会指向整个 bundle,导致 Worker 重新执行整个 CLI(esbuild 官方文档与上游 guard 都明确警告“esbuild JavaScript API cannot be bundled”,参见上游 issue #13574)。

framework-dist 与 Go 启动器的分发现场

packages/framework-dist 在 git 中是空壳,构建时生成内容。npm 上的 serverless 包(即 sf-core-installer)只负责下载 Go 启动器二进制;启动器按项目解析 frameworkVersion,把由 framework-dist 构建的发布 tarball 下载到 ~/.serverless/releases/<version> 并在其中执行 npm install —— 发布 tarball 的内容直接成为终端用户的安装内容。启动器行为(版本解析、缓存、24 小时更新节流)详见 binary-installer/README.md

七、Agent Skills(skills/)的版本契约

CLI 内置了 skills/ 目录下的 Agent Skills(当前含 skills/manifest.json 声明的条目,完整契约见 skills/README.md)。其变更规则非常严格:

  • 任何对 skill 内容的改动,必须 bump 其 metadata.version 并重新生成清单,否则 CI 失败(CI 的 Lint 阶段会执行 node packages/sf-core/scripts/lint-skills.js,见 ci-framework.yml):
node packages/sf-core/scripts/lint-skills.js --update
  • 提交时必须把 skills/manifest.json 一并带上;
  • 用户安装中的辅助文件永远不会被删除 —— 因此要新增或重命名文件,而不是复用/改用途一个既有文件名。

八、CI 流水线与发布工作流

CI 运行在指向 main 的 PR 上,使用 Node.js 24.x(见 ci-framework.ymlnode-version: 24.x):

Workflow 内容 触发条件
CI: Framework CLI Lint(ESLint + Prettier + Agent Skills)、Test: Engine、Test: Framework(unit + integration) 纯文档改动整体跳过(paths-ignore: docs/**),draft PR 也跳过
CI: Binary Installer Go 构建与测试(ci-binary-installer.yml 仅当 binary-installer/** 变更
CI: Python Requirements python 插件测试(ci-python.yml 路径过滤
CI: MCP Servers 实时 mcp 套件(ci-mcp.yml 路径过滤到 MCP 插件、api-gateway 与 esbuild 接缝、MCP 测试/fixture

由于 GitHub Actions 没有 job 级路径过滤,python 与 MCP 套件各自独立成 workflow 文件。

发布工作流(release-*.yml,如 release-framework.yml只在 push 到 main 或手动 dispatch 时运行,从不被 PR CI 验证 —— 修改它们必须格外仔细。其中 release-framework.yml 还带有路径过滤(packages/{sf-core,serverless,engine,mcp}/**):其他位置(如 packages/util)的改动不会单独触发 release 构建。

九、PR 规范与版本发布流程

  • PR 一律 squash merge,PR 标题即 commit message。使用约定式格式 type(scope): description —— 祈使语气、句尾无句号、约 72 字符上限。类型:featfixperfdocsrefactortestcichore
  • semver 映射:任何 feat: 触发 minor 发布;仅 fix:/chore: 才是 patch。完整的语义化解释见 VERSIONING.md —— 值得注意的是,CLI 输出结构的变更与生成的 CloudFormation 的变更都被视为 breaking
  • 非平凡的功能与修复应先有 open issue —— 见 CONTRIBUTING.md
  • 面向用户的变更(行为、配置面、CLI 输出)应在同一个 PR 中更新 docs/sf/ 文档。
  • README.md 在发布时被复制进发布的 npm 包 —— 对它的编辑本身就是面向用户的内容。
  • canary 通道:每次 push 到 main 且触碰 release 相关包时,自动发布按 git 短 SHA 命名的 canary 构建(用户在 serverless.yml 中以 frameworkVersion: canary 选择该通道)。合入 main 的代码数分钟内即出现在 canary 通道上,因此 main 必须时刻保持可发布状态
  • 正式 release:需在 packages/sf-core-installer/package.jsonpackages/sf-core/package.json 两处 bump 版本,PR 标题严格为 chore: release x.x.x;合并后 CI 打 tag sf-core@x.y.z(可用这些 tag 对比两次发布间的内容)。npm 只是次级分发渠道,主渠道是 curl 安装器(install.serverless.com)。完整流水线见 RELEASE_PROCESS.md

十、关键文件速查

文件 作用
packages/sf-core/bin/sf-core.js CLI 入口(yargs 解析 + ServerlessCore.run()
packages/sf-core/src/lib/router.js 命令调度器(Runner 选择、CLI schema 校验、状态/遥测收尾)
packages/serverless/lib/config-schema.js 基础 serverless.yml 模式(各插件在其上扩展)
packages/serverless/lib/classes/plugin-manager.js 原生/内置插件的权威注册表
packages/sf-core/scripts/prepareDistributionTarballs.js 发布用非打包资产注册表
packages/standards/src/eslint.js / packages/standards/src/prettier.js 共享 lint 与格式化配置

配套的流程文档入口:TESTING.md(集成测试前置资源)、VERSIONING.md(semver 细则)、RELEASE_PROCESS.md(发布流水线)、skills/README.md(Agent Skills 契约)。掌握以上结构、约束与流水线后,无论是改插件、加测试,还是排查“为什么我的改动没有触发发布”,都可以在这个仓库中找到明确依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388