@flagshark/core 1.2.0 → 1.3.1
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 +100 -18
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +6 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/excluder.d.ts +18 -0
- package/dist/config/excluder.d.ts.map +1 -0
- package/dist/config/excluder.js +51 -0
- package/dist/config/excluder.js.map +1 -0
- package/dist/config/ignore-file.d.ts +6 -0
- package/dist/config/ignore-file.d.ts.map +1 -0
- package/dist/config/ignore-file.js +23 -0
- package/dist/config/ignore-file.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +7 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +47 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/presets.d.ts +11 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +70 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/schema.d.ts +322 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +63 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/detection/detectors/go.d.ts +9 -3
- package/dist/detection/detectors/go.d.ts.map +1 -1
- package/dist/detection/detectors/go.js +8 -2
- package/dist/detection/detectors/go.js.map +1 -1
- package/dist/detection/detectors/javascript.d.ts +9 -3
- package/dist/detection/detectors/javascript.d.ts.map +1 -1
- package/dist/detection/detectors/javascript.js +8 -2
- package/dist/detection/detectors/javascript.js.map +1 -1
- package/dist/detection/detectors/python.d.ts +9 -3
- package/dist/detection/detectors/python.d.ts.map +1 -1
- package/dist/detection/detectors/python.js +8 -2
- package/dist/detection/detectors/python.js.map +1 -1
- package/dist/detection/detectors/typescript.d.ts +9 -3
- package/dist/detection/detectors/typescript.d.ts.map +1 -1
- package/dist/detection/detectors/typescript.js +8 -2
- package/dist/detection/detectors/typescript.js.map +1 -1
- package/dist/detection/helpers.d.ts +1 -1
- package/dist/detection/helpers.d.ts.map +1 -1
- package/dist/detection/helpers.js +9 -1
- package/dist/detection/helpers.js.map +1 -1
- package/dist/detection/index.d.ts +10 -0
- package/dist/detection/index.d.ts.map +1 -1
- package/dist/detection/index.js +31 -7
- package/dist/detection/index.js.map +1 -1
- package/dist/detection/interface.d.ts +3 -1
- package/dist/detection/interface.d.ts.map +1 -1
- package/dist/detection/registry.d.ts +1 -1
- package/dist/detection/registry.d.ts.map +1 -1
- package/dist/detection/tree-sitter/const-resolver.d.ts +8 -0
- package/dist/detection/tree-sitter/const-resolver.d.ts.map +1 -0
- package/dist/detection/tree-sitter/const-resolver.js +36 -0
- package/dist/detection/tree-sitter/const-resolver.js.map +1 -0
- package/dist/detection/tree-sitter/engine.d.ts +11 -0
- package/dist/detection/tree-sitter/engine.d.ts.map +1 -0
- package/dist/detection/tree-sitter/engine.js +70 -0
- package/dist/detection/tree-sitter/engine.js.map +1 -0
- package/dist/detection/tree-sitter/parser-cache.d.ts +6 -0
- package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -0
- package/dist/detection/tree-sitter/parser-cache.js +68 -0
- package/dist/detection/tree-sitter/parser-cache.js.map +1 -0
- package/dist/detection/tree-sitter/queries/go.scm +11 -0
- package/dist/detection/tree-sitter/queries/javascript.scm +13 -0
- package/dist/detection/tree-sitter/queries/python.scm +11 -0
- package/dist/detection/tree-sitter/queries/typescript.scm +11 -0
- package/dist/detection/tree-sitter/query-runner.d.ts +20 -0
- package/dist/detection/tree-sitter/query-runner.d.ts.map +1 -0
- package/dist/detection/tree-sitter/query-runner.js +83 -0
- package/dist/detection/tree-sitter/query-runner.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/scan-repo.d.ts +27 -0
- package/dist/scan-repo.d.ts.map +1 -1
- package/dist/scan-repo.js +26 -5
- package/dist/scan-repo.js.map +1 -1
- package/dist/scanner.d.ts +11 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +22 -7
- package/dist/scanner.js.map +1 -1
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @flagshark/core
|
|
2
2
|
|
|
3
|
-
The detection engine behind [FlagShark](https://github.com/FlagShark/flagshark) — finds feature flag references across 13 languages and
|
|
3
|
+
The detection engine behind [FlagShark](https://github.com/FlagShark/flagshark) — finds feature flag references across 13 languages and 13 providers, and identifies stale flags via git blame.
|
|
4
4
|
|
|
5
|
-
This is the **library**.
|
|
5
|
+
This is the **library**. For a CLI or GitHub Action, install [`flagshark`](https://www.npmjs.com/package/flagshark) instead.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -25,18 +25,26 @@ console.log(`Health score: ${result.healthScore}/100`)
|
|
|
25
25
|
console.log(`Languages: ${Object.keys(result.languageBreakdown).join(', ')}`)
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## Detection
|
|
29
|
+
|
|
30
|
+
For tier-1 languages (TypeScript, JavaScript, Go, Python), detection uses [tree-sitter](https://tree-sitter.github.io/) (via WASM) to parse files into a real AST. That eliminates the false-positive class where regex matched flag-shaped strings inside comments, string literals, or unrelated call expressions. It also handles multi-line calls and resolves const-bound flag keys (`const FLAG = 'X'; client.variation(FLAG, …)`) in TS/JS.
|
|
31
|
+
|
|
32
|
+
The remaining 9 languages currently use regex-based detection; each migrates to tree-sitter in future releases (no API break).
|
|
33
|
+
|
|
34
|
+
The engine is a runtime concern, not a public API surface. Consumers calling `scanRepo()` or `createDefaultRegistry()` get the right engine per-language automatically. Override per-detector if needed (see below).
|
|
35
|
+
|
|
28
36
|
## API
|
|
29
37
|
|
|
30
38
|
### `scanRepo(options) → Promise<ScanRepoResult>`
|
|
31
39
|
|
|
32
|
-
Top-level orchestrator. Walks a checked-out repository, detects flags,
|
|
40
|
+
Top-level orchestrator. Walks a checked-out repository, detects flags, runs staleness analysis, and returns a summary.
|
|
33
41
|
|
|
34
42
|
```ts
|
|
35
43
|
interface ScanRepoOptions {
|
|
36
44
|
/** Absolute path to the repository being scanned. */
|
|
37
45
|
cwd: string
|
|
38
46
|
|
|
39
|
-
/** Staleness threshold in months.
|
|
47
|
+
/** Staleness threshold in months. Overridden by `config.threshold` if config is supplied. */
|
|
40
48
|
threshold?: number
|
|
41
49
|
|
|
42
50
|
/** If set, only scan files changed since this git ref (e.g. `origin/main`). */
|
|
@@ -48,20 +56,85 @@ interface ScanRepoOptions {
|
|
|
48
56
|
|
|
49
57
|
/** Optional `{ debug, info, warn, error }` logger. Defaults to no-op. */
|
|
50
58
|
logger?: ScanLogger
|
|
59
|
+
|
|
60
|
+
/** Explicit FlagsharkConfig. If undefined, scanRepo auto-discovers
|
|
61
|
+
* `.flagshark.yml` walking upward from `cwd`. */
|
|
62
|
+
config?: FlagsharkConfig
|
|
63
|
+
|
|
64
|
+
/** Skip `.flagshark.yml` discovery entirely. */
|
|
65
|
+
noConfig?: boolean
|
|
66
|
+
|
|
67
|
+
/** Skip `.flagsharkignore` discovery entirely. */
|
|
68
|
+
noIgnoreFile?: boolean
|
|
69
|
+
|
|
70
|
+
/** Populate `result.excludedPaths` with the full list of skipped file paths.
|
|
71
|
+
* Off by default — counts only. */
|
|
72
|
+
collectExcludedPaths?: boolean
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
interface ScanRepoResult {
|
|
54
76
|
totalFlags: number
|
|
55
|
-
filesScanned: number
|
|
56
77
|
staleFlags: StaleFlag[]
|
|
57
78
|
detectedProviders: string[]
|
|
58
79
|
languageBreakdown: Record<string, number>
|
|
59
|
-
healthScore: number
|
|
60
|
-
scanDuration: number
|
|
80
|
+
healthScore: number // 0–100
|
|
81
|
+
scanDuration: number // ms
|
|
82
|
+
excludedCount?: number // files skipped by config + .flagsharkignore
|
|
83
|
+
excludedPaths?: string[] // set when collectExcludedPaths: true
|
|
84
|
+
effectiveExcludes?: EffectiveRules // for debug/verbose output
|
|
61
85
|
}
|
|
62
86
|
```
|
|
63
87
|
|
|
64
|
-
`scanRepo` walks the local filesystem. If you can't run a git checkout (e.g., serverless function reading from an API), use the lower-level primitives below.
|
|
88
|
+
`scanRepo` walks the local filesystem. If you can't run a git checkout (e.g., a serverless function reading from an API), use the lower-level primitives below.
|
|
89
|
+
|
|
90
|
+
### Config module
|
|
91
|
+
|
|
92
|
+
The same config used by the CLI is exported as a building block:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import {
|
|
96
|
+
buildDefaultConfig,
|
|
97
|
+
loadConfigFile,
|
|
98
|
+
loadIgnoreFile,
|
|
99
|
+
buildExcluder,
|
|
100
|
+
expandPresets,
|
|
101
|
+
PRESETS,
|
|
102
|
+
FlagsharkConfigSchema,
|
|
103
|
+
type FlagsharkConfig,
|
|
104
|
+
} from '@flagshark/core'
|
|
105
|
+
|
|
106
|
+
// Load a .flagshark.yml from disk (validated via zod)
|
|
107
|
+
const loaded = await loadConfigFile('/path/to/repo')
|
|
108
|
+
const config = loaded?.config ?? buildDefaultConfig()
|
|
109
|
+
|
|
110
|
+
// Load a .flagsharkignore (separate file at repo root)
|
|
111
|
+
const ignoreFile = await loadIgnoreFile('/path/to/repo')
|
|
112
|
+
|
|
113
|
+
// Build a unified excluder from all sources
|
|
114
|
+
const excluder = buildExcluder({
|
|
115
|
+
config,
|
|
116
|
+
ignoreFilePatterns: ignoreFile?.patterns ?? [],
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Use it directly:
|
|
120
|
+
excluder.shouldExclude('examples/demo.ts') // true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The schema accepts:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
threshold: 6
|
|
127
|
+
excludes:
|
|
128
|
+
paths: [] # gitignore-style globs
|
|
129
|
+
files: [] # same — split is purely organizational
|
|
130
|
+
presets: [] # ['test-files', 'snapshots', 'examples', 'stories', 'fixtures', 'generated']
|
|
131
|
+
suppress:
|
|
132
|
+
flags: [] # flag-name globs (post-detection filter)
|
|
133
|
+
paths:
|
|
134
|
+
- match: 'src/critical/**'
|
|
135
|
+
threshold: 3
|
|
136
|
+
providers: [] # custom provider definitions (see Custom Detectors)
|
|
137
|
+
```
|
|
65
138
|
|
|
66
139
|
### Lower-level primitives
|
|
67
140
|
|
|
@@ -71,7 +144,7 @@ For consumers that fetch file contents elsewhere (GitHub API, S3, in-memory, etc
|
|
|
71
144
|
import { createDefaultRegistry, PolyglotAnalyzer } from '@flagshark/core'
|
|
72
145
|
|
|
73
146
|
const registry = createDefaultRegistry() // 13 detectors pre-registered
|
|
74
|
-
const analyzer = new PolyglotAnalyzer(registry)
|
|
147
|
+
const analyzer = new PolyglotAnalyzer(registry, logger)
|
|
75
148
|
|
|
76
149
|
// `files` is a Map<filePath, content>
|
|
77
150
|
const result = await analyzer.analyzeFiles(files)
|
|
@@ -101,34 +174,43 @@ const registry = createDefaultRegistry()
|
|
|
101
174
|
registry.register(new MyCustomDetector())
|
|
102
175
|
```
|
|
103
176
|
|
|
104
|
-
Or import individual detectors and compose your own registry:
|
|
177
|
+
Or import individual detectors and compose your own registry. Each tier-1 detector accepts `{ engine: 'regex' | 'tree-sitter' }`:
|
|
105
178
|
|
|
106
179
|
```ts
|
|
107
|
-
import {
|
|
180
|
+
import {
|
|
181
|
+
LanguageRegistry,
|
|
182
|
+
TypeScriptDetector,
|
|
183
|
+
GoDetector,
|
|
184
|
+
type DetectorEngine,
|
|
185
|
+
} from '@flagshark/core'
|
|
108
186
|
|
|
109
187
|
const registry = new LanguageRegistry()
|
|
110
|
-
registry.register(new
|
|
111
|
-
registry.register(new
|
|
188
|
+
registry.register(new TypeScriptDetector({ engine: 'tree-sitter' }))
|
|
189
|
+
registry.register(new GoDetector({ engine: 'regex' })) // opt back to regex
|
|
112
190
|
```
|
|
113
191
|
|
|
114
192
|
## Supported languages
|
|
115
193
|
|
|
116
|
-
TypeScript
|
|
194
|
+
TypeScript, JavaScript, Go, Python, Java, Kotlin, Swift, Ruby, C#, PHP, Rust, C/C++, Objective-C.
|
|
117
195
|
|
|
118
196
|
## Supported providers
|
|
119
197
|
|
|
120
|
-
Auto-detected from imports — no configuration needed:
|
|
198
|
+
Auto-detected from imports — no configuration needed:
|
|
199
|
+
|
|
200
|
+
LaunchDarkly · Unleash · Flipt · Split.io · PostHog · Flagsmith · ConfigCat · Statsig · GrowthBook · DevCycle · Eppo · Optimizely · plus generic custom-flag patterns.
|
|
121
201
|
|
|
122
202
|
## How detection works
|
|
123
203
|
|
|
124
|
-
|
|
204
|
+
FlagShark only scans files that actually import a flag SDK. A function called `isEnabled()` in a file that doesn't import LaunchDarkly/Unleash/etc. won't be flagged — this prevents false positives from generic identifier names.
|
|
205
|
+
|
|
206
|
+
Once a file qualifies, the engine (tree-sitter for tier-1, regex for the rest) walks call expressions and extracts the flag-key argument at the configured position for each provider's method signatures. Provider attribution and source location come along automatically.
|
|
125
207
|
|
|
126
208
|
## How staleness works
|
|
127
209
|
|
|
128
210
|
A flag is marked stale if **any** signal fires:
|
|
129
211
|
|
|
130
|
-
1.
|
|
131
|
-
2.
|
|
212
|
+
1. **`age`** — `git blame` shows the flag's line was last modified more than `threshold` months ago.
|
|
213
|
+
2. **`low-usage`** — The flag name appears in only one file across the repo, suggesting a completed rollout.
|
|
132
214
|
|
|
133
215
|
## License
|
|
134
216
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzE,wBAAgB,kBAAkB,IAAI,eAAe,CAGpD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,aAAa,CAAA;AAEzE,MAAM,UAAU,kBAAkB;IAChC,wDAAwD;IACxD,OAAO,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FlagsharkConfig } from './schema.js';
|
|
2
|
+
export interface EffectiveRules {
|
|
3
|
+
paths: string[];
|
|
4
|
+
files: string[];
|
|
5
|
+
presets: string[];
|
|
6
|
+
presetPatterns: string[];
|
|
7
|
+
ignoreFile: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface Excluder {
|
|
10
|
+
shouldExclude(path: string): boolean;
|
|
11
|
+
effectiveRules: EffectiveRules;
|
|
12
|
+
}
|
|
13
|
+
export interface BuildExcluderInput {
|
|
14
|
+
config: FlagsharkConfig;
|
|
15
|
+
ignoreFilePatterns: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function buildExcluder(input: BuildExcluderInput): Excluder;
|
|
18
|
+
//# sourceMappingURL=excluder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excluder.d.ts","sourceRoot":"","sources":["../../src/config/excluder.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACpC,cAAc,EAAE,cAAc,CAAA;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAA;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAC7B;AAwBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CA4BjE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import ignoreModule from 'ignore';
|
|
2
|
+
import { expandPresets } from './presets.js';
|
|
3
|
+
const ignore = ignoreModule.default ?? ignoreModule;
|
|
4
|
+
/**
|
|
5
|
+
* Convert trailing-slash directory patterns (`examples/`) to recursive globs
|
|
6
|
+
* (`examples/**`) so that `!`-negation works downstream.
|
|
7
|
+
*
|
|
8
|
+
* The `ignore@5` package follows the strict gitignore spec: once a parent
|
|
9
|
+
* directory is excluded via a trailing-slash pattern, files inside it cannot
|
|
10
|
+
* be re-included via `!`. Converting to `examples/**` bypasses this restriction
|
|
11
|
+
* by treating the pattern as a glob rather than a directory match.
|
|
12
|
+
*
|
|
13
|
+
* Trade-off: `examples/**` is anchored to the repo root, while gitignore's
|
|
14
|
+
* `examples/` is unanchored (matches `examples/` anywhere in the tree).
|
|
15
|
+
* In practice the scanner's hardcoded SKIP_DIRS already filters node_modules,
|
|
16
|
+
* vendor, etc., so this anchoring change rarely affects real users — but it
|
|
17
|
+
* is a deliberate departure from strict gitignore semantics.
|
|
18
|
+
*/
|
|
19
|
+
function normalisePattern(p) {
|
|
20
|
+
if (!p.startsWith('!') && p.endsWith('/')) {
|
|
21
|
+
return p + '**';
|
|
22
|
+
}
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
export function buildExcluder(input) {
|
|
26
|
+
const { config, ignoreFilePatterns } = input;
|
|
27
|
+
const presetPatterns = expandPresets(config.excludes.presets);
|
|
28
|
+
const allPatterns = [
|
|
29
|
+
...config.excludes.paths,
|
|
30
|
+
...config.excludes.files,
|
|
31
|
+
...presetPatterns,
|
|
32
|
+
...ignoreFilePatterns,
|
|
33
|
+
];
|
|
34
|
+
const matcher = ignore().add(allPatterns.map(normalisePattern));
|
|
35
|
+
return {
|
|
36
|
+
shouldExclude(path) {
|
|
37
|
+
if (allPatterns.length === 0)
|
|
38
|
+
return false;
|
|
39
|
+
const normalised = path.startsWith('./') ? path.slice(2) : path;
|
|
40
|
+
return matcher.ignores(normalised);
|
|
41
|
+
},
|
|
42
|
+
effectiveRules: {
|
|
43
|
+
paths: [...config.excludes.paths],
|
|
44
|
+
files: [...config.excludes.files],
|
|
45
|
+
presets: [...config.excludes.presets],
|
|
46
|
+
presetPatterns,
|
|
47
|
+
ignoreFile: [...ignoreFilePatterns],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=excluder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excluder.js","sourceRoot":"","sources":["../../src/config/excluder.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,QAAQ,CAAA;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAI5C,MAAM,MAAM,GAAI,YAA6D,CAAC,OAAO,IAAI,YAAY,CAAA;AAoBrG;;;;;;;;;;;;;;GAcG;AACH,SAAS,gBAAgB,CAAC,CAAS;IACjC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,IAAI,CAAA;IACjB,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAA;IAE5C,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IAE7D,MAAM,WAAW,GAAa;QAC5B,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK;QACxB,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK;QACxB,GAAG,cAAc;QACjB,GAAG,kBAAkB;KACtB,CAAA;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAE/D,OAAO;QACL,aAAa,CAAC,IAAY;YACxB,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAC/D,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QACpC,CAAC;QACD,cAAc,EAAE;YACd,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YACjC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YACjC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YACrC,cAAc;YACd,UAAU,EAAE,CAAC,GAAG,kBAAkB,CAAC;SACpC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-file.d.ts","sourceRoot":"","sources":["../../src/config/ignore-file.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkBnF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
export async function loadIgnoreFile(startDir) {
|
|
5
|
+
const home = homedir();
|
|
6
|
+
let dir = resolve(startDir);
|
|
7
|
+
for (;;) {
|
|
8
|
+
const candidate = join(dir, '.flagsharkignore');
|
|
9
|
+
if (existsSync(candidate)) {
|
|
10
|
+
const raw = readFileSync(candidate, 'utf-8');
|
|
11
|
+
const patterns = raw
|
|
12
|
+
.split('\n')
|
|
13
|
+
.map((line) => line.trim())
|
|
14
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
15
|
+
return { patterns, path: candidate };
|
|
16
|
+
}
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir || dir === home || dir === '/')
|
|
19
|
+
return null;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=ignore-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-file.js","sourceRoot":"","sources":["../../src/config/ignore-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOlD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE3B,SAAS,CAAC;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;QAC/C,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;YAC5C,MAAM,QAAQ,GAAG,GAAG;iBACjB,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;QACtC,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC9D,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { buildDefaultConfig } from './defaults.js';
|
|
2
|
+
export { loadConfigFile, type LoadedConfig } from './loader.js';
|
|
3
|
+
export { loadIgnoreFile, type LoadedIgnore } from './ignore-file.js';
|
|
4
|
+
export { buildExcluder, type Excluder, type EffectiveRules, type BuildExcluderInput } from './excluder.js';
|
|
5
|
+
export { expandPresets, PRESETS } from './presets.js';
|
|
6
|
+
export { FlagsharkConfigSchema, type FlagsharkConfig, type ExcludesConfig, type SuppressConfig, type PathRuleConfig, type OutputConfig, type PresetName, } from './schema.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAC1G,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { buildDefaultConfig } from './defaults.js';
|
|
2
|
+
export { loadConfigFile } from './loader.js';
|
|
3
|
+
export { loadIgnoreFile } from './ignore-file.js';
|
|
4
|
+
export { buildExcluder } from './excluder.js';
|
|
5
|
+
export { expandPresets, PRESETS } from './presets.js';
|
|
6
|
+
export { FlagsharkConfigSchema, } from './schema.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,cAAc,EAAqB,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAqB,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,aAAa,EAA+D,MAAM,eAAe,CAAA;AAC1G,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EACL,qBAAqB,GAOtB,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAMA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAIzE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAenF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { parse as parseYaml, YAMLParseError } from 'yaml';
|
|
5
|
+
import { FlagsharkConfigSchema } from './schema.js';
|
|
6
|
+
const FILENAMES = ['.flagshark.yml', '.flagshark.yaml'];
|
|
7
|
+
export async function loadConfigFile(startDir) {
|
|
8
|
+
const home = homedir();
|
|
9
|
+
let dir = resolve(startDir);
|
|
10
|
+
for (;;) {
|
|
11
|
+
for (const name of FILENAMES) {
|
|
12
|
+
const candidate = join(dir, name);
|
|
13
|
+
if (existsSync(candidate)) {
|
|
14
|
+
return readAndValidate(candidate);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir || dir === home || dir === '/')
|
|
19
|
+
return null;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readAndValidate(path) {
|
|
24
|
+
const raw = readFileSync(path, 'utf-8');
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = parseYaml(raw);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof YAMLParseError) {
|
|
31
|
+
throw new Error(`Invalid YAML in ${path}: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
if (parsed == null || typeof parsed !== 'object') {
|
|
36
|
+
return { config: FlagsharkConfigSchema.parse({}), path };
|
|
37
|
+
}
|
|
38
|
+
const result = FlagsharkConfigSchema.safeParse(parsed);
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
const issues = result.error.issues
|
|
41
|
+
.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
42
|
+
.join('; ');
|
|
43
|
+
throw new Error(`Invalid .flagshark.yml at ${path}: ${issues}`);
|
|
44
|
+
}
|
|
45
|
+
return { config: result.data, path };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAEzD,OAAO,EAAE,qBAAqB,EAAwB,MAAM,aAAa,CAAA;AAEzE,MAAM,SAAS,GAAG,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAA;AAOvD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE3B,SAAS,CAAC;QACR,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1B,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC9D,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAEvC,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5D,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACjD,OAAO,EAAE,MAAM,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAA;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,KAAK,MAAM,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PresetName } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Built-in exclude presets. Each preset expands to a list of gitignore-style
|
|
4
|
+
* glob patterns that match common conventions for that category.
|
|
5
|
+
*
|
|
6
|
+
* Test-files patterns are derived from the private flag-shark monorepo's
|
|
7
|
+
* `packages/shared/lib/test-files.ts` `isTestFile()` helper — credit there.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PRESETS: Readonly<Record<PresetName, readonly string[]>>;
|
|
10
|
+
export declare function expandPresets(names: readonly PresetName[]): string[];
|
|
11
|
+
//# sourceMappingURL=presets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/config/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6ClE,CAAA;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,EAAE,CAcpE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in exclude presets. Each preset expands to a list of gitignore-style
|
|
3
|
+
* glob patterns that match common conventions for that category.
|
|
4
|
+
*
|
|
5
|
+
* Test-files patterns are derived from the private flag-shark monorepo's
|
|
6
|
+
* `packages/shared/lib/test-files.ts` `isTestFile()` helper — credit there.
|
|
7
|
+
*/
|
|
8
|
+
export const PRESETS = Object.freeze({
|
|
9
|
+
'test-files': Object.freeze([
|
|
10
|
+
// JavaScript / TypeScript
|
|
11
|
+
'**/*.test.ts', '**/*.test.tsx', '**/*.test.js', '**/*.test.jsx',
|
|
12
|
+
'**/*.test.mjs', '**/*.test.cjs',
|
|
13
|
+
'**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.js', '**/*.spec.jsx',
|
|
14
|
+
// Go
|
|
15
|
+
'**/*_test.go',
|
|
16
|
+
// Python
|
|
17
|
+
'**/*_test.py', '**/test_*.py',
|
|
18
|
+
// Java / Kotlin
|
|
19
|
+
'**/*Test.java', '**/*Tests.java', '**/*Test.kt', '**/*Tests.kt',
|
|
20
|
+
// Swift
|
|
21
|
+
'**/*Tests.swift',
|
|
22
|
+
// Ruby
|
|
23
|
+
'**/*_spec.rb', '**/spec/**/*.rb',
|
|
24
|
+
// C# / .NET
|
|
25
|
+
'**/*Test.cs', '**/*Tests.cs',
|
|
26
|
+
// PHP
|
|
27
|
+
'**/*Test.php',
|
|
28
|
+
// Rust
|
|
29
|
+
'**/tests/**',
|
|
30
|
+
// Directories that always indicate tests
|
|
31
|
+
'**/__tests__/**', '**/test/**',
|
|
32
|
+
]),
|
|
33
|
+
'snapshots': Object.freeze([
|
|
34
|
+
'**/*.snap',
|
|
35
|
+
'**/__snapshots__/**',
|
|
36
|
+
]),
|
|
37
|
+
'examples': Object.freeze([
|
|
38
|
+
'examples/**', 'example/**',
|
|
39
|
+
'demo/**', 'demos/**',
|
|
40
|
+
]),
|
|
41
|
+
'stories': Object.freeze([
|
|
42
|
+
'**/*.stories.ts', '**/*.stories.tsx',
|
|
43
|
+
'**/*.stories.js', '**/*.stories.jsx',
|
|
44
|
+
'**/*.story.ts', '**/*.story.tsx',
|
|
45
|
+
]),
|
|
46
|
+
'fixtures': Object.freeze([
|
|
47
|
+
'**/__fixtures__/**', '**/fixtures/**',
|
|
48
|
+
]),
|
|
49
|
+
'generated': Object.freeze([
|
|
50
|
+
'**/*.generated.ts', '**/*.generated.js',
|
|
51
|
+
'**/*.gen.go', '**/generated/**',
|
|
52
|
+
]),
|
|
53
|
+
});
|
|
54
|
+
export function expandPresets(names) {
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const patterns = PRESETS[name];
|
|
59
|
+
if (!patterns)
|
|
60
|
+
continue;
|
|
61
|
+
for (const pattern of patterns) {
|
|
62
|
+
if (!seen.has(pattern)) {
|
|
63
|
+
seen.add(pattern);
|
|
64
|
+
out.push(pattern);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=presets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/config/presets.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAoD,MAAM,CAAC,MAAM,CAAC;IACpF,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;QAC1B,0BAA0B;QAC1B,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe;QAChE,eAAe,EAAE,eAAe;QAChC,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe;QAChE,KAAK;QACL,cAAc;QACd,SAAS;QACT,cAAc,EAAE,cAAc;QAC9B,gBAAgB;QAChB,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc;QAChE,QAAQ;QACR,iBAAiB;QACjB,OAAO;QACP,cAAc,EAAE,iBAAiB;QACjC,YAAY;QACZ,aAAa,EAAE,cAAc;QAC7B,MAAM;QACN,cAAc;QACd,OAAO;QACP,aAAa;QACb,yCAAyC;QACzC,iBAAiB,EAAE,YAAY;KAChC,CAAC;IACF,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QACzB,WAAW;QACX,qBAAqB;KACtB,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,aAAa,EAAE,YAAY;QAC3B,SAAS,EAAE,UAAU;KACtB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC;QACvB,iBAAiB,EAAE,kBAAkB;QACrC,iBAAiB,EAAE,kBAAkB;QACrC,eAAe,EAAE,gBAAgB;KAClC,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,oBAAoB,EAAE,gBAAgB;KACvC,CAAC;IACF,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QACzB,mBAAmB,EAAE,mBAAmB;QACxC,aAAa,EAAE,iBAAiB;KACjC,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,UAAU,aAAa,CAAC,KAA4B;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACvB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;gBACjB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|