intentpatch 0.1.1 → 0.2.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.ko.md +39 -21
- package/README.md +36 -16
- package/dist/application/ports/module-alias-resolver.d.ts +11 -0
- package/dist/application/ports/module-alias-resolver.d.ts.map +1 -0
- package/dist/application/ports/module-alias-resolver.js +2 -0
- package/dist/application/ports/module-alias-resolver.js.map +1 -0
- package/dist/application/ports/package-lock-parser.d.ts +20 -0
- package/dist/application/ports/package-lock-parser.d.ts.map +1 -0
- package/dist/application/ports/package-lock-parser.js +2 -0
- package/dist/application/ports/package-lock-parser.js.map +1 -0
- package/dist/application/rules/npm-lockfile-rule.d.ts +12 -0
- package/dist/application/rules/npm-lockfile-rule.d.ts.map +1 -0
- package/dist/application/rules/npm-lockfile-rule.js +109 -0
- package/dist/application/rules/npm-lockfile-rule.js.map +1 -0
- package/dist/application/rules/npm-workspace-dependency-rule.d.ts +10 -0
- package/dist/application/rules/npm-workspace-dependency-rule.d.ts.map +1 -0
- package/dist/application/rules/npm-workspace-dependency-rule.js +155 -0
- package/dist/application/rules/npm-workspace-dependency-rule.js.map +1 -0
- package/dist/application/rules/package-dependency-rule.d.ts +4 -3
- package/dist/application/rules/package-dependency-rule.d.ts.map +1 -1
- package/dist/application/rules/package-dependency-rule.js +33 -28
- package/dist/application/rules/package-dependency-rule.js.map +1 -1
- package/dist/application/services/analyze-import-impact.d.ts +3 -1
- package/dist/application/services/analyze-import-impact.d.ts.map +1 -1
- package/dist/application/services/analyze-import-impact.js +12 -7
- package/dist/application/services/analyze-import-impact.js.map +1 -1
- package/dist/infrastructure/git/git-file-snapshot-source.d.ts.map +1 -1
- package/dist/infrastructure/git/git-file-snapshot-source.js +4 -2
- package/dist/infrastructure/git/git-file-snapshot-source.js.map +1 -1
- package/dist/infrastructure/npm/npm-package-lock-parser.d.ts +6 -0
- package/dist/infrastructure/npm/npm-package-lock-parser.d.ts.map +1 -0
- package/dist/infrastructure/npm/npm-package-lock-parser.js +93 -0
- package/dist/infrastructure/npm/npm-package-lock-parser.js.map +1 -0
- package/dist/infrastructure/typescript/typescript-config-loader.d.ts +17 -0
- package/dist/infrastructure/typescript/typescript-config-loader.d.ts.map +1 -0
- package/dist/infrastructure/typescript/typescript-config-loader.js +99 -0
- package/dist/infrastructure/typescript/typescript-config-loader.js.map +1 -0
- package/dist/infrastructure/typescript/typescript-module-alias-resolver.d.ts +10 -0
- package/dist/infrastructure/typescript/typescript-module-alias-resolver.d.ts.map +1 -0
- package/dist/infrastructure/typescript/typescript-module-alias-resolver.js +78 -0
- package/dist/infrastructure/typescript/typescript-module-alias-resolver.js.map +1 -0
- package/dist/presentation/analyze-repository.d.ts.map +1 -1
- package/dist/presentation/analyze-repository.js +7 -3
- package/dist/presentation/analyze-repository.js.map +1 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -21,9 +21,16 @@ LLM 없이도 재현 가능한 분석을 제공하고, AI 설명 기능은 선
|
|
|
21
21
|
|
|
22
22
|
## 빠른 시작
|
|
23
23
|
|
|
24
|
-
Node.js 20 이상과 Git이 설치되어 있어야 합니다.
|
|
24
|
+
Node.js 20 이상과 Git이 설치되어 있어야 합니다. npm에 공개된 v0.2.0은 전역 설치 없이
|
|
25
25
|
실행할 수 있습니다.
|
|
26
26
|
|
|
27
|
+
```bash
|
|
28
|
+
npx intentpatch analyze --cwd /path/to/repository
|
|
29
|
+
npx intentpatch analyze --format html --output intentpatch-report.html
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
현재 소스 버전을 실행하려면 저장소를 빌드합니다.
|
|
33
|
+
|
|
27
34
|
```bash
|
|
28
35
|
git clone https://github.com/xx2xxjaeil/intent-patch.git
|
|
29
36
|
cd intent-patch
|
|
@@ -33,13 +40,6 @@ node dist/presentation/cli/main.js --version
|
|
|
33
40
|
node dist/presentation/cli/main.js analyze --cwd /path/to/repository
|
|
34
41
|
```
|
|
35
42
|
|
|
36
|
-
npm 릴리스가 공개된 뒤에는 설치 없이 같은 CLI를 실행할 수 있습니다.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx intentpatch analyze --cwd /path/to/repository
|
|
40
|
-
npx intentpatch analyze --format html --output intentpatch-report.html
|
|
41
|
-
```
|
|
42
|
-
|
|
43
43
|
핵심 분석에는 API key, 유료 AI 모델, 서버 또는 데이터베이스가 필요하지 않습니다. 분석할 Git
|
|
44
44
|
저장소의 파일은 로컬에서 처리하며 LLM 연결은 현재 기본 실행 경로에 포함되지 않습니다.
|
|
45
45
|
|
|
@@ -67,7 +67,7 @@ jobs:
|
|
|
67
67
|
|
|
68
68
|
- name: Analyze pull request
|
|
69
69
|
id: intentpatch
|
|
70
|
-
uses: xx2xxjaeil/intent-patch@
|
|
70
|
+
uses: xx2xxjaeil/intent-patch@v0.2.0
|
|
71
71
|
with:
|
|
72
72
|
fail-on: high
|
|
73
73
|
|
|
@@ -79,9 +79,9 @@ jobs:
|
|
|
79
79
|
path: intentpatch-report
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
선택하고 다음 결과를 남깁니다.
|
|
82
|
+
Action은 릴리스 버전이나 불변 commit SHA로 고정하는 방식을 권장합니다. v0.2.0에는 TypeScript
|
|
83
|
+
경로 별칭과 npm lockfile/workspace 분석이 포함되며, `@main`은 미릴리스 변경을 따라갑니다.
|
|
84
|
+
Action은 PR·push 이벤트의 commit 범위를 자동으로 선택하고 다음 결과를 남깁니다.
|
|
85
85
|
|
|
86
86
|
- GitHub Actions Job Summary의 Markdown 보고서
|
|
87
87
|
- 심각도별 workflow annotation
|
|
@@ -129,7 +129,7 @@ LOW 구현체가 하나뿐인 추상화 추가
|
|
|
129
129
|
|
|
130
130
|
## 현재 구현된 기능
|
|
131
131
|
|
|
132
|
-
현재
|
|
132
|
+
현재 소스는 Git 변경사항 수집, 루트·npm workspace `package.json`의 직접 dependency와 npm lockfile 분석, TypeScript
|
|
133
133
|
최상위 심볼과 공개 API 변경 분석, 기존 구현 중복·단일 구현 추상화 신호, import graph 기반
|
|
134
134
|
영향 범위와 테스트 동반 변경 분석을 제공합니다.
|
|
135
135
|
|
|
@@ -146,6 +146,11 @@ LOW 구현체가 하나뿐인 추상화 추가
|
|
|
146
146
|
- production·development dependency 추가 탐지
|
|
147
147
|
- dependency 삭제, 버전 변경, 섹션 이동 탐지
|
|
148
148
|
- 잘못된 `package.json`을 예외 대신 근거가 포함된 finding으로 보고
|
|
149
|
+
- npm `package-lock.json` v2/v3의 실제 설치 패키지 추가·삭제·버전 변경 비교
|
|
150
|
+
- scoped·중첩 설치 경로를 구분하고 로컬 workspace 링크는 제외
|
|
151
|
+
- 잘못되었거나 미지원인 lockfile을 누락하지 않고 finding으로 보고
|
|
152
|
+
- 루트 `workspaces` 패턴에 속한 변경 manifest의 직접 dependency를 workspace별로 보고
|
|
153
|
+
- workspace 설정 변경과 manifest의 workspace 안팎 이동을 탐지
|
|
149
154
|
- finding 심각도(`high`, `medium`, `low`) 집계
|
|
150
155
|
- CI 품질 게이트를 위한 `--fail-on` 종료 코드
|
|
151
156
|
- 설치된 패키지 버전을 확인하는 `--version` 명령
|
|
@@ -160,6 +165,8 @@ LOW 구현체가 하나뿐인 추상화 추가
|
|
|
160
165
|
- rename 전후 파일 경로를 사용한 심볼 비교
|
|
161
166
|
- 구문 오류가 있는 파일을 누락시키지 않고 분석 불가 근거로 보고
|
|
162
167
|
- `.ts`·`.tsx` 파일의 상대 경로 정적 import와 re-export 관계 수집
|
|
168
|
+
- 가장 가까운 `tsconfig.json`·`jsconfig.json`의 `paths`·`baseUrl`과 상대 경로 `extends`를
|
|
169
|
+
사용해 로컬 별칭 import 해석
|
|
163
170
|
- `.js`·`.jsx`·`.mjs`·`.cjs` specifier를 대응하는 TypeScript 소스로 해석
|
|
164
171
|
- 변경 모듈을 import하는 직접 의존자와 여러 단계를 거친 간접 영향 파일 계산
|
|
165
172
|
- 해결하지 못한 상대 import와 읽기·파싱 실패를 분석 근거로 보존
|
|
@@ -174,7 +181,7 @@ LOW 구현체가 하나뿐인 추상화 추가
|
|
|
174
181
|
- Pull Request·push commit 범위를 자동 판별하는 GitHub Action
|
|
175
182
|
- GitHub Job Summary, workflow annotation, JSON·HTML artifact용 보고서 생성
|
|
176
183
|
|
|
177
|
-
아직 lockfile
|
|
184
|
+
아직 lockfile 전이 dependency의 부모 귀속 분석, JavaScript·메서드 구조 분석과 AI 리뷰
|
|
178
185
|
기능은 구현되지 않았습니다.
|
|
179
186
|
|
|
180
187
|
## 상세 사용법
|
|
@@ -412,9 +419,13 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
|
|
|
412
419
|
1. ✅ `package.json` 직접 dependency 변경 탐지와 규칙 엔진
|
|
413
420
|
2. ✅ TypeScript AST 기반 함수·클래스·인터페이스·타입 변경 분석
|
|
414
421
|
3. ✅ 상대 경로 정적 import graph 기반 변경 영향 범위 계산
|
|
422
|
+
- ✅ 가장 가까운 TypeScript 프로젝트 설정의 `paths`·`baseUrl` 별칭 해석
|
|
415
423
|
4. ✅ Change Contract 기반 예상 범위 이탈과 변경량 예산 탐지
|
|
416
424
|
5. ✅ Contract 기반 관련 테스트 변경 누락 탐지
|
|
417
425
|
6. lockfile과 workspace를 고려한 package manager adapter
|
|
426
|
+
- ✅ npm `package-lock.json` v2/v3의 설치 패키지 변경
|
|
427
|
+
- ✅ npm workspace manifest의 직접 dependency 변경
|
|
428
|
+
- pnpm·Yarn adapter
|
|
418
429
|
7. ✅ 직접 export된 공개 심볼 삭제와 export 해제 탐지
|
|
419
430
|
8. ✅ 단일 HTML 대시보드와 SVG 기반 dependency 영향 그래프
|
|
420
431
|
9. ✅ re-export·함수/인터페이스 호환성과 기존 코드 중복 가능성 탐지
|
|
@@ -428,9 +439,14 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
|
|
|
428
439
|
- 분석 대상은 최소 한 번 이상 커밋된 Git 저장소여야 합니다.
|
|
429
440
|
- untracked symbolic link는 안전을 위해 내용을 읽지 않습니다.
|
|
430
441
|
- 10 MiB를 초과하는 untracked 파일은 라인 수를 측정하지 않습니다.
|
|
431
|
-
- dependency 분석은 저장소
|
|
432
|
-
`devDependencies`를 대상으로 합니다.
|
|
433
|
-
-
|
|
442
|
+
- 직접 dependency 선언 분석은 저장소 루트와 변경된 npm workspace `package.json`의
|
|
443
|
+
`dependencies`와 `devDependencies`를 대상으로 합니다.
|
|
444
|
+
- npm `package-lock.json` v2/v3은 설치 경로와 버전을 비교합니다. 부모 dependency 귀속,
|
|
445
|
+
integrity hash나 취약점 정보까지 분석하지는 않습니다.
|
|
446
|
+
- workspace 판별은 루트의 npm `workspaces` 배열과 명시 경로, `*`·`**`·`?` 패턴을 지원합니다.
|
|
447
|
+
부정·중괄호·문자 클래스 패턴은 미지원 finding으로 보고합니다.
|
|
448
|
+
- pnpm·Yarn lockfile, 코드에서의 실제 dependency 사용 여부는 아직 분석하지 않습니다.
|
|
449
|
+
- working tree의 `package-lock.json`은 10 MiB, 그 외 텍스트 snapshot은 1 MiB로 제한합니다.
|
|
434
450
|
- 심볼 분석은 `.ts`와 `.tsx`의 이름이 있는 최상위 함수, 클래스, 인터페이스, 타입
|
|
435
451
|
별칭만 지원합니다.
|
|
436
452
|
- 공개 API 분석은 이름이 있는 최상위 함수·인터페이스, 로컬 `export { name }`, 외부
|
|
@@ -445,10 +461,12 @@ npm provenance가 자동 생성됩니다. 아직 npm에 존재하지 않는 신
|
|
|
445
461
|
보고합니다. TypeScript의 구조적 구현, factory 반환 타입과 런타임 등록은 계산하지 않습니다.
|
|
446
462
|
- 메서드, 변수 선언, enum, 중첩 선언, JavaScript 파일은 아직 심볼 분석 대상이 아닙니다.
|
|
447
463
|
- 선언 내부의 포맷이나 주석 변경도 심볼 수정으로 집계될 수 있습니다.
|
|
448
|
-
- 영향 분석은 `.ts`·`.tsx` 파일의
|
|
449
|
-
`
|
|
450
|
-
|
|
451
|
-
|
|
464
|
+
- 영향 분석은 `.ts`·`.tsx` 파일의 정적 `import`, side-effect import, `export ... from`,
|
|
465
|
+
`import = require()`를 대상으로 합니다. 로컬 별칭은 가장 가까운 `tsconfig.json` 또는
|
|
466
|
+
`jsconfig.json`과 상대 경로 `extends`를 해석합니다.
|
|
467
|
+
- 외부 패키지 설정을 참조하는 `extends`, project references, 임의 이름의 설정 파일,
|
|
468
|
+
package `imports`·`exports`, 외부 package import, dynamic `import()`, 일반 `require()`는
|
|
469
|
+
아직 그래프에 포함하지 않습니다.
|
|
452
470
|
- 영향 그래프는 비교 결과점(working tree 또는 head ref)의 파일을 기준으로 만듭니다. 따라서
|
|
453
471
|
삭제된 모듈을 가리키던 과거 import의 영향은 현재 단계에서 계산할 수 없습니다.
|
|
454
472
|
- 영향 분석용 소스 파일은 파일당 1 MiB로 제한하며, symbolic link는 읽지 않습니다.
|
package/README.md
CHANGED
|
@@ -18,7 +18,15 @@ IntentPatch combines **Git diff analysis, static analysis, and dependency analys
|
|
|
18
18
|
|
|
19
19
|
## Quick start
|
|
20
20
|
|
|
21
|
-
IntentPatch requires Node.js 20 or later and Git.
|
|
21
|
+
IntentPatch requires Node.js 20 or later and Git. Run the published v0.2.0 CLI without
|
|
22
|
+
installing it globally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx intentpatch analyze --cwd /path/to/repository
|
|
26
|
+
npx intentpatch analyze --format html --output intentpatch-report.html
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To run the current source version instead, build it from the repository:
|
|
22
30
|
|
|
23
31
|
```bash
|
|
24
32
|
git clone https://github.com/xx2xxjaeil/intent-patch.git
|
|
@@ -29,13 +37,6 @@ node dist/presentation/cli/main.js --version
|
|
|
29
37
|
node dist/presentation/cli/main.js analyze --cwd /path/to/repository
|
|
30
38
|
```
|
|
31
39
|
|
|
32
|
-
After the npm release becomes public, run the same CLI without installing it globally:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npx intentpatch analyze --cwd /path/to/repository
|
|
36
|
-
npx intentpatch analyze --format html --output intentpatch-report.html
|
|
37
|
-
```
|
|
38
|
-
|
|
39
40
|
The core analyzer needs no API key, paid AI model, server, or database. Repository files are processed locally, and no LLM is used in the default execution path.
|
|
40
41
|
|
|
41
42
|
## GitHub Action
|
|
@@ -61,7 +62,7 @@ jobs:
|
|
|
61
62
|
|
|
62
63
|
- name: Analyze pull request
|
|
63
64
|
id: intentpatch
|
|
64
|
-
uses: xx2xxjaeil/intent-patch@
|
|
65
|
+
uses: xx2xxjaeil/intent-patch@v0.2.0
|
|
65
66
|
with:
|
|
66
67
|
fail-on: high
|
|
67
68
|
|
|
@@ -73,7 +74,8 @@ jobs:
|
|
|
73
74
|
path: intentpatch-report
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
Pin the action to a released version or an immutable commit SHA. The v0.2.0 release includes
|
|
78
|
+
TypeScript path aliases and npm lockfile/workspace analysis; `@main` follows unreleased changes.
|
|
77
79
|
|
|
78
80
|
The action detects the comparison range for pull request and push events and produces:
|
|
79
81
|
|
|
@@ -121,7 +123,7 @@ Every finding includes the relevant file, a stable rule ID, and evidence for the
|
|
|
121
123
|
|
|
122
124
|
## Implemented capabilities
|
|
123
125
|
|
|
124
|
-
The current
|
|
126
|
+
The current source tree analyzes Git changes, direct dependencies in the root `package.json`, top-level TypeScript symbols and public API changes, possible duplicate implementations, single-implementation abstractions, import-graph impact, and related test changes.
|
|
125
127
|
|
|
126
128
|
### Git and reporting
|
|
127
129
|
|
|
@@ -143,6 +145,11 @@ The current version analyzes Git changes, direct dependencies in the root `packa
|
|
|
143
145
|
- Detect additions to production and development dependencies.
|
|
144
146
|
- Detect dependency removal, version changes, and section moves.
|
|
145
147
|
- Report malformed `package.json` files as evidence-backed findings instead of crashing.
|
|
148
|
+
- Compare resolved package additions, removals, and version changes in npm `package-lock.json` v2/v3.
|
|
149
|
+
- Keep scoped and nested installation paths distinct, and exclude local workspace links.
|
|
150
|
+
- Report malformed or unsupported lockfiles as findings rather than silently skipping them.
|
|
151
|
+
- Attribute direct dependency changes to changed npm workspace manifests selected by the root `workspaces` patterns.
|
|
152
|
+
- Report changes to workspace patterns and manifest moves into or out of a configured workspace.
|
|
146
153
|
- Extract top-level named functions, classes, interfaces, and type aliases from `.ts` and `.tsx` files.
|
|
147
154
|
- Detect symbol additions, modifications, and removals with source locations.
|
|
148
155
|
- Preserve direct exports, local export lists, and external re-exports as public-symbol evidence.
|
|
@@ -157,6 +164,8 @@ The current version analyzes Git changes, direct dependencies in the root `packa
|
|
|
157
164
|
### Impact, scope, and tests
|
|
158
165
|
|
|
159
166
|
- Build relative static import and re-export relationships for `.ts` and `.tsx` files.
|
|
167
|
+
- Resolve local imports through the nearest `tsconfig.json` or `jsconfig.json` `paths` and
|
|
168
|
+
`baseUrl`, including relative `extends` files.
|
|
160
169
|
- Resolve `.js`, `.jsx`, `.mjs`, and `.cjs` import specifiers to matching TypeScript sources.
|
|
161
170
|
- Calculate direct importers and transitively impacted files for changed modules.
|
|
162
171
|
- Preserve unresolved relative imports and file read or parse failures as evidence.
|
|
@@ -169,7 +178,7 @@ The current version analyzes Git changes, direct dependencies in the root `packa
|
|
|
169
178
|
- Report source files without a related test change as medium-severity findings.
|
|
170
179
|
- Support repository-relative `*`, `**`, and `?` path patterns.
|
|
171
180
|
|
|
172
|
-
|
|
181
|
+
Transitive lockfile dependency provenance, JavaScript and method-level structure analysis, and optional AI review are not implemented yet.
|
|
173
182
|
|
|
174
183
|
## CLI usage
|
|
175
184
|
|
|
@@ -396,9 +405,13 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
|
|
|
396
405
|
1. ✅ Direct `package.json` dependency detection and rule engine
|
|
397
406
|
2. ✅ TypeScript AST analysis for functions, classes, interfaces, and types
|
|
398
407
|
3. ✅ Relative static import graph and change-impact calculation
|
|
408
|
+
- ✅ TypeScript `paths` and `baseUrl` aliases from the nearest project config
|
|
399
409
|
4. ✅ Change Contract scope and change-budget analysis
|
|
400
410
|
5. ✅ Contract-based detection of missing related test changes
|
|
401
411
|
6. Package-manager adapters for lockfiles and workspaces
|
|
412
|
+
- ✅ npm `package-lock.json` v2/v3 resolved package changes
|
|
413
|
+
- ✅ npm workspace manifest dependency changes
|
|
414
|
+
- pnpm and Yarn adapters
|
|
402
415
|
7. ✅ Detection of removed public exports
|
|
403
416
|
8. ✅ Standalone HTML dashboard and SVG dependency-impact graph
|
|
404
417
|
9. ✅ Re-export and function/interface compatibility checks plus possible code duplication
|
|
@@ -412,8 +425,11 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
|
|
|
412
425
|
- The target must be a Git repository with at least one commit.
|
|
413
426
|
- Untracked symbolic links are not read for safety.
|
|
414
427
|
- Line counts are skipped for untracked files larger than 10 MiB.
|
|
415
|
-
-
|
|
416
|
-
-
|
|
428
|
+
- Direct dependency declarations are analyzed from `dependencies` and `devDependencies` in the root npm `package.json` and changed npm workspace manifests.
|
|
429
|
+
- npm `package-lock.json` v2/v3 analysis compares installed package paths and versions, not dependency provenance, integrity hashes, or vulnerability data. It does not attribute transitive packages to their parents.
|
|
430
|
+
- Workspace detection supports the root npm `workspaces` array with explicit paths and `*`, `**`, and `?` globs. Negation, brace, and character-class patterns are reported as unsupported.
|
|
431
|
+
- pnpm and Yarn lockfiles and real dependency usage in source code are not analyzed yet.
|
|
432
|
+
- Working-tree `package-lock.json` files are limited to 10 MiB; other text snapshots retain the 1 MiB limit.
|
|
417
433
|
- Symbol analysis supports named top-level functions, classes, interfaces, and type aliases in `.ts` and `.tsx` files.
|
|
418
434
|
- Public API analysis supports named top-level functions and interfaces, local `export { name }`, external `export { name } from`, and `export * from`. Package `exports`, anonymous default exports, and detailed class or type-alias contracts are not interpreted yet.
|
|
419
435
|
- Function compatibility compares explicitly written parameter and return-type text. It does not evaluate inferred return types or full TypeScript structural assignability.
|
|
@@ -421,8 +437,12 @@ No long-lived npm token is stored as a GitHub secret. Public packages published
|
|
|
421
437
|
- A single-implementation abstraction is reported only when exactly one named class explicitly uses `implements` for a new interface. Structural implementation, factory return types, and runtime registration are not calculated.
|
|
422
438
|
- Methods, variable declarations, enums, nested declarations, and JavaScript files are not included in symbol analysis.
|
|
423
439
|
- Formatting or comment changes inside a declaration can be counted as symbol modifications.
|
|
424
|
-
- Impact analysis covers
|
|
425
|
-
|
|
440
|
+
- Impact analysis covers static `import`, side-effect imports, `export ... from`, and
|
|
441
|
+
`import = require()` in `.ts` and `.tsx` files. Local aliases use the nearest `tsconfig.json` or
|
|
442
|
+
`jsconfig.json`, with relative `extends` support.
|
|
443
|
+
- Package-based config `extends`, project references, arbitrary config filenames, package
|
|
444
|
+
`imports`/`exports`, external package imports, dynamic `import()`, and ordinary `require()` are
|
|
445
|
+
not included in the graph.
|
|
426
446
|
- The graph is built from files at the comparison result point: the working tree or head ref. Impact from historical imports that referenced a deleted module cannot be calculated yet.
|
|
427
447
|
- Source files used for impact analysis are limited to 1 MiB per file, and symbolic links are not read.
|
|
428
448
|
- IntentPatch does not infer expected paths from natural-language intent. Scope decisions use the contract's explicit `include` and `allow` patterns.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ImpactAnalysisIssue } from "../../domain/impact.js";
|
|
2
|
+
import type { AnalysisTarget } from "../../domain/report.js";
|
|
3
|
+
/** 분석 결과점에서 해석 가능한 비상대 경로 import를 찾는 언어별 출력 포트다. */
|
|
4
|
+
export interface ModuleAliasResolver {
|
|
5
|
+
prepare(target: AnalysisTarget, repositoryPaths: readonly string[], sourcePaths: ReadonlySet<string>): Promise<PreparedModuleAliasResolver>;
|
|
6
|
+
}
|
|
7
|
+
export interface PreparedModuleAliasResolver {
|
|
8
|
+
readonly issues: readonly ImpactAnalysisIssue[];
|
|
9
|
+
resolve(importer: string, specifier: string): string | undefined;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=module-alias-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-alias-resolver.d.ts","sourceRoot":"","sources":["../../../src/application/ports/module-alias-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CACL,MAAM,EAAE,cAAc,EACtB,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAClE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-alias-resolver.js","sourceRoot":"","sources":["../../../src/application/ports/module-alias-resolver.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** npm lockfile의 설치 위치를 기준으로 비교할 수 있는 패키지 정보다. */
|
|
2
|
+
export interface LockedPackage {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly version: string;
|
|
6
|
+
readonly rootDirect: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type PackageLockParseResult = Readonly<{
|
|
9
|
+
ok: true;
|
|
10
|
+
packages: ReadonlyMap<string, LockedPackage>;
|
|
11
|
+
}> | Readonly<{
|
|
12
|
+
ok: false;
|
|
13
|
+
kind: "invalid" | "unsupported";
|
|
14
|
+
reason: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** 패키지 매니저의 파일 형식을 분석 규칙에서 분리하는 입력 포트다. */
|
|
17
|
+
export interface PackageLockParser {
|
|
18
|
+
parse(content: string): PackageLockParseResult;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=package-lock-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-lock-parser.d.ts","sourceRoot":"","sources":["../../../src/application/ports/package-lock-parser.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,sBAAsB,GAC9B,QAAQ,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CAAE,CAAC,GACpE,QAAQ,CAAC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7E,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAAC;CAChD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-lock-parser.js","sourceRoot":"","sources":["../../../src/application/ports/package-lock-parser.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from "../../domain/finding.js";
|
|
2
|
+
import type { AnalysisContext, AnalysisRule } from "../ports/analysis-rule.js";
|
|
3
|
+
import type { FileSnapshotSource } from "../ports/file-snapshot-source.js";
|
|
4
|
+
import type { PackageLockParser } from "../ports/package-lock-parser.js";
|
|
5
|
+
/** 선언 버전이 아닌, npm이 실제로 고정한 패키지 버전과 설치 위치의 변화를 분석한다. */
|
|
6
|
+
export declare class NpmLockfileRule implements AnalysisRule {
|
|
7
|
+
private readonly snapshots;
|
|
8
|
+
private readonly parser;
|
|
9
|
+
constructor(snapshots: FileSnapshotSource, parser: PackageLockParser);
|
|
10
|
+
analyze(context: AnalysisContext): Promise<readonly Finding[]>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=npm-lockfile-rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-lockfile-rule.d.ts","sourceRoot":"","sources":["../../../src/application/rules/npm-lockfile-rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAIxF,uDAAuD;AACvD,qBAAa,eAAgB,YAAW,YAAY;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,SAAS,EAAE,kBAAkB,EAC7B,MAAM,EAAE,iBAAiB;IAG/B,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC;CAiC5E"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const lockfilePath = "package-lock.json";
|
|
2
|
+
/** 선언 버전이 아닌, npm이 실제로 고정한 패키지 버전과 설치 위치의 변화를 분석한다. */
|
|
3
|
+
export class NpmLockfileRule {
|
|
4
|
+
snapshots;
|
|
5
|
+
parser;
|
|
6
|
+
constructor(snapshots, parser) {
|
|
7
|
+
this.snapshots = snapshots;
|
|
8
|
+
this.parser = parser;
|
|
9
|
+
}
|
|
10
|
+
async analyze(context) {
|
|
11
|
+
if (!context.changes.files.some((change) => change.path === lockfilePath || change.previousPath === lockfilePath)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const snapshot = await this.snapshots.read(context.target, lockfilePath);
|
|
15
|
+
const before = snapshot.before === undefined ? undefined : this.parser.parse(snapshot.before);
|
|
16
|
+
const after = snapshot.after === undefined ? undefined : this.parser.parse(snapshot.after);
|
|
17
|
+
const errors = [
|
|
18
|
+
...(before !== undefined && !before.ok ? [parseFinding("base", before)] : []),
|
|
19
|
+
...(after !== undefined && !after.ok ? [parseFinding("current", after)] : []),
|
|
20
|
+
];
|
|
21
|
+
if (errors.length > 0) {
|
|
22
|
+
return errors;
|
|
23
|
+
}
|
|
24
|
+
if (before === undefined && after === undefined) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
if (before === undefined && after?.ok) {
|
|
28
|
+
return [lockfilePresenceFinding("added", after.packages.size)];
|
|
29
|
+
}
|
|
30
|
+
if (after === undefined && before?.ok) {
|
|
31
|
+
return [lockfilePresenceFinding("removed", before.packages.size)];
|
|
32
|
+
}
|
|
33
|
+
if (before?.ok && after?.ok) {
|
|
34
|
+
return comparePackages(before.packages, after.packages);
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function comparePackages(before, after) {
|
|
40
|
+
const paths = [...new Set([...before.keys(), ...after.keys()])].sort();
|
|
41
|
+
const findings = [];
|
|
42
|
+
for (const path of paths) {
|
|
43
|
+
const previous = before.get(path);
|
|
44
|
+
const current = after.get(path);
|
|
45
|
+
if (previous === undefined && current !== undefined) {
|
|
46
|
+
findings.push({
|
|
47
|
+
ruleId: "dependency/locked-package-added",
|
|
48
|
+
severity: "low",
|
|
49
|
+
title: "Resolved package added",
|
|
50
|
+
description: `${current.name}@${current.version} was added at ${path}.`,
|
|
51
|
+
file: lockfilePath,
|
|
52
|
+
evidence: packageEvidence(current),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else if (previous !== undefined && current === undefined) {
|
|
56
|
+
findings.push({
|
|
57
|
+
ruleId: "dependency/locked-package-removed",
|
|
58
|
+
severity: "low",
|
|
59
|
+
title: "Resolved package removed",
|
|
60
|
+
description: `${previous.name}@${previous.version} was removed from ${path}.`,
|
|
61
|
+
file: lockfilePath,
|
|
62
|
+
evidence: packageEvidence(previous),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else if (previous !== undefined &&
|
|
66
|
+
current !== undefined &&
|
|
67
|
+
previous.version !== current.version) {
|
|
68
|
+
findings.push({
|
|
69
|
+
ruleId: "dependency/locked-version-changed",
|
|
70
|
+
severity: "low",
|
|
71
|
+
title: "Resolved package version changed",
|
|
72
|
+
description: `${current.name} at ${path} changed from ${previous.version} to ${current.version}.`,
|
|
73
|
+
file: lockfilePath,
|
|
74
|
+
evidence: { ...packageEvidence(current), from: previous.version, to: current.version },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return findings;
|
|
79
|
+
}
|
|
80
|
+
function packageEvidence(item) {
|
|
81
|
+
return {
|
|
82
|
+
package: item.name,
|
|
83
|
+
version: item.version,
|
|
84
|
+
installPath: item.path,
|
|
85
|
+
rootDirect: item.rootDirect,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function parseFinding(side, result) {
|
|
89
|
+
const unsupported = result.kind === "unsupported";
|
|
90
|
+
return {
|
|
91
|
+
ruleId: unsupported ? "dependency/unsupported-lockfile" : "dependency/invalid-lockfile",
|
|
92
|
+
severity: unsupported ? "low" : "high",
|
|
93
|
+
title: unsupported ? "Unsupported npm lockfile" : "Invalid npm lockfile",
|
|
94
|
+
description: `Could not analyze the ${side} package-lock.json: ${result.reason}`,
|
|
95
|
+
file: lockfilePath,
|
|
96
|
+
evidence: { side, reason: result.reason },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function lockfilePresenceFinding(kind, packageCount) {
|
|
100
|
+
return {
|
|
101
|
+
ruleId: `dependency/lockfile-${kind}`,
|
|
102
|
+
severity: "low",
|
|
103
|
+
title: kind === "added" ? "npm lockfile added" : "npm lockfile removed",
|
|
104
|
+
description: `package-lock.json was ${kind} with ${packageCount} resolved packages.`,
|
|
105
|
+
file: lockfilePath,
|
|
106
|
+
evidence: { packageCount },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=npm-lockfile-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-lockfile-rule.js","sourceRoot":"","sources":["../../../src/application/rules/npm-lockfile-rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Finding } from "../../domain/finding.js";
|
|
2
|
+
import type { AnalysisContext, AnalysisRule } from "../ports/analysis-rule.js";
|
|
3
|
+
import type { FileSnapshotSource } from "../ports/file-snapshot-source.js";
|
|
4
|
+
/** npm workspaces에 속한 변경 manifest를 찾아 기존 직접 dependency 규칙으로 분석한다. */
|
|
5
|
+
export declare class NpmWorkspaceDependencyRule implements AnalysisRule {
|
|
6
|
+
private readonly snapshots;
|
|
7
|
+
constructor(snapshots: FileSnapshotSource);
|
|
8
|
+
analyze(context: AnalysisContext): Promise<readonly Finding[]>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=npm-workspace-dependency-rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-workspace-dependency-rule.d.ts","sourceRoot":"","sources":["../../../src/application/rules/npm-workspace-dependency-rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAW3E,qEAAqE;AACrE,qBAAa,0BAA2B,YAAW,YAAY;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,kBAAkB;IAEpD,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC;CAwF5E"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { matchesPathPattern } from "../services/path-pattern.js";
|
|
2
|
+
import { PackageDependencyRule } from "./package-dependency-rule.js";
|
|
3
|
+
const rootManifestPath = "package.json";
|
|
4
|
+
const manifestSuffix = "/package.json";
|
|
5
|
+
/** npm workspaces에 속한 변경 manifest를 찾아 기존 직접 dependency 규칙으로 분석한다. */
|
|
6
|
+
export class NpmWorkspaceDependencyRule {
|
|
7
|
+
snapshots;
|
|
8
|
+
constructor(snapshots) {
|
|
9
|
+
this.snapshots = snapshots;
|
|
10
|
+
}
|
|
11
|
+
async analyze(context) {
|
|
12
|
+
const manifestChanges = context.changes.files.filter(isNestedManifestChange);
|
|
13
|
+
const rootChanged = context.changes.files.some((change) => change.path === rootManifestPath || change.previousPath === rootManifestPath);
|
|
14
|
+
if (manifestChanges.length === 0 && !rootChanged) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const snapshot = await this.snapshots.read(context.target, rootManifestPath);
|
|
18
|
+
const before = parseWorkspaceConfig(snapshot.before);
|
|
19
|
+
const after = parseWorkspaceConfig(snapshot.after);
|
|
20
|
+
const errors = [
|
|
21
|
+
...(!before.ok ? [configFinding("base", before)] : []),
|
|
22
|
+
...(!after.ok ? [configFinding("current", after)] : []),
|
|
23
|
+
];
|
|
24
|
+
if (errors.length > 0) {
|
|
25
|
+
return errors;
|
|
26
|
+
}
|
|
27
|
+
if (!before.ok || !after.ok) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const findings = [];
|
|
31
|
+
if (rootChanged && JSON.stringify(before.patterns) !== JSON.stringify(after.patterns)) {
|
|
32
|
+
findings.push({
|
|
33
|
+
ruleId: "dependency/workspace-config-changed",
|
|
34
|
+
severity: "low",
|
|
35
|
+
title: "npm workspace configuration changed",
|
|
36
|
+
description: "The root package.json workspace patterns changed.",
|
|
37
|
+
file: rootManifestPath,
|
|
38
|
+
evidence: {
|
|
39
|
+
from: JSON.stringify(before.patterns),
|
|
40
|
+
to: JSON.stringify(after.patterns),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const results = await Promise.all(manifestChanges.map(async (change) => {
|
|
45
|
+
const previousPath = change.kind === "renamed" ? (change.previousPath ?? change.path) : change.path;
|
|
46
|
+
const currentPath = change.path;
|
|
47
|
+
const wasWorkspace = change.kind !== "added" &&
|
|
48
|
+
change.kind !== "copied" &&
|
|
49
|
+
isConfiguredWorkspace(previousPath, before.patterns);
|
|
50
|
+
const isWorkspace = change.kind !== "deleted" && isConfiguredWorkspace(currentPath, after.patterns);
|
|
51
|
+
if (!wasWorkspace && !isWorkspace) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const manifestPath = isNestedManifestPath(currentPath) ? currentPath : previousPath;
|
|
55
|
+
const workspacePath = manifestPath.slice(0, -manifestSuffix.length);
|
|
56
|
+
const membershipFinding = change.kind === "renamed" && wasWorkspace !== isWorkspace
|
|
57
|
+
? [
|
|
58
|
+
{
|
|
59
|
+
ruleId: "dependency/workspace-membership-changed",
|
|
60
|
+
severity: "low",
|
|
61
|
+
title: "npm workspace membership changed",
|
|
62
|
+
description: `${previousPath} moved to ${currentPath}, changing workspace membership.`,
|
|
63
|
+
file: currentPath,
|
|
64
|
+
evidence: {
|
|
65
|
+
previousPath,
|
|
66
|
+
currentPath,
|
|
67
|
+
fromWorkspace: wasWorkspace,
|
|
68
|
+
toWorkspace: isWorkspace,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
: [];
|
|
73
|
+
const dependencyFindings = await new PackageDependencyRule(this.snapshots, manifestPath).analyze(context);
|
|
74
|
+
return [
|
|
75
|
+
...membershipFinding,
|
|
76
|
+
...dependencyFindings.map((finding) => ({
|
|
77
|
+
...finding,
|
|
78
|
+
evidence: { ...finding.evidence, workspace: workspacePath },
|
|
79
|
+
})),
|
|
80
|
+
];
|
|
81
|
+
}));
|
|
82
|
+
return [...findings, ...results.flat()];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isNestedManifestChange(change) {
|
|
86
|
+
return isNestedManifestPath(change.path) || isNestedManifestPath(change.previousPath);
|
|
87
|
+
}
|
|
88
|
+
function isNestedManifestPath(path) {
|
|
89
|
+
return path?.endsWith(manifestSuffix) === true;
|
|
90
|
+
}
|
|
91
|
+
function isConfiguredWorkspace(path, patterns) {
|
|
92
|
+
if (!isNestedManifestPath(path)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const directory = path.slice(0, -manifestSuffix.length);
|
|
96
|
+
return patterns.some((pattern) => matchesPathPattern(directory, pattern));
|
|
97
|
+
}
|
|
98
|
+
function parseWorkspaceConfig(content) {
|
|
99
|
+
if (content === undefined) {
|
|
100
|
+
return { ok: true, patterns: [] };
|
|
101
|
+
}
|
|
102
|
+
let manifest;
|
|
103
|
+
try {
|
|
104
|
+
manifest = JSON.parse(content);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return { ok: false, kind: "invalid", reason: "The root package.json is not valid JSON." };
|
|
108
|
+
}
|
|
109
|
+
if (!isRecord(manifest)) {
|
|
110
|
+
return { ok: false, kind: "invalid", reason: "The root package.json must be an object." };
|
|
111
|
+
}
|
|
112
|
+
if (manifest.workspaces === undefined) {
|
|
113
|
+
return { ok: true, patterns: [] };
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(manifest.workspaces)) {
|
|
116
|
+
return { ok: false, kind: "invalid", reason: "workspaces must be an array of paths." };
|
|
117
|
+
}
|
|
118
|
+
const patterns = [];
|
|
119
|
+
for (const rawPattern of manifest.workspaces) {
|
|
120
|
+
if (typeof rawPattern !== "string" || rawPattern.trim().length === 0) {
|
|
121
|
+
return { ok: false, kind: "invalid", reason: "Each workspace path must be a string." };
|
|
122
|
+
}
|
|
123
|
+
const pattern = rawPattern.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
124
|
+
if (pattern.length === 0 || pattern.startsWith("/") || pattern.split("/").includes("..")) {
|
|
125
|
+
return { ok: false, kind: "invalid", reason: `Invalid workspace path: ${rawPattern}.` };
|
|
126
|
+
}
|
|
127
|
+
if (["!", "{", "}", "[", "]", "\\"].some((character) => pattern.includes(character))) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
kind: "unsupported",
|
|
131
|
+
reason: `Unsupported workspace pattern: ${rawPattern}. Only *, **, and ? globs are supported.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
patterns.push(pattern);
|
|
135
|
+
}
|
|
136
|
+
return { ok: true, patterns };
|
|
137
|
+
}
|
|
138
|
+
function configFinding(side, result) {
|
|
139
|
+
return {
|
|
140
|
+
ruleId: result.kind === "invalid"
|
|
141
|
+
? "dependency/invalid-workspace-config"
|
|
142
|
+
: "dependency/unsupported-workspace-pattern",
|
|
143
|
+
severity: result.kind === "invalid" ? "high" : "low",
|
|
144
|
+
title: result.kind === "invalid"
|
|
145
|
+
? "Invalid npm workspace configuration"
|
|
146
|
+
: "Unsupported npm workspace pattern",
|
|
147
|
+
description: `Could not analyze the ${side} npm workspaces: ${result.reason}`,
|
|
148
|
+
file: rootManifestPath,
|
|
149
|
+
evidence: { side, reason: result.reason },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function isRecord(value) {
|
|
153
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=npm-workspace-dependency-rule.js.map
|