Skip to content

Migrate from dependency-cruiser

Markdown for agents

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.

  • Install archangel: npm install --save-dev @enhance-eng/archangel.
  • Keep dependency-cruiser until archangel gives the results that you expect.

This repository has three forbidden rules:

.dependency-cruiser.cjs
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'] },
},
],
};
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: 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 condition that reads importers.
archangel.config.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.
  1. Prove the rules:

    Terminal
    $ 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
    $ 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
    $ 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.
  • 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 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 or 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.