debtlens 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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +153 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +10 -0
- package/dist/cli/init.js +18 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/parseList.d.ts +2 -0
- package/dist/cli/parseList.js +29 -0
- package/dist/cli/parseList.js.map +1 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +40 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loadConfig.d.ts +2 -0
- package/dist/config/loadConfig.js +23 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/config/mergeConfig.d.ts +2 -0
- package/dist/config/mergeConfig.js +26 -0
- package/dist/config/mergeConfig.js.map +1 -0
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +63 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/template.d.ts +10 -0
- package/dist/config/template.js +32 -0
- package/dist/config/template.js.map +1 -0
- package/dist/core/baseline.d.ts +23 -0
- package/dist/core/baseline.js +118 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/scan.d.ts +2 -0
- package/dist/core/scan.js +129 -0
- package/dist/core/scan.js.map +1 -0
- package/dist/core/severity.d.ts +7 -0
- package/dist/core/severity.js +26 -0
- package/dist/core/severity.js.map +1 -0
- package/dist/core/types.d.ts +96 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/detectors/deadAbstraction.d.ts +2 -0
- package/dist/detectors/deadAbstraction.js +115 -0
- package/dist/detectors/deadAbstraction.js.map +1 -0
- package/dist/detectors/duplicateLogic.d.ts +2 -0
- package/dist/detectors/duplicateLogic.js +81 -0
- package/dist/detectors/duplicateLogic.js.map +1 -0
- package/dist/detectors/effectComplexity.d.ts +2 -0
- package/dist/detectors/effectComplexity.js +64 -0
- package/dist/detectors/effectComplexity.js.map +1 -0
- package/dist/detectors/index.d.ts +3 -0
- package/dist/detectors/index.js +20 -0
- package/dist/detectors/index.js.map +1 -0
- package/dist/detectors/largeComponent.d.ts +2 -0
- package/dist/detectors/largeComponent.js +46 -0
- package/dist/detectors/largeComponent.js.map +1 -0
- package/dist/detectors/namingDrift.d.ts +10 -0
- package/dist/detectors/namingDrift.js +82 -0
- package/dist/detectors/namingDrift.js.map +1 -0
- package/dist/detectors/propDrilling.d.ts +2 -0
- package/dist/detectors/propDrilling.js +97 -0
- package/dist/detectors/propDrilling.js.map +1 -0
- package/dist/detectors/stateSprawl.d.ts +2 -0
- package/dist/detectors/stateSprawl.js +47 -0
- package/dist/detectors/stateSprawl.js.map +1 -0
- package/dist/detectors/todoComment.d.ts +2 -0
- package/dist/detectors/todoComment.js +45 -0
- package/dist/detectors/todoComment.js.map +1 -0
- package/dist/reporters/index.d.ts +4 -0
- package/dist/reporters/index.js +14 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/jsonReporter.d.ts +2 -0
- package/dist/reporters/jsonReporter.js +4 -0
- package/dist/reporters/jsonReporter.js.map +1 -0
- package/dist/reporters/markdownReporter.d.ts +2 -0
- package/dist/reporters/markdownReporter.js +52 -0
- package/dist/reporters/markdownReporter.js.map +1 -0
- package/dist/reporters/sarifReporter.d.ts +7 -0
- package/dist/reporters/sarifReporter.js +77 -0
- package/dist/reporters/sarifReporter.js.map +1 -0
- package/dist/reporters/terminalReporter.d.ts +4 -0
- package/dist/reporters/terminalReporter.js +39 -0
- package/dist/reporters/terminalReporter.js.map +1 -0
- package/dist/utils/ast.d.ts +23 -0
- package/dist/utils/ast.js +132 -0
- package/dist/utils/ast.js.map +1 -0
- package/dist/utils/color.d.ts +8 -0
- package/dist/utils/color.js +27 -0
- package/dist/utils/color.js.map +1 -0
- package/dist/utils/createIssue.d.ts +13 -0
- package/dist/utils/createIssue.js +28 -0
- package/dist/utils/createIssue.js.map +1 -0
- package/dist/utils/git.d.ts +15 -0
- package/dist/utils/git.js +68 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/hostComponents.d.ts +12 -0
- package/dist/utils/hostComponents.js +57 -0
- package/dist/utils/hostComponents.js.map +1 -0
- package/dist/utils/identifiers.d.ts +3 -0
- package/dist/utils/identifiers.js +20 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/lines.d.ts +8 -0
- package/dist/utils/lines.js +19 -0
- package/dist/utils/lines.js.map +1 -0
- package/dist/utils/similarity.d.ts +8 -0
- package/dist/utils/similarity.js +63 -0
- package/dist/utils/similarity.js.map +1 -0
- package/docs/architecture.md +68 -0
- package/docs/example-report.md +141 -0
- package/docs/good-first-issues.md +63 -0
- package/docs/rules.md +139 -0
- package/docs/showcase-expensify-app.md +119 -0
- package/package.json +60 -0
- package/schema/debtlens.config.schema.json +116 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to DebtLens are documented here. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-06-01
|
|
7
|
+
|
|
8
|
+
First public release.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `debtlens scan` with 8 maintainability rules: `large-component`, `state-sprawl`,
|
|
12
|
+
`effect-complexity`, `duplicate-logic`, `dead-abstraction`, `prop-drilling`,
|
|
13
|
+
`todo-comment`, `naming-drift`.
|
|
14
|
+
- Output formats: terminal, JSON, Markdown, and **SARIF 2.1.0** (for GitHub code scanning).
|
|
15
|
+
- `debtlens init` to scaffold a `debtlens.config.json`.
|
|
16
|
+
- **Baseline mode** (`--write-baseline` / `--baseline`) with line-shift-stable
|
|
17
|
+
fingerprints, so teams can adopt on legacy code and only see newly introduced debt.
|
|
18
|
+
- **`--changed [ref]`** mode to scan only files changed vs HEAD or a base ref (PR scans).
|
|
19
|
+
- Configurable naming-drift **vocabulary** (concept id → terms), merged with a built-in pack.
|
|
20
|
+
- Structural **AST fingerprint** pre-filter for `duplicate-logic` (precision + speed).
|
|
21
|
+
- JSON config with a generated **JSON Schema** (`schema/debtlens.config.schema.json`).
|
|
22
|
+
- A composite **GitHub Action** (`action.yml`).
|
|
23
|
+
- Test suite (`node:test` via `tsx`) and CI on a Node 20 + 22 matrix.
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
- DebtLens reports **heuristic signals, not proof of defects**, and makes no claim about
|
|
27
|
+
how code was authored.
|
|
28
|
+
- See [`docs/showcase-expensify-app.md`](docs/showcase-expensify-app.md) for a curated
|
|
29
|
+
run against a large production React Native codebase.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DebtLens contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# DebtLens
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ColumbusLabs/debtlens/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/debtlens)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
DebtLens is a static-analysis CLI for finding maintainability debt common in fast-moving AI-assisted TypeScript, React, React Native, Expo, and Next.js codebases.
|
|
9
|
+
|
|
10
|
+
It is not an "AI code detector." It does not try to prove who wrote a line of code. Instead, it finds the patterns that tend to slip into codebases when teams move quickly with coding assistants: duplicated logic, bloated components, state sprawl, overloaded effects, thin abstractions, prop drilling, TODO debt, and naming drift.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx debtlens scan
|
|
14
|
+
npx debtlens scan src --format markdown
|
|
15
|
+
npx debtlens scan --min-severity medium --fail-on high
|
|
16
|
+
npx debtlens scan --rules duplicates,state,effects
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Example output
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
$ debtlens scan examples/react --min-severity medium
|
|
23
|
+
|
|
24
|
+
DebtLens Report
|
|
25
|
+
Scanned 3 files with 8 rules in 38ms.
|
|
26
|
+
Issues: 4 | high 2 | medium 2 | low 0 | info 0
|
|
27
|
+
|
|
28
|
+
HIGH (2)
|
|
29
|
+
Prop drilling [prop-drilling]
|
|
30
|
+
src/Dashboard.tsx:13
|
|
31
|
+
Dashboard forwards 7 props across 3 child components.
|
|
32
|
+
confidence 73%
|
|
33
|
+
- ReleaseHero: movie, userId, region, theme, onSelect, onSave
|
|
34
|
+
- ReleaseGrid: movie, userId, region, theme, onSelect, onSave, onShare
|
|
35
|
+
suggestion: Consider colocating the data owner closer to consumers, using a
|
|
36
|
+
composition slot, or extracting a focused context for stable cross-cutting values.
|
|
37
|
+
|
|
38
|
+
Duplicate logic [duplicate-logic]
|
|
39
|
+
src/duplicateOne.ts:1
|
|
40
|
+
normalizeMovieRelease is 100% structurally similar to normalizeGameRelease.
|
|
41
|
+
confidence 100%
|
|
42
|
+
- src/duplicateOne.ts:1-18 (18 lines)
|
|
43
|
+
- src/duplicateTwo.ts:1-18 (18 lines)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See [`docs/showcase-expensify-app.md`](./docs/showcase-expensify-app.md) for a curated run against a large production React Native codebase.
|
|
47
|
+
|
|
48
|
+
## Why this matters
|
|
49
|
+
|
|
50
|
+
AI coding assistants make it easier to generate working code quickly. That creates a new maintainer problem: code review must catch duplicated implementations, architectural drift, unnecessary abstractions, and components that quietly absorb too many responsibilities.
|
|
51
|
+
|
|
52
|
+
That review burden is especially hard for new coders who have not yet built the instinct for what maintainability debt looks like. A beginner can ship something that works and still miss warning signs: repeated logic, overloaded effects, local state scattered everywhere, thin wrappers, or names that drift across a feature.
|
|
53
|
+
|
|
54
|
+
DebtLens gives maintainers and newer contributors a neutral, explainable report before debt becomes permanent. It is meant to teach what to look for, not just fail a build.
|
|
55
|
+
|
|
56
|
+
## Current rule set
|
|
57
|
+
|
|
58
|
+
| Rule | What it catches | Default severity |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `large-component` | React-style components with too many lines, hooks, or branch points | Medium |
|
|
61
|
+
| `state-sprawl` | Components/hooks with many local stateful hooks | Medium |
|
|
62
|
+
| `effect-complexity` | Long or overloaded `useEffect` blocks | Medium |
|
|
63
|
+
| `duplicate-logic` | Near-duplicate functions/components using normalized AST/text similarity | Medium |
|
|
64
|
+
| `dead-abstraction` | Thin wrappers that add little behavior | Low |
|
|
65
|
+
| `prop-drilling` | Components that forward many props to children | Medium |
|
|
66
|
+
| `todo-comment` | TODO/FIXME/HACK/temporary implementation comments | Low |
|
|
67
|
+
| `naming-drift` | Files with multiple competing names for the same domain concept | Info |
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install --save-dev debtlens
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
or run without installing:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx debtlens scan
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
debtlens init # write a starter debtlens.config.json (use --force to overwrite)
|
|
85
|
+
debtlens scan [target]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
|
|
90
|
+
```txt
|
|
91
|
+
-i, --include <patterns> comma-separated glob patterns to include
|
|
92
|
+
-x, --exclude <patterns> comma-separated glob patterns to exclude
|
|
93
|
+
--min-severity <severity> info, low, medium, or high
|
|
94
|
+
--rules <rules> comma-separated rule ids
|
|
95
|
+
--threshold <thresholds> comma-separated key=value threshold overrides
|
|
96
|
+
--max-files <count> maximum files to scan
|
|
97
|
+
--format <format> terminal, json, markdown, or sarif
|
|
98
|
+
-o, --output <path> write the report to a file
|
|
99
|
+
--fail-on <severity> exit 1 when an issue meets this severity
|
|
100
|
+
--baseline <path> report only issues absent from this baseline file
|
|
101
|
+
--write-baseline [path] write current issues to a baseline file and exit
|
|
102
|
+
--changed [ref] scan only files changed vs HEAD (or vs <ref> if given)
|
|
103
|
+
--config <path> path to debtlens.config.json
|
|
104
|
+
--cwd <path> working directory
|
|
105
|
+
--no-color disable terminal color
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Scan the current project
|
|
112
|
+
debtlens scan
|
|
113
|
+
|
|
114
|
+
# Scan only app source files
|
|
115
|
+
debtlens scan . --include "app/**/*.{ts,tsx},src/**/*.{ts,tsx}"
|
|
116
|
+
|
|
117
|
+
# Create a Markdown report for a pull request artifact
|
|
118
|
+
debtlens scan --format markdown --output debtlens-report.md
|
|
119
|
+
|
|
120
|
+
# CI gate: allow low/medium debt but fail high-confidence high-severity debt
|
|
121
|
+
debtlens scan --min-severity medium --fail-on high
|
|
122
|
+
|
|
123
|
+
# Tune component-size threshold
|
|
124
|
+
debtlens scan --threshold "large-component.maxLines=320,state-sprawl.maxStatefulHooks=8"
|
|
125
|
+
|
|
126
|
+
# Adopt on a legacy repo: record existing debt, then only report newly introduced debt
|
|
127
|
+
debtlens scan --write-baseline
|
|
128
|
+
debtlens scan --baseline debtlens-baseline.json --fail-on high
|
|
129
|
+
|
|
130
|
+
# Pull-request scan: only the files this branch changed vs main
|
|
131
|
+
debtlens scan --changed origin/main --fail-on high
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Baseline fingerprints are stable across line shifts, so moving existing code up or down does not resurface already-recorded debt — only genuinely new issues are reported.
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
Create `debtlens.config.json`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"include": ["src/**/*.{ts,tsx,js,jsx}"],
|
|
143
|
+
"exclude": ["node_modules/**", "dist/**", "build/**", ".next/**"],
|
|
144
|
+
"minSeverity": "low",
|
|
145
|
+
"rules": [
|
|
146
|
+
"large-component",
|
|
147
|
+
"state-sprawl",
|
|
148
|
+
"effect-complexity",
|
|
149
|
+
"duplicate-logic",
|
|
150
|
+
"dead-abstraction",
|
|
151
|
+
"prop-drilling",
|
|
152
|
+
"todo-comment",
|
|
153
|
+
"naming-drift"
|
|
154
|
+
],
|
|
155
|
+
"thresholds": {
|
|
156
|
+
"large-component.maxLines": 250,
|
|
157
|
+
"state-sprawl.maxStatefulHooks": 6,
|
|
158
|
+
"effect-complexity.maxLines": 30,
|
|
159
|
+
"duplicate-logic.minSimilarity": 0.86
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Custom naming vocabulary
|
|
165
|
+
|
|
166
|
+
`naming-drift` ships with a built-in media/release vocabulary. Add your own domain concepts with `vocabulary` (concept id → competing terms). Your groups are merged with the built-ins, and a group with the same id overrides the built-in one.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"vocabulary": {
|
|
171
|
+
"commerce-entity": ["product", "sku", "item", "listing"],
|
|
172
|
+
"auth-user": ["user", "account", "member", "profile"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Output formats
|
|
178
|
+
|
|
179
|
+
Terminal output is designed for local development. JSON is designed for integrations. Markdown is designed for PR comments, release notes, and maintainer handoffs. SARIF (2.1.0) is designed for GitHub code scanning and other security/quality dashboards.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
debtlens scan --format json
|
|
183
|
+
debtlens scan --format markdown --output reports/debtlens.md
|
|
184
|
+
debtlens scan --format sarif --output debtlens.sarif
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## GitHub Action
|
|
188
|
+
|
|
189
|
+
Run DebtLens on pull requests and surface findings as code-scanning annotations:
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
name: DebtLens
|
|
193
|
+
on: pull_request
|
|
194
|
+
|
|
195
|
+
permissions:
|
|
196
|
+
contents: read
|
|
197
|
+
security-events: write # required to upload SARIF
|
|
198
|
+
|
|
199
|
+
jobs:
|
|
200
|
+
debtlens:
|
|
201
|
+
runs-on: ubuntu-latest
|
|
202
|
+
steps:
|
|
203
|
+
- uses: actions/checkout@v4
|
|
204
|
+
with:
|
|
205
|
+
fetch-depth: 0 # needed for --changed to diff against the base branch
|
|
206
|
+
- uses: ColumbusLabs/debtlens@v0
|
|
207
|
+
with:
|
|
208
|
+
changed: origin/${{ github.base_ref }}
|
|
209
|
+
format: sarif
|
|
210
|
+
output: debtlens.sarif
|
|
211
|
+
fail-on: high
|
|
212
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
213
|
+
if: always()
|
|
214
|
+
with:
|
|
215
|
+
sarif_file: debtlens.sarif
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Inputs: `target`, `min-severity`, `rules`, `fail-on`, `format`, `output`, `changed`, `baseline`, `config`, `working-directory`. Each maps to the matching `scan` flag. With `fail-on`, a qualifying issue fails the job (gating the merge); `if: always()` still uploads the SARIF so annotations appear even on a failing run.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm install
|
|
224
|
+
npm run typecheck
|
|
225
|
+
npm test # node:test suite (run via tsx)
|
|
226
|
+
npm run test:all # typecheck + tests
|
|
227
|
+
npm run build
|
|
228
|
+
npm run dev
|
|
229
|
+
node dist/cli/index.js scan examples/react --min-severity info
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Project status
|
|
233
|
+
|
|
234
|
+
DebtLens is currently a v0.1 proof-of-concept. The architecture is intentionally simple so maintainers can add rules quickly. The next milestones are documented in [`ROADMAP.md`](./ROADMAP.md), and starter contribution ideas are tracked in [`docs/good-first-issues.md`](./docs/good-first-issues.md).
|
|
235
|
+
|
|
236
|
+
## Positioning
|
|
237
|
+
|
|
238
|
+
DebtLens should be pitched as open-source maintainer infrastructure:
|
|
239
|
+
|
|
240
|
+
> AI-assisted development increases code throughput, but also increases review burden and architectural drift. DebtLens gives maintainers an explainable, open-source way to find maintainability debt before it ships.
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { loadConfig } from "../config/loadConfig.js";
|
|
6
|
+
import { mergeConfig } from "../config/mergeConfig.js";
|
|
7
|
+
import { DEFAULT_BASELINE_FILENAME, applyBaseline, createBaseline, loadBaseline, writeBaseline } from "../core/baseline.js";
|
|
8
|
+
import { scan } from "../core/scan.js";
|
|
9
|
+
import { getChangedFiles } from "../utils/git.js";
|
|
10
|
+
import { parseSeverity, severityRank } from "../core/severity.js";
|
|
11
|
+
import { detectorIds } from "../detectors/index.js";
|
|
12
|
+
import { renderReport } from "../reporters/index.js";
|
|
13
|
+
import { runInit } from "./init.js";
|
|
14
|
+
import { parseCommaList, parseThresholds } from "./parseList.js";
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name("debtlens")
|
|
18
|
+
.description("Find maintainability debt common in fast-moving AI-assisted TypeScript and React codebases.")
|
|
19
|
+
.version("0.1.0");
|
|
20
|
+
program.command("scan")
|
|
21
|
+
.description("Scan a project, directory, or file for maintainability debt.")
|
|
22
|
+
.argument("[target]", "directory or file to scan", ".")
|
|
23
|
+
.option("-i, --include <patterns>", "comma-separated glob patterns to include")
|
|
24
|
+
.option("-x, --exclude <patterns>", "comma-separated glob patterns to exclude")
|
|
25
|
+
.option("--min-severity <severity>", "info, low, medium, or high", "low")
|
|
26
|
+
.option("--rules <rules>", `comma-separated rule ids. Available: ${detectorIds.join(", ")}`)
|
|
27
|
+
.option("--threshold <thresholds>", "comma-separated key=value threshold overrides")
|
|
28
|
+
.option("--max-files <count>", "maximum files to scan", parseInteger)
|
|
29
|
+
.option("--format <format>", "terminal, json, markdown, or sarif", "terminal")
|
|
30
|
+
.option("-o, --output <path>", "write the report to a file instead of stdout")
|
|
31
|
+
.option("--fail-on <severity>", "exit with code 1 when any issue meets this severity")
|
|
32
|
+
.option("--baseline <path>", "report only issues absent from this baseline file")
|
|
33
|
+
.option("--write-baseline [path]", "write current issues to a baseline file and exit")
|
|
34
|
+
.option("--changed [ref]", "scan only files changed vs HEAD (or vs <ref> if given)")
|
|
35
|
+
.option("--config <path>", "path to debtlens.config.json")
|
|
36
|
+
.option("--cwd <path>", "working directory", process.cwd())
|
|
37
|
+
.option("--no-color", "disable ANSI color in terminal output")
|
|
38
|
+
.action(async (target, rawOptions) => {
|
|
39
|
+
try {
|
|
40
|
+
const format = parseFormat(String(rawOptions.format ?? "terminal"));
|
|
41
|
+
const cwd = resolve(String(rawOptions.cwd ?? process.cwd()));
|
|
42
|
+
const fileConfig = loadConfig(cwd, rawOptions.config ? String(rawOptions.config) : undefined);
|
|
43
|
+
const minSeverity = parseSeverity(String(rawOptions.minSeverity ?? "low"), "low");
|
|
44
|
+
const failOn = rawOptions.failOn ? parseSeverity(String(rawOptions.failOn), "high") : undefined;
|
|
45
|
+
let changedFiles;
|
|
46
|
+
if (rawOptions.changed) {
|
|
47
|
+
const base = rawOptions.changed === true ? undefined : String(rawOptions.changed);
|
|
48
|
+
const changed = getChangedFiles(cwd, base);
|
|
49
|
+
if (changed === null) {
|
|
50
|
+
process.stderr.write("DebtLens: --changed ignored (not a git repository).\n");
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
changedFiles = changed.files;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const options = mergeConfig(target, fileConfig, {
|
|
57
|
+
cwd,
|
|
58
|
+
include: parseCommaList(rawOptions.include),
|
|
59
|
+
exclude: parseCommaList(rawOptions.exclude),
|
|
60
|
+
rules: parseRuleList(rawOptions.rules),
|
|
61
|
+
thresholds: parseThresholds(rawOptions.threshold),
|
|
62
|
+
minSeverity,
|
|
63
|
+
maxFiles: rawOptions.maxFiles,
|
|
64
|
+
changedFiles,
|
|
65
|
+
});
|
|
66
|
+
if (rawOptions.writeBaseline && rawOptions.baseline) {
|
|
67
|
+
throw new Error("Use either --write-baseline or --baseline, not both.");
|
|
68
|
+
}
|
|
69
|
+
const result = await scan(options);
|
|
70
|
+
if (rawOptions.writeBaseline) {
|
|
71
|
+
const baselinePath = rawOptions.writeBaseline === true
|
|
72
|
+
? DEFAULT_BASELINE_FILENAME
|
|
73
|
+
: String(rawOptions.writeBaseline);
|
|
74
|
+
const written = writeBaseline(cwd, baselinePath, createBaseline(result.issues));
|
|
75
|
+
process.stdout.write(`Wrote baseline with ${result.issues.length} issues to ${written}\n`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const reported = rawOptions.baseline
|
|
79
|
+
? applyBaseline(result, loadBaseline(cwd, String(rawOptions.baseline)))
|
|
80
|
+
: result;
|
|
81
|
+
const report = renderReport(reported, format, { color: rawOptions.color !== false && format === "terminal" && process.stdout.isTTY });
|
|
82
|
+
if (rawOptions.output) {
|
|
83
|
+
const outputPath = resolve(cwd, String(rawOptions.output));
|
|
84
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
85
|
+
writeFileSync(outputPath, report, "utf8");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
process.stdout.write(report);
|
|
89
|
+
}
|
|
90
|
+
if (failOn && reported.issues.some((issue) => severityRank[issue.severity] >= severityRank[failOn])) {
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
process.stderr.write(`DebtLens failed: ${message}\n`);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
program.command("init")
|
|
101
|
+
.description("Create a starter debtlens.config.json in the current directory.")
|
|
102
|
+
.option("--force", "overwrite an existing config file")
|
|
103
|
+
.option("--cwd <path>", "working directory", process.cwd())
|
|
104
|
+
.action((rawOptions) => {
|
|
105
|
+
try {
|
|
106
|
+
const cwd = resolve(String(rawOptions.cwd ?? process.cwd()));
|
|
107
|
+
const result = runInit(cwd, rawOptions.force === true);
|
|
108
|
+
process.stdout.write(`${result.overwritten ? "Overwrote" : "Created"} ${result.path}\n`);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
process.stderr.write(`DebtLens failed: ${message}\n`);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (process.argv.length <= 2) {
|
|
117
|
+
program.help();
|
|
118
|
+
}
|
|
119
|
+
await program.parseAsync(process.argv);
|
|
120
|
+
function parseRuleList(value) {
|
|
121
|
+
const parsed = parseCommaList(value);
|
|
122
|
+
if (!parsed)
|
|
123
|
+
return undefined;
|
|
124
|
+
const aliases = {
|
|
125
|
+
components: "large-component",
|
|
126
|
+
component: "large-component",
|
|
127
|
+
state: "state-sprawl",
|
|
128
|
+
effects: "effect-complexity",
|
|
129
|
+
effect: "effect-complexity",
|
|
130
|
+
duplicates: "duplicate-logic",
|
|
131
|
+
duplicate: "duplicate-logic",
|
|
132
|
+
abstractions: "dead-abstraction",
|
|
133
|
+
abstraction: "dead-abstraction",
|
|
134
|
+
props: "prop-drilling",
|
|
135
|
+
comments: "todo-comment",
|
|
136
|
+
todos: "todo-comment",
|
|
137
|
+
naming: "naming-drift",
|
|
138
|
+
};
|
|
139
|
+
return parsed.map((rule) => aliases[rule] ?? rule);
|
|
140
|
+
}
|
|
141
|
+
function parseInteger(value) {
|
|
142
|
+
const parsed = Number(value);
|
|
143
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
144
|
+
throw new Error(`Expected a positive integer, received "${value}".`);
|
|
145
|
+
}
|
|
146
|
+
return parsed;
|
|
147
|
+
}
|
|
148
|
+
function parseFormat(value) {
|
|
149
|
+
if (value === "terminal" || value === "json" || value === "markdown" || value === "sarif")
|
|
150
|
+
return value;
|
|
151
|
+
throw new Error(`Invalid format "${value}". Expected terminal, json, markdown, or sarif.`);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const CONFIG_FILENAME = "debtlens.config.json";
|
|
2
|
+
export interface InitResult {
|
|
3
|
+
path: string;
|
|
4
|
+
overwritten: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Write a starter `debtlens.config.json` into `cwd`. Refuses to clobber an existing
|
|
8
|
+
* config unless `force` is set.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runInit(cwd: string, force?: boolean): InitResult;
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { renderConfigFile } from "../config/template.js";
|
|
4
|
+
export const CONFIG_FILENAME = "debtlens.config.json";
|
|
5
|
+
/**
|
|
6
|
+
* Write a starter `debtlens.config.json` into `cwd`. Refuses to clobber an existing
|
|
7
|
+
* config unless `force` is set.
|
|
8
|
+
*/
|
|
9
|
+
export function runInit(cwd, force = false) {
|
|
10
|
+
const path = resolve(cwd, CONFIG_FILENAME);
|
|
11
|
+
const exists = existsSync(path);
|
|
12
|
+
if (exists && !force) {
|
|
13
|
+
throw new Error(`${CONFIG_FILENAME} already exists. Pass --force to overwrite it.`);
|
|
14
|
+
}
|
|
15
|
+
writeFileSync(path, renderConfigFile(), "utf8");
|
|
16
|
+
return { path, overwritten: exists };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,CAAC,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAOtD;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,KAAK,GAAG,KAAK;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,eAAe,gDAAgD,CAAC,CAAC;IACtF,CAAC;IAED,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function parseCommaList(value) {
|
|
2
|
+
if (!value)
|
|
3
|
+
return undefined;
|
|
4
|
+
const values = Array.isArray(value) ? value : [value];
|
|
5
|
+
const parsed = values
|
|
6
|
+
.flatMap((item) => item.split(","))
|
|
7
|
+
.map((item) => item.trim())
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
return parsed.length > 0 ? parsed : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function parseThresholds(value) {
|
|
12
|
+
if (!value)
|
|
13
|
+
return undefined;
|
|
14
|
+
const entries = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
15
|
+
const result = {};
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const [key, rawValue] = entry.split("=").map((part) => part.trim());
|
|
18
|
+
if (!key || !rawValue) {
|
|
19
|
+
throw new Error(`Invalid threshold "${entry}". Use key=value, for example large-component.maxLines=300.`);
|
|
20
|
+
}
|
|
21
|
+
const numberValue = Number(rawValue);
|
|
22
|
+
if (!Number.isFinite(numberValue)) {
|
|
23
|
+
throw new Error(`Invalid numeric threshold value in "${entry}".`);
|
|
24
|
+
}
|
|
25
|
+
result[key] = numberValue;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=parseList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseList.js","sourceRoot":"","sources":["../../src/cli/parseList.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,KAAoC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM;SAClB,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SAClC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAyB;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9E,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,6DAA6D,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,uCAAuC,KAAK,IAAI,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;IAC5B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const defaultConfig = {
|
|
2
|
+
include: ["**/*.{ts,tsx,js,jsx}"],
|
|
3
|
+
exclude: [
|
|
4
|
+
"node_modules/**",
|
|
5
|
+
"dist/**",
|
|
6
|
+
"build/**",
|
|
7
|
+
"coverage/**",
|
|
8
|
+
".next/**",
|
|
9
|
+
".expo/**",
|
|
10
|
+
".turbo/**",
|
|
11
|
+
"ios/**",
|
|
12
|
+
"android/**",
|
|
13
|
+
"**/*.d.ts",
|
|
14
|
+
"**/*.min.js",
|
|
15
|
+
"**/*.test.{ts,tsx,js,jsx}",
|
|
16
|
+
"**/*.spec.{ts,tsx,js,jsx}",
|
|
17
|
+
"**/__tests__/**",
|
|
18
|
+
"**/__mocks__/**"
|
|
19
|
+
],
|
|
20
|
+
minSeverity: "low",
|
|
21
|
+
rules: [],
|
|
22
|
+
thresholds: {
|
|
23
|
+
"large-component.maxLines": 250,
|
|
24
|
+
"large-component.maxBranches": 16,
|
|
25
|
+
"large-component.maxHooks": 10,
|
|
26
|
+
"state-sprawl.maxStatefulHooks": 6,
|
|
27
|
+
"effect-complexity.maxLines": 30,
|
|
28
|
+
"effect-complexity.maxDependencies": 8,
|
|
29
|
+
"duplicate-logic.minSimilarity": 0.86,
|
|
30
|
+
"duplicate-logic.minLines": 8,
|
|
31
|
+
"duplicate-logic.maxSnippets": 450,
|
|
32
|
+
"dead-abstraction.maxWrapperLines": 8,
|
|
33
|
+
"prop-drilling.maxForwardedProps": 4,
|
|
34
|
+
"naming-drift.minVariants": 4,
|
|
35
|
+
"duplicate-logic.minStructuralSimilarity": 0.6
|
|
36
|
+
},
|
|
37
|
+
maxFiles: 2000,
|
|
38
|
+
vocabulary: {},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,OAAO,EAAE,CAAC,sBAAsB,CAAC;IACjC,OAAO,EAAE;QACP,iBAAiB;QACjB,SAAS;QACT,UAAU;QACV,aAAa;QACb,UAAU;QACV,UAAU;QACV,WAAW;QACX,QAAQ;QACR,YAAY;QACZ,WAAW;QACX,aAAa;QACb,2BAA2B;QAC3B,2BAA2B;QAC3B,iBAAiB;QACjB,iBAAiB;KAClB;IACD,WAAW,EAAE,KAAK;IAClB,KAAK,EAAE,EAAE;IACT,UAAU,EAAE;QACV,0BAA0B,EAAE,GAAG;QAC/B,6BAA6B,EAAE,EAAE;QACjC,0BAA0B,EAAE,EAAE;QAC9B,+BAA+B,EAAE,CAAC;QAClC,4BAA4B,EAAE,EAAE;QAChC,mCAAmC,EAAE,CAAC;QACtC,+BAA+B,EAAE,IAAI;QACrC,0BAA0B,EAAE,CAAC;QAC7B,6BAA6B,EAAE,GAAG;QAClC,kCAAkC,EAAE,CAAC;QACrC,iCAAiC,EAAE,CAAC;QACpC,0BAA0B,EAAE,CAAC;QAC7B,yCAAyC,EAAE,GAAG;KAC/C;IACD,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,EAAE;CACf,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const configNames = [
|
|
4
|
+
"debtlens.config.json",
|
|
5
|
+
".debtlensrc.json"
|
|
6
|
+
];
|
|
7
|
+
export function loadConfig(cwd, explicitPath) {
|
|
8
|
+
const configPath = explicitPath
|
|
9
|
+
? resolve(cwd, explicitPath)
|
|
10
|
+
: configNames.map((name) => resolve(cwd, name)).find((candidate) => existsSync(candidate));
|
|
11
|
+
if (!configPath || !existsSync(configPath)) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const raw = readFileSync(configPath, "utf8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
throw new Error(`Could not read DebtLens config at ${configPath}: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=loadConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,WAAW,GAAG;IAClB,sBAAsB;IACtB,kBAAkB;CACnB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,YAAqB;IAC3D,MAAM,UAAU,GAAG,YAAY;QAC7B,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC;QAC5B,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IAE7F,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,qCAAqC,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { defaultConfig } from "./defaults.js";
|
|
3
|
+
export function mergeConfig(target, fileConfig, cliOptions) {
|
|
4
|
+
const cwd = resolve(cliOptions.cwd ?? process.cwd());
|
|
5
|
+
return {
|
|
6
|
+
cwd,
|
|
7
|
+
target: resolve(cwd, target),
|
|
8
|
+
include: cliOptions.include?.length ? cliOptions.include : fileConfig.include ?? defaultConfig.include,
|
|
9
|
+
exclude: [
|
|
10
|
+
...defaultConfig.exclude,
|
|
11
|
+
...(fileConfig.exclude ?? []),
|
|
12
|
+
...(cliOptions.exclude ?? []),
|
|
13
|
+
],
|
|
14
|
+
minSeverity: cliOptions.minSeverity ?? fileConfig.minSeverity ?? defaultConfig.minSeverity,
|
|
15
|
+
rules: cliOptions.rules?.length ? cliOptions.rules : fileConfig.rules,
|
|
16
|
+
thresholds: {
|
|
17
|
+
...defaultConfig.thresholds,
|
|
18
|
+
...(fileConfig.thresholds ?? {}),
|
|
19
|
+
...(cliOptions.thresholds ?? {}),
|
|
20
|
+
},
|
|
21
|
+
maxFiles: cliOptions.maxFiles ?? fileConfig.maxFiles ?? defaultConfig.maxFiles,
|
|
22
|
+
vocabulary: { ...defaultConfig.vocabulary, ...(fileConfig.vocabulary ?? {}) },
|
|
23
|
+
changedFiles: cliOptions.changedFiles,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=mergeConfig.js.map
|