architecture-linter 0.1.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 ADDED
@@ -0,0 +1,686 @@
1
+ # architecture-linter
2
+
3
+ > Enforce architectural layer rules in TypeScript projects from the command line.
4
+
5
+ `architecture-linter` reads a `.context.yml` configuration file and scans your
6
+ TypeScript source tree for dependency violations — such as a controller importing
7
+ a repository directly, bypassing the service layer.
8
+
9
+ ---
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ npm install --save-dev architecture-linter
15
+ npx architecture-linter init # generate .context.yml from your folder structure
16
+ npx architecture-linter scan # check for violations
17
+ ```
18
+
19
+ Expected output when a violation exists:
20
+
21
+ ```
22
+ Scanning project...
23
+
24
+ ❌ Architecture violation detected
25
+
26
+ File: controllers/orderController.ts
27
+ Import: repositories/orderRepository
28
+ Rule: Controller cannot import Repository
29
+
30
+ ── Violations by layer ──────────────────
31
+ controller 1 violation(s)
32
+ service 0 violation(s)
33
+ repository 0 violation(s)
34
+
35
+ Found 1 violation in 3 file(s) scanned.
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ ### As a local dev dependency (recommended)
43
+
44
+ ```bash
45
+ npm install --save-dev architecture-linter
46
+ ```
47
+
48
+ Add a script to your `package.json`:
49
+
50
+ ```json
51
+ {
52
+ "scripts": {
53
+ "lint:arch": "architecture-linter scan"
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### Global install
59
+
60
+ ```bash
61
+ npm install -g architecture-linter
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Quick setup for a new project
67
+
68
+ Run `init` to auto-generate a `.context.yml` by inspecting your folder structure.
69
+ The command detects common layer names (`controller`, `service`, `repository`,
70
+ `middleware`, etc.) from top-level and `src/` subdirectory names.
71
+
72
+ ```bash
73
+ architecture-linter init
74
+ ```
75
+
76
+ Then edit the generated file to add your constraints, and run:
77
+
78
+ ```bash
79
+ architecture-linter scan
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Framework presets
85
+
86
+ Use a built-in preset to get a sensible starting configuration for popular
87
+ architectural patterns. Declare it with the `extends` key in `.context.yml`:
88
+
89
+ ```yaml
90
+ extends: nestjs
91
+ ```
92
+
93
+ User-defined layers and rules always take precedence over preset defaults.
94
+
95
+ | Preset | Layers |
96
+ |---|---|
97
+ | `nestjs` | module, controller, service, repository, guard, interceptor, pipe, decorator, dto, entity |
98
+ | `clean-architecture` | entity, usecase, repository, infrastructure, interface |
99
+ | `hexagonal` | domain, port, adapter, application, infrastructure |
100
+ | `nextjs` | page, component, hook, lib, api, store, util |
101
+
102
+ ### Extending multiple presets
103
+
104
+ ```yaml
105
+ extends:
106
+ - clean-architecture
107
+ - nestjs
108
+ ```
109
+
110
+ ### Overriding a preset rule
111
+
112
+ ```yaml
113
+ extends: nestjs
114
+
115
+ rules:
116
+ # Override the nestjs default — allow controllers to import repositories directly
117
+ controller:
118
+ cannot_import: []
119
+ ```
120
+
121
+ ---
122
+
123
+ ## Configuration reference
124
+
125
+ Create a `.context.yml` in your project root (or pass `--context` to override).
126
+ When `--context` is omitted, the linter walks up the directory tree until a
127
+ `.context.yml` is found — just like ESLint.
128
+
129
+ ```yaml
130
+ # Optional: extend a built-in preset
131
+ extends: nestjs
132
+
133
+ architecture:
134
+ layers:
135
+ - controller
136
+ - service
137
+ - repository
138
+
139
+ rules:
140
+ # Blacklist: this layer must NOT import from any layer in the list.
141
+ controller:
142
+ cannot_import:
143
+ - repository
144
+
145
+ # Whitelist: this layer may ONLY import from layers in the list.
146
+ service:
147
+ can_only_import:
148
+ - repository
149
+
150
+ repository:
151
+ cannot_import: []
152
+
153
+ # Glob patterns (project-relative) for files to skip entirely.
154
+ exclude:
155
+ - "**/*.spec.ts"
156
+ - "**/*.test.ts"
157
+ - "**/__mocks__/**"
158
+
159
+ # Manual path alias overrides (supplements tsconfig.json paths automatically).
160
+ aliases:
161
+ "@repositories": "src/repositories"
162
+ "@services": "src/services"
163
+ ```
164
+
165
+ ### Rule options
166
+
167
+ | Option | Type | Description |
168
+ |---|---|---|
169
+ | `cannot_import` | `string[]` | Blacklist — the layer must not import from any listed layer |
170
+ | `can_only_import` | `string[]` | Whitelist — the layer may only import from listed layers |
171
+ | `files` | `string` (glob) | Scope this rule to source files matching the pattern |
172
+
173
+ `cannot_import` and `can_only_import` are mutually exclusive. Use one per layer rule.
174
+
175
+ #### Scoping a rule to specific files
176
+
177
+ ```yaml
178
+ rules:
179
+ controller:
180
+ files: "src/controllers/admin/**"
181
+ cannot_import:
182
+ - repository
183
+ ```
184
+
185
+ ### Path alias resolution
186
+
187
+ The linter automatically reads `compilerOptions.paths` from your `tsconfig.json`
188
+ and resolves aliased imports before checking rules. No extra config needed for
189
+ standard TypeScript path aliases.
190
+
191
+ For monorepos or non-standard setups, add manual overrides via the `aliases` key:
192
+
193
+ ```yaml
194
+ aliases:
195
+ "@repositories": "src/repositories"
196
+ ```
197
+
198
+ Manual aliases take precedence over any `tsconfig.json` entries with the same key.
199
+
200
+ ### Inline suppression with `arch-ignore`
201
+
202
+ To suppress a single violation without removing the import, add an
203
+ `// arch-ignore:` comment on the line immediately before the import:
204
+
205
+ ```ts
206
+ // arch-ignore: controller cannot import repository
207
+ import { OrderRepository } from '../repositories/orderRepository';
208
+ ```
209
+
210
+ The hint must match the rule string (case-insensitive): `<sourceLayer> cannot import <targetLayer>`.
211
+
212
+ ---
213
+
214
+ ### How layer detection works
215
+
216
+ The linter infers a file's layer from its **directory name**. Both singular and
217
+ plural forms are recognised (including irregular plurals such as `repository` → `repositories`).
218
+
219
+ | Path | Detected layer |
220
+ |---|---|
221
+ | `controllers/orderController.ts` | `controller` |
222
+ | `services/orderService.ts` | `service` |
223
+ | `repositories/orderRepository.ts` | `repository` |
224
+ | `src/controllers/admin/ctrl.ts` | `controller` |
225
+
226
+ ---
227
+
228
+ ## CLI reference
229
+
230
+ ### `scan`
231
+
232
+ ```
233
+ architecture-linter scan [options]
234
+
235
+ Options:
236
+ -c, --context <path> Path to the .context.yml file (auto-detected if omitted)
237
+ -p, --project <path> Root directory of the project to scan (default: .)
238
+ -f, --format <format> Output format: text or json (default: text)
239
+ -s, --strict Report files not assigned to any layer
240
+ -q, --quiet Suppress the "Scanning project..." banner
241
+ -e, --explain Print why/impact/how-to-fix guidance per violation
242
+ -x, --fix Show a suggested fix for each violation
243
+ -w, --watch Watch for file changes and re-scan automatically
244
+ -h, --help Display help
245
+ ```
246
+
247
+ #### `--explain` — understand each violation
248
+
249
+ ```bash
250
+ architecture-linter scan --explain
251
+ ```
252
+
253
+ Adds three sections below each violation:
254
+ - **Why this matters** — the architectural reason this rule exists
255
+ - **Impact** — what goes wrong if the violation is left in place
256
+ - **How to fix** — a concrete recommendation
257
+
258
+ #### `--fix` — get a suggested fix
259
+
260
+ ```bash
261
+ architecture-linter scan --fix
262
+ ```
263
+
264
+ Prints a short actionable message per violation, e.g.:
265
+
266
+ ```
267
+ 🔧 Suggested fix
268
+ Instead of importing 'repository' directly, route through an allowed
269
+ intermediary layer: 'service'.
270
+ ```
271
+
272
+ #### `--watch` — re-scan on file changes
273
+
274
+ ```bash
275
+ architecture-linter scan --watch
276
+ ```
277
+
278
+ Watches the project directory for `.ts` file changes and re-runs the scan
279
+ automatically. Press `Ctrl+C` to stop.
280
+
281
+ ### `init`
282
+
283
+ ```
284
+ architecture-linter init [options]
285
+
286
+ Options:
287
+ -p, --project <path> Root directory of the project (default: .)
288
+ ```
289
+
290
+ Generates a starter `.context.yml` by detecting layer names from directory
291
+ structure. Fails safely if a `.context.yml` already exists.
292
+
293
+ ### `ci`
294
+
295
+ ```
296
+ architecture-linter ci [options]
297
+
298
+ Options:
299
+ --platform <platform> CI platform to target: github (default: github)
300
+ -p, --project <path> Root directory of the project (default: .)
301
+ ```
302
+
303
+ Generates a ready-to-use CI workflow file. Currently supports GitHub Actions:
304
+
305
+ ```bash
306
+ architecture-linter ci
307
+ # Creates: .github/workflows/arch-lint.yml
308
+ ```
309
+
310
+ Fails safely if the workflow file already exists.
311
+
312
+ ### Exit codes
313
+
314
+ | Code | Meaning |
315
+ |---|---|
316
+ | `0` | No violations found |
317
+ | `1` | One or more violations found (or a fatal error occurred) |
318
+
319
+ ---
320
+
321
+ ## CI integration
322
+
323
+ Run the linter on every push and pull request. The `ci` command generates this
324
+ for you (`architecture-linter ci`), or add the step manually:
325
+
326
+ ```yaml
327
+ # .github/workflows/arch-lint.yml
328
+ name: Architecture Lint
329
+
330
+ on:
331
+ push:
332
+ branches: ["**"]
333
+ pull_request:
334
+ branches: ["**"]
335
+
336
+ jobs:
337
+ arch-lint:
338
+ runs-on: ubuntu-latest
339
+ steps:
340
+ - uses: actions/checkout@v4
341
+ - uses: actions/setup-node@v4
342
+ with:
343
+ node-version: "20"
344
+ cache: "npm"
345
+ - run: npm ci
346
+ - run: npx architecture-linter scan --strict
347
+ ```
348
+
349
+ ---
350
+
351
+ ## JSON output
352
+
353
+ ```bash
354
+ architecture-linter scan --format json
355
+ architecture-linter scan --format json --fix --explain
356
+ ```
357
+
358
+ ```json
359
+ {
360
+ "filesScanned": 3,
361
+ "violations": [
362
+ {
363
+ "file": "controllers/orderController.ts",
364
+ "importPath": "repositories/orderRepository",
365
+ "rawSpecifier": "../repositories/orderRepository",
366
+ "sourceLayer": "controller",
367
+ "targetLayer": "repository",
368
+ "rule": "Controller cannot import Repository",
369
+ "fix": "Instead of importing 'repository' directly, route through an allowed intermediary layer: 'service'.",
370
+ "explanation": {
371
+ "why": "...",
372
+ "impact": "...",
373
+ "fix": "..."
374
+ }
375
+ }
376
+ ],
377
+ "unclassifiedFiles": [],
378
+ "violationsByLayer": {
379
+ "controller": 1,
380
+ "service": 0,
381
+ "repository": 0
382
+ }
383
+ }
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Development
389
+
390
+ ```bash
391
+ # Install dependencies
392
+ npm install
393
+
394
+ # Run directly with ts-node (no build required)
395
+ npx ts-node src/cli.ts scan --context examples/sample.context.yml --project examples/sample-project
396
+
397
+ # Build to dist/
398
+ npm run build
399
+
400
+ # Run the full test suite
401
+ npm test
402
+
403
+ # Run tests in watch mode
404
+ npm run test:watch
405
+
406
+ # Run tests with coverage report
407
+ npm run test:coverage
408
+
409
+ # Clean build artefacts
410
+ npm run clean
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Project structure
416
+
417
+ ```
418
+ architecture-linter/
419
+ ├── src/
420
+ │ ├── cli.ts # Commander-based CLI entry point
421
+ │ ├── contextParser.ts # Loads and validates .context.yml; walks up directory tree
422
+ │ ├── dependencyScanner.ts # Walks .ts files and extracts imports via ts-morph
423
+ │ ├── ruleEngine.ts # Matches imports against rules; builds violations
424
+ │ ├── aliasResolver.ts # Resolves tsconfig.json path aliases
425
+ │ ├── presets.ts # Built-in framework presets
426
+ │ ├── explainer.ts # Why/impact/fix guidance for --explain
427
+ │ └── types.ts # Shared TypeScript interfaces
428
+
429
+ ├── src/__tests__/ # Jest test suite (105 tests)
430
+
431
+ ├── examples/
432
+ │ ├── sample.context.yml # Example rule configuration
433
+ │ ├── sample-project/ # ❌ intentional violation for demo
434
+ │ ├── alias-test/ # Demo of path alias resolution
435
+ │ └── preset-test/ # Demo of framework presets
436
+
437
+ ├── .github/workflows/ci.yml # Runs tests on every push/PR
438
+ ├── jest.config.js
439
+ ├── package.json
440
+ ├── tsconfig.json
441
+ └── README.md
442
+ ```
443
+
444
+ ---
445
+
446
+ ## How it works
447
+
448
+ 1. **Parse config** — `contextParser` loads `.context.yml`, merges any preset
449
+ declared via `extends`, validates required fields, and walks up the directory
450
+ tree when no explicit path is provided.
451
+ 2. **Scan files** — `dependencyScanner` uses `fast-glob` to find every `.ts`
452
+ file (respecting `exclude` patterns) and `ts-morph` to parse import
453
+ declarations. Path aliases are resolved via `aliasResolver` before rules are
454
+ applied. Each import is checked for a preceding `// arch-ignore:` comment.
455
+ 3. **Check rules** — `ruleEngine` evaluates `cannot_import` / `can_only_import`
456
+ rules against each import. Violations are collected with optional fix
457
+ suggestions and layer-level counts.
458
+ 4. **Report** — results are printed as human-readable text (with colour) or
459
+ machine-readable JSON. The process exits `0` for clean, `1` for violations.
460
+ 3. **Apply rules** — `ruleEngine` maps each file and resolved import to an
461
+ architectural layer, then evaluates `cannot_import` (blacklist) and
462
+ `can_only_import` (whitelist) rules. Per-rule `files` glob scoping is
463
+ applied via `minimatch`.
464
+ 4. **Report** — The CLI prints every violation with the file, import path, and
465
+ rule broken. A per-layer summary is shown at the end. `--format json` emits
466
+ machine-readable output.
467
+
468
+ ---
469
+
470
+ ## Roadmap (post-MVP)
471
+
472
+ - `--fix` flag to suggest corrected import paths
473
+ - SARIF output format for GitHub Advanced Security integration
474
+ - Watch mode (`--watch`)
475
+ - Support for TypeScript path alias resolution (`@app/repositories`)
476
+
477
+ ---
478
+
479
+ ## License
480
+
481
+ MIT
482
+
483
+
484
+ `architecture-linter` reads a `.context.yml` configuration file and scans your
485
+ TypeScript source tree for dependency violations — such as a controller importing
486
+ a repository directly, bypassing the service layer.
487
+
488
+ ---
489
+
490
+ ## Quick start
491
+
492
+ ```bash
493
+ # 1. Install dependencies
494
+ npm install
495
+
496
+ # 2. Build
497
+ npm run build
498
+
499
+ # 3. Scan the bundled example project
500
+ node dist/cli.js scan \
501
+ --context examples/sample.context.yml \
502
+ --project examples/sample-project
503
+ ```
504
+
505
+ Expected output:
506
+
507
+ ```
508
+ Scanning project...
509
+
510
+ ❌ Architecture violation detected
511
+
512
+ File: controllers/orderController.ts
513
+ Import: repositories/orderRepository
514
+ Rule: Controller cannot import Repository
515
+
516
+ Found 1 violation in 3 file(s) scanned.
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Installation
522
+
523
+ ### As a local dev dependency
524
+
525
+ ```bash
526
+ npm install --save-dev architecture-linter
527
+ ```
528
+
529
+ Then add a script to your `package.json`:
530
+
531
+ ```json
532
+ {
533
+ "scripts": {
534
+ "lint:arch": "architecture-linter scan"
535
+ }
536
+ }
537
+ ```
538
+
539
+ ### Global install
540
+
541
+ ```bash
542
+ npm install -g architecture-linter
543
+ ```
544
+
545
+ ---
546
+
547
+ ## Configuration
548
+
549
+ Create a `.context.yml` file in your project root (or pass `--context` to point
550
+ to a different path).
551
+
552
+ ```yaml
553
+ architecture:
554
+ layers:
555
+ - controller
556
+ - service
557
+ - repository
558
+
559
+ rules:
560
+ controller:
561
+ cannot_import:
562
+ - repository # Controllers must go through the service layer
563
+
564
+ service:
565
+ cannot_import: []
566
+
567
+ repository:
568
+ cannot_import: []
569
+ ```
570
+
571
+ ### How layer detection works
572
+
573
+ The linter infers a file's layer from its **directory name**. A file inside a
574
+ directory called `controllers/` or `controller/` is automatically assigned to
575
+ the `controller` layer. Both singular and plural forms are recognised.
576
+
577
+ | Path | Detected layer |
578
+ |---|---|
579
+ | `controllers/orderController.ts` | `controller` |
580
+ | `services/orderService.ts` | `service` |
581
+ | `repositories/orderRepository.ts` | `repository` |
582
+
583
+ The same logic applies to import paths: a relative import that resolves into a
584
+ `repositories/` directory is treated as a `repository`-layer import.
585
+
586
+ ---
587
+
588
+ ## CLI reference
589
+
590
+ ```
591
+ architecture-linter scan [options]
592
+
593
+ Options:
594
+ -c, --context <path> Path to the .context.yml file (default: .context.yml)
595
+ -p, --project <path> Root directory of the project (default: .)
596
+ -V, --version Print version number
597
+ -h, --help Display help
598
+ ```
599
+
600
+ ### Exit codes
601
+
602
+ | Code | Meaning |
603
+ |---|---|
604
+ | `0` | No violations found |
605
+ | `1` | One or more violations found (or a fatal error occurred) |
606
+
607
+ This makes the tool suitable for use in CI pipelines:
608
+
609
+ ```yaml
610
+ # .github/workflows/ci.yml (example)
611
+ - name: Architecture lint
612
+ run: npx architecture-linter scan
613
+ ```
614
+
615
+ ---
616
+
617
+ ## Development
618
+
619
+ ```bash
620
+ # Run directly with ts-node (no build step required)
621
+ npx ts-node src/cli.ts scan --context examples/sample.context.yml --project examples/sample-project
622
+
623
+ # Build to dist/
624
+ npm run build
625
+
626
+ # Run the compiled output
627
+ node dist/cli.js scan --context examples/sample.context.yml --project examples/sample-project
628
+
629
+ # Clean build artefacts
630
+ npm run clean
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Project structure
636
+
637
+ ```
638
+ architecture-linter/
639
+ ├── src/
640
+ │ ├── cli.ts # Commander-based CLI entry point
641
+ │ ├── contextParser.ts # Loads and validates .context.yml
642
+ │ ├── dependencyScanner.ts # Walks .ts files and extracts imports via ts-morph
643
+ │ ├── ruleEngine.ts # Matches imports against rules and returns violations
644
+ │ └── types.ts # Shared TypeScript interfaces
645
+
646
+ ├── examples/
647
+ │ ├── sample.context.yml # Example rule configuration
648
+ │ └── sample-project/
649
+ │ ├── controllers/orderController.ts # ❌ contains an intentional violation
650
+ │ ├── services/orderService.ts
651
+ │ └── repositories/orderRepository.ts
652
+
653
+ ├── package.json
654
+ ├── tsconfig.json
655
+ └── README.md
656
+ ```
657
+
658
+ ---
659
+
660
+ ## How it works
661
+
662
+ 1. **Parse config** — `contextParser` loads `.context.yml` using `js-yaml` and
663
+ validates the required fields.
664
+ 2. **Scan files** — `dependencyScanner` uses `fast-glob` to find every `.ts`
665
+ file in the project and `ts-morph` to parse its import declarations.
666
+ Relative imports are resolved to project-relative paths.
667
+ 3. **Apply rules** — `ruleEngine` maps each file and each resolved import path
668
+ to an architectural layer, then checks the `cannot_import` rules.
669
+ 4. **Report** — The CLI prints every violation with the offending file, the
670
+ resolved import path, and the rule that was broken.
671
+
672
+ ---
673
+
674
+ ## Roadmap (post-MVP)
675
+
676
+ - `--fix` flag to suggest corrected import paths
677
+ - JSON / SARIF output format for CI integration
678
+ - Wildcard layer patterns (`src/*/controllers/**`)
679
+ - Support for path alias resolution (`@app/repositories`)
680
+ - Watch mode
681
+
682
+ ---
683
+
684
+ ## License
685
+
686
+ MIT
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A resolved alias map where each key is an alias prefix (e.g. '@repositories')
3
+ * and each value is the absolute path to the corresponding directory on disk.
4
+ */
5
+ export interface AliasMap {
6
+ [prefix: string]: string;
7
+ }
8
+ /**
9
+ * Reads tsconfig.json from the project root and extracts compilerOptions.paths
10
+ * entries, resolving them relative to compilerOptions.baseUrl (or the project root).
11
+ *
12
+ * Handles tsconfig files that contain // line comments (which TypeScript allows).
13
+ *
14
+ * Returns an empty map if tsconfig.json is absent or cannot be parsed.
15
+ */
16
+ export declare function loadTsConfigAliases(projectDir: string): AliasMap;
17
+ /**
18
+ * Merges tsconfig-derived aliases with any manual aliases declared in .context.yml.
19
+ * Manual aliases take precedence over tsconfig aliases.
20
+ *
21
+ * @param tsconfigAliases Aliases loaded from tsconfig.json
22
+ * @param configAliases Aliases from the `aliases:` field in .context.yml (project-relative dirs)
23
+ * @param projectDir Absolute path to the project root (used to resolve configAliases)
24
+ */
25
+ export declare function mergeAliases(tsconfigAliases: AliasMap, configAliases: Record<string, string> | undefined, projectDir: string): AliasMap;
26
+ /**
27
+ * Attempts to resolve a bare (non-relative) import specifier using the alias map.
28
+ *
29
+ * Returns a project-relative forward-slash path on success, or null if the specifier
30
+ * does not match any known alias (e.g. it's a node_modules package).
31
+ *
32
+ * @param specifier The raw import string (e.g. '@repositories/orderRepo')
33
+ * @param aliases The merged alias map
34
+ * @param projectDir Absolute path to the project root
35
+ */
36
+ export declare function resolveAlias(specifier: string, aliases: AliasMap, projectDir: string): string | null;
37
+ //# sourceMappingURL=aliasResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aliasResolver.d.ts","sourceRoot":"","sources":["../src/aliasResolver.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CA0ChE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,eAAe,EAAE,QAAQ,EACzB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACjD,UAAU,EAAE,MAAM,GACjB,QAAQ,CAUV;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,QAAQ,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CASf"}