isthmus-cli 0.4.0 → 0.5.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 +31 -9
- package/README.md +36 -16
- package/dist/cli/atomic-write.d.ts +5 -2
- package/dist/cli/atomic-write.js +10 -3
- package/dist/cli/atomic-write.js.map +1 -1
- package/dist/cli/check-command.d.ts +2 -25
- package/dist/cli/check-command.js +27 -179
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/command-support.d.ts +48 -0
- package/dist/cli/command-support.js +144 -0
- package/dist/cli/command-support.js.map +1 -0
- package/dist/cli/diff-command.d.ts +1 -1
- package/dist/cli/diff-command.js +1 -1
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/graph-command.d.ts +1 -1
- package/dist/cli/graph-command.js +15 -14
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/main.js +20 -13
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parse-arguments.d.ts +18 -0
- package/dist/cli/parse-arguments.js +48 -0
- package/dist/cli/parse-arguments.js.map +1 -0
- package/dist/cli/query-command.d.ts +1 -1
- package/dist/cli/query-command.js +30 -10
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.d.ts +1 -1
- package/dist/cli/retentions-command.js +11 -10
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/exchange/parse.js +12 -3
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.js +6 -1
- package/dist/join/join.js.map +1 -1
- package/dist/report/diff.d.ts +40 -33
- package/dist/report/diff.js +4 -1
- package/dist/report/diff.js.map +1 -1
- package/dist/report/query.js +9 -2
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.d.ts +7 -0
- package/dist/report/retentions.js +20 -5
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/sarif.d.ts +9 -2
- package/dist/report/sarif.js +4 -10
- package/dist/report/sarif.js.map +1 -1
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -28,14 +28,22 @@ isthmus는 각 언어 도구가 내보낸 **브리지 사실**(채널 이름 ·
|
|
|
28
28
|
|
|
29
29
|
## 상태
|
|
30
30
|
|
|
31
|
-
**0.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
**0.5.0.** CLI 사용성을 통일하고 진단 가독성을 높였다: 모든 명령에서 옵션이 입력
|
|
32
|
+
파일 앞뒤 어디에 와도 동작하고(`-`로 시작하는 경로는 `--` 구분자 뒤에 쓴다),
|
|
33
|
+
`-h`/`--help`는 임의 위치에서 이기며 `help <command>`가 명령별 사용법을 낸다.
|
|
34
|
+
조인이 보류되면 어느 명령이든 관찰한 fact 수·문서 수를 함께 알리고
|
|
35
|
+
(check·graph·query·retentions·diff), query의 `notFound`·`ambiguous`는 원인 한 줄을
|
|
36
|
+
stderr에 출력한다. query `qualifiedName`은 `:`까지 이스케이프해 첫 `:`와 `#` 기준
|
|
37
|
+
분해가 가역이다. 내부로는 공유 CLI 인프라를 check-command에서 분리하고 SARIF 지문
|
|
38
|
+
해싱을 주입해 report 계층이 Node 내장 모듈에 의존하지 않으며, diff 문서 형태를
|
|
39
|
+
`BridgeDiffDocument`로 명시했다. 출력 문서·종료 코드·필드는 불변이고 0.4.1과 값이
|
|
40
|
+
달라지는 것은 이름에 `:`를 포함하는 채널·메서드의 `qualifiedName`뿐이다.
|
|
41
|
+
0.4.0의 추가 사항 — GitHub code scanning용 check SARIF 2.1.0 렌더링(`check --format sarif`,
|
|
42
|
+
additive, 소스 줄 이동에 강한 논리 키 지문), 브리지가 없는 프로젝트와 아무것도
|
|
43
|
+
관찰하지 못한 실행을 구분하는 check summary 관찰량(`observedFacts`·`observedLimitations`),
|
|
44
|
+
다중 호출자 보존 근거(`evidence.callers` — 근거당 상한·명시적 `callersOmitted` 계수,
|
|
45
|
+
단일 호출자는 기존과 바이트 동일), 인덱스 없이 빌드된 Objective-C 핸들러의 usr 없는
|
|
46
|
+
`qualifiedName` 신원 — 과 0.3.0의 핵심 계약·명령은 그대로 유지된다. 외부 입력·
|
|
39
47
|
혼합 target·그래프 크기·Dart/Swift Phase 0 추출 경계는 fail-closed를 유지하고,
|
|
40
48
|
조인하지 못한 사실은 소비자 쪽에서 다시 세며 근거를 만들지 못한 보존 대상은 조용히
|
|
41
49
|
사라지는 대신 실패로 보고한다. 수신 측이 신고한 분석 공백은 불일치가 아니라 판정
|
|
@@ -104,6 +112,14 @@ isthmus --version
|
|
|
104
112
|
isthmus check dart-bridges.json swift-bridges.json --strict
|
|
105
113
|
```
|
|
106
114
|
|
|
115
|
+
옵션은 어떤 명령에서든 입력 파일 앞뒤 어디에 와도 된다
|
|
116
|
+
(`isthmus graph --format dot dart-bridges.json swift-bridges.json`도 동작).
|
|
117
|
+
`-`로 시작하는 값은 항상 다음 옵션으로 읽히므로, `-`로 시작하는 경로나 이름은
|
|
118
|
+
옵션 해석을 끝내는 `--` 구분자 뒤에 쓴다:
|
|
119
|
+
`isthmus query -- -unusual-name dart-bridges.json swift-bridges.json`.
|
|
120
|
+
`-h`/`--help`는 어느 위치에 있든 도움말을 내고, `isthmus help <command>`로 명령의
|
|
121
|
+
사용법을 볼 수 있으며, 모르는 명령은 루트 도움말을 출력한다.
|
|
122
|
+
|
|
107
123
|
### SARIF 출력
|
|
108
124
|
|
|
109
125
|
check 결과를 GitHub code scanning(또는 그 밖의 SARIF 2.1.0 소비자)에 올리려면
|
|
@@ -163,7 +179,8 @@ cartograph dead --external-retentions external-retentions.json
|
|
|
163
179
|
여러 위치에서 호출하면 근거가 전체 호출 위치를 `callers`로 실고(대표 `caller`는 옛
|
|
164
180
|
소비자를 위해 유지), 근거당 100개 상한을 넘은 호출은 조용히 버리지 않고
|
|
165
181
|
`callersOmitted`로 계수를 밝힌다. `mixed-targets` 문서는 v1에서 사실별 target을
|
|
166
|
-
복원할 수 없어 모든 소비 명령이 종료 코드 2로 조인을
|
|
182
|
+
복원할 수 없어 모든 소비 명령이 종료 코드 2로 조인을 보류하며, 이때 몇 개의 문서에서
|
|
183
|
+
관찰한 fact 몇 개가 조인되지 못했는지를 함께 알린다. 먼저 생산 단계에서
|
|
167
184
|
target별 문서로 분리해야 한다.
|
|
168
185
|
|
|
169
186
|
cartograph는 Swift 심볼만 보존하므로 `--for cartograph`는 수신 측 Swift 문서를 최소
|
|
@@ -216,6 +233,11 @@ isthmus graph dart-bridges.json swift-bridges.json --format mermaid
|
|
|
216
233
|
|
|
217
234
|
`query`는 같은 메서드가 여러 채널에 있으면 후보를 반환하고 임의로 고르지 않는다.
|
|
218
235
|
반환된 `qualifiedName`을 같은 subject 자리에 넣어 정확한 후보를 다시 조회할 수 있다.
|
|
236
|
+
`qualifiedName`은 `target:` 뒤에 퍼센트 이스케이프된 구성 요소가 붙는 형태로
|
|
237
|
+
`%`·`#`·`:`가 모두 이스케이프되므로, 첫 `:`와 `#` 기준으로 나눈 뒤 디코딩하면
|
|
238
|
+
채널·메서드 이름이 항상 되돌아온다. `notFound`·`ambiguous` 질의는 종료 코드 64와
|
|
239
|
+
함께 원인 한 줄을 stderr에 출력하므로, 스크립트가 stdout을 파싱하지 않고도
|
|
240
|
+
호출 오류와 이름 부재를 구분할 수 있다.
|
|
219
241
|
`graph`는 매치된 간선만 내보내며, 입력의 `limitations`를 JSON 필드 또는 DOT/Mermaid
|
|
220
242
|
주석으로 보존한다. 증거의 Cartesian 곱이 100,000개 간선을 넘으면 메모리 폭주를 막기
|
|
221
243
|
위해 종료 코드 2로 실패한다.
|
package/README.md
CHANGED
|
@@ -30,20 +30,27 @@ calls it over channel `com.example/camera`".
|
|
|
30
30
|
|
|
31
31
|
## Status
|
|
32
32
|
|
|
33
|
-
**0.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
**0.5.0.** Makes the CLI consistent and its diagnostics more legible: options may appear
|
|
34
|
+
before or after the input files in every command (with a `--` escape for `-`-leading paths),
|
|
35
|
+
`-h`/`--help` wins from any position alongside `help <command>`, deferred joins report how
|
|
36
|
+
many observed facts could not be joined (check, graph, query, retentions, and diff),
|
|
37
|
+
`notFound`/`ambiguous` queries state their cause on stderr, and query `qualifiedName` escapes
|
|
38
|
+
`:` so splitting on the first `:` and `#` always recovers the names. Internally, shared CLI
|
|
39
|
+
infrastructure moved out of check-command, the SARIF fingerprint hash is injected so the
|
|
40
|
+
report layer stays free of Node built-ins, and the diff document shape is named
|
|
41
|
+
(`BridgeDiffDocument`). Output documents, exit codes, and fields are unchanged — the only
|
|
42
|
+
value that differs from 0.4.1 is the `qualifiedName` of channels or methods whose names
|
|
43
|
+
contain `:`.
|
|
44
|
+
The 0.4.0 additions — SARIF 2.1.0 rendering of check results for GitHub code scanning
|
|
45
|
+
(`check --format sarif`, additive, with logic-key fingerprints that survive source line
|
|
46
|
+
moves), observation volume in the check summary (`observedFacts`, `observedLimitations`),
|
|
47
|
+
multi-caller retention evidence (`evidence.callers` with a per-retention cap and an explicit
|
|
48
|
+
`callersOmitted` count, byte-identical for single callers), and usr-less `qualifiedName`
|
|
49
|
+
identity for Objective-C handlers built without an index — and the core 0.3.0 contract and
|
|
50
|
+
commands remain intact. isthmus keeps fail-closed behavior for external input, mixed targets,
|
|
51
|
+
graph size, and the Dart/Swift Phase 0 extraction boundary. Facts that could not be joined
|
|
52
|
+
are re-counted on the consumer side, and retention subjects whose evidence cannot be built
|
|
53
|
+
are refused loudly, so neither disappears silently. Coverage gaps a receiver reports about
|
|
47
54
|
itself come back as undecidable, not as mismatches, and gap mitigation never leaks across
|
|
48
55
|
targets. Next: dogfooding it on a real Flutter app, and React Native support.
|
|
49
56
|
|
|
@@ -113,6 +120,13 @@ To make CI fail when bridge errors exist, add `--strict`:
|
|
|
113
120
|
isthmus check dart-bridges.json swift-bridges.json --strict
|
|
114
121
|
```
|
|
115
122
|
|
|
123
|
+
Options may appear before or after the input files in any command (`isthmus graph --format dot
|
|
124
|
+
dart-bridges.json swift-bridges.json` works). Because a value that starts with `-` is always
|
|
125
|
+
read as the next option, paths or names that begin with `-` go after a `--` separator, which
|
|
126
|
+
ends option parsing: `isthmus query -- -unusual-name dart-bridges.json swift-bridges.json`.
|
|
127
|
+
`-h`/`--help` shows help from any position, `isthmus help <command>` names a command's usage,
|
|
128
|
+
and an unknown command prints the root help.
|
|
129
|
+
|
|
116
130
|
### SARIF output
|
|
117
131
|
|
|
118
132
|
To upload check results to GitHub code scanning (or any SARIF 2.1.0 consumer), ask for SARIF
|
|
@@ -176,7 +190,8 @@ invoked from several caller locations, the evidence carries all of them in `call
|
|
|
176
190
|
representative first `caller` stays for older consumers) and counts any entries beyond the
|
|
177
191
|
100-per-retention cap in `callersOmitted` instead of dropping them silently. A
|
|
178
192
|
`mixed-targets` document cannot have per-fact targets restored in v1, so every consuming
|
|
179
|
-
command defers the join with exit code 2
|
|
193
|
+
command defers the join with exit code 2, reporting how many observed facts across how many
|
|
194
|
+
documents could not be joined; split such a document per target at production time
|
|
180
195
|
first.
|
|
181
196
|
|
|
182
197
|
cartograph retains Swift symbols only, so `--for cartograph` requires at least one
|
|
@@ -231,7 +246,12 @@ isthmus graph dart-bridges.json swift-bridges.json --format mermaid
|
|
|
231
246
|
```
|
|
232
247
|
|
|
233
248
|
When the same method exists on several channels, `query` returns candidates instead of picking
|
|
234
|
-
one; feed a returned `qualifiedName` back into the same subject position to disambiguate.
|
|
249
|
+
one; feed a returned `qualifiedName` back into the same subject position to disambiguate. A
|
|
250
|
+
`qualifiedName` is `target:` followed by percent-escaped components — `%`, `#`, and `:` are
|
|
251
|
+
escaped — so splitting on the first `:` and on `#` and decoding the parts always recovers the
|
|
252
|
+
channel and method names. A `notFound` or `ambiguous` query exits 64 and prints a one-line
|
|
253
|
+
cause on stderr, so a script can tell a bad invocation from a missing name without parsing
|
|
254
|
+
stdout.
|
|
235
255
|
`graph` emits matched edges only and preserves the input `limitations` as a JSON field or as
|
|
236
256
|
DOT/Mermaid comments. If the Cartesian product of evidence would exceed 100,000 edges, the
|
|
237
257
|
command fails with exit code 2 to prevent a memory blowup.
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* 같은 디렉터리의 배타적 임시 파일에 쓰고 rename으로 원자 교체한다.
|
|
3
3
|
*
|
|
4
4
|
* 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이
|
|
5
|
-
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서
|
|
5
|
+
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이지만 fsync가
|
|
6
|
+
* 없어 전원 손실까지의 내구성은 보장하지 않는다.
|
|
6
7
|
*
|
|
7
8
|
* 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로
|
|
8
9
|
* 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일
|
|
9
|
-
* 덮어쓰기로 이어지지 않는다.
|
|
10
|
+
* 덮어쓰기로 이어지지 않는다. 소유자만 읽을 수 있는 모드로 쓰고, 쓰기에
|
|
11
|
+
* 실패하면 임시 파일만 지운다. rename은 대상이 심링크여도 링크 자체를
|
|
12
|
+
* 교체하므로 링크 대상을 건드리지 않는다.
|
|
10
13
|
*/
|
|
11
14
|
export declare function writeTextAtomically(path: string, text: string, temporaryPathFor?: (path: string) => string): Promise<void>;
|
package/dist/cli/atomic-write.js
CHANGED
|
@@ -4,16 +4,23 @@ import { rename, rm, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
* 같은 디렉터리의 배타적 임시 파일에 쓰고 rename으로 원자 교체한다.
|
|
5
5
|
*
|
|
6
6
|
* 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이
|
|
7
|
-
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서
|
|
7
|
+
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이지만 fsync가
|
|
8
|
+
* 없어 전원 손실까지의 내구성은 보장하지 않는다.
|
|
8
9
|
*
|
|
9
10
|
* 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로
|
|
10
11
|
* 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일
|
|
11
|
-
* 덮어쓰기로 이어지지 않는다.
|
|
12
|
+
* 덮어쓰기로 이어지지 않는다. 소유자만 읽을 수 있는 모드로 쓰고, 쓰기에
|
|
13
|
+
* 실패하면 임시 파일만 지운다. rename은 대상이 심링크여도 링크 자체를
|
|
14
|
+
* 교체하므로 링크 대상을 건드리지 않는다.
|
|
12
15
|
*/
|
|
13
16
|
export async function writeTextAtomically(path, text, temporaryPathFor = unpredictableTemporaryPath) {
|
|
14
17
|
const temporaryPath = temporaryPathFor(path);
|
|
15
18
|
try {
|
|
16
|
-
await writeFile(temporaryPath, text, {
|
|
19
|
+
await writeFile(temporaryPath, text, {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
mode: 0o600,
|
|
22
|
+
flag: 'wx',
|
|
23
|
+
});
|
|
17
24
|
await rename(temporaryPath, path);
|
|
18
25
|
}
|
|
19
26
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/cli/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/cli/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,IAAY,EACZ,mBAA6C,0BAA0B;IAEvE,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE;YACnC,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gDAAgD;QAChD,6BAA6B;QAC7B,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,wCAAwC;AACxC,SAAS,0BAA0B,CAAC,IAAY;IAC9C,OAAO,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;AACzE,CAAC","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { rename, rm, writeFile } from 'node:fs/promises';\n\n/**\n * 같은 디렉터리의 배타적 임시 파일에 쓰고 rename으로 원자 교체한다.\n *\n * 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이\n * 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이지만 fsync가\n * 없어 전원 손실까지의 내구성은 보장하지 않는다.\n *\n * 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로\n * 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일\n * 덮어쓰기로 이어지지 않는다. 소유자만 읽을 수 있는 모드로 쓰고, 쓰기에\n * 실패하면 임시 파일만 지운다. rename은 대상이 심링크여도 링크 자체를\n * 교체하므로 링크 대상을 건드리지 않는다.\n */\nexport async function writeTextAtomically(\n path: string,\n text: string,\n temporaryPathFor: (path: string) => string = unpredictableTemporaryPath,\n): Promise<void> {\n const temporaryPath = temporaryPathFor(path);\n try {\n await writeFile(temporaryPath, text, {\n encoding: 'utf8',\n mode: 0o600,\n flag: 'wx',\n });\n await rename(temporaryPath, path);\n } catch (error) {\n // EEXIST는 배타 생성이 거부된 것으로 임시 파일이 우리 것이 아니라는 뜻이다.\n // 남은 오류에서만 직접 만든 임시 파일을 지운다.\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n }\n throw error;\n }\n}\n\n/** 같은 디렉터리 안의 예측 불가능한 임시 파일 이름을 만든다. */\nfunction unpredictableTemporaryPath(path: string): string {\n return `${path}.${process.pid}.${randomBytes(12).toString('hex')}.tmp`;\n}\n"]}
|
|
@@ -1,31 +1,8 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
export type ReadTextFile = (path: string) => Promise<string>;
|
|
4
|
-
/** 파일 경로에 UTF-8 텍스트를 쓰는 주입 경계다. */
|
|
5
|
-
export type WriteTextFile = (path: string, text: string) => Promise<void>;
|
|
6
|
-
/** 생성 시각을 테스트 가능하게 주입하는 시계다. */
|
|
7
|
-
export type Clock = () => Date;
|
|
1
|
+
import { type Clock, type CommandResult, type ReadTextFile, type WriteTextFile } from './command-support.ts';
|
|
2
|
+
export type { Clock, CommandResult, ReadTextFile, WriteTextFile };
|
|
8
3
|
/** check가 내는 보고서 형식이다. SARIF는 isthmus 소유의 additive 출력이다. */
|
|
9
4
|
export type CheckOutputFormat = 'json' | 'sarif';
|
|
10
|
-
/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
|
|
11
|
-
export declare const MAX_INPUT_TEXT_LENGTH: number;
|
|
12
|
-
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
13
|
-
export declare const MAX_TOTAL_INPUT_TEXT_LENGTH: number;
|
|
14
|
-
/** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */
|
|
15
|
-
export interface CommandResult {
|
|
16
|
-
readonly standardOutput: string;
|
|
17
|
-
readonly standardError: string;
|
|
18
|
-
readonly exitCode: 0 | 1 | 2 | 64;
|
|
19
|
-
}
|
|
20
5
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
21
6
|
export declare function runCheckCommand(arguments_: readonly string[], readTextFile: ReadTextFile, writeTextFile?: WriteTextFile, now?: Clock, producerVersion?: string): Promise<CommandResult>;
|
|
22
|
-
/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
|
|
23
|
-
export declare function readBridgeDocuments(inputPaths: readonly string[], readTextFile: ReadTextFile): Promise<BridgeFactsDocument[]>;
|
|
24
|
-
/** 알려진 입력·조인 실패를 원인별 해결 방향을 담은 코드 2 결과로 바꾼다. */
|
|
25
|
-
export declare function inputFailureResult(error: unknown): CommandResult | undefined;
|
|
26
|
-
/** 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다. */
|
|
27
|
-
export declare function bridgeJoinDeferredError(): CommandResult;
|
|
28
|
-
/** 내부 결함을 입력 탓으로 돌리지 않는 경로 없는 코드 2 결과다. */
|
|
29
|
-
export declare function internalError(): CommandResult;
|
|
30
7
|
/** check 명령의 한 줄 사용법이다. */
|
|
31
8
|
export declare const checkUsage: string;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { applyBaseline, BridgeBaselineValidationError, createBaselineDocument, encodeBaselineDocument, MAX_BASELINE_ENTRIES, parseBaselineDocument, } from "../report/baseline.js";
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
3
|
+
import { applyBaseline, baselineEntryKey, BridgeBaselineValidationError, createBaselineDocument, encodeBaselineDocument, MAX_BASELINE_ENTRIES, parseBaselineDocument, } from "../report/baseline.js";
|
|
4
4
|
import { createCheckReport, encodeCheckReport } from "../report/check-report.js";
|
|
5
5
|
import { createSarifLog, encodeSarifLog, } from "../report/sarif.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
9
|
-
export const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;
|
|
6
|
+
import { bridgeJoinDeferredError, inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, readBridgeDocuments, } from "./command-support.js";
|
|
7
|
+
import { parseCommandArguments } from "./parse-arguments.js";
|
|
10
8
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
11
9
|
export async function runCheckCommand(arguments_, readTextFile, writeTextFile, now = () => new Date(), producerVersion) {
|
|
12
10
|
if (arguments_[0] !== 'check')
|
|
@@ -24,15 +22,16 @@ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, n
|
|
|
24
22
|
try {
|
|
25
23
|
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
26
24
|
const joined = joinBridgeDocuments(documents);
|
|
27
|
-
if (isBridgeJoinDeferred(joined))
|
|
28
|
-
return bridgeJoinDeferredError();
|
|
25
|
+
if (isBridgeJoinDeferred(joined)) {
|
|
26
|
+
return bridgeJoinDeferredError(joined.observedFacts, documents.length);
|
|
27
|
+
}
|
|
29
28
|
let report = createCheckReport(joined);
|
|
30
29
|
if (baselinePath !== undefined) {
|
|
31
30
|
const baseline = await readBaselineDocument(baselinePath, readTextFile);
|
|
32
31
|
report = applyBaseline(report, baseline.entries);
|
|
33
32
|
}
|
|
34
33
|
const standardOutput = format === 'sarif'
|
|
35
|
-
? encodeSarifLog(createSarifLog(report, producerVersion))
|
|
34
|
+
? encodeSarifLog(createSarifLog(report, producerVersion, issueFingerprint))
|
|
36
35
|
: encodeCheckReport(report);
|
|
37
36
|
if (updateBaselinePath !== undefined && writeTextFile !== undefined) {
|
|
38
37
|
await writeBaselineDocument(updateBaselinePath, report.issues, now(), writeTextFile);
|
|
@@ -44,60 +43,30 @@ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, n
|
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
|
-
return inputFailureResult(error) ??
|
|
46
|
+
return inputFailureResult(error) ?? baselineFailureResult(error)
|
|
47
|
+
?? internalError();
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */
|
|
51
51
|
function parseCheckOptions(rest) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const argument = rest[index];
|
|
59
|
-
if (argument === undefined)
|
|
60
|
-
return undefined;
|
|
61
|
-
if (argument === '--strict') {
|
|
62
|
-
strict = true;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (argument === '--format' || argument === '--baseline' || argument === '--update-baseline') {
|
|
66
|
-
const value = rest[index + 1];
|
|
67
|
-
if (value === undefined || value.startsWith('-'))
|
|
68
|
-
return undefined;
|
|
69
|
-
if (argument === '--format') {
|
|
70
|
-
if (format !== undefined || (value !== 'json' && value !== 'sarif')) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
format = value;
|
|
74
|
-
}
|
|
75
|
-
else if (argument === '--baseline') {
|
|
76
|
-
if (baselinePath !== undefined)
|
|
77
|
-
return undefined;
|
|
78
|
-
baselinePath = value;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
if (updateBaselinePath !== undefined)
|
|
82
|
-
return undefined;
|
|
83
|
-
updateBaselinePath = value;
|
|
84
|
-
}
|
|
85
|
-
index++;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (argument.startsWith('-'))
|
|
89
|
-
return undefined;
|
|
90
|
-
inputPaths.push(argument);
|
|
52
|
+
const parsed = parseCommandArguments(rest, ['--format', '--baseline', '--update-baseline'], ['--strict']);
|
|
53
|
+
if (parsed === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const format = parsed.valueFlags.get('--format');
|
|
56
|
+
if (format !== undefined && format !== 'json' && format !== 'sarif') {
|
|
57
|
+
return undefined;
|
|
91
58
|
}
|
|
59
|
+
const baselinePath = parsed.valueFlags.get('--baseline');
|
|
60
|
+
const updateBaselinePath = parsed.valueFlags.get('--update-baseline');
|
|
92
61
|
if (baselinePath !== undefined && updateBaselinePath !== undefined) {
|
|
93
62
|
return undefined;
|
|
94
63
|
}
|
|
95
64
|
return {
|
|
96
|
-
strict,
|
|
65
|
+
strict: parsed.booleanFlags.has('--strict'),
|
|
97
66
|
format: format ?? 'json',
|
|
98
67
|
baselinePath,
|
|
99
68
|
updateBaselinePath,
|
|
100
|
-
inputPaths,
|
|
69
|
+
inputPaths: [...parsed.positionals],
|
|
101
70
|
};
|
|
102
71
|
}
|
|
103
72
|
/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */
|
|
@@ -125,15 +94,6 @@ async function readBaselineDocument(path, readTextFile) {
|
|
|
125
94
|
}
|
|
126
95
|
return parseBaselineDocument(parsed);
|
|
127
96
|
}
|
|
128
|
-
/**
|
|
129
|
-
* 사용자 입력 JSON 파싱 실패인지 확인한다.
|
|
130
|
-
*
|
|
131
|
-
* 깊은 중첩은 SyntaxError가 아니라 RangeError(스택 초과)로 실패한다.
|
|
132
|
-
* 둘 다 입력 탓이라 내부 오류 메시지로 오분류하지 않는다.
|
|
133
|
-
*/
|
|
134
|
-
function isJsonParseFailure(error) {
|
|
135
|
-
return error instanceof SyntaxError || error instanceof RangeError;
|
|
136
|
-
}
|
|
137
97
|
/** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */
|
|
138
98
|
async function writeBaselineDocument(path, issues, generatedAt, writeTextFile) {
|
|
139
99
|
let document;
|
|
@@ -192,101 +152,8 @@ class BridgeBaselineWriteLimitError extends Error {
|
|
|
192
152
|
this.name = 'BridgeBaselineWriteLimitError';
|
|
193
153
|
}
|
|
194
154
|
}
|
|
195
|
-
/**
|
|
196
|
-
|
|
197
|
-
const documents = [];
|
|
198
|
-
let totalTextLength = 0;
|
|
199
|
-
for (const [index, path] of inputPaths.entries()) {
|
|
200
|
-
const inputPosition = index + 1;
|
|
201
|
-
let text;
|
|
202
|
-
try {
|
|
203
|
-
text = await readTextFile(path);
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
throw new BridgeInputReadError(inputPosition);
|
|
207
|
-
}
|
|
208
|
-
totalTextLength += text.length;
|
|
209
|
-
if (text.length > MAX_INPUT_TEXT_LENGTH ||
|
|
210
|
-
totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH) {
|
|
211
|
-
throw new BridgeInputLimitError(inputPosition);
|
|
212
|
-
}
|
|
213
|
-
// JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가
|
|
214
|
-
// 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.
|
|
215
|
-
let parsed;
|
|
216
|
-
try {
|
|
217
|
-
parsed = JSON.parse(text);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (isJsonParseFailure(error)) {
|
|
221
|
-
throw new BridgeInputJsonError(inputPosition);
|
|
222
|
-
}
|
|
223
|
-
throw error;
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
documents.push(parseBridgeFactsDocument(parsed));
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
if (error instanceof BridgeFactsValidationError) {
|
|
230
|
-
throw new BridgeInputContractError(inputPosition, error.message);
|
|
231
|
-
}
|
|
232
|
-
throw error;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return documents;
|
|
236
|
-
}
|
|
237
|
-
/** 실패한 입력의 1부터 시작하는 순서를 보존하는 입력 오류다. */
|
|
238
|
-
class BridgeInputError extends Error {
|
|
239
|
-
inputPosition;
|
|
240
|
-
constructor(name, inputPosition) {
|
|
241
|
-
super(`${name} at input ${inputPosition}`);
|
|
242
|
-
this.name = name;
|
|
243
|
-
this.inputPosition = inputPosition;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/** 입력 파일을 읽지 못한 경우를 구분한다. */
|
|
247
|
-
class BridgeInputReadError extends BridgeInputError {
|
|
248
|
-
constructor(inputPosition) {
|
|
249
|
-
super('BridgeInputReadError', inputPosition);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
/** 입력 텍스트가 JSON이 아닌 경우를 구분한다. */
|
|
253
|
-
class BridgeInputJsonError extends BridgeInputError {
|
|
254
|
-
constructor(inputPosition) {
|
|
255
|
-
super('BridgeInputJsonError', inputPosition);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
/** 입력 문서가 교환 계약을 어긴 경우와 그 이유를 구분한다. */
|
|
259
|
-
class BridgeInputContractError extends BridgeInputError {
|
|
260
|
-
reason;
|
|
261
|
-
constructor(inputPosition, reason) {
|
|
262
|
-
super('BridgeInputContractError', inputPosition);
|
|
263
|
-
this.reason = reason;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
/** 입력 텍스트의 크기만 계약을 넘은 경우를 구분한다. */
|
|
267
|
-
class BridgeInputLimitError extends BridgeInputError {
|
|
268
|
-
constructor(inputPosition) {
|
|
269
|
-
super('BridgeInputLimitError', inputPosition);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/** 알려진 입력·조인 실패를 원인별 해결 방향을 담은 코드 2 결과로 바꾼다. */
|
|
273
|
-
export function inputFailureResult(error) {
|
|
274
|
-
if (error instanceof BridgeInputReadError) {
|
|
275
|
-
return inputFailure(`Unable to read bridge facts input ${error.inputPosition}; `
|
|
276
|
-
+ 'check that the file exists and is readable.\n');
|
|
277
|
-
}
|
|
278
|
-
if (error instanceof BridgeInputJsonError) {
|
|
279
|
-
return inputFailure(`Bridge facts input ${error.inputPosition} is not valid JSON; `
|
|
280
|
-
+ 'regenerate it with a bridge-facts producer.\n');
|
|
281
|
-
}
|
|
282
|
-
if (error instanceof BridgeInputContractError) {
|
|
283
|
-
return inputFailure(`Bridge facts input ${error.inputPosition} violates the bridge-facts `
|
|
284
|
-
+ `contract: ${error.reason}\n`);
|
|
285
|
-
}
|
|
286
|
-
if (error instanceof BridgeInputLimitError) {
|
|
287
|
-
return inputFailure(`Bridge facts input ${error.inputPosition} exceeds the input size limits; `
|
|
288
|
-
+ 'split the extraction into smaller documents.\n');
|
|
289
|
-
}
|
|
155
|
+
/** check 고유의 baseline 실패를 원인별 해결 방향을 담은 코드 2로 바꾼다. */
|
|
156
|
+
function baselineFailureResult(error) {
|
|
290
157
|
if (error instanceof BridgeBaselineReadError) {
|
|
291
158
|
return inputFailure('Unable to read the baseline file; check that it exists and is readable.\n');
|
|
292
159
|
}
|
|
@@ -309,31 +176,8 @@ export function inputFailureResult(error) {
|
|
|
309
176
|
return inputFailure(`The baseline file is not a valid isthmus-baseline document: `
|
|
310
177
|
+ `${error.message}\n`);
|
|
311
178
|
}
|
|
312
|
-
if (error instanceof BridgeJoinValidationError) {
|
|
313
|
-
return inputFailure(`${error.message}\n`);
|
|
314
|
-
}
|
|
315
179
|
return undefined;
|
|
316
180
|
}
|
|
317
|
-
/** 원인 메시지만 stdout 없이 코드 2로 내보낸다. */
|
|
318
|
-
function inputFailure(standardError) {
|
|
319
|
-
return { standardOutput: '', standardError, exitCode: 2 };
|
|
320
|
-
}
|
|
321
|
-
/** 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다. */
|
|
322
|
-
export function bridgeJoinDeferredError() {
|
|
323
|
-
return {
|
|
324
|
-
standardOutput: '',
|
|
325
|
-
standardError: 'Bridge facts could not be joined; split mixed bridge targets and retry.\n',
|
|
326
|
-
exitCode: 2,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
/** 내부 결함을 입력 탓으로 돌리지 않는 경로 없는 코드 2 결과다. */
|
|
330
|
-
export function internalError() {
|
|
331
|
-
return {
|
|
332
|
-
standardOutput: '',
|
|
333
|
-
standardError: 'Internal isthmus error; retry with a current version.\n',
|
|
334
|
-
exitCode: 2,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
181
|
/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */
|
|
338
182
|
function usageError() {
|
|
339
183
|
return {
|
|
@@ -347,4 +191,8 @@ export const checkUsage = 'Usage: isthmus check <bridge-facts.json> <bridge-fact
|
|
|
347
191
|
+ '[more...] [--strict] [--format json|sarif] '
|
|
348
192
|
+ '[--baseline <isthmus-baseline.json>] '
|
|
349
193
|
+ '[--update-baseline <isthmus-baseline.json>]';
|
|
194
|
+
/** 논리 이슈 키의 SHA-256 지문이다. SARIF partialFingerprints에 쓴다. */
|
|
195
|
+
function issueFingerprint(issue) {
|
|
196
|
+
return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');
|
|
197
|
+
}
|
|
350
198
|
//# sourceMappingURL=check-command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BridgeFactsValidationError,\n parseBridgeFactsDocument,\n type BridgeFactsDocument,\n} from '../exchange/parse.ts';\nimport {\n BridgeJoinValidationError,\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n applyBaseline,\n BridgeBaselineValidationError,\n createBaselineDocument,\n encodeBaselineDocument,\n MAX_BASELINE_ENTRIES,\n parseBaselineDocument,\n type BaselineDocument,\n} from '../report/baseline.ts';\nimport type { CheckIssue } from '../report/check-report.ts';\nimport { createCheckReport, encodeCheckReport } from '../report/check-report.ts';\nimport {\n createSarifLog,\n encodeSarifLog,\n} from '../report/sarif.ts';\n\n/** 파일 경로를 받아 UTF-8 텍스트를 읽는 주입 경계다. */\nexport type ReadTextFile = (path: string) => Promise<string>;\n\n/** 파일 경로에 UTF-8 텍스트를 쓰는 주입 경계다. */\nexport type WriteTextFile = (path: string, text: string) => Promise<void>;\n\n/** 생성 시각을 테스트 가능하게 주입하는 시계다. */\nexport type Clock = () => Date;\n\n/** check가 내는 보고서 형식이다. SARIF는 isthmus 소유의 additive 출력이다. */\nexport type CheckOutputFormat = 'json' | 'sarif';\n\n/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */\nexport const MAX_INPUT_TEXT_LENGTH = 16 * 1024 * 1024;\n\n/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */\nexport const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;\n\n/** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */\nexport interface CommandResult {\n readonly standardOutput: string;\n readonly standardError: string;\n readonly exitCode: 0 | 1 | 2 | 64;\n}\n\n/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */\nexport async function runCheckCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n writeTextFile?: WriteTextFile,\n now: Clock = () => new Date(),\n producerVersion?: string,\n): Promise<CommandResult> {\n if (arguments_[0] !== 'check') return usageError();\n const options = parseCheckOptions(arguments_.slice(1));\n if (options === undefined) return usageError();\n const { strict, format, baselinePath, updateBaselinePath, inputPaths } = options;\n if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n if (updateBaselinePath !== undefined && writeTextFile === undefined) {\n return internalError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) return bridgeJoinDeferredError();\n let report = createCheckReport(joined);\n if (baselinePath !== undefined) {\n const baseline = await readBaselineDocument(baselinePath, readTextFile);\n report = applyBaseline(report, baseline.entries);\n }\n const standardOutput = format === 'sarif'\n ? encodeSarifLog(createSarifLog(report, producerVersion))\n : encodeCheckReport(report);\n if (updateBaselinePath !== undefined && writeTextFile !== undefined) {\n await writeBaselineDocument(\n updateBaselinePath,\n report.issues,\n now(),\n writeTextFile,\n );\n }\n return {\n standardOutput,\n standardError: '',\n exitCode: strict && report.summary.errors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */\nfunction parseCheckOptions(\n rest: readonly string[],\n): {\n strict: boolean;\n format: CheckOutputFormat;\n baselinePath: string | undefined;\n updateBaselinePath: string | undefined;\n inputPaths: string[];\n} | undefined {\n let strict = false;\n let format: CheckOutputFormat | undefined;\n let baselinePath: string | undefined;\n let updateBaselinePath: string | undefined;\n const inputPaths: string[] = [];\n for (let index = 0; index < rest.length; index++) {\n const argument = rest[index];\n if (argument === undefined) return undefined;\n if (argument === '--strict') {\n strict = true;\n continue;\n }\n if (argument === '--format' || argument === '--baseline' || argument === '--update-baseline') {\n const value = rest[index + 1];\n if (value === undefined || value.startsWith('-')) return undefined;\n if (argument === '--format') {\n if (format !== undefined || (value !== 'json' && value !== 'sarif')) {\n return undefined;\n }\n format = value;\n } else if (argument === '--baseline') {\n if (baselinePath !== undefined) return undefined;\n baselinePath = value;\n } else {\n if (updateBaselinePath !== undefined) return undefined;\n updateBaselinePath = value;\n }\n index++;\n continue;\n }\n if (argument.startsWith('-')) return undefined;\n inputPaths.push(argument);\n }\n if (baselinePath !== undefined && updateBaselinePath !== undefined) {\n return undefined;\n }\n return {\n strict,\n format: format ?? 'json',\n baselinePath,\n updateBaselinePath,\n inputPaths,\n };\n}\n\n/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */\nasync function readBaselineDocument(\n path: string,\n readTextFile: ReadTextFile,\n): Promise<BaselineDocument> {\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeBaselineReadError();\n }\n if (text.length > MAX_INPUT_TEXT_LENGTH) {\n throw new BridgeBaselineLimitError();\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) throw new BridgeBaselineJsonError();\n throw error;\n }\n return parseBaselineDocument(parsed);\n}\n\n/**\n * 사용자 입력 JSON 파싱 실패인지 확인한다.\n *\n * 깊은 중첩은 SyntaxError가 아니라 RangeError(스택 초과)로 실패한다.\n * 둘 다 입력 탓이라 내부 오류 메시지로 오분류하지 않는다.\n */\nfunction isJsonParseFailure(error: unknown): boolean {\n return error instanceof SyntaxError || error instanceof RangeError;\n}\n\n/** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */\nasync function writeBaselineDocument(\n path: string,\n issues: readonly CheckIssue[],\n generatedAt: Date,\n writeTextFile: WriteTextFile,\n): Promise<void> {\n let document: BaselineDocument;\n try {\n document = createBaselineDocument(issues, generatedAt.toISOString());\n } catch (error) {\n // 만들 수 없는 문서는 쓰지 않는다. 상한 초과 산출물은 다음 실행이\n // 소비할 수 없어 부분 성공보다 실패가 안전하다.\n if (error instanceof BridgeBaselineValidationError) {\n throw new BridgeBaselineWriteLimitError();\n }\n throw error;\n }\n // 인코딩 결함은 쓰기 실패가 아니라 내부 오류로 분류되어야 한다.\n const encoded = encodeBaselineDocument(document);\n try {\n await writeTextFile(path, encoded);\n } catch {\n throw new BridgeBaselineWriteError();\n }\n}\n\n/** 베이스라인 파일을 읽지 못한 경우를 구분한다. */\nclass BridgeBaselineReadError extends Error {\n constructor() {\n super('BridgeBaselineReadError');\n this.name = 'BridgeBaselineReadError';\n }\n}\n\n/** 베이스라인 파일이 JSON이 아닌 경우를 구분한다. */\nclass BridgeBaselineJsonError extends Error {\n constructor() {\n super('BridgeBaselineJsonError');\n this.name = 'BridgeBaselineJsonError';\n }\n}\n\n/** 베이스라인 파일이 크기 상한을 넘은 경우를 구분한다. */\nclass BridgeBaselineLimitError extends Error {\n constructor() {\n super('BridgeBaselineLimitError');\n this.name = 'BridgeBaselineLimitError';\n }\n}\n\n/** 베이스라인 파일을 쓰지 못한 경우를 구분한다. */\nclass BridgeBaselineWriteError extends Error {\n constructor() {\n super('BridgeBaselineWriteError');\n this.name = 'BridgeBaselineWriteError';\n }\n}\n\n/** 쓸 베이스라인이 항목 상한을 넘는 경우를 구분한다. */\nclass BridgeBaselineWriteLimitError extends Error {\n constructor() {\n super('BridgeBaselineWriteLimitError');\n this.name = 'BridgeBaselineWriteLimitError';\n }\n}\n\n/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */\nexport async function readBridgeDocuments(\n inputPaths: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<BridgeFactsDocument[]> {\n const documents: BridgeFactsDocument[] = [];\n let totalTextLength = 0;\n for (const [index, path] of inputPaths.entries()) {\n const inputPosition = index + 1;\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeInputReadError(inputPosition);\n }\n totalTextLength += text.length;\n if (\n text.length > MAX_INPUT_TEXT_LENGTH ||\n totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH\n ) {\n throw new BridgeInputLimitError(inputPosition);\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) {\n throw new BridgeInputJsonError(inputPosition);\n }\n throw error;\n }\n try {\n documents.push(parseBridgeFactsDocument(parsed));\n } catch (error) {\n if (error instanceof BridgeFactsValidationError) {\n throw new BridgeInputContractError(inputPosition, error.message);\n }\n throw error;\n }\n }\n return documents;\n}\n\n/** 실패한 입력의 1부터 시작하는 순서를 보존하는 입력 오류다. */\nclass BridgeInputError extends Error {\n readonly inputPosition: number;\n\n constructor(name: string, inputPosition: number) {\n super(`${name} at input ${inputPosition}`);\n this.name = name;\n this.inputPosition = inputPosition;\n }\n}\n\n/** 입력 파일을 읽지 못한 경우를 구분한다. */\nclass BridgeInputReadError extends BridgeInputError {\n constructor(inputPosition: number) {\n super('BridgeInputReadError', inputPosition);\n }\n}\n\n/** 입력 텍스트가 JSON이 아닌 경우를 구분한다. */\nclass BridgeInputJsonError extends BridgeInputError {\n constructor(inputPosition: number) {\n super('BridgeInputJsonError', inputPosition);\n }\n}\n\n/** 입력 문서가 교환 계약을 어긴 경우와 그 이유를 구분한다. */\nclass BridgeInputContractError extends BridgeInputError {\n readonly reason: string;\n\n constructor(inputPosition: number, reason: string) {\n super('BridgeInputContractError', inputPosition);\n this.reason = reason;\n }\n}\n\n/** 입력 텍스트의 크기만 계약을 넘은 경우를 구분한다. */\nclass BridgeInputLimitError extends BridgeInputError {\n constructor(inputPosition: number) {\n super('BridgeInputLimitError', inputPosition);\n }\n}\n\n/** 알려진 입력·조인 실패를 원인별 해결 방향을 담은 코드 2 결과로 바꾼다. */\nexport function inputFailureResult(error: unknown): CommandResult | undefined {\n if (error instanceof BridgeInputReadError) {\n return inputFailure(\n `Unable to read bridge facts input ${error.inputPosition}; `\n + 'check that the file exists and is readable.\\n',\n );\n }\n if (error instanceof BridgeInputJsonError) {\n return inputFailure(\n `Bridge facts input ${error.inputPosition} is not valid JSON; `\n + 'regenerate it with a bridge-facts producer.\\n',\n );\n }\n if (error instanceof BridgeInputContractError) {\n return inputFailure(\n `Bridge facts input ${error.inputPosition} violates the bridge-facts `\n + `contract: ${error.reason}\\n`,\n );\n }\n if (error instanceof BridgeInputLimitError) {\n return inputFailure(\n `Bridge facts input ${error.inputPosition} exceeds the input size limits; `\n + 'split the extraction into smaller documents.\\n',\n );\n }\n if (error instanceof BridgeBaselineReadError) {\n return inputFailure(\n 'Unable to read the baseline file; check that it exists and is readable.\\n',\n );\n }\n if (error instanceof BridgeBaselineJsonError) {\n return inputFailure(\n 'The baseline file is not valid JSON; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineLimitError) {\n return inputFailure(\n 'The baseline file exceeds the input size limits; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteError) {\n return inputFailure(\n 'Unable to write the baseline file; check that the path is writable.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteLimitError) {\n return inputFailure(\n `Cannot write a baseline with more than ${MAX_BASELINE_ENTRIES} entries; `\n + 'narrow the join inputs.\\n',\n );\n }\n if (error instanceof BridgeBaselineValidationError) {\n return inputFailure(\n `The baseline file is not a valid isthmus-baseline document: `\n + `${error.message}\\n`,\n );\n }\n if (error instanceof BridgeJoinValidationError) {\n return inputFailure(`${error.message}\\n`);\n }\n return undefined;\n}\n\n/** 원인 메시지만 stdout 없이 코드 2로 내보낸다. */\nfunction inputFailure(standardError: string): CommandResult {\n return { standardOutput: '', standardError, exitCode: 2 };\n}\n\n/** 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다. */\nexport function bridgeJoinDeferredError(): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Bridge facts could not be joined; split mixed bridge targets and retry.\\n',\n exitCode: 2,\n };\n}\n\n/** 내부 결함을 입력 탓으로 돌리지 않는 경로 없는 코드 2 결과다. */\nexport function internalError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Internal isthmus error; retry with a current version.\\n',\n exitCode: 2,\n };\n}\n\n/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */\nfunction usageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${checkUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** check 명령의 한 줄 사용법이다. */\nexport const checkUsage =\n 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--strict] [--format json|sarif] '\n + '[--baseline <isthmus-baseline.json>] '\n + '[--update-baseline <isthmus-baseline.json>]';\n"]}
|
|
1
|
+
{"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n applyBaseline,\n baselineEntryKey,\n BridgeBaselineValidationError,\n createBaselineDocument,\n encodeBaselineDocument,\n MAX_BASELINE_ENTRIES,\n parseBaselineDocument,\n type BaselineDocument,\n} from '../report/baseline.ts';\nimport type { CheckIssue } from '../report/check-report.ts';\nimport { createCheckReport, encodeCheckReport } from '../report/check-report.ts';\nimport {\n createSarifLog,\n encodeSarifLog,\n} from '../report/sarif.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailure,\n inputFailureResult,\n internalError,\n isJsonParseFailure,\n MAX_INPUT_TEXT_LENGTH,\n readBridgeDocuments,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n type WriteTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\nexport type { Clock, CommandResult, ReadTextFile, WriteTextFile };\n\n/** check가 내는 보고서 형식이다. SARIF는 isthmus 소유의 additive 출력이다. */\nexport type CheckOutputFormat = 'json' | 'sarif';\n\n/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */\nexport async function runCheckCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n writeTextFile?: WriteTextFile,\n now: Clock = () => new Date(),\n producerVersion?: string,\n): Promise<CommandResult> {\n if (arguments_[0] !== 'check') return usageError();\n const options = parseCheckOptions(arguments_.slice(1));\n if (options === undefined) return usageError();\n const { strict, format, baselinePath, updateBaselinePath, inputPaths } = options;\n if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n if (updateBaselinePath !== undefined && writeTextFile === undefined) {\n return internalError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, documents.length);\n }\n let report = createCheckReport(joined);\n if (baselinePath !== undefined) {\n const baseline = await readBaselineDocument(baselinePath, readTextFile);\n report = applyBaseline(report, baseline.entries);\n }\n const standardOutput = format === 'sarif'\n ? encodeSarifLog(createSarifLog(report, producerVersion, issueFingerprint))\n : encodeCheckReport(report);\n if (updateBaselinePath !== undefined && writeTextFile !== undefined) {\n await writeBaselineDocument(\n updateBaselinePath,\n report.issues,\n now(),\n writeTextFile,\n );\n }\n return {\n standardOutput,\n standardError: '',\n exitCode: strict && report.summary.errors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? baselineFailureResult(error)\n ?? internalError();\n }\n}\n\n/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */\nfunction parseCheckOptions(\n rest: readonly string[],\n): {\n strict: boolean;\n format: CheckOutputFormat;\n baselinePath: string | undefined;\n updateBaselinePath: string | undefined;\n inputPaths: string[];\n} | undefined {\n const parsed = parseCommandArguments(\n rest,\n ['--format', '--baseline', '--update-baseline'],\n ['--strict'],\n );\n if (parsed === undefined) return undefined;\n const format = parsed.valueFlags.get('--format');\n if (format !== undefined && format !== 'json' && format !== 'sarif') {\n return undefined;\n }\n const baselinePath = parsed.valueFlags.get('--baseline');\n const updateBaselinePath = parsed.valueFlags.get('--update-baseline');\n if (baselinePath !== undefined && updateBaselinePath !== undefined) {\n return undefined;\n }\n return {\n strict: parsed.booleanFlags.has('--strict'),\n format: format ?? 'json',\n baselinePath,\n updateBaselinePath,\n inputPaths: [...parsed.positionals],\n };\n}\n\n/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */\nasync function readBaselineDocument(\n path: string,\n readTextFile: ReadTextFile,\n): Promise<BaselineDocument> {\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeBaselineReadError();\n }\n if (text.length > MAX_INPUT_TEXT_LENGTH) {\n throw new BridgeBaselineLimitError();\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) throw new BridgeBaselineJsonError();\n throw error;\n }\n return parseBaselineDocument(parsed);\n}\n\n/** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */\nasync function writeBaselineDocument(\n path: string,\n issues: readonly CheckIssue[],\n generatedAt: Date,\n writeTextFile: WriteTextFile,\n): Promise<void> {\n let document: BaselineDocument;\n try {\n document = createBaselineDocument(issues, generatedAt.toISOString());\n } catch (error) {\n // 만들 수 없는 문서는 쓰지 않는다. 상한 초과 산출물은 다음 실행이\n // 소비할 수 없어 부분 성공보다 실패가 안전하다.\n if (error instanceof BridgeBaselineValidationError) {\n throw new BridgeBaselineWriteLimitError();\n }\n throw error;\n }\n // 인코딩 결함은 쓰기 실패가 아니라 내부 오류로 분류되어야 한다.\n const encoded = encodeBaselineDocument(document);\n try {\n await writeTextFile(path, encoded);\n } catch {\n throw new BridgeBaselineWriteError();\n }\n}\n\n/** 베이스라인 파일을 읽지 못한 경우를 구분한다. */\nclass BridgeBaselineReadError extends Error {\n constructor() {\n super('BridgeBaselineReadError');\n this.name = 'BridgeBaselineReadError';\n }\n}\n\n/** 베이스라인 파일이 JSON이 아닌 경우를 구분한다. */\nclass BridgeBaselineJsonError extends Error {\n constructor() {\n super('BridgeBaselineJsonError');\n this.name = 'BridgeBaselineJsonError';\n }\n}\n\n/** 베이스라인 파일이 크기 상한을 넘은 경우를 구분한다. */\nclass BridgeBaselineLimitError extends Error {\n constructor() {\n super('BridgeBaselineLimitError');\n this.name = 'BridgeBaselineLimitError';\n }\n}\n\n/** 베이스라인 파일을 쓰지 못한 경우를 구분한다. */\nclass BridgeBaselineWriteError extends Error {\n constructor() {\n super('BridgeBaselineWriteError');\n this.name = 'BridgeBaselineWriteError';\n }\n}\n\n/** 쓸 베이스라인이 항목 상한을 넘는 경우를 구분한다. */\nclass BridgeBaselineWriteLimitError extends Error {\n constructor() {\n super('BridgeBaselineWriteLimitError');\n this.name = 'BridgeBaselineWriteLimitError';\n }\n}\n\n/** check 고유의 baseline 실패를 원인별 해결 방향을 담은 코드 2로 바꾼다. */\nfunction baselineFailureResult(error: unknown): CommandResult | undefined {\n if (error instanceof BridgeBaselineReadError) {\n return inputFailure(\n 'Unable to read the baseline file; check that it exists and is readable.\\n',\n );\n }\n if (error instanceof BridgeBaselineJsonError) {\n return inputFailure(\n 'The baseline file is not valid JSON; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineLimitError) {\n return inputFailure(\n 'The baseline file exceeds the input size limits; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteError) {\n return inputFailure(\n 'Unable to write the baseline file; check that the path is writable.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteLimitError) {\n return inputFailure(\n `Cannot write a baseline with more than ${MAX_BASELINE_ENTRIES} entries; `\n + 'narrow the join inputs.\\n',\n );\n }\n if (error instanceof BridgeBaselineValidationError) {\n return inputFailure(\n `The baseline file is not a valid isthmus-baseline document: `\n + `${error.message}\\n`,\n );\n }\n return undefined;\n}\n\n/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */\nfunction usageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${checkUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** check 명령의 한 줄 사용법이다. */\nexport const checkUsage =\n 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--strict] [--format json|sarif] '\n + '[--baseline <isthmus-baseline.json>] '\n + '[--update-baseline <isthmus-baseline.json>]';\n\n/** 논리 이슈 키의 SHA-256 지문이다. SARIF partialFingerprints에 쓴다. */\nfunction issueFingerprint(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');\n}\n"]}
|