@lucasacoutinho/archer 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -387
- package/dist/analyzer/Analyzer.d.ts +17 -0
- package/dist/analyzer/Analyzer.d.ts.map +1 -0
- package/dist/analyzer/Analyzer.js +15 -0
- package/dist/analyzer/Analyzer.js.map +1 -0
- package/dist/analyzer/bindings.d.ts +26 -0
- package/dist/analyzer/bindings.d.ts.map +1 -0
- package/dist/analyzer/bindings.js +61 -0
- package/dist/analyzer/bindings.js.map +1 -0
- package/dist/analyzer/errors.d.ts +34 -0
- package/dist/analyzer/errors.d.ts.map +1 -0
- package/dist/analyzer/errors.js +20 -0
- package/dist/analyzer/errors.js.map +1 -0
- package/dist/analyzer/graph-file.d.ts +13 -0
- package/dist/analyzer/graph-file.d.ts.map +1 -0
- package/dist/analyzer/graph-file.js +22 -0
- package/dist/analyzer/graph-file.js.map +1 -0
- package/dist/analyzer/paths.d.ts +12 -0
- package/dist/analyzer/paths.d.ts.map +1 -0
- package/dist/analyzer/paths.js +26 -0
- package/dist/analyzer/paths.js.map +1 -0
- package/dist/analyzer/server-exit.d.ts +19 -0
- package/dist/analyzer/server-exit.d.ts.map +1 -0
- package/dist/analyzer/server-exit.js +70 -0
- package/dist/analyzer/server-exit.js.map +1 -0
- package/dist/analyzer/source.d.ts +29 -0
- package/dist/analyzer/source.d.ts.map +1 -0
- package/dist/analyzer/source.js +58 -0
- package/dist/analyzer/source.js.map +1 -0
- package/dist/analyzer/syntax.d.ts +29 -0
- package/dist/analyzer/syntax.d.ts.map +1 -0
- package/dist/analyzer/syntax.js +226 -0
- package/dist/analyzer/syntax.js.map +1 -0
- package/dist/analyzer/targets.d.ts +26 -0
- package/dist/analyzer/targets.d.ts.map +1 -0
- package/dist/analyzer/targets.js +84 -0
- package/dist/analyzer/targets.js.map +1 -0
- package/dist/analyzer/typescript.d.ts +5 -0
- package/dist/analyzer/typescript.d.ts.map +1 -0
- package/dist/analyzer/typescript.js +221 -0
- package/dist/analyzer/typescript.js.map +1 -0
- package/dist/analyzer/workspace.d.ts +24 -0
- package/dist/analyzer/workspace.d.ts.map +1 -0
- package/dist/analyzer/workspace.js +9 -0
- package/dist/analyzer/workspace.js.map +1 -0
- package/dist/cli/explain.d.ts +3 -0
- package/dist/cli/explain.d.ts.map +1 -0
- package/dist/cli/explain.js +21 -0
- package/dist/cli/explain.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +159 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/config/input.d.ts +59 -0
- package/dist/config/input.d.ts.map +1 -0
- package/dist/config/input.js +115 -0
- package/dist/config/input.js.map +1 -0
- package/dist/config/rules.d.ts +94 -0
- package/dist/config/rules.d.ts.map +1 -0
- package/dist/config/rules.js +130 -0
- package/dist/config/rules.js.map +1 -0
- package/dist/core/baseline.d.ts +16 -0
- package/dist/core/baseline.d.ts.map +1 -0
- package/dist/core/baseline.js +25 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/check.d.ts +16 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +271 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/config.d.ts +72 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +78 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/fingerprint.d.ts +54 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +182 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/keys.d.ts +10 -0
- package/dist/core/keys.d.ts.map +1 -0
- package/dist/core/keys.js +16 -0
- package/dist/core/keys.js.map +1 -0
- package/dist/core/lower.d.ts +13 -0
- package/dist/core/lower.d.ts.map +1 -0
- package/dist/core/lower.js +82 -0
- package/dist/core/lower.js.map +1 -0
- package/dist/core/model.d.ts +155 -0
- package/dist/core/model.d.ts.map +1 -0
- package/dist/core/model.js +116 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/pattern.d.ts +13 -0
- package/dist/core/pattern.d.ts.map +1 -0
- package/dist/core/pattern.js +29 -0
- package/dist/core/pattern.js.map +1 -0
- package/dist/core/reach.d.ts +10 -0
- package/dist/core/reach.d.ts.map +1 -0
- package/dist/core/reach.js +81 -0
- package/dist/core/reach.js.map +1 -0
- package/dist/core/rule.d.ts +200 -0
- package/dist/core/rule.d.ts.map +1 -0
- package/dist/core/rule.js +162 -0
- package/dist/core/rule.js.map +1 -0
- package/dist/core/selector.d.ts +7 -0
- package/dist/core/selector.d.ts.map +1 -0
- package/dist/core/selector.js +27 -0
- package/dist/core/selector.js.map +1 -0
- package/dist/core/structural.d.ts +10 -0
- package/dist/core/structural.d.ts.map +1 -0
- package/dist/core/structural.js +24 -0
- package/dist/core/structural.js.map +1 -0
- package/dist/core/text.d.ts +26 -0
- package/dist/core/text.d.ts.map +1 -0
- package/dist/core/text.js +144 -0
- package/dist/core/text.js.map +1 -0
- package/dist/effect.d.ts +19 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +21 -0
- package/dist/effect.js.map +1 -0
- package/dist/index.d.ts +24 -44
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -52
- package/dist/index.js.map +1 -0
- package/dist/report/agent.d.ts +3 -0
- package/dist/report/agent.d.ts.map +1 -0
- package/dist/report/agent.js +35 -0
- package/dist/report/agent.js.map +1 -0
- package/dist/report/dot.d.ts +4 -0
- package/dist/report/dot.d.ts.map +1 -0
- package/dist/report/dot.js +15 -0
- package/dist/report/dot.js.map +1 -0
- package/dist/report/github.d.ts +3 -0
- package/dist/report/github.d.ts.map +1 -0
- package/dist/report/github.js +18 -0
- package/dist/report/github.js.map +1 -0
- package/dist/report/json.d.ts +3 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +4 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/message.d.ts +20 -0
- package/dist/report/message.d.ts.map +1 -0
- package/dist/report/message.js +73 -0
- package/dist/report/message.js.map +1 -0
- package/dist/report/pretty.d.ts +8 -0
- package/dist/report/pretty.d.ts.map +1 -0
- package/dist/report/pretty.js +89 -0
- package/dist/report/pretty.js.map +1 -0
- package/dist/report/render.d.ts +9 -0
- package/dist/report/render.d.ts.map +1 -0
- package/dist/report/render.js +10 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/report.d.ts +44 -0
- package/dist/report/report.d.ts.map +1 -0
- package/dist/report/report.js +49 -0
- package/dist/report/report.js.map +1 -0
- package/dist/report/sarif.d.ts +3 -0
- package/dist/report/sarif.d.ts.map +1 -0
- package/dist/report/sarif.js +68 -0
- package/dist/report/sarif.js.map +1 -0
- package/dist/run.d.ts +49 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +87 -0
- package/dist/run.js.map +1 -0
- package/dist/shell/baseline-file.d.ts +20 -0
- package/dist/shell/baseline-file.d.ts.map +1 -0
- package/dist/shell/baseline-file.js +46 -0
- package/dist/shell/baseline-file.js.map +1 -0
- package/dist/shell/load-config.d.ts +19 -0
- package/dist/shell/load-config.d.ts.map +1 -0
- package/dist/shell/load-config.js +39 -0
- package/dist/shell/load-config.js.map +1 -0
- package/package.json +29 -177
- package/skills/archer/SKILL.md +127 -45
- package/skills/archer/agents/openai.yaml +2 -2
- package/skills/archer/references/rules.md +267 -54
- package/dist/bun/plugin.cjs +0 -85
- package/dist/bun/plugin.d.cts +0 -7
- package/dist/bun/plugin.d.ts +0 -7
- package/dist/bun/plugin.js +0 -11
- package/dist/bun.cjs +0 -2238
- package/dist/bun.d.cts +0 -13
- package/dist/bun.d.ts +0 -13
- package/dist/bun.js +0 -28
- package/dist/chunk-2AKMBLHG.js +0 -2165
- package/dist/chunk-67YEOTXP.js +0 -25
- package/dist/chunk-IDVLHNL2.js +0 -46
- package/dist/chunk-ZZ6TR4PO.js +0 -201
- package/dist/cli.js +0 -2643
- package/dist/index.cjs +0 -2484
- package/dist/index.d.cts +0 -45
- package/dist/jest/plugin.cjs +0 -85
- package/dist/jest/plugin.d.cts +0 -7
- package/dist/jest/plugin.d.ts +0 -7
- package/dist/jest/plugin.js +0 -11
- package/dist/jest.cjs +0 -2237
- package/dist/jest.d.cts +0 -13
- package/dist/jest.d.ts +0 -13
- package/dist/jest.js +0 -27
- package/dist/matchers-Cqnbsyxe.d.cts +0 -11
- package/dist/matchers-Cqnbsyxe.d.ts +0 -11
- package/dist/node.cjs +0 -2237
- package/dist/node.d.cts +0 -13
- package/dist/node.d.ts +0 -13
- package/dist/node.js +0 -27
- package/dist/reporters-BbAXMpuW.d.cts +0 -6
- package/dist/reporters-xh-HQneS.d.ts +0 -6
- package/dist/suite-BwnPDCvU.d.cts +0 -337
- package/dist/suite-BwnPDCvU.d.ts +0 -337
- package/dist/vitest/plugin.cjs +0 -85
- package/dist/vitest/plugin.d.cts +0 -7
- package/dist/vitest/plugin.d.ts +0 -7
- package/dist/vitest/plugin.js +0 -11
- package/dist/vitest.cjs +0 -2531
- package/dist/vitest.d.cts +0 -37
- package/dist/vitest.d.ts +0 -37
- package/dist/vitest.js +0 -139
package/README.md
CHANGED
|
@@ -1,460 +1,217 @@
|
|
|
1
1
|
# Archer
|
|
2
2
|
|
|
3
|
-
Architecture tests for TypeScript and JavaScript.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
It takes its readable rule style from [Pest architecture testing](https://pestphp.com/docs/arch-testing),
|
|
8
|
-
but models files, packages, imports, exports, and TypeScript declarations.
|
|
9
|
-
|
|
10
|
-
## Install
|
|
3
|
+
Architecture tests for TypeScript and JavaScript. You describe which parts of
|
|
4
|
+
your code may depend on which. Archer analyzes the project with TypeScript and
|
|
5
|
+
reports every import that breaks the rules.
|
|
11
6
|
|
|
12
7
|
```sh
|
|
13
8
|
npm install --save-dev @lucasacoutinho/archer
|
|
14
9
|
```
|
|
15
10
|
|
|
16
|
-
Archer
|
|
17
|
-
TypeScript, so the project's TypeScript version does not matter. The core
|
|
18
|
-
package has no test-runner dependency.
|
|
19
|
-
|
|
20
|
-
## Start with the CLI
|
|
21
|
-
|
|
22
|
-
Create `archer.config.ts`:
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import { defineConfig } from '@lucasacoutinho/archer'
|
|
26
|
-
|
|
27
|
-
export default defineConfig({
|
|
28
|
-
include: ['src/**/*.ts', 'packages/*/src/**/*.ts'],
|
|
29
|
-
exclude: ['**/*.generated.ts'],
|
|
11
|
+
Archer needs Node 22.18 or newer and is ESM only.
|
|
30
12
|
|
|
31
|
-
|
|
32
|
-
archer
|
|
33
|
-
.expect('src/domain/**')
|
|
34
|
-
.not.toDependOn('src/infrastructure/**')
|
|
35
|
-
.because('The domain must remain portable.')
|
|
36
|
-
.as('boundaries/domain-isolation')
|
|
37
|
-
|
|
38
|
-
archer.expect('src/**').toBeFreeOfCycles()
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Then run:
|
|
13
|
+
## Quick start
|
|
44
14
|
|
|
45
15
|
```sh
|
|
46
|
-
npx archer
|
|
47
|
-
npx archer check
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`archer init` creates a starter config. Config paths, output paths, and the
|
|
51
|
-
project root are resolved relative to the config file.
|
|
52
|
-
|
|
53
|
-
## Test-runner adapters
|
|
54
|
-
|
|
55
|
-
Each adapter registers a test immediately and returns the same rule builder.
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
// test/architecture.test.ts
|
|
59
|
-
import { arch } from '@lucasacoutinho/archer/vitest'
|
|
60
|
-
|
|
61
|
-
arch('the domain stays independent')
|
|
62
|
-
.expect('src/domain/**')
|
|
63
|
-
.not.toDependOn('src/infrastructure/**')
|
|
64
|
-
.and.toOnlyDependOn(['src/domain/**', 'src/shared/**'])
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The other entry points are:
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { arch } from '@lucasacoutinho/archer/jest'
|
|
71
|
-
import { arch } from '@lucasacoutinho/archer/bun'
|
|
72
|
-
import { arch } from '@lucasacoutinho/archer/node'
|
|
16
|
+
npx archer init # writes archer.config.ts
|
|
17
|
+
npx archer check # exit 0: pass, 1: violations, 2: cannot check
|
|
73
18
|
```
|
|
74
19
|
|
|
75
|
-
Jest and Bun accept `timeout` as an Archer adapter option. The Node adapter
|
|
76
|
-
accepts built-in `node:test` options under `test`.
|
|
77
|
-
|
|
78
|
-
## Vitest config plugin
|
|
79
|
-
|
|
80
|
-
Vitest can run Archer once, before it starts collecting tests. The plugin loads
|
|
81
|
-
`archer.config.*` from the Vitest project root by default:
|
|
82
|
-
|
|
83
20
|
```ts
|
|
84
|
-
//
|
|
85
|
-
import {
|
|
86
|
-
import { defineConfig } from 'vitest/config'
|
|
21
|
+
// archer.config.ts
|
|
22
|
+
import { defineConfig, presets, r } from "@lucasacoutinho/archer"
|
|
87
23
|
|
|
88
24
|
export default defineConfig({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Pass `config`, `reporter`, `baseline`, `output`, or `failOnWarnings` to
|
|
94
|
-
`archer()` when the defaults are not enough. This is a configuration plugin,
|
|
95
|
-
so it performs one architecture check for the Vitest project instead of
|
|
96
|
-
registering a test in every worker.
|
|
97
|
-
|
|
98
|
-
## Matcher setup modules
|
|
99
|
-
|
|
100
|
-
Vitest, Jest, and Bun can also install Archer as an `expect` plugin. This keeps
|
|
101
|
-
the runner integration in its normal setup hook and lets architecture tests use
|
|
102
|
-
the same assertion in every runner.
|
|
103
|
-
|
|
104
|
-
For Vitest, add the plugin to `setupFiles`:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
// vitest.config.ts
|
|
108
|
-
import { defineConfig } from 'vitest/config'
|
|
109
|
-
|
|
110
|
-
export default defineConfig({
|
|
111
|
-
test: {
|
|
112
|
-
setupFiles: ['@lucasacoutinho/archer/vitest/plugin'],
|
|
25
|
+
layers: {
|
|
26
|
+
app: "src/app",
|
|
27
|
+
domain: "src/domain",
|
|
28
|
+
infrastructure: "src/infrastructure",
|
|
113
29
|
},
|
|
30
|
+
// Top to bottom: a layer may depend only on the layers below it.
|
|
31
|
+
stack: ["app", "domain"],
|
|
32
|
+
// Further dependencies, by layer.
|
|
33
|
+
allow: { infrastructure: ["domain"] },
|
|
34
|
+
rules: [
|
|
35
|
+
...presets.recommended, // no import cycles, every import resolves
|
|
36
|
+
r.forbid("domain/pure", "layer:domain", ["npm:*", "node:*"]),
|
|
37
|
+
],
|
|
114
38
|
})
|
|
115
39
|
```
|
|
116
40
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
// jest.config.ts
|
|
121
|
-
export default {
|
|
122
|
-
setupFilesAfterEnv: ['@lucasacoutinho/archer/jest/plugin'],
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
For Bun, preload it from `bunfig.toml`:
|
|
127
|
-
|
|
128
|
-
```toml
|
|
129
|
-
[test]
|
|
130
|
-
preload = ["@lucasacoutinho/archer/bun/plugin"]
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Then create a normal test and await the asynchronous matcher:
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
import { createArcher } from '@lucasacoutinho/archer'
|
|
137
|
-
|
|
138
|
-
test('the architecture is respected', async () => {
|
|
139
|
-
const archer = createArcher()
|
|
140
|
-
archer.expect('src/domain/**').not.toDependOn('src/infrastructure/**')
|
|
141
|
-
|
|
142
|
-
await expect(archer).toRespectArchitecture()
|
|
143
|
-
})
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
The plugin installs the matcher; it does not silently inject a test into every
|
|
147
|
-
worker. Use the `arch()` adapter when the fluent one-call form is preferable.
|
|
148
|
-
|
|
149
|
-
## Dependency rules
|
|
41
|
+
To run the same check in your test suite, add one test. This works in Vitest,
|
|
42
|
+
`node:test`, Bun, and Jest in ESM mode:
|
|
150
43
|
|
|
151
44
|
```ts
|
|
152
|
-
archer
|
|
153
|
-
.expect('src/application/**')
|
|
154
|
-
.toDependOn('src/domain/**')
|
|
155
|
-
.not.toDependOn('src/web/**')
|
|
156
|
-
.toOnlyDependOn(['src/application/**', 'src/domain/**', 'npm:zod'])
|
|
157
|
-
.toBeDependedOnBy('src/http/**')
|
|
158
|
-
.toOnlyBeDependedOnBy(['src/http/**', 'src/jobs/**'])
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Aliases are available for Pest-style wording: `toUse`, `toOnlyUse`,
|
|
162
|
-
`toBeUsedIn`, `toOnlyBeUsedIn`, `toUseNothing`, and `toBeUsed`.
|
|
45
|
+
import { assertArchitecture } from "@lucasacoutinho/archer"
|
|
163
46
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
archer
|
|
168
|
-
.expect('src/application/**')
|
|
169
|
-
.typeDependencies()
|
|
170
|
-
.toOnlyDependOn('src/contracts/**')
|
|
171
|
-
.runtimeDependencies()
|
|
172
|
-
.not.toDependOn('src/testing/**')
|
|
173
|
-
.dynamicDependencies()
|
|
174
|
-
.not.toDependOn('src/admin/**')
|
|
175
|
-
.localDependencies()
|
|
176
|
-
.toOnlyDependOn(['src/application/**', 'src/domain/**'])
|
|
177
|
-
.toTransitivelyDependOn('src/domain/events.ts')
|
|
47
|
+
test("architecture", () => assertArchitecture())
|
|
178
48
|
```
|
|
179
49
|
|
|
180
|
-
|
|
181
|
-
or `.transitively().toDependOn(...)` for explicit reachability. Transitive
|
|
182
|
-
violations include the shortest discovered import chain.
|
|
183
|
-
|
|
184
|
-
### Selectors and resolution
|
|
185
|
-
|
|
186
|
-
| Selector | Meaning |
|
|
187
|
-
| --------------- | ------------------------------------------------ |
|
|
188
|
-
| `src/domain/**` | Files resolved inside the configured root |
|
|
189
|
-
| `npm:react` | An installed package and its subpaths |
|
|
190
|
-
| `node:fs` | A Node built-in |
|
|
191
|
-
| `bun:test` | A Bun built-in |
|
|
192
|
-
| `unresolved:*` | Imports TypeScript could not resolve |
|
|
193
|
-
| `dynamic:*` | Computed dynamic imports without a static target |
|
|
194
|
-
|
|
195
|
-
Archer resolves path aliases, package exports, project references, workspace
|
|
196
|
-
packages, symlinks, `.js` imports of TypeScript sources, re-exports,
|
|
197
|
-
`import type`, import types, CommonJS `require`, and lazy imports.
|
|
198
|
-
|
|
199
|
-
Literal lazy imports such as `import('./admin.js')` are normal resolved edges.
|
|
200
|
-
A computed import such as `import(moduleName)` becomes `dynamic:unknown`.
|
|
201
|
-
Negative boundary rules fail closed on that edge because the target cannot be
|
|
202
|
-
proven safe; an allow-list can explicitly include `dynamic:*`.
|
|
203
|
-
|
|
204
|
-
## Named layers and public APIs
|
|
50
|
+
## Selectors
|
|
205
51
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
domain: 'src/domain/**',
|
|
209
|
-
application: 'src/application/**',
|
|
210
|
-
infrastructure: 'src/infrastructure/**',
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
layers.where('domain').mayNotDependOn('infrastructure')
|
|
214
|
-
layers.where('application').mayOnlyDependOn('domain')
|
|
215
|
-
layers.where('domain').mayOnlyBeUsedBy('application')
|
|
216
|
-
layers.where('domain').exposeThrough('src/domain/index.ts')
|
|
217
|
-
layers.toBeFreeOfCycles()
|
|
218
|
-
```
|
|
52
|
+
A selector is a string, an array of selectors (any of them), `{ not: … }`, or
|
|
53
|
+
`{ all: [...] }`.
|
|
219
54
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
55
|
+
| Text | Selects |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `src/domain` | Every file at or under `src/domain` |
|
|
58
|
+
| `src/**/*.ts`, `src/*/index.ts`, `src/a?.ts` | Path globs: `**` spans directories, `*` and `?` stay within one name |
|
|
59
|
+
| `{src/a,src/b}/**` | Either alternative |
|
|
60
|
+
| `npm:react`, `npm:@scope/pkg`, `npm:*` | Installed packages, with any subpath |
|
|
61
|
+
| `node:fs`, `node:*`, `bun:*` | Built-in modules |
|
|
62
|
+
| `layer:domain` | The files of a layer |
|
|
63
|
+
| `unresolved:*` | Imports that TypeScript could not resolve |
|
|
64
|
+
| `computed:*` | `import(x)` and `require(x)` without a literal specifier |
|
|
223
65
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
archer.expect('src/**').toBeFreeOfFolderCycles(2)
|
|
228
|
-
```
|
|
66
|
+
Paths are relative to the project root and always use `/`.
|
|
229
67
|
|
|
230
|
-
##
|
|
68
|
+
## Rules
|
|
231
69
|
|
|
232
|
-
|
|
233
|
-
heritage, exports, decorators, method visibility, and global-call detection.
|
|
70
|
+
Every rule takes a stable ID first. Findings and baselines key on it.
|
|
234
71
|
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
.expect('src/controllers/**')
|
|
238
|
-
.toBeClasses()
|
|
239
|
-
.toExtend('BaseController')
|
|
240
|
-
.toImplement('Runnable')
|
|
241
|
-
.toHaveDecorator('Controller')
|
|
242
|
-
.toHaveReadonlyProperties()
|
|
243
|
-
.toHaveMethods('execute')
|
|
244
|
-
.toHavePublicMethods('execute')
|
|
245
|
-
.toHaveProtectedMethods('reset')
|
|
246
|
-
.toOnlyHavePublicMethods()
|
|
247
|
-
.toBeExported()
|
|
248
|
-
|
|
249
|
-
archer.expect('src/contracts/**').toBeInterfaces()
|
|
250
|
-
archer.expect('src/base-controller.ts').toBeAbstract()
|
|
251
|
-
archer.expect('src/status.ts').toBeEnums()
|
|
252
|
-
archer.expect('src/factories/**').toBeFunctions()
|
|
253
|
-
archer.expect('src/models/**').toBeTypes()
|
|
254
|
-
archer.expect('src/index.ts').toOnlyExport('Client', 'ClientOptions')
|
|
255
|
-
archer.expect('src/**').toUseStrictEquality()
|
|
256
|
-
archer.expect('src/**').not.toUseGlobals('eval', 'Function')
|
|
257
|
-
archer.expect('src/**').not.toUseImportedSymbols('react', 'useEffect')
|
|
258
|
-
archer.expect('src/**').toHaveResolvableDependencies()
|
|
259
|
-
archer.expect('src/index.ts').toBePublicApi()
|
|
72
|
+
```text
|
|
73
|
+
r.forbid(id, subject, object, options?)
|
|
260
74
|
```
|
|
261
75
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
76
|
+
| Rule | Meaning |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `r.forbid` | The subject must not depend on the object |
|
|
79
|
+
| `r.require` | The subject must depend on something the object selects |
|
|
80
|
+
| `r.onlyDependOn` | The subject may depend only on the object |
|
|
81
|
+
| `r.forbidDependents` | Files the object selects must not depend on the subject |
|
|
82
|
+
| `r.requireDependents` | Some file the object selects must depend on the subject |
|
|
83
|
+
| `r.onlyDependedOnBy` | Only files the object selects may depend on the subject |
|
|
84
|
+
| `r.acyclic(id, subject, { folderDepth? })` | No import cycles, among files or among folders |
|
|
85
|
+
| `r.resolvable(id, subject, { allowComputed? })` | Every import resolves |
|
|
86
|
+
| `r.declares(id, subject, predicate)` / `r.declaresNone` | Every (or no) declaration satisfies a `p.*` predicate |
|
|
87
|
+
| `r.exportsOnly(id, subject, names)` | The files export only these names |
|
|
88
|
+
| `r.forbidUse(id, subject, use)` / `r.requireUse` | Calls and `==` checks, from `use.*` |
|
|
89
|
+
| `r.prefix` / `r.suffix(id, subject, text)` | File names start or end with `text` |
|
|
90
|
+
| `r.maxLines(id, subject, max)` | Files have at most `max` lines |
|
|
91
|
+
| `r.custom(id, subject, ({ graph, subject }) => messages)` | Your own check; each message is a finding |
|
|
267
92
|
|
|
268
|
-
|
|
93
|
+
Options for every rule: `severity` (`"error"`, `"warning"`, or `"info"`) and
|
|
94
|
+
`reason`. Dependency rules also take:
|
|
269
95
|
|
|
270
|
-
`
|
|
271
|
-
|
|
272
|
-
|
|
96
|
+
- `transitive: true` follows imports through other files. A finding then shows
|
|
97
|
+
the shortest import chain.
|
|
98
|
+
- `kinds` limits the import kinds: `"static"`, `"type"`, `"require"`, or
|
|
99
|
+
`"dynamic"`.
|
|
100
|
+
- `localOnly: true` ignores packages and built-ins.
|
|
273
101
|
|
|
274
|
-
|
|
275
|
-
written from generic rules:
|
|
102
|
+
Predicates for `r.declares`:
|
|
276
103
|
|
|
277
104
|
```ts
|
|
278
|
-
archer
|
|
279
|
-
.expect('src/frontend/components/**/*.{ts,tsx}')
|
|
280
|
-
.not.toBeClasses()
|
|
281
|
-
.and.not.toUseImportedSymbols('react', 'useEffect', 'useContext')
|
|
105
|
+
import { p, use } from "@lucasacoutinho/archer"
|
|
282
106
|
|
|
283
|
-
|
|
107
|
+
p.kind("class")
|
|
108
|
+
p.extends("Base")
|
|
109
|
+
p.implements("Runnable")
|
|
110
|
+
p.decorator("Controller")
|
|
111
|
+
p.hasMethods(["run"], "public")
|
|
112
|
+
p.onlyPublicMethods
|
|
113
|
+
p.abstract
|
|
114
|
+
p.exported
|
|
115
|
+
p.readonlyProperties
|
|
116
|
+
p.all(p.kind("class"), p.exported)
|
|
284
117
|
```
|
|
285
118
|
|
|
286
|
-
|
|
287
|
-
`setContext` and `getContext`, in JavaScript and TypeScript modules:
|
|
119
|
+
Uses for `r.forbidUse`:
|
|
288
120
|
|
|
289
121
|
```ts
|
|
290
|
-
|
|
291
|
-
|
|
122
|
+
use.looseEquality
|
|
123
|
+
use.globals("eval")
|
|
124
|
+
use.imported("react", "useEffect")
|
|
292
125
|
```
|
|
293
126
|
|
|
294
|
-
|
|
295
|
-
`.vue` or `.svelte` file is not a TypeScript source file. Archer does not parse
|
|
296
|
-
those formats as TypeScript or claim that their template dependencies were
|
|
297
|
-
checked. They belong in source-analyzer integrations, while the core rules
|
|
298
|
-
remain framework-neutral.
|
|
127
|
+
Archer applies these semantics:
|
|
299
128
|
|
|
300
|
-
|
|
129
|
+
- **Unknown imports fail closed.** An import whose target Archer cannot know
|
|
130
|
+
(`computed:*`) breaks every `forbid` rule. Allow it explicitly when you mean
|
|
131
|
+
it.
|
|
132
|
+
- **Layer rules check direct imports between declared layers.** Files outside
|
|
133
|
+
every layer are unconstrained.
|
|
134
|
+
- **Mistyped paths are listed, not failed.** A rule that selects no files is
|
|
135
|
+
listed as information under `emptySubjects`, and a rule whose target glob
|
|
136
|
+
matches nothing is listed under `emptyObjects`.
|
|
301
137
|
|
|
302
|
-
|
|
303
|
-
less sensitive to wording changes.
|
|
138
|
+
## Configuration
|
|
304
139
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
140
|
+
| Field | Default | Meaning |
|
|
141
|
+
| --- | --- | --- |
|
|
142
|
+
| `layers`, `stack`, `allow` | | Layers and the dependencies allowed between them |
|
|
143
|
+
| `publicApis` | | `{ domain: "src/domain/index.ts" }`: other layers may import a layer only through its entry |
|
|
144
|
+
| `rules` | | Rules from `r.*` and `presets` |
|
|
145
|
+
| `root` | `.` | The project root, relative to the config file |
|
|
146
|
+
| `projects` | `["tsconfig.json"]` | The tsconfigs whose files Archer analyzes |
|
|
147
|
+
| `baseline` | `.archer-baseline` | The baseline file, or `false` |
|
|
148
|
+
| `failOnWarnings` | `false` | Whether warnings fail the check |
|
|
313
149
|
|
|
314
|
-
|
|
150
|
+
## CLI
|
|
315
151
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
rule: 'features expose one entrypoint',
|
|
325
|
-
severity: 'error',
|
|
326
|
-
},
|
|
327
|
-
]
|
|
328
|
-
: [],
|
|
329
|
-
),
|
|
330
|
-
)
|
|
331
|
-
```
|
|
152
|
+
| Command | Does |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `archer check` | Checks and prints a report |
|
|
155
|
+
| `archer check -w` | Checks again after every change, in a fresh process |
|
|
156
|
+
| `archer baseline record` | Accepts every current violation |
|
|
157
|
+
| `archer baseline prune` | Drops baseline entries that no violation has |
|
|
158
|
+
| `archer graph -o graph.json` | Saves the analysis. `archer check --graph graph.json` checks it without TypeScript |
|
|
159
|
+
| `archer init` | Writes a starter config |
|
|
332
160
|
|
|
333
|
-
|
|
161
|
+
`check` options:
|
|
334
162
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
archer.
|
|
163
|
+
| Option | Meaning |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `-c, --config <file>` | The config file. Default: `archer.config.{ts,mts,js,mjs}` |
|
|
166
|
+
| `-r, --reporter <name>` | `pretty`, `agent` (one line per finding, for tools and agents), `json`, `sarif`, `github` (annotations), or `dot` (the import graph) |
|
|
167
|
+
| `-o, --output <file>` | Write the report to a file |
|
|
168
|
+
| `--ignore-baseline` | Report every violation |
|
|
338
169
|
|
|
339
|
-
|
|
340
|
-
suite.expect('src/**').not.toDependOn('npm:deprecated-sdk')
|
|
341
|
-
})
|
|
342
|
-
```
|
|
170
|
+
## Baselines
|
|
343
171
|
|
|
344
|
-
|
|
172
|
+
To adopt Archer in a codebase with existing violations, run
|
|
173
|
+
`archer baseline record` and commit `.archer-baseline`. Later checks fail
|
|
174
|
+
only on new violations. When you fix old violations, run
|
|
175
|
+
`archer baseline prune`.
|
|
345
176
|
|
|
346
|
-
|
|
347
|
-
can also declare projects and override their source selection:
|
|
177
|
+
## Programmatic use
|
|
348
178
|
|
|
349
179
|
```ts
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
'packages/core/tsconfig.json',
|
|
353
|
-
{
|
|
354
|
-
tsconfig: 'apps/*/tsconfig.json',
|
|
355
|
-
include: ['src/**/*.ts', 'scripts/**/*.ts'],
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
rules(archer) {
|
|
359
|
-
archer.expect('packages/**').toBeFreeOfCycles()
|
|
360
|
-
},
|
|
361
|
-
})
|
|
362
|
-
```
|
|
180
|
+
import { check, graphOf, run } from "@lucasacoutinho/archer"
|
|
181
|
+
import config from "./archer.config.ts"
|
|
363
182
|
|
|
364
|
-
|
|
365
|
-
`tsconfig: false` to override the corresponding defaults.
|
|
183
|
+
const { report } = await run({ config: "archer.config.ts" })
|
|
366
184
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
The CLI supports `pretty`, `agent`, `llm`, `json`, `sarif`, `github`, and `dot` reporters.
|
|
370
|
-
`agent` and its `llm` alias emit one stable, single-line record per finding without ANSI output:
|
|
371
|
-
|
|
372
|
-
```sh
|
|
373
|
-
npx archer check --reporter agent
|
|
374
|
-
npx archer check --reporter sarif --output reports/archer.sarif
|
|
375
|
-
npx archer check --reporter github
|
|
376
|
-
npx archer check --reporter dot --output reports/dependencies.dot
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
Adopt Archer in an existing codebase without accepting new violations:
|
|
380
|
-
|
|
381
|
-
```sh
|
|
382
|
-
npx archer check --update-baseline
|
|
383
|
-
npx archer check
|
|
185
|
+
// Test a policy against a graph in memory, without TypeScript:
|
|
186
|
+
const result = check(config, graphOf({ "src/domain/a.ts": ["src/app/b.ts"], "src/app/b.ts": [] }))
|
|
384
187
|
```
|
|
385
188
|
|
|
386
|
-
|
|
387
|
-
the
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
The core API exposes the same data for integrations:
|
|
391
|
-
|
|
392
|
-
```ts
|
|
393
|
-
import { ArchitectureError, createArcher } from '@lucasacoutinho/archer'
|
|
394
|
-
|
|
395
|
-
const archer = createArcher({ root: process.cwd() })
|
|
396
|
-
archer.expect('src/**').toBeFreeOfCycles()
|
|
397
|
-
|
|
398
|
-
const result = await archer.check()
|
|
399
|
-
if (!result.ok) throw new ArchitectureError(result)
|
|
400
|
-
```
|
|
189
|
+
`@lucasacoutinho/archer/effect` exposes the same operations as Effect values.
|
|
190
|
+
It also exposes the `Analyzer` service, with `typescript`, `memory`, and
|
|
191
|
+
`replay` adapters.
|
|
401
192
|
|
|
402
193
|
## Agent skill
|
|
403
194
|
|
|
404
|
-
|
|
405
|
-
`
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
Install it into any supported coding agent with the Skills CLI:
|
|
409
|
-
|
|
410
|
-
```sh
|
|
411
|
-
npx skills add lucasacoutinho/archer --skill archer
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
Agents can then use `$archer` to set up rules, diagnose findings, and fix source
|
|
415
|
-
violations without weakening architecture policy.
|
|
195
|
+
[`skills/archer`](skills/archer/SKILL.md) teaches a coding agent to set up
|
|
196
|
+
Archer, read `archer check -r agent`, and fix violations in source. Install it
|
|
197
|
+
with `npx skills add lucasacoutinho/archer --skill archer`.
|
|
416
198
|
|
|
417
199
|
## Development
|
|
418
200
|
|
|
419
|
-
Archer dogfoods its rules in both `test/architecture.test.ts` and
|
|
420
|
-
`archer.config.ts`. The repository checks Node 20, 22, and 24 on Linux and
|
|
421
|
-
Windows, plus Jest, Bun, `node:test`, the CLI, package exports, and type
|
|
422
|
-
definitions.
|
|
423
|
-
|
|
424
201
|
```sh
|
|
425
|
-
npm install
|
|
426
|
-
npm run
|
|
427
|
-
npm run lint
|
|
428
|
-
npm run check
|
|
429
|
-
npm run check:package
|
|
430
|
-
npm run release:check
|
|
202
|
+
npm install # also patches tsc with @effect/tsgo
|
|
203
|
+
npm run check # Lean proofs, type check, tests, build, Archer on itself
|
|
431
204
|
```
|
|
432
205
|
|
|
433
|
-
|
|
206
|
+
`npm run check` needs [elan](https://github.com/leanprover/elan): Lean is
|
|
207
|
+
pinned in `lean/lean-toolchain`. See [architecture](docs/architecture.md) for
|
|
208
|
+
how Archer is built, and [verification](docs/verification.md) for what is
|
|
209
|
+
proved and how the proofs reach the code.
|
|
434
210
|
|
|
435
|
-
|
|
436
|
-
npm
|
|
437
|
-
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
The scripts prefer an installed `@lucasacoutinho/tiacore` package and otherwise
|
|
441
|
-
use `../tiacore`. Set `TIACORE_VITEST_ENTRY` or `TIACORE_CLI` for a checkout in
|
|
442
|
-
another location.
|
|
443
|
-
|
|
444
|
-
## Releasing
|
|
445
|
-
|
|
446
|
-
Releases publish to the public npm registry from the `Publish package` workflow
|
|
447
|
-
(`.github/workflows/publish.yml`) with npm trusted publishing, so no npm token is
|
|
448
|
-
stored in the repository and npm attaches provenance automatically. npm can only
|
|
449
|
-
attach a trusted publisher to a package that already exists, so the first
|
|
450
|
-
version is published manually by a maintainer with two-factor authentication.
|
|
451
|
-
After that, link the workflow once:
|
|
452
|
-
|
|
453
|
-
```sh
|
|
454
|
-
npm trust github @lucasacoutinho/archer --file publish.yml --repo lucasacoutinho/archer --allow-publish
|
|
455
|
-
```
|
|
211
|
+
Releases publish from the `Publish package` workflow when you publish a
|
|
212
|
+
GitHub release. The workflow uses npm trusted publishing with provenance.
|
|
213
|
+
Add the release to [CHANGELOG.md](CHANGELOG.md) first.
|
|
456
214
|
|
|
457
215
|
## License
|
|
458
216
|
|
|
459
|
-
|
|
460
|
-
[NOTICE](NOTICE) for attribution.
|
|
217
|
+
[Apache 2.0](LICENSE). See [NOTICE](NOTICE).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The only part of Archer that talks to TypeScript. Contract: docs/architecture.md#analyzer. */
|
|
2
|
+
import { Context, FileSystem, Layer, type Path } from "effect";
|
|
3
|
+
import type { Graph } from "../core/model.ts";
|
|
4
|
+
import { AnalyzerUnavailable, type AnalysisError, GraphFileInvalid, ProjectNotFound, ProjectParseFailed } from "./errors.ts";
|
|
5
|
+
import type { AnalyzerShape } from "./workspace.ts";
|
|
6
|
+
export { AbsolutePath, RootRelativePath, Workspace } from "./workspace.ts";
|
|
7
|
+
export { AnalyzerUnavailable, GraphFileInvalid, ProjectNotFound, ProjectParseFailed };
|
|
8
|
+
export type { AnalysisError };
|
|
9
|
+
declare const Analyzer_base: Context.ServiceClass<Analyzer, "archer/Analyzer", AnalyzerShape>;
|
|
10
|
+
export declare class Analyzer extends Analyzer_base {
|
|
11
|
+
/** TypeScript's native API. The server starts on the first `analyze` and stops with the layer's scope. */
|
|
12
|
+
static readonly typescript: Layer.Layer<Analyzer, never, FileSystem.FileSystem | Path.Path>;
|
|
13
|
+
static readonly memory: (graph: Graph) => Layer.Layer<Analyzer>;
|
|
14
|
+
/** A graph saved by `archer graph`, read again on every `analyze`; the workspace is not consulted. */
|
|
15
|
+
static readonly replay: (path: string) => Layer.Layer<Analyzer, never, FileSystem.FileSystem>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=Analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Analyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/Analyzer.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,EAAE,OAAO,EAAU,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAA;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAG5H,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1E,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA;AACrF,YAAY,EAAE,aAAa,EAAE,CAAA;;AAE7B,qBAAa,QAAS,SAAQ,aAA6D;IACzF,0GAA0G;IAC1G,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAyC;IAEpI,MAAM,CAAC,QAAQ,CAAC,MAAM,UAAW,KAAK,KAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CACkB;IAEhF,sGAAsG;IACtG,MAAM,CAAC,QAAQ,CAAC,MAAM,SAAU,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,CAMzF;CACJ"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The only part of Archer that talks to TypeScript. Contract: docs/architecture.md#analyzer. */
|
|
2
|
+
import { Context, Effect, FileSystem, Layer } from "effect";
|
|
3
|
+
import { AnalyzerUnavailable, GraphFileInvalid, ProjectNotFound, ProjectParseFailed } from "./errors.js";
|
|
4
|
+
import { readGraphFile } from "./graph-file.js";
|
|
5
|
+
import { makeTypescript } from "./typescript.js";
|
|
6
|
+
export { AbsolutePath, RootRelativePath, Workspace } from "./workspace.js";
|
|
7
|
+
export { AnalyzerUnavailable, GraphFileInvalid, ProjectNotFound, ProjectParseFailed };
|
|
8
|
+
export class Analyzer extends Context.Service()("archer/Analyzer") {
|
|
9
|
+
/** TypeScript's native API. The server starts on the first `analyze` and stops with the layer's scope. */
|
|
10
|
+
static typescript = Layer.effect(Analyzer, makeTypescript);
|
|
11
|
+
static memory = (graph) => Layer.succeed(Analyzer, Analyzer.of({ analyze: () => Effect.succeed(graph) }));
|
|
12
|
+
/** A graph saved by `archer graph`, read again on every `analyze`; the workspace is not consulted. */
|
|
13
|
+
static replay = (path) => Layer.effect(Analyzer, Effect.map(FileSystem.FileSystem, (fs) => Analyzer.of({ analyze: () => readGraphFile(path).pipe(Effect.provideService(FileSystem.FileSystem, fs)) })));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=Analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Analyzer.js","sourceRoot":"","sources":["../../src/analyzer/Analyzer.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAa,MAAM,QAAQ,CAAA;AAEtE,OAAO,EAAE,mBAAmB,EAAsB,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC5H,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGhD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1E,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA;AAGrF,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAA2B,CAAC,iBAAiB,CAAC;IACzF,0GAA0G;IAC1G,MAAM,CAAU,UAAU,GAAoE,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAA;IAEpI,MAAM,CAAU,MAAM,GAAG,CAAC,KAAY,EAAyB,EAAE,CAC/D,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IAEhF,sGAAsG;IACtG,MAAM,CAAU,MAAM,GAAG,CAAC,IAAY,EAAuD,EAAE,CAC7F,KAAK,CAAC,MAAM,CACV,QAAQ,EACR,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CACvC,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAC3G,CACF,CAAA;CACJ"}
|