Parts, groups and externals
Markdown for agentsBefore you write a test, you give names to sets of code. A rule then says “these files may not import those files”, and each name in the sentence is one of these sets. Archangel has three types of names:
| Name | What it holds | How many per file |
|---|---|---|
| Part | Files, found by a path pattern | One part at most |
| Group | Files, found by a pattern, a directive or a combination | Any number |
| Externals set | Outside packages, by package name | Not about files |
This config uses all three:
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({ parts: { feature: 'src/features/{feature}/**', shared: 'src/shared/**', }, groups: { tests: '**/*.test.ts', client: { directive: 'use client' }, }, externals: { browser: ['react', 'react-dom'], }, rules: ({ mayImport, rule }) => [ mayImport('app', { feature: ['shared', 'browser'], shared: [], }),
rule('tests-stay-out', { files: { not: 'tests' }, mayNotImport: 'tests', because: 'Code that ships never needs a test file.', examples: { rejected: ['src/shared/a.ts imports src/shared/a.test.ts'] }, }), ],});A part is a named area of your code. Archangel gives each file one part at most, so a part answers the question “where does this file live?”. Parts are the names that most tables and rules use.
- A part is a path pattern, or a list of path patterns. Paths start at the config folder.
- A file that no part finds is in no part. That is not an error. To make it an error, write a rule
with
mustNotExiston the files that are in no part. - If two parts find one file, archangel shows the error
part-overlap. It cannot know which part judges the file. Narrow one pattern withnot, or make one of the two a group. - If a part finds no file, archangel shows the error
element-empty. A part that finds nothing usually has a typing error in its pattern.
A part can have a capture, such as {feature}. A capture makes each folder one instance of
the part. Captures and instances tells you more.
Groups
Section titled “Groups”A group is a named set of files that can cross parts. Use a group for files that have something in common, but live in many places: tests, stories, client components, generated code.
- A file can be in many groups, and in a part too.
- A group can be a path pattern, a list, a directive such as
{ directive: 'use client' }, or a combination withall,anyandnot. Sets of files lists each form. - A group that finds no file is the error
selector-empty.
Externals sets
Section titled “Externals sets”An externals set is a named set of outside packages. Use one in a table cell, so that a row may
import those packages. On the other side of a rule, you can use the name, or write the packages
in place with { packages: ['react'] }.
- A name matches the package and each of its subpaths.
reactmatchesreact/jsx-runtime. @aws-sdk/*matches each package in a scope.*matches each package.- Write a Node built-in module with
node:, for examplenode:fs. Archangel refusesfs. - Archangel refuses a name with a subpath, a name that starts with
@types/, and the name of a package in your repository.
See the names of a file
Section titled “See the names of a file”archangel explain shows the part, the instance and the groups of one file:
$ npx archangel explain src/features/cart/cart-button.tsxsrc/features/cart/cart-button.tsx part: "feature", instance "cart" groups: client rules that apply: app.feature, tests-stay-out app.feature: feature may import only shared, browser, and itself in the same instance (judges its imports) tests-stay-out: files in not tests may not import tests (judges its imports) imports: line 2 'react' -> package react ("browser") line 3 '../../shared/money' -> src/shared/money.ts ("shared") imported by: noneThe file is in the part feature, in the instance cart, and in the group client, because it
starts with 'use client'. The import of react shows its externals set, browser.
The same file is in the table row feature, and the rule tests-stay-out judges it, because the
file is not in the group tests. A group is the right tool for this rule. A test file lives next
to the code that it tests, so a part cannot hold the test files.
$ npx archangel rulesapp 2 rows archangel.config.ts:16
app.feature: feature may import only shared, browser, and itself in the same instance 1 file · 2 imports judged · 0 problems · 0 exceptions app.shared: shared may import only itself 2 files · 1 import judged · 0 problems · 0 exceptions app.every-file-has-a-kind: a file in no row is not checked here; add `files` to check it 3 files · 0 problems
tests-stay-out archangel.config.ts:21 because: Code that ships never needs a test file. files in not tests may not import tests 3 files · 3 imports judged · 0 problems · 0 exceptions
Not judged by any rule: 0 files, 0 imports between repository files.Outside packages that no rule names: vitest (1 import).The last line lists vitest. No rule names it, so no rule can refuse an import of it. A table
judges a package only when a cell names an externals set that holds it.
Tables, rows and cells tells you why.
Which name to use
Section titled “Which name to use”- Use a part for an area that has its own name and that many tests use: an app, a layer, a feature, a package.
- Use a group for files that cross parts: tests, stories, client components.
- Use a kind when one pattern gives the names, for example each
*.entity.tsfile. A kind needs no declaration, but it has a meaning only in its table. Kinds tells you more. - Use an externals set for packages that a table row may import.