cvalingam/architecture-linter
GitHub: cvalingam/architecture-linter
一个用于 TypeScript 项目的架构规则强制工具,通过静态分析检测分层架构中的依赖违规,内置多种架构预设并支持 CI 集成。
Stars: 1 | Forks: 0
# architecture-linter
`architecture-linter` 读取 `.context.yml` 配置文件并扫描您的 TypeScript 源码树以查找依赖违规——例如 controller 直接导入 repository,绕过了 service 层。
## 快速开始
```
npm install --save-dev architecture-linter
npx architecture-linter init # generate .context.yml from your folder structure
npx architecture-linter scan # check for violations
```
当存在违规时的预期输出:
```
Scanning project...
❌ Architecture violation detected
File: controllers/orderController.ts
Import: repositories/orderRepository
Rule: Controller cannot import Repository
── Violations by layer ──────────────────
controller 1 violation(s)
service 0 violation(s)
repository 0 violation(s)
Found 1 violation in 3 file(s) scanned.
```
## 安装
### 作为本地开发依赖(推荐)
```
npm install --save-dev architecture-linter
```
在您的 `package.json` 中添加脚本:
```
{
"scripts": {
"lint:arch": "architecture-linter scan"
}
}
```
### 全局安装
```
npm install -g architecture-linter
```
## 新项目快速设置
运行 `init` 通过检查您的文件夹结构自动生成 `.context.yml`。
该命令从顶级目录和 `src/` 子目录名称中检测常见的层名称(`controller`、`service`、`repository`、`middleware` 等)。
```
architecture-linter init
```
然后编辑生成的文件以添加您的约束,并运行:
```
architecture-linter scan
```
## 框架预设
使用内置预设为流行的架构模式获取合理的起始配置。在 `.context.yml` 中使用 `extends` 键声明它:
```
extends: nestjs
```
用户定义的层和规则始终优先于预设默认值。
| 预设 | 层 |
|---|---|
| `nestjs` | module, controller, service, repository, guard, interceptor, pipe, decorator, dto, entity |
| `clean-architecture` | entity, usecase, repository, infrastructure, interface |
| `hexagonal` | domain, port, adapter, application, infrastructure |
| `nextjs` | page, component, hook, lib, api, store, util |
### 扩展多个预设
```
extends:
- clean-architecture
- nestjs
```
### 覆盖预设规则
```
extends: nestjs
rules:
# Override the nestjs default — allow controllers to import repositories directly
controller:
cannot_import: []
```
## 配置参考
在您的项目根目录创建一个 `.context.yml`(或传递 `--context` 以覆盖)。
当省略 `--context` 时,linter 会向上遍历目录树直到找到 `.context.yml` —— 就像 ESLint 一样。
```
# 可选:扩展现有内置 preset
extends: nestjs
architecture:
layers:
- controller
- service
- repository
rules:
# Blacklist: this layer must NOT import from any layer in the list.
controller:
cannot_import:
- repository
# Whitelist: this layer may ONLY import from layers in the list.
service:
can_only_import:
- repository
repository:
cannot_import: []
# 完全跳过的文件的 Glob patterns(相对于项目)。
exclude:
- "**/*.spec.ts"
- "**/*.test.ts"
- "**/__mocks__/**"
# 手动路径别名覆盖(自动补充 tsconfig.json paths)。
aliases:
"@repositories": "src/repositories"
"@services": "src/services"
```
### 规则选项
| 选项 | 类型 | 描述 |
|---|---|---|
| `cannot_import` | `string[]` | 黑名单 —— 该层不得从任何列出的层导入 |
| `can_only_import` | `string[]` | 白名单 —— 该层只能从列出的层导入 |
| `files` | `string` (glob) | 将此规则的范围限定为匹配该模式的源文件 |
`cannot_import` 和 `can_only_import` 是互斥的。每个层规则使用其中一个。
#### 将规则范围限定到特定文件
```
rules:
controller:
files: "src/controllers/admin/**"
cannot_import:
- repository
```
### 路径别名解析
Linter 自动从您的 `tsconfig.json` 读取 `compilerOptions.paths`,并在检查规则之前解析别名导入。对于标准 TypeScript 路径别名,无需额外配置。
对于 monorepo 或非标准设置,通过 `aliases` 键添加手动覆盖:
```
aliases:
"@repositories": "src/repositories"
```
手动别名优先于任何具有相同键的 `tsconfig.json` 条目。
### 使用 `arch-ignore` 进行内联抑制
要抑制单个违规而不移除导入,请在导入正前方的行添加 `// arch-ignore:` 注释:
```
// arch-ignore: controller cannot import repository
import { OrderRepository } from '../repositories/orderRepository';
```
提示必须匹配规则字符串(不区分大小写):` cannot import `。
### 层检测的工作原理
Linter 从文件的**目录名称**推断其所属层。单数和复数形式均能识别(包括不规则复数,如 `repository` → `repositories`)。
| 路径 | 检测到的层 |
|---|---|
| `controllers/orderController.ts` | `controller` |
| `services/orderService.ts` | `service` |
| `repositories/orderRepository.ts` | `repository` |
| `src/controllers/admin/ctrl.ts` | `controller` |
## CLI 参考
### `scan`
```
architecture-linter scan [options]
Options:
-c, --context Path to the .context.yml file (auto-detected if omitted)
-p, --project Root directory of the project to scan (default: .)
-f, --format Output format: text or json (default: text)
-s, --strict Report files not assigned to any layer
-q, --quiet Suppress the "Scanning project..." banner
-e, --explain Print why/impact/how-to-fix guidance per violation
-x, --fix Show a suggested fix for each violation
-w, --watch Watch for file changes and re-scan automatically
-h, --help Display help
```
#### `--explain` —— 理解每个违规
```
architecture-linter scan --explain
```
在每个违规下方添加三个部分:
- **Why this matters** —— 此规则存在的架构原因
- **Impact** —— 如果保留违规会发生什么问题
- **How to fix** —— 具体建议
#### `--fix` —— 获取建议的修复
```
architecture-linter scan --fix
```
为每个违规打印简短的可操作消息,例如:
```
🔧 Suggested fix
Instead of importing 'repository' directly, route through an allowed
intermediary layer: 'service'.
```
#### `--watch` —— 文件更改时重新扫描
```
architecture-linter scan --watch
```
监视项目目录中的 `.ts` 文件更改并自动重新运行扫描。按 `Ctrl+C` 停止。
### `init`
```
architecture-linter init [options]
Options:
-p, --project Root directory of the project (default: .)
```
通过从目录结构检测层名称来生成起始 `.context.yml`。如果 `.context.yml` 已存在,则安全失败。
### `ci`
```
architecture-linter ci [options]
Options:
--platform CI platform to target: github (default: github)
-p, --project Root directory of the project (default: .)
```
生成现成的 CI 工作流文件。目前支持 GitHub Actions:
```
architecture-linter ci
# 创建:.github/workflows/arch-lint.yml
```
如果工作流文件已存在,则安全失败。
### `score`
```
architecture-linter score [options]
Options:
-c, --context Path to the .context.yml config file (auto-detected)
-p, --project Root directory of the project to scan (default: .)
-f, --format Output format: text or json (default: text)
```
基于三个加权组件计算 **0 到 100 的架构健康分**:
| 组件 | 最高分 | 衡量内容 |
|---|---|---|
| 违规密度 | 60 | 相对于总导入数,存在多少导入违规 |
| 层覆盖率 | 25 | 属于已声明层的文件比例 |
| 规则完整性 | 15 | 至少定义了一条规则的层比例 |
**等级:** A (90–100) · B (75–89) · C (60–74) · D (40–59) · F (0–39)
```
npx architecture-linter score
```
示例输出:
```
Architecture Health Score
87/100 Grade: B █████████████████░░░
Breakdown:
Violation density 52/60 pts
Layer coverage 25/25 pts
Rule completeness 10/15 pts
Stats:
Files scanned: 24
Total imports: 87
Violations: 4
Classified files: 24/24
Layers with rules: 2/3
```
该分数也包含在 `scan --format json` 输出的 `score` 键下。
### `badge`
为架构健康分生成 [shields.io](https://shields.io) 徽章 URL。将其放入您的 README 以便一目了然地显示当前等级。
```
architecture-linter badge [options]
```
| 选项 | 描述 | 默认值 |
|---|---|---|
| `-c, --context ` | `.context.yml` 的路径 | 自动检测 |
| `-p, --project ` | 项目根目录 | `.` |
| `-f, --format ` | 输出格式:`url` 或 `markdown` | `url` |
| `-o, --output ` | 将徽章写入文件而不是标准输出 | — |
```
# 打印 shields.io URL
npx architecture-linter badge
# 打印可直接粘贴到 README 的 Markdown 图片标签
npx architecture-linter badge --format markdown
# 将 badge URL 写入文件(在 CI 中有用)
npx architecture-linter badge --output .badge-url.txt
```
徽章颜色:**A** = 亮绿色,**B** = 绿色,**C** = 黄色,**D** = 橙色,**F** = 红色。
### `scan --format sarif`
输出 [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) 文档 —— GitHub Code Scanning 的标准格式。使用 `github/codeql-action/upload-sarif` 操作上传它,以将违规作为原生 PR 注释获取。
```
npx architecture-linter scan --format sarif > results.sarif
```
GitHub Actions 步骤示例:
```
- name: Run architecture linter (SARIF)
run: npx architecture-linter scan --format sarif > arch.sarif
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: arch.sarif
```
### `scan --baseline` —— 棘轮模式
保存当前违规计数,仅在违规**增加**时失败。非常适合在现有代码库上采用 linter,而无需立即修复所有问题。
```
# 首次运行:创建 .arch-baseline.json 并以状态码 0 退出
npx architecture-linter scan --baseline
# 后续运行:仅在违规数超过保存的 baseline 时失败
npx architecture-linter scan --baseline
# 在清理违规后显式更新 baseline
npx architecture-linter scan --baseline --update-baseline
# 使用自定义 baseline 文件路径
npx architecture-linter scan --baseline baselines/prod.json
```
基线文件(默认为 `.arch-baseline.json`)记录违规计数、时间戳和工具版本。提交它以跟踪随时间推移的回归。
### `scan --detect-circular`
使用 Tarjan SCC 算法检测**架构层之间的循环依赖**。当层 A(传递性地)导入层 B,而层 B 又导入回层 A 时,就存在循环。
```
npx architecture-linter scan --detect-circular
```
文本输出示例:
```
↻ Circular dependencies detected between layers:
controller → service → controller
```
循环依赖也包含在 `--format json` 输出的 `circularDeps` 键下:
```
{
"circularDeps": [
{ "cycle": ["controller", "service", "controller"] }
]
}
```
### `scan --monorepo`
扫描根 `package.json` 的 `workspaces` 字段中定义的**所有工作区包**。每个包使用自己的 `.context.yml`(如果存在);否则回退到根配置。
```
npx architecture-linter scan --monorepo
```
每个包独立扫描,并在输出中添加前缀:
```
Scanning @myorg/api-gateway...
✅ No architecture violations found. (12 file(s) scanned)
Scanning @myorg/user-service...
❌ Architecture violation detected
...
```
如果任何包存在违规,命令将以 `1` 退出。
### 退出码
| 代码 | 含义 |
|---|---|
| `0` | 未发现违规 |
| `1` | 发现一个或多个违规(或发生致命错误) |
## GitHub Action
在每个 pull request 上强制执行架构规则的最简单方法 —— 无需 Node.js 设置,违规显示为内联代码注释。
```
# .github/workflows/arch-lint.yml
name: Architecture Lint
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
arch-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cvalingam/architecture-linter@v0.1.2
with:
config: .context.yml # path to your config (default: .context.yml)
fail-on-violations: 'true' # fail the job on violations (default: true)
token: ${{ secrets.GITHUB_TOKEN }} # enables PR comment summary (optional)
```
### Action 输入
| 输入 | 默认值 | 描述 |
|---|---|---|
| `config` | `.context.yml` | 配置文件的路径 |
| `working-directory` | `.` | 要扫描的根目录 |
| `fail-on-violations` | `true` | 发现违规时步骤失败 |
| `token` | `''` | GitHub token —— 启用带有违规表的 PR 评论 |
### Action 输出
| 输出 | 描述 |
|---|---|
| `violations` | 发现的违规数量(可在后续步骤中使用) |
当发现违规时,每个违规都作为**红色注释**直接显示在 PR 的差异行上,并且会在 PR 线程中发布摘要评论。
## GitHub Action
在每个 pull request 上强制执行架构规则的最简单方法 —— 无需手动设置。违规作为内联 PR 注释和可选的 PR 评论摘要发布:
```
# .github/workflows/arch-lint.yml
name: Architecture Lint
on:
pull_request:
branches: ["**"]
jobs:
arch-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Enforce architecture rules
uses: cvalingam/architecture-linter@v0.1.2
with:
token: ${{ secrets.GITHUB_TOKEN }} # for PR comment (optional)
```
**输入**
| 输入 | 默认值 | 描述 |
|---|---|---|
| `config` | `.context.yml` | 配置文件的路径 |
| `working-directory` | `.` | 要扫描的项目根目录 |
| `fail-on-violations` | `true` | 发现违规时步骤失败 |
| `token` | `''` | `GITHUB_TOKEN` —— 启用 PR 评论摘要 |
**输出**
| 输出 | 描述 |
|---|---|
| `violations` | 发现的违规数量 |
## CI 集成(手动设置)
在每次推送和 pull request 时运行 linter。`ci` 命令会为您生成此配置(`architecture-linter ci`),或者手动添加步骤:
```
# .github/workflows/arch-lint.yml
name: Architecture Lint
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
arch-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npx architecture-linter scan --strict
```
## JSON 输出
```
architecture-linter scan --format json
architecture-linter scan --format json --fix --explain
```
```
{
"filesScanned": 3,
"violations": [
{
"file": "controllers/orderController.ts",
"importPath": "repositories/orderRepository",
"rawSpecifier": "../repositories/orderRepository",
"sourceLayer": "controller",
"targetLayer": "repository",
"rule": "Controller cannot import Repository",
"fix": "Instead of importing 'repository' directly, route through an allowed intermediary layer: 'service'.",
"explanation": {
"why": "...",
"impact": "...",
"fix": "..."
}
}
],
"unclassifiedFiles": [],
"violationsByLayer": {
"controller": 1,
"service": 0,
"repository": 0
}
}
```
## 开发
```
# 安装依赖
npm install
# 直接使用 ts-node 运行(无需构建)
npx ts-node src/cli.ts scan --context examples/sample.context.yml --project examples/sample-project
# 构建到 dist/
npm run build
# 运行完整测试套件
npm test
# 在监视模式下运行测试
npm run test:watch
# 运行带有覆盖率报告的测试
npm run test:coverage
# 清理构建产物
npm run clean
```
## 项目结构
```
architecture-linter/
├── src/
│ ├── cli.ts # Commander-based CLI entry point
│ ├── contextParser.ts # Loads and validates .context.yml; walks up directory tree
│ ├── dependencyScanner.ts # Walks .ts files and extracts imports via ts-morph
│ ├── ruleEngine.ts # Matches imports against rules; builds violations
│ ├── aliasResolver.ts # Resolves tsconfig.json path aliases
│ ├── presets.ts # Built-in framework presets
│ ├── explainer.ts # Why/impact/fix guidance for --explain
│ └── types.ts # Shared TypeScript interfaces
│
├── src/__tests__/ # Jest test suite (105 tests)
│
├── examples/
│ ├── sample.context.yml # Example rule configuration
│ ├── sample-project/ # ❌ intentional violation for demo
│ ├── alias-test/ # Demo of path alias resolution
│ └── preset-test/ # Demo of framework presets
│
├── .github/workflows/ci.yml # Runs tests on every push/PR
├── jest.config.js
├── package.json
├── tsconfig.json
└── README.md
```
## 工作原理
1. **解析配置** —— `contextParser` 加载 `.context.yml`,合并通过 `extends` 声明的任何预设,验证必填字段,并在未提供显式路径时向上遍历目录树。
2. **扫描文件** —— `dependencyScanner` 使用 `fast-glob` 查找每个 `.ts` 文件(遵循 `exclude` 模式),并使用 `ts-morph` 解析导入声明。路径别名通过 `aliasResolver` 在应用规则之前解析。每个导入都会检查是否有前置的 `// arch-ignore:` 注释。
3. **检查规则** —— `ruleEngine` 针对每个导入评估 `cannot_import` / `can_only_import` 规则。违规与可选的修复建议和层级计数一起被收集。
4. **报告** —— 结果以人类可读的文本(带颜色)或机器可读的 JSON 打印。进程以 `0` 表示通过,以 `1` 表示违规退出。
5. **应用规则** —— `ruleEngine` 将每个文件和解析后的导入映射到架构层,然后评估 `cannot_import`(黑名单)和 `can_only_import`(白名单)规则。通过 `minimatch` 应用每个规则的 `files` glob 范围。
6. **报告** —— CLI 打印每个违规及其文件、导入路径和违反的规则。最后显示每层摘要。`--format json` 发出机器可读的输出。
## 路线图(MVP 后)
- `--fix` 标志以建议修正的导入路径
- SARIF 输出格式用于 GitHub Advanced Security 集成
- 监视模式(`--watch`)
- 支持 TypeScript 路径别名解析(`@app/repositories`)
## 许可证
MIT
`architecture-linter` 读取 `.context.yml` 配置文件并扫描您的 TypeScript 源码树以查找依赖违规——例如 controller 直接导入 repository,绕过了 service 层。
## 快速开始
```
# 1. 安装依赖
npm install
# 2. 构建
npm run build
# 3. 扫描打包的示例项目
node dist/cli.js scan \
--context examples/sample.context.yml \
--project examples/sample-project
```
预期输出:
```
Scanning project...
❌ Architecture violation detected
File: controllers/orderController.ts
Import: repositories/orderRepository
Rule: Controller cannot import Repository
Found 1 violation in 3 file(s) scanned.
```
## 安装
### 作为本地开发依赖
```
npm install --save-dev architecture-linter
```
然后在您的 `package.json` 中添加脚本:
```
{
"scripts": {
"lint:arch": "architecture-linter scan"
}
}
```
### 全局安装
```
npm install -g architecture-linter
```
## 配置
在您的项目根目录创建一个 `.context.yml` 文件(或传递 `--context` 以指向不同的路径)。
```
architecture:
layers:
- controller
- service
- repository
rules:
controller:
cannot_import:
- repository # Controllers must go through the service layer
service:
cannot_import: []
repository:
cannot_import: []
```
### 层检测的工作原理
Linter 从文件的**目录名称**推断其所属层。位于名为 `controllers/` 或 `controller/` 的目录中的文件会自动分配到 `controller` 层。单数和复数形式均能识别。
| 路径 | 检测到的层 |
|---|---|
| `controllers/orderController.ts` | `controller` |
| `services/orderService.ts` | `service` |
| `repositories/orderRepository.ts` | `repository` |
同样的逻辑也适用于导入路径:解析到 `repositories/` 目录的相对导入被视为 `repository` 层导入。
## CLI 参考
```
architecture-linter scan [options]
Options:
-c, --context Path to the .context.yml file (default: .context.yml)
-p, --project Root directory of the project (default: .)
-V, --version Print version number
-h, --help Display help
```
### 退出码
| 代码 | 含义 |
|---|---|
| `0` | 未发现违规 |
| `1` | 发生或多个违规(或发生致命错误) |
这使得该工具适用于 CI 管道:
```
# .github/workflows/ci.yml (示例)
- name: Architecture lint
run: npx architecture-linter scan
```
## 开发
```
# 直接使用 ts-node 运行(无需构建步骤)
npx ts-node src/cli.ts scan --context examples/sample.context.yml --project examples/sample-project
# 构建到 dist/
npm run build
# 运行编译后的输出
node dist/cli.js scan --context examples/sample.context.yml --project examples/sample-project
# 清理构建产物
npm run clean
```
## 项目结构
```
architecture-linter/
├── src/
│ ├── cli.ts # Commander-based CLI entry point
│ ├── contextParser.ts # Loads and validates .context.yml
│ ├── dependencyScanner.ts # Walks .ts files and extracts imports via ts-morph
│ ├── ruleEngine.ts # Matches imports against rules and returns violations
│ └── types.ts # Shared TypeScript interfaces
│
├── examples/
│ ├── sample.context.yml # Example rule configuration
│ └── sample-project/
│ ├── controllers/orderController.ts # ❌ contains an intentional violation
│ ├── services/orderService.ts
│ └── repositories/orderRepository.ts
│
├── package.json
├── tsconfig.json
└── README.md
```
## 工作原理
1. **解析配置** —— `contextParser` 使用 `js-yaml` 加载 `.context.yml` 并验证必填字段。
2. **扫描文件** —— `dependencyScanner` 使用 `fast-glob` 查找项目中的每个 `.ts` 文件,并使用 `ts-morph` 解析其导入声明。相对导入被解析为项目相对路径。
3. **应用规则** —— `ruleEngine` 将每个文件和每个解析后的导入路径映射到架构层,然后检查 `cannot_import` 规则。
4. **报告** —— CLI 打印每个违规及其违规文件、解析后的导入路径和违反的规则。
## 路线图(MVP 后)
- `--fix` 标志以建议修正的导入路径
- JSON / SARIF 输出格式用于 CI 集成
- 通配符层模式(`src/*/controllers/**`)
- 支持路径别名解析(`@app/repositories`)
- 监视模式
## 许可证
MIT
标签:Linter, MITM代理, NestJS, NPM包, OSV-Scalibr, pptx, SOC Prime, TypeScript, Watch模式, 云安全监控, 代码规范, 依赖检测, 六边形架构, 分层架构, 安全插件, 开发工具, 整洁架构, 架构约束, 自动修复, 自动化攻击, 静态分析