i-afaqrashid/cms-lab
GitHub: i-afaqrashid/cms-lab
部署前CMS漏洞扫描工具
Stars: 2 | Forks: 0
# CMS 实验室
在部署前捕捉 CMS 缺陷。
[](https://www.npmjs.com/package/@cms-lab/cli)
[](https://github.com/i-afaqrashid/cms-lab/actions/workflows/ci.yml)
[](./LICENSE)
`cms-lab` 捕捉当你的无头 CMS 和你的 Next.js 路由分离时出现的缺陷 - 破损的路由、重复的 slug、指向未发布内容的链接、缺失 SEO/OG/canonical/JSON-LD、图像 alt 和尺寸、地区差异 - 并在它们达到生产环境之前使 CI 失败。与 Prismic、Strapi、Directus、WordPress、Contentful、Sanity 和 Payload 兼容。
无需安装即可运行:
```
npx @cms-lab/cli scan
```
它读取您的配置,获取 CMS 条目,探测您的运行站点,并写入终端、JSON、Markdown、JUnit、Slack 和 HTML 报告输出。没有托管 cms-lab 服务。CLI 在您的项目中运行,并直接与您配置的 CMS 端点通信。

## 安装
```
pnpm add -D @cms-lab/cli @cms-lab/core
```
您也可以不将其添加到项目中运行:
```
npx @cms-lab/cli scan
```
## 在 StackBlitz 中试用
在将 cms-lab 集成到您自己的 CMS 之前,先打开一个公开示例:
- [在 StackBlitz 中运行破损的 Prismic 示例](https://stackblitz.com/fork/github/i-afaqrashid/cms-lab/tree/main/examples/broken-prismic-demo?title=cms-lab%20Broken%20Prismic%20demo)
- [在 StackBlitz 中运行 Next Prismic 配置参考](https://stackblitz.com/fork/github/i-afaqrashid/cms-lab/tree/main/examples/next-prismic?title=cms-lab%20Next%20Prismic%20config)
默认浏览器启动器也可在以下位置找到:
https://cmslab.afaqrashid.com/new.
## 快速开始
在 Next.js 项目中创建 `cms-lab.config.ts`:
```
import { defineConfig } from "@cms-lab/core";
export default defineConfig({
site: { url: "http://localhost:3000" },
framework: { type: "next", router: "app" },
cms: {
provider: "prismic",
repositoryName: "my-repo",
accessToken: process.env.PRISMIC_ACCESS_TOKEN,
},
routes: [
{ type: "page", pattern: "/:uid", getPath: (doc) => `/${doc.uid}` },
{
type: "blog_post",
pattern: "/blog/:uid",
getPath: (doc) => `/blog/${doc.uid}`,
},
],
checks: {
fields: {
required: [
{ type: "page", path: "headline" },
{ type: "blog_post", path: "author.name", severity: "warning" },
],
},
relationships: [
{
from: "blog_post",
to: "author",
where: { fromField: "author.id", toField: "id" },
min: 1,
severity: "warning",
},
],
},
});
```
对于本地化应用程序,其中 `/` 不是您想要首先探测的页面,请将路由相对于 `site.url` 保持相对,并单独设置健康路由:
```
site: {
url: "http://localhost:3000",
healthPath: "/en",
}
```
运行您的 Next.js 应用程序,然后扫描它:
```
pnpm next dev
pnpm cms-lab scan
```
对于 CI:
```
pnpm cms-lab scan --ci --report --markdown --junit
```
或使用 GitHub Action:
```
- uses: i-afaqrashid/cms-lab@v1
with:
config: cms-lab.config.ts
report: true
# node-version: "20" # default; override to "22" or "24" if you need
```
该操作默认安装 Node 20 以匹配 `@cms-lab/cli` 的 `engines.node >= 20.10`。如果您的流程运行在较新的 Node 版本上,请使用 `node-version` 覆盖。
## CMS 适配器
Prismic:
```
cms: {
provider: "prismic",
repositoryName: "my-repo",
accessToken: process.env.PRISMIC_ACCESS_TOKEN,
}
```
Strapi:
```
import { strapiRelationSlug } from "@cms-lab/core";
cms: {
provider: "strapi",
url: "http://localhost:1337",
token: process.env.STRAPI_TOKEN,
locale: "en",
collections: [
{
type: "page",
endpoint: "pages",
uidField: "routing.slug",
urlField: "routing.url",
},
],
singleTypes: [
{
type: "navbar",
endpoint: "navbar",
},
],
},
routes: [
{ type: "page", pattern: "/:slug", getPath: (doc) => `/${doc.uid}` },
{
type: "article",
pattern: "/blog/:topic/:slug",
getPath: (doc) => {
const topic = strapiRelationSlug(doc.data, "topic") ?? "uncategorized";
return `/blog/${topic}/${doc.uid}`;
},
},
]
```
Directus:
```
cms: {
provider: "directus",
url: "http://localhost:8055",
token: process.env.DIRECTUS_TOKEN,
collections: [
{
type: "page",
collection: "pages",
uidField: "routing.slug",
urlField: "routing.url",
},
],
}
```
WordPress:
```
cms: {
provider: "wordpress",
url: "http://localhost:8080",
contentTypes: [
{ type: "page", endpoint: "pages" },
{
type: "post",
endpoint: "posts",
uidField: "acf.handle",
urlField: "acf.permalink",
},
],
}
```
Contentful:
```
cms: {
provider: "contentful",
spaceId: "my-space",
environment: "master",
accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
contentTypes: [
{
type: "page",
contentType: "page",
uidField: "routing.slug",
urlField: "routing.url",
},
],
}
```
Sanity:
```
cms: {
provider: "sanity",
projectId: "my-project",
dataset: "production",
apiVersion: "2025-02-19",
token: process.env.SANITY_READ_TOKEN,
contentTypes: [
{
type: "page",
documentType: "page",
uidField: "slug.current",
urlField: "seo.canonical",
},
],
}
```
Payload:
```
cms: {
provider: "payload",
url: "http://localhost:3000",
apiPath: "/api",
token: process.env.PAYLOAD_TOKEN,
collections: [
{ type: "page", collection: "pages", uidField: "slug" },
{ type: "post", collection: "posts", uidField: "slug" },
],
}
```
所有适配器将内容标准化为相同的扫描模型,因此路由检查、字段检查、SEO 检查、报告输出和 CI 行为保持一致。当您的 CMS 不公开纯 `uid` 或 `slug` 字段时,请使用 `uidField`。当 CMS 已经存储公共永久链接时,请使用 `urlField`。这两个字段都从 `document.data` 读取点分隔路径。
## 命令
```
cms-lab init
cms-lab init --cms strapi --router pages
cms-lab init --cms directus --router pages
cms-lab init --cms payload
cms-lab init --cms wordpress
cms-lab init --cms contentful --space-id my-space
cms-lab init --cms sanity --project-id my-project
cms-lab doctor
cms-lab scan
cms-lab agent-context
cms-lab explain CMS-ROUTE-404
```
有用的扫描选项:
```
cms-lab scan --url https://staging.example.com
cms-lab scan --config ./cms-lab.config.ts
cms-lab scan --json
cms-lab scan --ci
cms-lab scan --report
cms-lab scan --report --share-report
cms-lab scan --markdown
cms-lab scan --junit
cms-lab scan --slack-webhook "$CMS_LAB_SLACK_WEBHOOK"
cms-lab scan --type page
cms-lab scan --only routes
cms-lab scan --only relationships
cms-lab scan --skip seo --skip a11y
cms-lab scan --fail-on warning
cms-lab scan --max-warnings 0
cms-lab scan --strict
cms-lab scan --timeout 10000
cms-lab scan --concurrency 4
cms-lab scan --retries 2
cms-lab scan --debug --verbose 2
```
为编码代理生成上下文文件:
```
cms-lab agent-context
cms-lab agent-context --preset all
cms-lab agent-context --preset claude
cms-lab agent-context --preset gemini
cms-lab agent-context --preset copilot
cms-lab agent-context --force
cms-lab agent-context --no-agents-md
cms-lab agent-context --out .cms-lab
```
默认预设写入 `AGENTS.md`、`.cms-lab/agent-context.md` 和 `.cms-lab/agent-prompt.md`。工具特定的预设也可以写入 `CLAUDE.md`、`GEMINI.md`、`.github/copilot-instructions.md` 和 `.github/prompts/cms-lab-fix.prompt.md`。
生成的文件将代理指向 cms-lab GitHub 仓库、npm 包、文档、本地命令示例、配置的路由模式以及安全的项目事实,而不包括令牌、原始 CMS 有效负载、私有 URL、webhook URL 或本地绝对路径。
## 测试与
公共测试矩阵位于
[`/docs/tested-with`](https://cmslab.afaqrashid.com/docs/tested-with)。它列出了
仅由 fixture、适配器测试、公共演示或可重复的烟雾测试覆盖的路径,并明确标记适配器成熟度限制。
当前覆盖率包括 Prismic 与 Next.js App Router、Strapi v4 与 Next.js Pages Router、Strapi 单个类型,以及 Directus、WordPress、Contentful、Sanity 和 Payload 的适配器 fixture 检查。
有关 cms-lab 建造来捕捉的普通 CMS 失败,请参阅
[`/docs/bug-examples`](https://cmslab.afaqrashid.com/docs/bug-examples)。
有关 cms-lab 如何与链接检查器、Playwright、Lighthouse CI 和自定义路由爬取配合使用,请参阅
[`/docs/comparison`](https://cmslab.afaqrashid.com/docs/comparison)。
有关常见首次运行失败和修复方法,请参阅
[`/docs/troubleshooting`](https://cmslab.afaqrashid.com/docs/troubleshooting)。
有关较大 CMS 库的基线、过滤和并发指南,请参阅
[`/docs/large-catalogs`](https://cmslab.afaqrashid.com/docs/large-catalogs)。
有关 Prismic、Strapi、Directus、WordPress、Contentful 和 Sanity 的提供者特定设置说明,请参阅
[`/docs/providers`](https://cmslab.afaqrashid.com/docs/providers)。
有关通用 Directus 餐厅/目录配置,请参阅
[`/docs/examples/directus-restaurant`](https://cmslab.afaqrashid.com/docs/examples/directus-restaurant).
## 检查
cms-lab 当前检查:
- 无法生成配置路由的 CMS 文档
- 返回 `404` 的预期 CMS 路由
- 返回 `5xx` 的预期 CMS 路由
- 站点可达后失败的路线探测
- 缺失 SEO 标题和描述
- 缺失或占位符图像 alt 文本
- 在 `checks.fields.required` 中声明的自定义必需字段
- 在 `checks.relationships` 中声明的跨文档关系最小值
扫描器保留原始 CMS 有效负载在 `document.data` 中,当 CMS 公开时保留公共永久链接,在可用时使用类似 slug 的字段作为 `uid`,并将非公开条目(如草稿、存档内容和计划中的 WordPress 帖子)视为 `draft`。
## 输出和隐私
默认情况下,`--json` 会删除原始 CMS 文档数据、文档 URL、文档 UIDs 和绝对项目路径。仅在使用需要完整有效负载的私有自动化时使用 `--include-sensitive-output`。
报告输出:
- `--report` 写入 `.cms-lab/report.html`
- `--share-report` 从 HTML 报告中删除 CMS 源 ID 和本地项目路径,同时保留诊断代码、严重性、路由路径和字段路径可见
- `--markdown` 写入 `.cms-lab/summary.md`
- `--junit` 写入 `.cms-lab/junit.xml`
- `--slack-webhook` 发送紧凑的删除 Slack 摘要
终端、JSON、Markdown 和 HTML 报告包括当相同的内容类型/模板多次产生相同的诊断时,重复发现的摘要。
原始行级诊断始终可用于调试。
Slack 摘要包括计数和诊断代码。它们不包括 CMS 令牌、webhook URL、原始 CMS 有效负载、本地项目路径或完整的 JSON 输出。
## 退出行为
`cms-lab scan` 退出:
- `0` 当扫描在配置的失败阈值以下完成时
- `1` 当诊断超过阈值时
- `2` 对于配置、加载或验证错误
- `3` 当 CMS 不可达或认证失败时
- `4` 当站点不可达时
- `130` 当被中断时
当您想要没有失败的工件时,请使用 `--fail-on never`。当警告和 info 诊断应该使 CI 失败时,请使用 `--strict`。
## 开发
```
pnpm install
pnpm test
pnpm bench
pnpm typecheck
pnpm build
pnpm site:build
pnpm lint
pnpm verify
pnpm smoke:pack
```
在本地运行文档站点:
```
pnpm site:dev
```
运行公共 Prismic 烟雾 fixture:
```
pnpm build
pnpm live:doctor
pnpm live:scan
pnpm smoke:pack:live
```
`pnpm smoke:pack` 打包可发布的包,将它们安装到干净临时应用程序中,并运行安装的 `cms-lab` 二进制文件。`pnpm smoke:pack:live` 执行相同的包烟雾测试,然后扫描公共 Prismic fixture。
## 仓库
```
packages/core config, types, diagnostics, checks
packages/cli cms-lab binary and output
packages/next Next.js project detection
packages/prismic Prismic adapter
packages/strapi Strapi adapter
packages/directus Directus adapter
packages/wordpress WordPress adapter
packages/contentful Contentful adapter
packages/sanity Sanity adapter
packages/payload Payload adapter
packages/reporter local HTML report renderer
apps/site marketing site and docs
test-fixtures/ public Prismic fixture
```
## 社区
- [讨论](https://github.com/i-afaqrashid/cms-lab/discussions) 用于
问题、新检查的想法和展示。
- [路线图](https://cmslab.afaqrashid.com/roadmap) 用于已发布、计划和
正在研究的内容。
- [问题](https://github.com/i-afaqrashid/cms-lab/issues) 用于错误和具体的
功能请求。
贡献或审查?请参阅 [CONTRIBUTING.md](./CONTRIBUTING.md) 和
[.github/CODEOWNERS](./.github/CODEOWNERS) 以了解所有权和审查路径。
## 发布和维护
阅读 [CHANGELOG.md](./CHANGELOG.md) 和
[GitHub Releases](https://github.com/i-afaqrashid/cms-lab/releases) 了解
按版本发布的发布历史。
在使用 cms-lab 作为生产项目中严格的部署门之前,请阅读 [版本和稳定性策略](https://cmslab.afaqrashid.com/docs/versioning)
阅读 [LAUNCH.md](./LAUNCH.md) 了解发布清单、npm 发布流程、发布后验证和发布日笔记。
阅读 [TESTING.md](./TESTING.md) 了解本地测试者工作流程。在打开公共问题或 PR 之前,请阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)、[SECURITY.md](./SECURITY.md)、[SUPPORT.md](./SUPPORT.md)、[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)。
cms-lab 是 MIT 许可。请参阅 [LICENSE](./LICENSE).
标签:CMS, Contentful, Directus, MITM代理, Payload, Prismic, Sanity, SEO, Strapi, WordPress, 代码审查, 内容管理系统, 安全可观测性, 版本控制, 自动化攻击, 错误报告