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模式, 云安全监控, 代码规范, 依赖检测, 六边形架构, 分层架构, 安全插件, 开发工具, 整洁架构, 架构约束, 自动修复, 自动化攻击, 静态分析