Guides: Migrate from dependency-cruiser: Move forbidden rules from dependency-cruiser to archangel, and know which results change. # Migrate from dependency-cruiser > Move forbidden rules from dependency-cruiser to archangel, and know which results change. This guide moves the rules of a `.dependency-cruiser.cjs` file to archangel. Each rule keeps its name, so a team can find it again. At the end, archangel checks the same decisions, with examples that prove each rule. ## Before you start * Install archangel: `npm install --save-dev @enhance-eng/archangel`. * Keep dependency-cruiser until archangel gives the results that you expect. ## The config before This repository has three forbidden rules: .dependency-cruiser.cjs ```js module.exports = { forbidden: [ { name: 'no-circular', severity: 'error', from: {}, to: { circular: true }, }, { name: 'ui-not-to-db', severity: 'error', comment: 'The UI reads data through the api folder.', from: { path: '^src/ui' }, to: { path: '^src/db' }, }, { name: 'not-to-dev-dep', severity: 'error', from: { path: '^src', pathNot: '\\.test\\.ts$' }, to: { dependencyTypes: ['npm-dev'] }, }, ], }; ``` ## Map each rule | dependency-cruiser | archangel | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `from: { path }` | The rule’s `files`: a part, a group or a path pattern. | | `to: { path }` | `mayNotImport`, with a part, a group or a path pattern. | | `pathNot` | `{ all: [..., { not: ... }] }` in `files` or on the other side. | | `to: { circular: true }` | `noCycles: true`. Add `imports: 'all'` to count type imports too. | | `to: { dependencyTypes: ['npm-dev'] }` | `mayNotImport: { packages: ['*'] }` with `dependency: 'dev'`. | | `to: { dependencyTypes: ['npm-no-pkg'] }` | `mayNotImport: { packages: ['*'] }` with `dependency: 'undeclared'`. | | `allowed` with `allowedSeverity` | A [table](/archangel/concepts/tables-rows-and-cells/): each row lists what it may import, and the table refuses the rest. | | `comment` | `because`. Archangel shows it with each problem. | | `severity: 'warn'` | No equivalent. A rule is on or off. | | `no-orphans` | No built-in condition. Write a [plugin](/archangel/guides/plugins/) condition that reads `importers`. | ## The config after archangel.config.ts ```ts import { defineConfig } from '@enhance-eng/archangel'; export default defineConfig({ parts: { ui: 'src/ui/**', api: 'src/api/**', db: 'src/db/**', }, groups: { tests: '**/*.test.ts', }, rules: ({ rule }) => [ rule('no-circular', { files: 'src/**', noCycles: true, imports: 'all', examples: { rejected: ['src/a.ts imports src/b.ts imports src/a.ts'] }, }), rule('ui-not-to-db', { files: 'ui', mayNotImport: 'db', because: 'The UI reads data through the api folder.', examples: { rejected: ['src/ui/list.ts imports src/db/client.ts'] }, }), rule('not-to-dev-dep', { files: { all: ['src/**', { not: 'tests' }] }, mayNotImport: { packages: ['*'] }, dependency: 'dev', examples: { rejected: [{ 'src/api/a.ts': "import 'vitest';" }], }, }), ], }); ``` * The paths become parts: `ui`, `api` and `db`. Many rules can use a part, and a part that finds no file is an error. * `pathNot: '\\.test\\.ts$'` becomes the group `tests`, and `files` takes all of `src` except that group. * Each rule has a rejected example. `archangel test` fails if a rule cannot refuse its example. ## Run it 1. Prove the rules: Terminal ```ansi $ npx archangel test pass no-circular rejected #1 src/a.ts, src/b.ts pass ui-not-to-db rejected #1 src/db/client.ts, src/ui/list.ts pass not-to-dev-dep rejected #1 src/api/a.ts 3 examples, none failed; 0 rules without a rejected example ``` 2. Check the repository: Terminal ```ansi $ npx archangel check error[no-cycles]: 2 files import each other in a cycle: src/api/note.ts → src/api/notes.ts → src/api/note.ts --> src/api/note.ts:1:32 = help: remove one import of the cycle, or move what both files need into a file that neither of them imports = note: rule: no-circular error[may-not-import]: src/ui/note-list.ts may not import "../db/client", because files in "ui" may not import "db" --> src/ui/note-list.ts:2:22 | 2 | import { pool } from '../db/client'; | ^^^^^^^^^^^^^^ remove the import, or move the code to files that may import it | = note: rule: ui-not-to-db = note: because: The UI reads data through the api folder. error[may-not-import]: src/ui/note-list.ts may not import "vitest", because files in "all of (src/**, not tests)" may not import any package that package.json lists only under devDependencies --> src/ui/note-list.ts:3:20 | 3 | import { vi } from 'vitest'; | ^^^^^^^^ remove the import, or move the code to files that may import it | = note: rule: not-to-dev-dep checked 6 files and 10 imports: 3 problems For what a code means, run `archangel docs no-cycles` or `archangel docs may-not-import`. ``` 3. Compare each problem with the output of dependency-cruiser. The next section lists the results that can change. 4. See what the rules cover: Terminal ```ansi $ npx archangel rules no-circular archangel.config.ts:13 files in src/** may not import each other in a cycle 5 files · 1 problem ui-not-to-db archangel.config.ts:20 because: The UI reads data through the api folder. files in ui may not import db 1 file · 3 imports judged · 1 problem · 0 exceptions not-to-dev-dep archangel.config.ts:27 files in all of (src/**, not tests) may not import * that the nearest package.json lists only for development 4 files · 1 import judged · 1 problem · 0 exceptions Not judged by any rule: 0 files, 0 imports between repository files. Outside packages that no rule names: none. ``` ## Results that change * **The cycle above is made of type imports.** `notes.ts` and `note.ts` import each other with `import type`. dependency-cruiser with `tsPreCompilationDeps` reports cycles of type imports. Archangel’s `noCycles` counts only runtime imports by default, because the compiler removes a type import. The config above adds `imports: 'all'` to keep the result of dependency-cruiser. Remove it to report only the cycles that exist when the code runs. * **An import that does not resolve is an error.** Archangel reports [`import-unresolved`](/archangel/reference/diagnostics/import-unresolved/) for an import with no file and no installed package. dependency-cruiser does not stop on it. So expect some new errors when you move: each one is an import or a path that was wrong before. For an import that a tool makes at build time, add it to `allowUnresolved` in the config, with the reason. * **A rule that finds no files is an error.** A path in dependency-cruiser that matches nothing makes a rule that never fires, and the run passes. In archangel, a part, a group or a pattern that matches nothing gives [`selector-empty`](/archangel/reference/diagnostics/selector-empty/) or [`element-empty`](/archangel/reference/diagnostics/element-empty/). * **One table can replace many forbidden rules.** A set of `forbidden` rules that each refuse one pair of folders says, in effect, what each folder may import. A table says that directly, and it also refuses an import into a folder that no rule named. Keep the names Use the dependency-cruiser rule name as the archangel rule name. A team member who searches for `not-to-dev-dep` then finds the new rule. ## Next steps * Turn the forbidden rules about folders into a table: [Layer a backend](/archangel/guides/backend-layers/). * Start on code that already breaks the rules: [Exceptions and baselines](/archangel/guides/exceptions-and-baselines/).