Getting started
Markdown for agentsThis page takes you from an empty config to a table that finds a real problem. It takes about ten minutes. Each output on this page is what archangel 0.3.5 prints for the files on this page.
Before you start
Section titled “Before you start”- You need Node 22.18 or later. Node runs
archangel.config.tswithout a build step. - You need a TypeScript or JavaScript repository.
The example repository is a small shop. It has two features and some shared code:
- package.json
Directorysrc/
Directoryfeatures/
Directorycart/
- cart.ts
- index.ts
Directorycheckout/
- checkout.ts
Directoryshared/
- money.ts
Install archangel and write a config
Section titled “Install archangel and write a config”-
Install the package as a development dependency:
Terminal window npm install --save-dev @enhance-eng/archangel -
Write a starter config:
Terminal $ npx archangel init --typescriptwrote archangel.config.ts with 2 parts: features, sharedIt imports `@enhance-eng/archangel`, so install the package first: npm install --save-dev @enhance-eng/archangelWrite what each part may import in a table, then run `archangel check`; `archangel docs may-import` shows how.initmakes one part for each folder of source files. It also adds one rule against import cycles:archangel.config.ts import { defineConfig } from '@enhance-eng/archangel';export default defineConfig({parts: {features: 'src/features/**',shared: 'src/shared/**',},rules: ({ rule }) => [rule('no-cycles', {files: '**',noCycles: true,because: 'Files that import each other cannot change or load one at a time.',examples: {accepted: ['cycle/a.ts imports cycle/b.ts'],rejected: ['cycle/a.ts imports cycle/b.ts imports cycle/a.ts'],},}),],}); -
Check the repository:
Terminal $ npx archangel checkchecked 5 files and 4 imports: no problemsThe count includes the config file. The starter config finds no problem, because it has no rule about your architecture yet.
Write a table
Section titled “Write a table”A table is the main form of an architecture test. Each row of the table is a set of files. The list on the row is what those files may import. The table refuses each other import into the repository.
The shop has one decision: a feature does not import another feature. Code that two features
need goes in shared.
-
Replace the config with this config:
archangel.config.ts import { defineConfig } from '@enhance-eng/archangel';export default defineConfig({parts: {feature: 'src/features/{feature}/**',shared: 'src/shared/**',},rules: ({ mayImport, rule }) => [mayImport('app', {feature: ['shared'],shared: [],}, {because: 'A feature changes alone. Code that two features need goes in shared.',}),rule('no-react-in-shared', {files: 'shared',mayNotImport: { packages: ['react'] },because: 'Shared code also runs on the server.',examples: {rejected: ['src/shared/money.ts imports react'],},}),],});partsgives names to the parts of the code.{feature}is a capture. It makes each folder insrc/featuresone instance of the partfeature, socartandcheckoutare two instances.- The table
apphas two rows. A file infeaturemay importshared, and files in its own instance. A file insharedmay import onlyshared. becauseis your reason. Archangel shows it with each problem.- The rule
no-react-in-sharedis a rule with one condition. The next section tells you about it.
-
Check the repository again:
Terminal $ npx archangel checkerror[may-import]: feature may not import another instance of feature, through "../cart/cart": feature may import only shared, and itself in the same instance--> src/features/checkout/checkout.ts:1:27|1 | import { cartTotal } from '../cart/cart';| ^^^^^^^^^^^^^^ feature may import only shared, and itself in the same instance|= help: remove the import, or move the code to a file whose row permits the import= note: rule: app.feature= note: because: A feature changes alone. Code that two features need goes in shared.checked 5 files and 4 imports: 1 problemFor what a code means, run `archangel docs may-import`.checkout.tsimports a file inside the cart feature. The error shows the line, the ruleapp.feature, your reason, and what the row permits. -
Fix the problem.
checkout.tsneeds only the formatted total, so importformatMoneyfromshared:src/features/checkout/checkout.ts import { formatMoney } from '../../shared/money';export function checkoutSummary(prices: number[]): string {return `Total: ${formatMoney(prices.reduce((sum, price) => sum + price, 0))}`;} -
Check again:
Terminal $ npx archangel checkchecked 5 files and 4 imports: no problems
Prove a rule with an example
Section titled “Prove a rule with an example”A rule can be wrong. For example, a typing error in a path can make a rule find no files, and then it never finds a problem. So each rule that is not a table row must have a rejected example: code that the rule must refuse.
The rule no-react-in-shared has one example, on one line:
rule('no-react-in-shared', { files: 'shared', mayNotImport: { packages: ['react'] }, because: 'Shared code also runs on the server.', examples: { rejected: ['src/shared/money.ts imports react'], },})Run the examples:
$ npx archangel testpass no-react-in-shared rejected #1 src/shared/money.ts1 examples, none failed; 0 rules without a rejected example2 table rows need no written example; `archangel rules` shows what each coversArchangel makes the files of each example in memory, and checks that the rule refuses them. It
does not write files. A table row needs no example, because archangel rules shows what each row
judged, and a table that finds no file is an error.
See what your rules do
Section titled “See what your rules do”archangel rules shows each rule as a sentence, with the number of files and imports that it
judged:
$ npx archangel rulesapp 2 rows archangel.config.ts:9 because: A feature changes alone. Code that two features need goes in shared.
app.feature: feature may import only shared, and itself in the same instance 3 files · 3 imports judged · 1 problem · 0 exceptions app.shared: shared may import only itself 1 file · 0 imports 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 4 files · 0 problems
no-react-in-shared archangel.config.ts:16 because: Shared code also runs on the server. files in shared may not import react 1 file · 0 imports judged · 0 problems · 0 exceptions
Not judged by any rule: 0 files, 0 imports between repository files.Outside packages that no rule names: none.The last two lines are important. They list the files and imports that no rule judges, and the outside packages that no rule names. A file in that list can break your architecture, and no test fails.
Understand one import
Section titled “Understand one import”To see why archangel refused an import, give the file and the line:
$ npx archangel explain src/features/checkout/checkout.ts:1src/features/checkout/checkout.ts:1 '../cart/cart' -> src/features/cart/cart.ts file: part feature (instance checkout) target: part feature (instance cart) judged by: app.feature refuses: another instance of feature is not in its row (feature may import only shared, and itself in the same instance) skipped by: app.shared the file is not in shared app.every-file-has-a-kind this rule checks files, not imports no-react-in-shared the file is not in sharedNext steps
Section titled “Next steps”- Add archangel to a repository that already breaks its rules.
- Use archangel in CI and in your editor.
- Read Tables, rows and cells to write larger tables.