{"id":32195046,"url":"https://github.com/LukasNiessen/ArchUnitTS","last_synced_at":"2025-10-22T02:08:48.394Z","repository":{"id":295272422,"uuid":"582366658","full_name":"LukasNiessen/ArchUnitTS","owner":"LukasNiessen","description":"ArchUnitTS is an architecture testing library. Specify and ensure architecture rules in your TypeScript app. Easy setup and pipeline integration.","archived":false,"fork":false,"pushed_at":"2025-09-28T17:25:40.000Z","size":3283,"stargazers_count":194,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-28T17:39:42.395Z","etag":null,"topics":["architecture","architecture-rules","architecture-test","archunit","javascript","static-analysis","testing","typescript"],"latest_commit_sha":null,"homepage":"https://lukasniessen.github.io/ArchUnitTS/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LukasNiessen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-12-26T15:45:25.000Z","updated_at":"2025-09-28T17:25:44.000Z","dependencies_parsed_at":"2025-06-09T09:37:50.556Z","dependency_job_id":null,"html_url":"https://github.com/LukasNiessen/ArchUnitTS","commit_stats":null,"previous_names":["lukasniessen/archunitts"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/LukasNiessen/ArchUnitTS","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LukasNiessen%2FArchUnitTS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LukasNiessen%2FArchUnitTS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LukasNiessen%2FArchUnitTS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LukasNiessen%2FArchUnitTS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LukasNiessen","download_url":"https://codeload.github.com/LukasNiessen/ArchUnitTS/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LukasNiessen%2FArchUnitTS/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280365621,"owners_count":26318385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-22T02:00:06.515Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","architecture-rules","architecture-test","archunit","javascript","static-analysis","testing","typescript"],"created_at":"2025-10-22T02:01:47.078Z","updated_at":"2025-10-22T02:08:48.387Z","avatar_url":"https://github.com/LukasNiessen.png","language":"TypeScript","readme":"# ArchUnitTS - Architecture Testing\n\n\u003cdiv align=\"center\" name=\"top\"\u003e\n  \u003cimg align=\"center\" src=\"assets/logo-rounded.png\" width=\"150\" height=\"150\" alt=\"ArchUnitTS Logo\"\u003e\n\n\u003c!-- spacer --\u003e\n\u003cp\u003e\u003c/p\u003e\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![npm version](https://img.shields.io/npm/v/archunit.svg)](https://www.npmjs.com/package/archunit)\n[![npm downloads](https://img.shields.io/npm/dm/archunit.svg)](https://www.npmjs.com/package/archunit)\n[![GitHub stars](https://img.shields.io/github/stars/LukasNiessen/ArchUnitTS.svg)](https://github.com/LukasNiessen/ArchUnitTS)\n\n\u003c/div\u003e\n\nEnforce architecture rules in TypeScript and JavaScript projects. Check for dependency directions, detect circular dependencies, enforce coding standards and much more. Integrates with every testing framework. Very simple setup and pipeline integration.\n\nThe #1 architecture testing library for TypeScript, measured by GitHub stars.\n\n_Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._\n\n[Setup](#-setup) • [Demo](#-demo) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitTS?](#-library-comparison) • [Contributing](CONTRIBUTING.md) • [Documentation](https://lukasniessen.github.io/ArchUnitTS/)\n\n## ⚡ 5 min Quickstart\n\n### Installation\n\n```bash\nnpm install archunit --save-dev\n```\n\n### Add tests\n\nSimply add tests to your existing test suites. The following is an example using Jest. First we ensure that we have no circular dependencies.\n\n```typescript\nimport { projectFiles, metrics } from 'archunit';\n\nit('should not have circular dependencies', async () =\u003e {\n  const rule = projectFiles().inFolder('src/**').should().haveNoCycles();\n  await expect(rule).toPassAsync();\n});\n```\n\nNext we ensure that our layered architecture is respected.\n\n```typescript\nit('presentation layer should not depend on database layer', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/presentation/**')\n    .shouldNot()\n    .dependOnFiles()\n    .inFolder('src/database/**');\n\n  await expect(rule).toPassAsync();\n});\n\nit('business layer should not depend on database layer', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/business/**')\n    .shouldNot()\n    .dependOnFiles()\n    .inFolder('src/database/**');\n\n  await expect(rule).toPassAsync();\n});\n\n// More layers ...\n```\n\nLastly we ensure that some code metric rules are met.\n\n```typescript\nit('should not contain too large files', async () =\u003e {\n  const rule = metrics().count().linesOfCode().shouldBeBelow(1000);\n  await expect(rule).toPassAsync();\n});\n\nit('should only have classes with high cohesion', async () =\u003e {\n  // LCOM metric (lack of cohesion of methods), low = high cohesion\n  const rule = metrics().lcom().lcom96b().shouldBeBelow(0.3);\n  await expect(rule).toPassAsync();\n});\n```\n\n### CI Integration\n\nThese tests will run automatically in your testing setup, for example in your CI pipeline, so that's basically it. This setup ensures that the architectural rules you have defined are always adhered to! 🌻🐣\n\nAdditionally, you can generate reports and save them as artifacts. Here's a simple example using GitLab CI. _Note that reports are in beta._\n\n```typescript\nit('should generate HTML reports', async () =\u003e {\n  const countMetrics = metrics().count();\n  const lcomMetrics = metrics().lcom();\n\n  // Saves HTML report files to /reports\n  await countMetrics.exportAsHTML();\n  await lcomMetrics.exportAsHTML();\n\n  // So we get no warning about an empty test\n  expect(0).toBe(0);\n});\n```\n\nIn your `gitlab-ci.yml`:\n\n```yml\ntest:\n  script:\n    - npm test\n  artifacts:\n    when: always\n    paths:\n      - reports\n```\n\n## 🚐 Setup \n\nInstallation:\n\n```bash\nnpm install archunit --save-dev\n```\n\nIf you're using Jest, that's it already. For Vitest, Jasmine or any other framework, please read below.\n\nWe have added special syntax for Jest, Vitest and Jasmine: `toPassAsync()`. We strongly recommend using it. Many benefits come with it, for example beautiful error messages in case of a failing tests.\n\n### Jest\n\nWorks out of the box.\n\n### Vitest\n\nWorks out of the box too, **but** you must have configured Vitest with `globals: true` in your `vitest.config.ts`. This means you need a `vitest.config.ts` file at project root with content that may look like this:\n\n```ts\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n\ttest: {\n\t\tglobals: true,  // This line matters !!\n\t\t...\n\t},\n});\n```\n\n### Jasmine\n\nJasmine unfortunately has some constraints and requires minimal setup. However, you will need just one line of code:\n\n```typescript\nbeforeEach(() =\u003e {\n  jasmine.addAsyncMatchers(jasmineMatcher);\n});\n```\n\nInclude this in your test files. And, since this is an asynchronous matcher, you must use `expectAsync` with Jasmine, not `expect`. Example:\n\n```typescript\ndescribe('architecture', () =\u003e {\n\tbeforeEach(() =\u003e {\n\t\tjasmine.addAsyncMatchers(jasmineMatcher);\n\t});\n\n\tit('business logic should not depend on the ui', async () =\u003e {\n\t\tconst rule = projectFiles()\n\t\t\t.inFolder('business')\n\t\t\t.shouldNot()\n\t\t\t.dependOnFiles()\n\t\t\t.inFolder('ui');\n\n\t\tawait expectAsync(rule).toPassAsync(); // expectAsync, not expect !!\n\t});\n```\n\n### Other Framework\n\nIf you're not using Jest, Vitest or Jasmine, we do not have special syntax support but you can of course still use ArchUnitTS. Please read [here](#check).\n\n## 🎬 Demo\n\nhttps://github.com/user-attachments/assets/426f7b47-5157-4e92-98a3-f5ab4f7a388a\n\n## 🐹 Use Cases\n\nMany common uses cases are covered in our examples folder. Note that they are not fully working repositories but code snippets. Here is an overview.\n\n**Layered Architecture:**\n\n- [Fastify BackEnd using a UML Diagram](examples/layered-architecture/fastify-uml/README.md)\n\n**Micro Frontends:**\n\n- [React Micro Frontends using Nx](examples/micro-frontends/react/README.md)\n\n**Clean Architecture:**\n\n- [Clean Architecture implementation using NestJS](examples/clean-architecture/nestjs/README.md)\n\n**Hexagonal Architecture:**\n\n- [Hexagonal Architecture using Express](examples/hexagonal-architecture/express/README.md) - Ports and Adapters pattern implementation with Express.js\n\n**Angular Example:**\n\n- [A typical Angular FrontEnd](examples/angular-example/README.md)\n\n## 🐲 Example Repositories\n\nHere are a few repositories with fully functioning examples that use ArchUnitTS to ensure architectural rules:\n\n- **[Vitest Example](https://github.com/LukasNiessen/ArchUnitTS-Vitest-Example)**: Complete Vitest setup with architecture tests\n- **[Jest Example](https://github.com/LukasNiessen/ArchUnitTS-Jest-Example)**: Full Jest integration examples\n- **[Jasmine Example](https://github.com/LukasNiessen/ArchUnitTS-Jasmine-Example)**: Jasmine testing framework integration\n\n## 🐣 Features\n\nThis is an overview of what you can do with ArchUnitTS.\n\n### Circular Dependencies\n\n```typescript\nit('services should be free of cycles', async () =\u003e {\n  const rule = projectFiles().inFolder('src/services/**').should().haveNoCycles();\n  await expect(rule).toPassAsync();\n});\n```\n\n### Layer Dependencies\n\n```typescript\nit('should respect clean architecture layers', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/presentation/**')\n    .shouldNot()\n    .dependOnFiles()\n    .inFolder('src/database/**');\n  await expect(rule).toPassAsync();\n});\n\nit('business layer should not depend on presentation', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/business/**')\n    .shouldNot()\n    .dependOnFiles()\n    .inFolder('src/presentation/**');\n  await expect(rule).toPassAsync();\n});\n```\n\n### Naming Conventions\n\n```typescript\nit('should follow naming patterns', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/services/**')\n    .should()\n    .haveName('*-service.ts'); // my-service.ts for example\n  await expect(rule).toPassAsync();\n});\n\nit('components should be PascalCase', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/components/**')\n    .should()\n    .haveName(/^[A-Z][a-zA-Z]*Commponent\\.ts$/); // MyComponent.ts for example\n  await expect(rule).toPassAsync();\n});\n```\n\n### Code Metrics\n\n```typescript\nit('should not contain too large files', async () =\u003e {\n  const rule = metrics().count().linesOfCode().shouldBeBelow(1000);\n  await expect(rule).toPassAsync();\n});\n\nit('should have high class cohesion', async () =\u003e {\n  const rule = metrics().lcom().lcom96b().shouldBeBelow(0.3);\n  await expect(rule).toPassAsync();\n});\n\nit('should count methods per class', async () =\u003e {\n  const rule = metrics().count().methodCount().shouldBeBelow(20);\n  await expect(rule).toPassAsync();\n});\n\nit('should limit statements per file', async () =\u003e {\n  const rule = metrics().count().statements().shouldBeBelowOrEqual(100);\n  await expect(rule).toPassAsync();\n});\n\nit('should have 3 fields per Data class', async () =\u003e {\n  const rule = metrics()\n    .forClassesMatching(/.*Data.*/)\n    .count()\n    .fieldCount()\n    .shouldBe(3);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Distance Metrics\n\n```typescript\nit('should maintain proper coupling', async () =\u003e {\n  const rule = metrics().distance().couplingFactor().shouldBeBelow(0.5);\n  await expect(rule).toPassAsync();\n});\n\nit('should stay close to main sequence', async () =\u003e {\n  const rule = metrics().distance().distanceFromMainSequence().shouldBeBelow(0.3);\n  await expect(rule).toPassAsync();\n});\n```\n\n### Custom Rules\n\nYou can define your own custom rules.\n\n```typescript\nconst ruleDesc = 'TypeScript files should export functionality';\nconst myCustomRule = (file: FileInfo) =\u003e {\n  // TypeScript files should contain export statements\n  return file.content.includes('export');\n};\n\nconst violations = await projectFiles()\n  .withName('*.ts') // all ts files\n  .should()\n  .adhereTo(myCustomRule, ruleDesc)\n  .check();\n\nexpect(violations).toStrictEqual([]);\n```\n\n### Custom Metrics\n\nYou can define your own metrics as well.\n\n```typescript\nit('should have a nice method field ratio', async () =\u003e {\n  const rule = metrics()\n    .customMetric(\n      'methodFieldRatio',\n      'Ratio of methods to fields',\n      (classInfo) =\u003e classInfo.methods.length / Math.max(classInfo.fields.length, 1)\n    )\n    .shouldBeBelowOrEqual(10);\n  await expect(rule).toPassAsync();\n});\n```\n\n### Architecture Slices\n\n```typescript\nit('should adhere to UML diagram', async () =\u003e {\n  const diagram = `\n@startuml\n  component [controllers]\n  component [services]\n  [controllers] --\u003e [services]\n@enduml`;\n\n  const rule = projectSlices().definedBy('src/(**)/').should().adhereToDiagram(diagram);\n  await expect(rule).toPassAsync();\n});\n\nit('should not contain forbidden dependencies', async () =\u003e {\n  const rule = projectSlices()\n    .definedBy('src/(**)/')\n    .shouldNot()\n    .containDependency('services', 'controllers');\n  await expect(rule).toPassAsync();\n});\n```\n\n### Reports\n\nGenerate beautiful HTML reports for your metrics. The default output path is `/reports`. _Note that this features is in beta._\n\n```typescript\n// Export count metrics report\nawait metrics().count().exportAsHTML('reports/count-metrics.html', {\n  title: 'Count Metrics Dashboard',\n  includeTimestamp: true,\n});\n\n// Export LCOM cohesion metrics report\nawait metrics().lcom().exportAsHTML('reports/lcom-metrics.html', {\n  title: 'Code Cohesion Analysis',\n  includeTimestamp: false,\n});\n\n// Export distance metrics report\nawait metrics().distance().exportAsHTML('reports/distance-metrics.html');\n```\n\n```typescript\n// Export comprehensive report with all metrics\nimport { MetricsExporter } from 'archunitts';\n\nawait MetricsExporter.exportComprehensiveAsHTML(undefined, {\n  outputPath: 'reports/comprehensive-metrics.html',\n  title: 'Complete Architecture Metrics Dashboard',\n  customCss: '.metric-card { border-radius: 8px; }',\n});\n```\n\nThe export functionality can be customized, for example by specifying an output path and custom CSS. Thanks to this, it's also very easy to include generated reports into your deploy process of, let's say, your GitHub page or GitLab page.\n\n## 🔎 Pattern Matching System\n\nWe offer three targeting options for pattern matching across all modules:\n\n- **`withName(pattern)`** - Pattern is checked against the filename (eg. `Service.ts` from `src/services/Service.ts`)\n- **`inPath(pattern)`** - Pattern is checked against against the full relative path (eg. `src/services/Service.ts`)\n- **`inFolder(pattern)`** - Pattern is checked against the path without filename (eg. `src/services` from `src/services/Service.ts`)\n\nFor the metrics module there is an additional one:\n\n- **`forClassesMatching(pattern)`** - Pattern is checked against class names. The filepath or filename does not matter here\n\n### Pattern Types\n\nWe support string patterns and regular expressions. String patterns support glob, see below.\n\n```typescript\n// String patterns with glob support (case sensitive)\n.withName('*.service.ts')     // All files ending with .service.ts\n.inFolder('**/services')      // All files in any services folder\n.inPath('src/api/**/*.ts')    // All TypeScript files under src/api\n\n// Regular expressions (case sensitive - use when you need exact case matching)\n.withName(/^.*Service\\.ts$/)  // Same as *.service.ts but case-sensitive\n.inFolder(/services$/)        // Folders ending with 'services' (case-sensitive)\n\n// For metrics module: Class name matching with regex\n.forClassesMatching(/.*Service$/)  // Classes ending with 'Service'\n.forClassesMatching(/^User.*/)     // Classes starting with 'User'\n```\n\n### Case Sensitivity\n\n- **Strings/glob patterns**: Case **sensitive** by default\n- **Regular expressions**: Case **sensitive** by default\n\nIf you need case-insensitive matching, use regular expressions with the `i` flag:\n\n```typescript\n// Case sensitive regex (default)\n.withName(/^.*service\\.ts$/)  // Matches service.ts\n```\n\n```typescript\n// Case insensitive regex\n.withName(/^.*service\\.ts$/i)  // Matches Service.ts, service.ts, SERVICE.ts\n```\n\n### Glob Patterns Guide\n\nGlob patterns provide powerful wildcard matching for paths and filenames:\n\n#### Basic Wildcards\n\n- `*` - Matches any characters within a single path segment (except `/`)\n- `**` - Matches any characters across multiple path segments\n- `?` - Matches exactly one character\n- `[abc]` - Matches any character in the bracket set\n- `[a-z]` - Matches any character in the range\n\n#### Common Glob Examples\n\n```typescript\n// Filename patterns\n.withName('*.ts')           // All TypeScript files\n.withName('*.{js,ts}')      // All JavaScript or TypeScript files\n.withName('*Service.ts')    // Files ending with 'Service.ts'\n.withName('User*.ts')       // Files starting with 'User'\n.withName('?est.ts')        // test.ts, nest.ts, etc\n\n// Folder patterns\n.inFolder('**/services')    // Any 'services' folder at any depth\n.inFolder('src/services')   // Exact 'src/services' folder\n.inFolder('**/test/**')     // Any folder containing 'test' in path\n.inFolder('src/*')          // Direct subfolders of 'src'\n\n// Path patterns\n.inPath('src/**/*.service.ts')     // Service files anywhere under src\n.inPath('**/test/**/*.spec.ts')    // Test files in any test folder\n.inPath('src/domain/*/*.ts')       // TypeScript files one level under domain\n```\n\n### Recommendation\n\nWe generally recommend to use string with glob support unless you need to deal with very special cases. Writing regular expressions yourself is not necessary for most cases and comes with extra complexity.\n\nFor example, let's say you want to enforce some rule upon files inside `src/components`. If you use a RegExp you might first try this:\n\n```typescript\n.inFolder(/.*\\/components\\/.*/)\n```\n\nBut this will not work reliably. It will not match `src/components/my-component.ts`. That's because ArchUnitTS will compare the _'folder path'_ here, that is the path without the filename, so in this case: `src/components`. The RegExp does not match this because it does not have an ending `/`. So the RegExp should be something like `.*\\/components(\\/.*)?`. Much simpler would be `'**/components/**`.\n\nThat being said, of course there are cases where glob syntax is just not strong enough and you will have to go with a RegExp.\n\n### Check Methods: .check() vs toPassAsync()\n\nArchUnitTS provides two main methods for executing architecture rules.\n\n#### `toPassAsync()`\n\nThis is special syntax we have added for Jest, Vitest and Jasmine. If you're using one of these testing frameworks, you should always use `toPassAsync()`. Many benefits come with it, for example beautiful error messages in case of a failing tests.\n\n**Important:** If you're using Vitest or Jasmine, please read below!\n\n```typescript\n// Jest/Vitest\nawait expect(rule).toPassAsync();\n\n// With configuration options\nawait expect(rule).toPassAsync(options);\n```\n\nHere `options` can be used for enabling logging, disable caching, or to not fail on empty tests.\n\n```javascript\n{\n  logging: {\n    enabled: true, // show logs\n    level: 'debug', // show lots of logs\n    logFile: true // write logs to file inside ./logs folder. You can specify a custom path too.\n  },\n  // if your rule 'passes' because it 'passed' zero files, the test normally fails. You can turn this off by setting this true\n  allowEmptyTests: true,\n  clearCache: true // reading nodes, imports etc is normally cached,\n}\n```\n\n**Vitest Hint:**\n\nIf you're using Vitest, you must have configured Vitest with `globals: true` in your `vitest.config.ts`. This means you need a `vitest.config.ts` file at project root with content that may look like this:\n\n```ts\nimport { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n\ttest: {\n\t\tglobals: true,  // This line matters !!\n\t\tenvironment: 'node',\n\t\tcoverage: {\n\t\t\tprovider: 'v8',\n\t\t\treporter: ['text', 'json', 'html'],\n\t\t},\n\t\tinclude: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],\n\t},\n});\n```\n\n**Jasmine Hint:**\n\nIf you're using Jasmine, unfortunately the full automatic set up is not possible due to Jasmine constraints. You will need just one line of code though:\n\n```typescript\nbeforeEach(() =\u003e {\n  jasmine.addAsyncMatchers(jasmineMatcher);\n});\n```\n\nInclude this in your test files. And, since this is an asynchronous matcher, you must use `expectAsync` with Jasmine.\n\n```typescript\ndescribe('architecture', () =\u003e {\n\tbeforeEach(() =\u003e {\n\t\tjasmine.addAsyncMatchers(jasmineMatcher);\n\t});\n\n\tit('business logic should not depend on the ui', async () =\u003e {\n\t\tconst rule = projectFiles()\n\t\t\t.inFolder('business')\n\t\t\t.shouldNot()\n\t\t\t.dependOnFiles()\n\t\t\t.inFolder('ui');\n\n\t\tawait expectAsync(rule).toPassAsync(); // expectAsync, not expect !!\n\t});\n```\n\n#### `check()`\n\nFor all other testing frameworks we don't have special syntax support but you can still easily use ArchUnitTS as follows:\n\n```typescript\n// Mocha example\nconst violations = await rule.check();\nexpect(violations).to.have.length(0);\n```\n\nThe `check()` method works universally. It returns a violations array and is designed for testing frameworks without custom matcher support (Mocha, Node.js assert, etc.). You can assert the violations arrays length for example.\n\n```typescript\n// With configuration options, the same ones as mentioned above\nconst violations = await rule.check(options);\n...\n```\n\n#### Configuration Options\n\nBoth methods accept the same configuration options:\n\n```typescript\ninterface CheckOptions {\n  // default undefined, which is treated as no logging\n  logging?: {\n    enabled: boolean;\n    level: 'debug' | 'info' | 'warn' | 'error';\n  };\n  allowEmptyTests?: boolean; // Default: false\n  clearCache?: boolean; // Default: false\n}\n```\n\n#### When to Use Which Method\n\n- **Use `toPassAsync()`** with Jest, Vitest, or Jasmine for better integration and error reporting\n- **Use `check()`** with Mocha, Node.js assert, or any other testing framework\n- **Use `check()`** when you need to inspect violations programmatically before deciding how to handle them\n\n### Basic Pattern Matching Examples\n\n```typescript\nimport { projectFiles, metrics } from 'archunit';\n\n// Files module - Test architectural rules\nprojectFiles().withName('*.service.ts').should().beInFolder('**/services/**');\n\n// Metrics module - Test only service classes\nmetrics().withName('*.service.ts').lcom().lcom96b().shouldBeBelow(0.7);\n\n// Files module - Test classes in specific folders\nprojectFiles()\n  .inFolder('**/controllers/**')\n  .shouldNot()\n  .dependOnFiles()\n  .inFolder('**/database/**');\n\n// Metrics module - Test classes in specific folders\nmetrics().inFolder('**/controllers/**').count().methodCount().shouldBeBelow(20);\n\n// Files module - Test classes matching full path patterns\nprojectFiles().inPath('src/domain/**/*.ts').should().haveNoCycles();\n\n// Metrics module - Test classes matching full path patterns\nmetrics().inPath('src/domain/**/*.ts').lcom().lcom96a().shouldBeBelow(0.8);\n```\n\n### Advanced Pattern Matching\n\nYou can combine multiple pattern matching methods for precise targeting across all modules:\n\n```typescript\n// Files module - Combine folder and filename patterns\nprojectFiles()\n  .inFolder('**/services/**')\n  .withName('*.service.ts')\n  .should()\n  .haveNoCycles();\n\n// Metrics module - Combine folder and filename patterns\nmetrics().inFolder('**/services/**').withName('*.service.ts').lcom().lcom96b();\n\n// Files module - Mix pattern matching with dependency rules\nprojectFiles().inPath('src/api/**').shouldNot().dependOnFiles().inPath('src/database/**');\n\n// Metrics module - Mix pattern matching with class name matching\nmetrics()\n  .inPath('src/api/**')\n  .forClassesMatching(/.*Controller/)\n  .count()\n  .methodCount()\n  .shouldBeBelow(15);\n```\n\n### Naming Convention Examples\n\nPattern matching is particularly useful for enforcing naming conventions:\n\n```typescript\n// Match camelCase test files\nprojectFiles()\n  .withName(/^[a-z][a-zA-Z]*\\.spec\\.ts$/)\n  .should()\n  .beInFolder('**/test/**')\n  .check();\n\n// Match interface files (starting with I)\nprojectFiles()\n  .withName(/^I[A-Z][a-zA-Z]*\\.ts$/)\n  .should()\n  .beInFolder('**/interfaces/**')\n  .check();\n\n// Match constant files (all uppercase)\nprojectFiles()\n  .withName(/^[A-Z_]+\\.ts$/)\n  .should()\n  .beInFolder('**/constants/**')\n  .check();\n\n// Metrics for PascalCase controllers\nmetrics()\n  .withName(/^[A-Z][a-zA-Z]*Controller\\.ts$/)\n  .lcom()\n  .lcom96b()\n  .shouldBeBelow(0.5)\n  .check();\n```\n\n### Complex Pattern Matching Scenarios\n\nHere are more advanced use cases combining different pattern types:\n\n```typescript\n// Ensure all TypeScript files in feature folders follow naming conventions\nprojectFiles()\n  .inPath('src/features/**/*.ts')\n  .withName(/^[A-Z][a-zA-Z]*\\.(service|controller|model)\\.ts$/)\n  .should()\n  .haveNoCycles();\n\n// Test that utility files have low complexity\nmetrics()\n  .inFolder('**/utils/**')\n  .withName('*.util.ts')\n  .complexity()\n  .cyclomaticComplexity()\n  .shouldBeBelow(5);\n\n// Ensure test files don't depend on implementation details\nprojectFiles()\n  .withName('*.spec.ts')\n  .shouldNot()\n  .dependOnFiles()\n  .inPath('src/**/internal/**');\n\n// Check cohesion of domain entities\nmetrics()\n  .inPath('src/domain/entities/**/*.ts')\n  .withName(/^[A-Z][a-zA-Z]*Entity\\.ts$/)\n  .lcom()\n  .lcom96a()\n  .shouldBeBelow(0.6);\n```\n\n### Supported Metrics Types\n\n#### LCOM (Lack of Cohesion of Methods)\n\nThe LCOM metrics measure how well the methods and fields of a class are connected, indicating the cohesion level of the class. Lower values indicate better cohesion.\n\n```typescript\n// LCOM96a (Handerson et al.)\nmetrics().lcom().lcom96a().shouldBeBelow(0.8);\n\n// LCOM96b (Handerson et al.)\nmetrics().lcom().lcom96b().shouldBeBelow(0.7);\n```\n\nThe LCOM96b metric is calculated as:\n\n```\nLCOM96b = (m - sum(μ(A))/m)/(1-1/m)\n```\n\nWhere:\n\n- `m` is the number of methods in the class\n- `μ(A)` is the number of methods that access an attribute (field) A\n\nThe result is a value between 0 and 1:\n\n- 0: perfect cohesion (all methods access all attributes)\n- 1: complete lack of cohesion (each method accesses its own attribute)\n\n#### Count Metrics\n\nMeasure various counts within classes:\n\n```typescript\n// Method count\nmetrics().count().methodCount().shouldBeBelow(20);\n\n// Field count\nmetrics().count().fieldCount().shouldBeBelow(15).;\n\n// Lines of code\nmetrics().count().linesOfCode().shouldBeBelow(200);\n```\n\n#### Distance Metrics\n\nMeasure architectural distance metrics:\n\n```typescript\n// Abstractness\nmetrics().distance().abstractness().shouldBeAbove(0.3);\n\n// Instability\nmetrics().distance().instability().shouldBeBelow(0.8);\n\n// Distance from main sequence\nmetrics().distance().distanceFromMainSequence().shouldBeBelow(0.5);\n```\n\n#### Custom Metrics\n\nDefine your own metrics with custom calculation logic:\n\n```typescript\nmetrics()\n  .customMetric(\n    'complexityRatio',\n    'Ratio of methods to fields',\n    (classInfo) =\u003e classInfo.methods.length / Math.max(classInfo.fields.length, 1)\n  )\n  .shouldBeBelow(3.0);\n```\n\n## Slices API\n\nThe above info regarding filtering (`inFolder()` etc) does not apply to the slices API but only to the files and metrics APIs. the slices API has different a way of doing filtering. See more in the examples or below.\n\n## 🔧 Nx Monorepo Support\n\nArchUnitTS provides support for Nx monorepos by reading the Nx project graph and making it accessible through the slices API. This allows you to validate architecture rules based on your actual Nx project structure and dependencies.\n\n### Nx Project Slices\n\nThe `nxProjectSlices()` function reads your Nx workspace configuration and creates slices based on your Nx projects:\n\n```typescript\nimport { nxProjectSlices } from 'archunit';\nimport * as path from 'path';\n\nit('should adhere to Nx project architecture', async () =\u003e {\n  const rule = nxProjectSlices().should().haveNoCycles();\n  await expect(rule).toPassAsync();\n});\n```\n\n### Nx Project Boundaries\n\nEnforce boundaries between Nx applications and libraries using the project graph:\n\n```typescript\nit('should respect Nx project boundaries', async () =\u003e {\n  // Apps should not depend on other apps\n  const rule = nxProjectSlices()\n    .matching('apps/admin')\n    .shouldNot()\n    .dependOnSlices()\n    .matching('apps/client');\n  await expect(rule).toPassAsync();\n});\n\nit('should enforce library type boundaries', async () =\u003e {\n  // Feature libs should not depend on other feature libs\n  const rule = nxProjectSlices()\n    .matching('feature-*')\n    .shouldNot()\n    .dependOnSlices()\n    .matching('feature-*');\n  await expect(rule).toPassAsync();\n});\n```\n\n### UML Diagram Validation with Nx\n\nValidate your Nx architecture against PlantUML diagrams:\n\n```typescript\nit('should adhere to Nx architecture diagram', async () =\u003e {\n  const diagramLocation = path.resolve('docs', 'components.puml');\n\n  const rule = nxProjectSlices()\n    .ignoringExternalDependencies()\n    .should()\n    .adhereToDiagramInFile(diagramLocation);\n\n  await expect(rule).toPassAsync();\n});\n\nit('should follow inline diagram', async () =\u003e {\n  const diagram = `\n@startuml\ncomponent [shared-ui] as UI\ncomponent [feature-auth] as Auth\ncomponent [feature-dashboard] as Dashboard\ncomponent [shared-data-access] as Data\n\nAuth --\u003e UI\nDashboard --\u003e UI\nAuth --\u003e Data\nDashboard --\u003e Data\n@enduml`;\n\n  const rule = nxProjectSlices().should().adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Nx Project Type Validation\n\nEnforce Nx project categorization and naming conventions:\n\n```typescript\nit('should follow Nx project naming patterns', async () =\u003e {\n  // Feature projects should follow naming convention\n  const rule = nxProjectSlices()\n    .matching(/^feature-/)\n    .should()\n    .containSlices()\n    .matching(/^feature-[a-z-]+$/);\n\n  await expect(rule).toPassAsync();\n});\n\nit('should enforce shared library dependencies', async () =\u003e {\n  // Shared libs should not depend on feature libs\n  const rule = nxProjectSlices()\n    .matching('shared-*')\n    .shouldNot()\n    .dependOnSlices()\n    .matching('feature-*');\n\n  await expect(rule).toPassAsync();\n});\n```\n\n## 📐 UML Diagram Support\n\nArchUnitTS can validate your architecture against PlantUML diagrams, ensuring your code matches your architectural designs.\n\n### Component Diagrams\n\nValidate component relationships using PlantUML component diagrams:\n\n```typescript\nit('should adhere to component architecture', async () =\u003e {\n  const diagram = `\n@startuml\ncomponent [UserInterface] as UI\ncomponent [BusinessLogic] as BL\ncomponent [DataAccess] as DA\ncomponent [Database] as DB\n\nUI --\u003e BL\nBL --\u003e DA\nDA --\u003e DB\n@enduml`;\n\n  const rule = projectSlices()\n    .definedBy('src/(**)/') // Group by folder structure\n    .should()\n    .adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Package Diagrams\n\nEnforce package dependencies with UML package diagrams:\n\n```typescript\nit('should follow layered architecture diagram', async () =\u003e {\n  const diagram = `\n@startuml\npackage \"Presentation Layer\" {\n  [Controllers]\n  [ViewModels]\n}\n\npackage \"Business Layer\" {\n  [Services]\n  [Domain Models]\n}\n\npackage \"Data Layer\" {\n  [Repositories]\n  [Entities]\n}\n\n[Controllers] --\u003e [Services]\n[Services] --\u003e [Repositories]\n[ViewModels] --\u003e [Domain Models]\n@enduml`;\n\n  const rule = projectSlices().definedBy('src/**/(**)').should().adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Class Diagrams\n\nValidate class relationships and inheritance hierarchies:\n\n```typescript\nit('should match domain model diagram', async () =\u003e {\n  const diagram = `\n@startuml\nclass User {\n  +id: string\n  +email: string\n  +name: string\n}\n\nclass Order {\n  +id: string\n  +userId: string\n  +total: number\n}\n\nclass OrderItem {\n  +orderId: string\n  +productId: string\n  +quantity: number\n}\n\nUser ||--o{ Order : places\nOrder ||--o{ OrderItem : contains\n@enduml`;\n\n  const rule = projectSlices()\n    .definedBy('src/domain/(**)')\n    .should()\n    .adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Microservices Architecture\n\nValidate microservices boundaries with UML diagrams:\n\n```typescript\nit('should respect microservices boundaries', async () =\u003e {\n  const diagram = `\n@startuml\ncomponent [UserService] as US\ncomponent [OrderService] as OS\ncomponent [PaymentService] as PS\ncomponent [NotificationService] as NS\n\nUS --\u003e OS : getUserOrders()\nOS --\u003e PS : processPayment()\nOS --\u003e NS : sendNotification()\n\nnote right of US : No direct dependencies\\nbetween services except\\nthrough defined APIs\n@enduml`;\n\n  const rule = projectSlices()\n    .definedBy('services/(**)/') // Group by service folders\n    .should()\n    .adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n### Custom Architecture Diagrams\n\nDefine and validate your own architectural patterns:\n\n```typescript\nit('should follow hexagonal architecture', async () =\u003e {\n  const diagram = `\n@startuml\nhexagon \"Application Core\" as Core {\n  component [Domain]\n  component [Use Cases]\n}\n\ncomponent [Web API] as Web\ncomponent [Database] as DB\ncomponent [External Service] as Ext\n\nWeb --\u003e Core : HTTP\nCore --\u003e DB : Repository\nCore --\u003e Ext : Gateway\n\nnote top of Core : Business logic isolated\\nfrom external concerns\n@enduml`;\n\n  const rule = projectSlices().definedBy('src/(**)/').should().adhereToDiagram(diagram);\n\n  await expect(rule).toPassAsync();\n});\n```\n\n## 📊 Library Comparison\n\nHere's how ArchUnitTS compares to other TypeScript architecture testing libraries:\n\n| Feature                           | **ArchUnitTS**                                    | **ts-arch**           | **arch-unit-ts**   | **ts-arch-unit** |\n| --------------------------------- | ------------------------------------------------- | --------------------- | ------------------ | ---------------- |\n| **API Stability**                 | ✅ Stable                                         | ✅ Stable             | ⚠️ Unstable        | ⚠️ Unstable      |\n| **Circular Dependency Detection** | ✅ Supported                                      | ✅ Supported          | ❌ Limited         | ❌ No            |\n| **Layer Dependency Rules**        | ✅ Advanced patterns                              | ✅ Advanced patterns  | ⚠️ Limited         | ❌ No            |\n| **File Pattern Matching**         | ✅ Glob + Regex                                   | ✅ Glob + Regex       | ⚠️ Simple patterns | ❌ Basic         |\n| **Custom Rules**                  | ✅ Full support                                   | ❌ No                 | ❌ No              | ❌ No            |\n| **Code Metrics**                  | ✅ Comprehensive                                  | ❌ No                 | ❌ No              | ❌ No            |\n| **Empty Test Detection**          | ✅ Fails by default (configurable)                | ❌ No                 | ❌ No              | ❌ No            |\n| **Debug Logging**                 | ✅ Optional (off by default)                      | ❌ No                 | ❌ No              | ❌ No            |\n| **LCOM Cohesion Analysis**        | ✅ Multiple algorithms                            | ❌ No                 | ❌ No              | ❌ No            |\n| **Distance Metrics**              | ✅ Coupling \u0026 abstraction                         | ❌ No                 | ❌ No              | ❌ No            |\n| **UML Diagram Validation**        | ✅ Supported                                      | ✅ Supported          | ❌ No              | ❌ No            |\n| **Architecture Slicing**          | ✅ Supported                                      | ✅ Supported          | ❌ No              | ❌ No            |\n| **Testing Framework Integration** | ✅ Universal (Jest, Vitest, Jasmine, Mocha, etc.) | ⚠️ Jest only          | ⚠️ Limited         | ⚠️ Basic         |\n| **HTML Report Generation**        | ✅ Rich dashboards                                | ❌ No                 | ❌ No              | ❌ No            |\n| **TypeScript AST Analysis**       | ✅ Deep analysis                                  | ⚠️ Basic              | ⚠️ Limited         | ⚠️ Basic         |\n| **Performance Optimization**      | ✅ Caching + parallel                             | ⚠️ Basic              | ❌ No              | ❌ No            |\n| **Error Messages**                | ✅ Detailed + clickable                           | ⚠️ Basic              | ⚠️ Basic           | ⚠️ Basic         |\n| **Documentation**                 | ✅ Comprehensive                                  | ⚠️ Basic              | ⚠️ Minimal         | ⚠️ Minimal       |\n| **Community Support**             | ✅ Active maintenance                             | ✅ Active maintenance | ❌ Inactive        | ❌ Inactive      |\n\nAs you see in the table, there are some features that are only supported by us. Here is a brief highlight of those that we believe are the most critical of them:\n\n- **Empty Test Protection**: This one is extremely important. Let's say you define architectural boundaries that shall not be crossed - but you have a typo in the path to some folder. **Your test will just pass with other libraries!** They will _'check the rule'_ on _0 files_ and the test _'passes'_. ArchUnitTS detects this, we call it an _empty test_, and your test fails. This is the default behvaior, you can customize it to allow empty tests if you want to.\n\n- **Testing framework support**: ArchUnitTS works with any testing framework, plus we have special syntax extensions for Jest, Vitest and Jasmine. Other libraries such as ts-arch only have special support for Jest, or no special support at all.\n\n- **Logging**: We have great support for logs and different log levels. This can help to understand what files are being analyzed and why tests pass/fail. Other libraries have no logging support at all.\n\n- **Code Metrics**: Metrics such as cohesion, coupling metrics, distance from main sequence, and even custom metrics provide important insights into any projects code. ArchUnitTS is the only library with code metrics support.\n\n- **Intelligent Error Messages**: Our error messages contain clickable file paths and detailed violation descriptions. Again, other libraries do not have this.\n\n- **Custom rules**: ArchUnitTS is the only library that allows you to define custom rules and custom metrics.\n\n- **HTML Reports**: We support auto generated dashboards with charts and detailed breakdowns. Other libraries do not.\n\n### ArchUnitTS vs Linters (ESLint plugins)\n\nMany developers wonder how ArchUnitTS compares to linter plugins like `eslint-plugin-import`. While both can validate dependencies between modules, **ArchUnitTS goes far beyond what linter plugins offer**:\n\n**What ArchUnitTS provides that linters don't:**\n\n- **Code Metrics Analysis**: LCOM (cohesion) metrics, cyclomatic complexity, coupling factor, abstractness, instability, distance from main sequence, and custom metrics\n- **Architecture Slices and Layers**: UML diagram validation, slice-to-slice dependency rules, multi-layer architecture validation\n- **Nx Monorepo Support**: Built-in validation against Nx project graphs, boundaries, and naming conventions\n- **HTML Report Generation**: Rich dashboards with charts and comprehensive architecture analysis reports\n- **Empty Test Protection**: Fails by default when no files match patterns (prevents false positives from typos)\n- **Custom Architecture Rules**: Define completely custom rules with arbitrary validation logic\n- **Test Framework Integration**: Works with ANY testing framework (Jest, Vitest, Jasmine, Mocha, etc.) with special async matchers\n- **Advanced Logging**: Multiple log levels, file logging for CI/CD, detailed violation tracking with timestamps\n- **Class-Level Analysis**: Analyze class structures, methods, fields, and cohesion - not just module imports\n- **Circular Dependency Detection with Context**: More granular control and better reporting than linter cycle detection\n\n**When to use linters:**\n\nLinters like `eslint-plugin-import` excel at:\n- **Real-time feedback** while coding\n- **Auto-fixing** certain issues automatically\n- **Simpler setup** with just ESLint configuration\n\n**The Bottom Line:**\n\nUse **eslint-plugin-import** if you want immediate feedback on import/export issues while coding. Use **ArchUnitTS** if you want comprehensive architecture testing, metrics analysis, declarative rules, and enforcing architectural boundaries as part of your test suite and CI/CD pipeline.\n\n## 📢 Informative Error Messages\n\nWhen tests fail, you get helpful, colorful output with clickable file paths.\n\nhttps://github.com/user-attachments/assets/04b26afb-53e9-4507-ba24-c8308b3a7922\n\n_Click on file paths to jump directly to the issue in your IDE._\n\n## 📝 Debug Logging \u0026 Configuration\n\nWe support logging to help you understand what files are being analyzed and troubleshoot test failures. Logging is disabled by default to keep test output clean.\n\n### Enabling Debug Logging\n\n```typescript\nit('should respect layered architecture', async () =\u003e {\n  const rule = projectFiles()\n    .inFolder('src/presentation/**')\n    .shouldNot()\n    .dependOnFiles()\n    .inFolder('src/database');\n\n  const options = {\n    logging: {\n      enabled: true,\n      level: 'debug', // 'error' | 'warn' | 'info' | 'debug'\n    },\n  };\n\n  await expect(rule).toPassAsync(options);\n});\n```\n\n### Sample Debug Output\n\nWhen debug logging is enabled, you'll see detailed information about the analysis:\n\n```\n[2025-06-02T12:08:26.355Z] [INFO] Starting architecture rule check: Dependency check: patterns [(^|.*/)src/database/.*]\n[2025-06-02T12:08:26.445Z] [DEBUG] Analyzing 12 files in 'src/presentation' folder\n[2025-06-02T12:08:26.456Z] [DEBUG] Found file: src/presentation/controllers/UserController.ts\n[2025-06-02T12:08:26.467Z] [DEBUG] Found file: src/presentation/views/UserView.tsx\n[2025-06-02T12:08:26.478Z] [DEBUG] Checking dependencies against 'src/database' pattern\n[2025-06-02T12:08:26.489Z] [DEBUG] Violation detected: src/presentation/controllers/UserController.ts depends on src/database/UserRepository.ts\n[2025-06-02T12:08:26.772Z] [WARN] Completed architecture rule check: Dependency check: patterns [(^|.*/)src/database/.*] (1 violations)\n```\n\n### File Logging for CI/CD Integration (Beta)\n\nArchUnitTS supports writing logs to files, making it super easy to integrate into CI pipelines and save logs as artifacts for debugging purposes. This is particularly useful for analyzing test failures in production environments.\n\n**This feature is in beta**. Note that if your testing framework runs tests in parallel, like Jest does for example, the log file may look confusing for large test suites.\n\n#### Basic File Logging\n\n```typescript\n// Write logs to a specific file\nconst options = {\n  logging: {\n    enabled: true,\n    level: 'debug',\n    logFile: true,\n  },\n};\n\nawait expect(rule).toPassAsync(options);\n```\n\n#### Easy CI Integration\n\n```typescript\n// Automatically generates timestamped log files in ./logs/\nconst options = {\n  logging: {\n    enabled: true,\n    level: 'info',\n    logFile: true, // Creates logs/archunit-YYYY-MM-DD_HH-MM-SS.log\n  },\n};\n\nawait expect(rule).toPassAsync(options);\n```\n\nWhen `logFile: true`, ArchUnitTS automatically:\n\n- Creates a `logs/` directory if it doesn't exist\n- Generates timestamped log files like `archunit-2025-06-06_14-30-45.log`\n- Includes session headers with start times\n- Formats all log messages with timestamps and log levels\n\n#### CI Pipeline Integration Example\n\nThis makes it incredibly easy to save logs as CI artifacts for debugging:\n\n```yaml\n# GitHub Actions example\n- name: Run Architecture Tests\n  run: npm test\n\n- name: Upload Test Logs\n  if: always()\n  uses: actions/upload-artifact@v3\n  with:\n    name: architecture-test-logs\n    path: logs/\n```\n\n```yaml\n# GitLab CI example\ntest:\n  script:\n    - npm test\n  artifacts:\n    when: always\n    paths:\n      - logs/\n    expire_in: 1 week\n```\n\n## 🏈 Architecture Fitness Functions\n\nThe features of ArchUnitTS can very well be used as architectural fitness functions. See [here](https://www.thoughtworks.com/en-de/insights/articles/fitness-function-driven-development) for more information about that topic.\n\n## 🔲 Core Modules\n\nArchUnitTS has the following core modules.\n\n| Module      | Description                          | Status       | Links                                                                            |\n| ----------- | ------------------------------------ | ------------ | -------------------------------------------------------------------------------- |\n| **Files**   | File and folder based rules          | Stable       | [`src/files/`](src/files/) • [README](src/files/README.md)                       |\n| **Metrics** | Code quality metrics                 | Stable       | [`src/metrics/`](src/metrics/) • [README](src/metrics/README.md)                 |\n| **Slices**  | Architecture slicing                 | Stable       | [`src/slices/`](src/slices/) • [README](src/slices/README.md)                    |\n| **Testing** | Universal test framework integration | Stable       | [`src/testing/`](src/testing/) • [README](src/testing/README.md)                 |\n| **Common**  | Shared utilities                     | Stable       | [`src/common/`](src/common/)                                                     |\n| **Reports** | Generate reports                     | Experimental | [`src/metrics/fluentapi/export-utils.ts`](src/metrics/fluentapi/export-utils.ts) |\n\n## 🕵️ Technical Deep Dive\n\nHow does ArchUnitTS work under the hood? See [here](info/TECHNICAL.md) for a deep dive!\n\n### ArchUnitTS uses ArchUnitTS\n\nWe used ourselves to ensure the architectural rules for this repository 😎\n\n## 🦊 Contributing\n\nWe highly appreciate contributions. We use GitHub Flow, meaning that we use feature branches, similar to GitFlow, but with proper CI and CD. As soon as something is merged or pushed to `main` it gets deployed. See more in [Contributing](CONTRIBUTING.md). See also our _'[Backlog](TODO.md)'_.\n\nNote that _deploy_ here means updating the docs. We consider auto deploying the library to npm too risky given the fact that there are no full time maintainers.\n\n## ℹ️ FAQ\n\n**Q: What TypeScript/JavaScript testing frameworks are supported?**\n\nArchUnitTS works with Jest, Jasmine, Vitest, Mocha, and any other testing framework. We have added special syntax support for Jest, Jasmine and Vitest, namely `toPassAsync` but, as said, ArchUnitTS works with any existing testing framework.\n\n**Q: Can I use ArchUnitTS with JavaScript projects?**\n\nYes! While ArchUnitTS is built for TypeScript, it works with JavaScript projects too. You'll get the most benefit with TypeScript due to better static analysis capabilities.\n\n**Q: How do I handle false positives in architecture rules?**\n\nUse the filtering and targeting capabilities to exclude specific files or patterns. You can filter by file paths, class names, or custom predicates to fine-tune your rules.\n\n**Q: What's the difference between file-based and class-based rules?**\n\nFile-based rules analyze import relationships between files, while class-based rules examine dependencies between classes and their members. Choose based on your architecture validation needs.\n\n## 🐣 Origin Story\n\nWhile working as a consultant on an Express backend project, I needed to implement architectural fitness functions similar to how one can do it with ArchUnit. Finding no good TypeScript library for this purpose, I decided to build ArchUnitTS. With the rise of LLMs and AI integration in companies, enforcing architectural boundaries and QA in general has become more critical than ever.\n\n## 💟 Community\n\n### Maintainers\n\n• **[LukasNiessen](https://github.com/LukasNiessen)** - Creator and main maintainer\n\n• **[janMagnusHeimann](https://github.com/janMagnusHeimann)** - Maintainer\n\n• **[TristanKruse](https://github.com/TristanKruse)** - Maintainer\n\n### Contributors\n\n\u003ca href=\"https://github.com/LukasNiessen/ArchUnitTS/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=LukasNiessen/ArchUnitTS\u0026max=1000\u0026contributors=10\" /\u003e\n\u003c/a\u003e\n\n### Questions\n\nFound a bug? Want to discuss features?\n\n- Submit an [issue on GitHub](https://github.com/LukasNiessen/ArchUnitTS/issues/new/choose)\n- Join our [GitHub Discussions](https://github.com//LukasNiessen/ArchUnitTS/discussions)\n- Questions? Post on [Stack Overflow](https://stackoverflow.com/questions/tagged/ArchUnitTS) with the ArchUnitTS tag\n- Leave a comment or thoughts on our [X account](https://x.com/ArchUnitTS)\n- Visit our [documentation](https://lukasniessen.github.io/ArchUnitTS/)\n\nIf ArchUnitTS helps your project, please consider:\n\n- Starring the repository 💚\n- Suggesting new features 💭\n- Contributing code or documentation ⌨️\n\n### Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=LukasNiessen/ArchUnitTS\u0026type=Date)](https://www.star-history.com/#LukasNiessen/ArchUnitTS\u0026Date)\n\n## 📄 License\n\nThis project is under the **MIT** license.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#top\"\u003e\u003cstrong\u003eGo Back to Top\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## P.S.\n\n### Special Note on Cycle-Free Checks\n\nEmpty checks are particularly nuanced for cycle-free assertions. Consider this scenario: folder A contains one file that only depends on folder B. When testing `.inFolder(\"A\").should().haveNoCycles()`, we want to check for cycles _within_ folder A only. However, if we report an empty test error, users might be confused since folder A does contain a file. Therefore, cycle-free checks use a more permissive approach and check the unfiltered file set for emptiness, rather than the filtered set that's actually analyzed for cycles.\n","funding_links":[],"categories":["Testing"],"sub_categories":["Helpers"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLukasNiessen%2FArchUnitTS","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLukasNiessen%2FArchUnitTS","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLukasNiessen%2FArchUnitTS/lists"}