isthmus-cli 0.3.0 → 0.4.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 +56 -11
- package/README.md +61 -11
- package/dist/cli/atomic-write.d.ts +11 -0
- package/dist/cli/atomic-write.js +32 -0
- package/dist/cli/atomic-write.js.map +1 -0
- package/dist/cli/check-command.d.ts +3 -1
- package/dist/cli/check-command.js +42 -10
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/main.js +5 -19
- package/dist/cli/main.js.map +1 -1
- package/dist/exchange/parse.js +3 -1
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.d.ts +2 -0
- package/dist/join/join.js +8 -4
- package/dist/join/join.js.map +1 -1
- package/dist/report/check-report.d.ts +4 -0
- package/dist/report/check-report.js +2 -0
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/retentions.d.ts +10 -0
- package/dist/report/retentions.js +21 -7
- package/dist/report/retentions.js.map +1 -1
- package/dist/report/sarif.d.ts +74 -0
- package/dist/report/sarif.js +105 -0
- package/dist/report/sarif.js.map +1 -0
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -28,14 +28,19 @@ isthmus는 각 언어 도구가 내보낸 **브리지 사실**(채널 이름 ·
|
|
|
28
28
|
|
|
29
29
|
## 상태
|
|
30
30
|
|
|
31
|
-
**0.
|
|
32
|
-
cartograph용 외부 보존 근거 왕복,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
**0.4.0.** 0.3.0의 계약과 명령 — bridge-facts 버전 1 파서, `check`, `query`, `graph`,
|
|
32
|
+
`diff`, cartograph용 외부 보존 근거 왕복, 논리 이슈 식별자로 억제하되 증거는 보존하는
|
|
33
|
+
check 베이스라인 — 에 이번 주기의 추가를 더했다: GitHub code scanning용 check 결과의
|
|
34
|
+
SARIF 2.1.0 렌더링(`check --format sarif`, additive, 소스 줄 이동에 강한 논리 키
|
|
35
|
+
지문), 브리지가 없는 프로젝트와 아무것도 관찰하지 못한 실행을 구분하는 check summary
|
|
36
|
+
관찰량(`observedFacts`·`observedLimitations`), 다중 호출자 보존 근거(`evidence.callers`
|
|
37
|
+
— 근거당 상한·명시적 `callersOmitted` 계수, 단일 호출자는 기존과 바이트 동일),
|
|
38
|
+
인덱스 없이 빌드된 Objective-C 핸들러의 usr 없는 `qualifiedName` 신원. 외부 입력·
|
|
39
|
+
혼합 target·그래프 크기·Dart/Swift Phase 0 추출 경계는 fail-closed를 유지하고,
|
|
40
|
+
조인하지 못한 사실은 소비자 쪽에서 다시 세며 근거를 만들지 못한 보존 대상은 조용히
|
|
41
|
+
사라지는 대신 실패로 보고한다. 수신 측이 신고한 분석 공백은 불일치가 아니라 판정
|
|
42
|
+
불가로 보고되고 공백 완화는 target을 넘지 않는다. 다음 단계는 실제 Flutter 앱
|
|
43
|
+
도그푸딩과 React Native 지원이다.
|
|
39
44
|
|
|
40
45
|
정식 producer는 cartograph 0.5.3 이상과 dartograph 0.1.1 이상이다. 두 도구의 실제 출력과
|
|
41
46
|
공개 battery 플러그인의 Swift USR·Dart 호출 근거 왕복을 검증했다.
|
|
@@ -99,6 +104,24 @@ isthmus --version
|
|
|
99
104
|
isthmus check dart-bridges.json swift-bridges.json --strict
|
|
100
105
|
```
|
|
101
106
|
|
|
107
|
+
### SARIF 출력
|
|
108
|
+
|
|
109
|
+
check 결과를 GitHub code scanning(또는 그 밖의 SARIF 2.1.0 소비자)에 올리려면
|
|
110
|
+
isthmus-check JSON 대신 SARIF를 요청한다.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
isthmus check dart-bridges.json swift-bridges.json --format sarif > isthmus.sarif
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
기본값은 `--format json`으로 버전이 붙은 isthmus-check 문서를 유지한다. SARIF는 같은
|
|
117
|
+
조인 결과의 additive·isthmus 소유 렌더링이다. 모든 이슈는 check 진단 코드를 규칙 id로
|
|
118
|
+
하는 결과가 되고, 첫 증거 끝점이 주 위치가 되며(프로젝트 상대 경로가 퍼센트 인코딩된
|
|
119
|
+
저장소 상대 URI가 된다), 나머지 끝점은 관련 위치로 실린다. 베이스라인이 억제한 이슈는
|
|
120
|
+
`external` suppression을 달고 나온다. 결과마다 논리 이슈 식별자(code·target·channel·
|
|
121
|
+
method)의 `partialFingerprints` 해시가 있어 소스 줄이 움직여도 중복 판정이 베이스라인
|
|
122
|
+
억제와 같은 기준으로 살아남는다. `--strict`·`--baseline`·`--update-baseline`은 두 형식
|
|
123
|
+
모두와 조합되고 문서화된 종료 코드 동작을 유지한다.
|
|
124
|
+
|
|
102
125
|
### 베이스라인
|
|
103
126
|
|
|
104
127
|
현재 발견된 이슈를 인정해 베이스라인으로 기록하고, 다음 실행부터 그 파일을 적용한다.
|
|
@@ -136,9 +159,12 @@ isthmus retentions \
|
|
|
136
159
|
cartograph dead --external-retentions external-retentions.json
|
|
137
160
|
```
|
|
138
161
|
|
|
139
|
-
`retentions`는 핸들러의 USR을 우선 사용하고 없으면 `qualifiedName`을 남긴다.
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
`retentions`는 핸들러의 USR을 우선 사용하고 없으면 `qualifiedName`을 남긴다. 메서드를
|
|
163
|
+
여러 위치에서 호출하면 근거가 전체 호출 위치를 `callers`로 실고(대표 `caller`는 옛
|
|
164
|
+
소비자를 위해 유지), 근거당 100개 상한을 넘은 호출은 조용히 버리지 않고
|
|
165
|
+
`callersOmitted`로 계수를 밝힌다. `mixed-targets` 문서는 v1에서 사실별 target을
|
|
166
|
+
복원할 수 없어 모든 소비 명령이 종료 코드 2로 조인을 보류한다. 먼저 생산 단계에서
|
|
167
|
+
target별 문서로 분리해야 한다.
|
|
142
168
|
|
|
143
169
|
cartograph는 Swift 심볼만 보존하므로 `--for cartograph`는 수신 측 Swift 문서를 최소
|
|
144
170
|
하나 요구하고, 없으면 빈 보존 문서 대신 종료 코드 2로 거부한다. 호출자가 있는데도
|
|
@@ -206,6 +232,11 @@ isthmus graph dart-bridges.json swift-bridges.json --format mermaid
|
|
|
206
232
|
핸들러를 놓쳤을 수 있다고 스스로 신고해 없는 것인지 못 본 것인지 판정할 수 없음
|
|
207
233
|
- `unregistered-channel-creation-unverified` (warning): 같은 이유로 등록 여부를 판정할 수 없음
|
|
208
234
|
|
|
235
|
+
`summary`는 이슈 계수와 함께 관찰량을 싣는다. `observedFacts`는 입력 문서 전체의 fact
|
|
236
|
+
총수이고 `observedLimitations`는 보고된 분석 한계 수다. 이로써 브리지가 없는 프로젝트와
|
|
237
|
+
아무것도 관찰하지 못한 실행이 같은 보고서를 내지 않는다 — `observedFacts`가 0이면
|
|
238
|
+
생산자가 서술할 것을 아무것도 보지 못했다는 뜻이다.
|
|
239
|
+
|
|
209
240
|
`-unverified` 종류는 수신 측 문서의 한계에서 나온다. 예를 들어 Flutter 핸들러가
|
|
210
241
|
Objective-C로 쓰인 플러그인에서 cartograph는 `objective-c-sources:`를 신고하고 핸들러
|
|
211
242
|
사실을 완전히 열거하지 못할 수 있다. 이때 "핸들러 없는 호출"을 error로 단정하면 이
|
|
@@ -276,6 +307,20 @@ node scripts/verify-cartograph-roundtrip.mjs \
|
|
|
276
307
|
/path/to/FalsePositiveCorpus
|
|
277
308
|
```
|
|
278
309
|
|
|
310
|
+
생산자가 발행한 `limitationScopes`가 소비자까지 전달되어 진단을 채널 단위로만
|
|
311
|
+
완화하는지 확인하려면 자기완결 스코프 dogfood를 실행한다. 스크립트는 스캐너가
|
|
312
|
+
본문을 볼 수 없으면서 채널 이름은 리터럴로 알려지는 형태(위임 핸들러 등록)의
|
|
313
|
+
최소 Swift 패키지와 Dart 호출 측을 합성하고, 스코프가 붙은 채널의 미처리 호출만
|
|
314
|
+
`-unverified` 경고로 낮아지고 같은 target의 인접 미처리 호출과 등록 없는 채널
|
|
315
|
+
생성은 error로 남는지 검증한다. cartograph 0.9.0 이상, dartograph 0.1.1 이상,
|
|
316
|
+
Swift 6이 필요하며 네트워크 접근은 하지 않는다.
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
node scripts/verify-limitation-scopes.mjs \
|
|
320
|
+
/path/to/cartograph \
|
|
321
|
+
/path/to/dartograph
|
|
322
|
+
```
|
|
323
|
+
|
|
279
324
|
## 변경 전후 비교 (0.1.4 이상)
|
|
280
325
|
|
|
281
326
|
같은 프로젝트의 변경 전후 Dart·Swift 교환 파일을 비교하려면:
|
package/README.md
CHANGED
|
@@ -30,16 +30,22 @@ calls it over channel `com.example/camera`".
|
|
|
30
30
|
|
|
31
31
|
## Status
|
|
32
32
|
|
|
33
|
-
**0.
|
|
34
|
-
the external retention evidence round trip for cartograph, and
|
|
35
|
-
accepted findings by logical issue identity while preserving
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
**0.4.0.** The 0.3.0 contract and commands — the bridge-facts version 1 parser, `check`,
|
|
34
|
+
`query`, `graph`, `diff`, the external retention evidence round trip for cartograph, and
|
|
35
|
+
check baselines that suppress accepted findings by logical issue identity while preserving
|
|
36
|
+
their evidence — plus this cycle's additions: a SARIF 2.1.0 rendering of check results for
|
|
37
|
+
GitHub code scanning (`check --format sarif`, additive, with logic-key fingerprints that
|
|
38
|
+
survive source line moves), observation volume in the check summary (`observedFacts`,
|
|
39
|
+
`observedLimitations`) so a project without bridges and a run that observed nothing are no
|
|
40
|
+
longer indistinguishable, multi-caller retention evidence (`evidence.callers` with a
|
|
41
|
+
per-retention cap and an explicit `callersOmitted` count, byte-identical for single
|
|
42
|
+
callers), and usr-less `qualifiedName` identity for Objective-C handlers built without an
|
|
43
|
+
index. isthmus keeps fail-closed behavior for external input, mixed targets, graph size,
|
|
44
|
+
and the Dart/Swift Phase 0 extraction boundary. Facts that could not be joined are
|
|
45
|
+
re-counted on the consumer side, and retention subjects whose evidence cannot be built are
|
|
46
|
+
refused loudly, so neither disappears silently. Coverage gaps a receiver reports about
|
|
47
|
+
itself come back as undecidable, not as mismatches, and gap mitigation never leaks across
|
|
48
|
+
targets. Next: dogfooding it on a real Flutter app, and React Native support.
|
|
43
49
|
|
|
44
50
|
The supported producers are cartograph 0.5.3+ and dartograph 0.1.1+. Both were verified on
|
|
45
51
|
their real output, and on a Swift USR ↔ Dart invocation evidence round trip over the public
|
|
@@ -107,6 +113,26 @@ To make CI fail when bridge errors exist, add `--strict`:
|
|
|
107
113
|
isthmus check dart-bridges.json swift-bridges.json --strict
|
|
108
114
|
```
|
|
109
115
|
|
|
116
|
+
### SARIF output
|
|
117
|
+
|
|
118
|
+
To upload check results to GitHub code scanning (or any SARIF 2.1.0 consumer), ask for SARIF
|
|
119
|
+
instead of the isthmus-check JSON:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
isthmus check dart-bridges.json swift-bridges.json --format sarif > isthmus.sarif
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The default is `--format json`, which keeps the versioned isthmus-check document. SARIF is an
|
|
126
|
+
additive, isthmus-owned rendering of the same join: every issue becomes a result with its
|
|
127
|
+
`check` issue code as the rule id, the first evidence endpoint as the primary location
|
|
128
|
+
(project-relative paths become percent-encoded, repository-relative URIs), remaining
|
|
129
|
+
endpoints as related locations, and baseline-suppressed issues carry an `external`
|
|
130
|
+
suppression. Results include a
|
|
131
|
+
`partialFingerprints` hash of the logical issue identity (code, target, channel, method), so
|
|
132
|
+
deduplication survives source line moves exactly like baseline suppression. `--strict`,
|
|
133
|
+
`--baseline`, and `--update-baseline` combine with either format and keep their documented
|
|
134
|
+
exit-code behavior.
|
|
135
|
+
|
|
110
136
|
### Baselines
|
|
111
137
|
|
|
112
138
|
To accept the current findings as a baseline, write them to a file once and apply that file
|
|
@@ -145,7 +171,10 @@ isthmus retentions \
|
|
|
145
171
|
cartograph dead --external-retentions external-retentions.json
|
|
146
172
|
```
|
|
147
173
|
|
|
148
|
-
`retentions` prefers each handler's USR and falls back to its `qualifiedName`.
|
|
174
|
+
`retentions` prefers each handler's USR and falls back to its `qualifiedName`. When a method is
|
|
175
|
+
invoked from several caller locations, the evidence carries all of them in `callers` (the
|
|
176
|
+
representative first `caller` stays for older consumers) and counts any entries beyond the
|
|
177
|
+
100-per-retention cap in `callersOmitted` instead of dropping them silently. A
|
|
149
178
|
`mixed-targets` document cannot have per-fact targets restored in v1, so every consuming
|
|
150
179
|
command defers the join with exit code 2; split such a document per target at production time
|
|
151
180
|
first.
|
|
@@ -223,6 +252,12 @@ The output is `isthmus-check` version 1 JSON, reporting these facts:
|
|
|
223
252
|
- `unregistered-channel-creation-unverified` (warning): registration undecidable for the same
|
|
224
253
|
reason
|
|
225
254
|
|
|
255
|
+
The `summary` carries the issue counts plus observation volume: `observedFacts` is the total
|
|
256
|
+
number of facts across all input documents and `observedLimitations` counts the reported
|
|
257
|
+
analysis limitations. This keeps a project with no bridges and a run that observed nothing
|
|
258
|
+
from producing indistinguishable reports — an `observedFacts` of 0 means the producers saw
|
|
259
|
+
nothing to describe.
|
|
260
|
+
|
|
226
261
|
The `-unverified` kinds come from limitations in receiver-side documents. For example, in a
|
|
227
262
|
plugin whose Flutter handler is written in Objective-C, cartograph reports
|
|
228
263
|
`objective-c-sources:` and may fail to enumerate the handler facts completely. Asserting
|
|
@@ -300,6 +335,21 @@ node scripts/verify-cartograph-roundtrip.mjs \
|
|
|
300
335
|
/path/to/FalsePositiveCorpus
|
|
301
336
|
```
|
|
302
337
|
|
|
338
|
+
To verify that producer-emitted `limitationScopes` reach the consumer and relax diagnostics at
|
|
339
|
+
channel granularity, run the self-contained scope dogfood. It synthesizes a minimal Swift
|
|
340
|
+
package with a delegated handler registration — a literal channel whose handler body the
|
|
341
|
+
scanner cannot inspect, the one shape whose channel upper bound is provable — plus a Dart
|
|
342
|
+
caller, and checks that only the scoped channel's unhandled invocation becomes an unverified
|
|
343
|
+
warning while an adjacent unhandled invocation and an unregistered channel creation stay
|
|
344
|
+
errors. It needs cartograph 0.9.0+, dartograph 0.1.1+, and Swift 6, and performs no network
|
|
345
|
+
access.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
node scripts/verify-limitation-scopes.mjs \
|
|
349
|
+
/path/to/cartograph \
|
|
350
|
+
/path/to/dartograph
|
|
351
|
+
```
|
|
352
|
+
|
|
303
353
|
## Comparing before and after a change (0.1.4+)
|
|
304
354
|
|
|
305
355
|
To compare the Dart and Swift exchange files of one project before and after a change:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 같은 디렉터리의 배타적 임시 파일에 쓰고 rename으로 원자 교체한다.
|
|
3
|
+
*
|
|
4
|
+
* 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이
|
|
5
|
+
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이다.
|
|
6
|
+
*
|
|
7
|
+
* 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로
|
|
8
|
+
* 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일
|
|
9
|
+
* 덮어쓰기로 이어지지 않는다. 쓰기에 실패하면 임시 파일만 지운다.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeTextAtomically(path: string, text: string, temporaryPathFor?: (path: string) => string): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
/**
|
|
4
|
+
* 같은 디렉터리의 배타적 임시 파일에 쓰고 rename으로 원자 교체한다.
|
|
5
|
+
*
|
|
6
|
+
* 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이
|
|
7
|
+
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이다.
|
|
8
|
+
*
|
|
9
|
+
* 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로
|
|
10
|
+
* 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일
|
|
11
|
+
* 덮어쓰기로 이어지지 않는다. 쓰기에 실패하면 임시 파일만 지운다.
|
|
12
|
+
*/
|
|
13
|
+
export async function writeTextAtomically(path, text, temporaryPathFor = unpredictableTemporaryPath) {
|
|
14
|
+
const temporaryPath = temporaryPathFor(path);
|
|
15
|
+
try {
|
|
16
|
+
await writeFile(temporaryPath, text, { encoding: 'utf8', flag: 'wx' });
|
|
17
|
+
await rename(temporaryPath, path);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// EEXIST는 배타 생성이 거부된 것으로 임시 파일이 우리 것이 아니라는 뜻이다.
|
|
21
|
+
// 남은 오류에서만 직접 만든 임시 파일을 지운다.
|
|
22
|
+
if (error.code !== 'EEXIST') {
|
|
23
|
+
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** 같은 디렉터리 안의 예측 불가능한 임시 파일 이름을 만든다. */
|
|
29
|
+
function unpredictableTemporaryPath(path) {
|
|
30
|
+
return `${path}.${process.pid}.${randomBytes(12).toString('hex')}.tmp`;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +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;;;;;;;;;GASG;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,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACvE,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은 같은 파일시스템 안에서 원자적이다.\n *\n * 임시 파일 이름은 pid와 무작위 바이트로 만들어 예측할 수 없고 `wx`로\n * 배타 생성한다. 미리 놓인 심링크를 예측된 이름으로 통한 임의 파일\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, { encoding: 'utf8', flag: 'wx' });\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"]}
|
|
@@ -5,6 +5,8 @@ export type ReadTextFile = (path: string) => Promise<string>;
|
|
|
5
5
|
export type WriteTextFile = (path: string, text: string) => Promise<void>;
|
|
6
6
|
/** 생성 시각을 테스트 가능하게 주입하는 시계다. */
|
|
7
7
|
export type Clock = () => Date;
|
|
8
|
+
/** check가 내는 보고서 형식이다. SARIF는 isthmus 소유의 additive 출력이다. */
|
|
9
|
+
export type CheckOutputFormat = 'json' | 'sarif';
|
|
8
10
|
/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
|
|
9
11
|
export declare const MAX_INPUT_TEXT_LENGTH: number;
|
|
10
12
|
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
@@ -16,7 +18,7 @@ export interface CommandResult {
|
|
|
16
18
|
readonly exitCode: 0 | 1 | 2 | 64;
|
|
17
19
|
}
|
|
18
20
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
19
|
-
export declare function runCheckCommand(arguments_: readonly string[], readTextFile: ReadTextFile, writeTextFile?: WriteTextFile, now?: Clock): Promise<CommandResult>;
|
|
21
|
+
export declare function runCheckCommand(arguments_: readonly string[], readTextFile: ReadTextFile, writeTextFile?: WriteTextFile, now?: Clock, producerVersion?: string): Promise<CommandResult>;
|
|
20
22
|
/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
|
|
21
23
|
export declare function readBridgeDocuments(inputPaths: readonly string[], readTextFile: ReadTextFile): Promise<BridgeFactsDocument[]>;
|
|
22
24
|
/** 알려진 입력·조인 실패를 원인별 해결 방향을 담은 코드 2 결과로 바꾼다. */
|
|
@@ -2,18 +2,19 @@ import { BridgeFactsValidationError, parseBridgeFactsDocument, } from "../exchan
|
|
|
2
2
|
import { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
3
3
|
import { applyBaseline, BridgeBaselineValidationError, createBaselineDocument, encodeBaselineDocument, MAX_BASELINE_ENTRIES, parseBaselineDocument, } from "../report/baseline.js";
|
|
4
4
|
import { createCheckReport, encodeCheckReport } from "../report/check-report.js";
|
|
5
|
+
import { createSarifLog, encodeSarifLog, } from "../report/sarif.js";
|
|
5
6
|
/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
|
|
6
7
|
export const MAX_INPUT_TEXT_LENGTH = 16 * 1024 * 1024;
|
|
7
8
|
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
8
9
|
export const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;
|
|
9
10
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
10
|
-
export async function runCheckCommand(arguments_, readTextFile, writeTextFile, now = () => new Date()) {
|
|
11
|
+
export async function runCheckCommand(arguments_, readTextFile, writeTextFile, now = () => new Date(), producerVersion) {
|
|
11
12
|
if (arguments_[0] !== 'check')
|
|
12
13
|
return usageError();
|
|
13
14
|
const options = parseCheckOptions(arguments_.slice(1));
|
|
14
15
|
if (options === undefined)
|
|
15
16
|
return usageError();
|
|
16
|
-
const { strict, baselinePath, updateBaselinePath, inputPaths } = options;
|
|
17
|
+
const { strict, format, baselinePath, updateBaselinePath, inputPaths } = options;
|
|
17
18
|
if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
18
19
|
return usageError();
|
|
19
20
|
}
|
|
@@ -30,7 +31,9 @@ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, n
|
|
|
30
31
|
const baseline = await readBaselineDocument(baselinePath, readTextFile);
|
|
31
32
|
report = applyBaseline(report, baseline.entries);
|
|
32
33
|
}
|
|
33
|
-
const standardOutput =
|
|
34
|
+
const standardOutput = format === 'sarif'
|
|
35
|
+
? encodeSarifLog(createSarifLog(report, producerVersion))
|
|
36
|
+
: encodeCheckReport(report);
|
|
34
37
|
if (updateBaselinePath !== undefined && writeTextFile !== undefined) {
|
|
35
38
|
await writeBaselineDocument(updateBaselinePath, report.issues, now(), writeTextFile);
|
|
36
39
|
}
|
|
@@ -47,6 +50,7 @@ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, n
|
|
|
47
50
|
/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */
|
|
48
51
|
function parseCheckOptions(rest) {
|
|
49
52
|
let strict = false;
|
|
53
|
+
let format;
|
|
50
54
|
let baselinePath;
|
|
51
55
|
let updateBaselinePath;
|
|
52
56
|
const inputPaths = [];
|
|
@@ -58,11 +62,17 @@ function parseCheckOptions(rest) {
|
|
|
58
62
|
strict = true;
|
|
59
63
|
continue;
|
|
60
64
|
}
|
|
61
|
-
if (argument === '--baseline' || argument === '--update-baseline') {
|
|
65
|
+
if (argument === '--format' || argument === '--baseline' || argument === '--update-baseline') {
|
|
62
66
|
const value = rest[index + 1];
|
|
63
67
|
if (value === undefined || value.startsWith('-'))
|
|
64
68
|
return undefined;
|
|
65
|
-
if (argument === '--
|
|
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') {
|
|
66
76
|
if (baselinePath !== undefined)
|
|
67
77
|
return undefined;
|
|
68
78
|
baselinePath = value;
|
|
@@ -82,7 +92,13 @@ function parseCheckOptions(rest) {
|
|
|
82
92
|
if (baselinePath !== undefined && updateBaselinePath !== undefined) {
|
|
83
93
|
return undefined;
|
|
84
94
|
}
|
|
85
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
strict,
|
|
97
|
+
format: format ?? 'json',
|
|
98
|
+
baselinePath,
|
|
99
|
+
updateBaselinePath,
|
|
100
|
+
inputPaths,
|
|
101
|
+
};
|
|
86
102
|
}
|
|
87
103
|
/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */
|
|
88
104
|
async function readBaselineDocument(path, readTextFile) {
|
|
@@ -96,14 +112,18 @@ async function readBaselineDocument(path, readTextFile) {
|
|
|
96
112
|
if (text.length > MAX_INPUT_TEXT_LENGTH) {
|
|
97
113
|
throw new BridgeBaselineLimitError();
|
|
98
114
|
}
|
|
115
|
+
// JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가
|
|
116
|
+
// 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.
|
|
117
|
+
let parsed;
|
|
99
118
|
try {
|
|
100
|
-
|
|
119
|
+
parsed = JSON.parse(text);
|
|
101
120
|
}
|
|
102
121
|
catch (error) {
|
|
103
122
|
if (isJsonParseFailure(error))
|
|
104
123
|
throw new BridgeBaselineJsonError();
|
|
105
124
|
throw error;
|
|
106
125
|
}
|
|
126
|
+
return parseBaselineDocument(parsed);
|
|
107
127
|
}
|
|
108
128
|
/**
|
|
109
129
|
* 사용자 입력 JSON 파싱 실패인지 확인한다.
|
|
@@ -128,8 +148,10 @@ async function writeBaselineDocument(path, issues, generatedAt, writeTextFile) {
|
|
|
128
148
|
}
|
|
129
149
|
throw error;
|
|
130
150
|
}
|
|
151
|
+
// 인코딩 결함은 쓰기 실패가 아니라 내부 오류로 분류되어야 한다.
|
|
152
|
+
const encoded = encodeBaselineDocument(document);
|
|
131
153
|
try {
|
|
132
|
-
await writeTextFile(path,
|
|
154
|
+
await writeTextFile(path, encoded);
|
|
133
155
|
}
|
|
134
156
|
catch {
|
|
135
157
|
throw new BridgeBaselineWriteError();
|
|
@@ -188,13 +210,22 @@ export async function readBridgeDocuments(inputPaths, readTextFile) {
|
|
|
188
210
|
totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH) {
|
|
189
211
|
throw new BridgeInputLimitError(inputPosition);
|
|
190
212
|
}
|
|
213
|
+
// JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가
|
|
214
|
+
// 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.
|
|
215
|
+
let parsed;
|
|
191
216
|
try {
|
|
192
|
-
|
|
217
|
+
parsed = JSON.parse(text);
|
|
193
218
|
}
|
|
194
219
|
catch (error) {
|
|
195
220
|
if (isJsonParseFailure(error)) {
|
|
196
221
|
throw new BridgeInputJsonError(inputPosition);
|
|
197
222
|
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
documents.push(parseBridgeFactsDocument(parsed));
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
198
229
|
if (error instanceof BridgeFactsValidationError) {
|
|
199
230
|
throw new BridgeInputContractError(inputPosition, error.message);
|
|
200
231
|
}
|
|
@@ -313,6 +344,7 @@ function usageError() {
|
|
|
313
344
|
}
|
|
314
345
|
/** check 명령의 한 줄 사용법이다. */
|
|
315
346
|
export const checkUsage = 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '
|
|
316
|
-
+ '[more...] [--strict] [--
|
|
347
|
+
+ '[more...] [--strict] [--format json|sarif] '
|
|
348
|
+
+ '[--baseline <isthmus-baseline.json>] '
|
|
317
349
|
+ '[--update-baseline <isthmus-baseline.json>]';
|
|
318
350
|
//# 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';\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/** 한 입력 파일에서 허용하는 최대 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): 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, 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 = 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 baselinePath: string | undefined;\n updateBaselinePath: string | undefined;\n inputPaths: string[];\n} | undefined {\n let strict = false;\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 === '--baseline' || argument === '--update-baseline') {\n const value = rest[index + 1];\n if (value === undefined || value.startsWith('-')) return undefined;\n 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 { strict, baselinePath, updateBaselinePath, inputPaths };\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 try {\n return parseBaselineDocument(JSON.parse(text));\n } catch (error) {\n if (isJsonParseFailure(error)) throw new BridgeBaselineJsonError();\n throw error;\n }\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 try {\n await writeTextFile(path, encodeBaselineDocument(document));\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 try {\n documents.push(parseBridgeFactsDocument(JSON.parse(text)));\n } catch (error) {\n if (isJsonParseFailure(error)) {\n throw new BridgeInputJsonError(inputPosition);\n }\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] [--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 {\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"]}
|
package/dist/cli/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { checkUsage, runCheckCommand, } from "./check-command.js";
|
|
4
|
+
import { writeTextAtomically } from "./atomic-write.js";
|
|
4
5
|
import { graphUsage, runGraphCommand } from "./graph-command.js";
|
|
5
6
|
import { diffUsage, runDiffCommand } from "./diff-command.js";
|
|
6
7
|
import { queryUsage, runQueryCommand } from "./query-command.js";
|
|
@@ -29,23 +30,7 @@ Options:
|
|
|
29
30
|
`;
|
|
30
31
|
const arguments_ = process.argv.slice(2);
|
|
31
32
|
const readTextFile = (path) => readFile(path, 'utf8');
|
|
32
|
-
|
|
33
|
-
* 같은 디렉터리의 임시 파일에 쓰고 rename으로 교체한다.
|
|
34
|
-
*
|
|
35
|
-
* 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이
|
|
36
|
-
* 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이다.
|
|
37
|
-
*/
|
|
38
|
-
const writeTextFile = async (path, text) => {
|
|
39
|
-
const temporaryPath = `${path}.${process.pid}.tmp`;
|
|
40
|
-
try {
|
|
41
|
-
await writeFile(temporaryPath, text, 'utf8');
|
|
42
|
-
await rename(temporaryPath, path);
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
33
|
+
const writeTextFile = (path, text) => writeTextAtomically(path, text);
|
|
49
34
|
const informationalResult = await runInformationalCommand(arguments_);
|
|
50
35
|
const result = informationalResult ?? await dispatchCommand(arguments_);
|
|
51
36
|
process.stdout.write(result.standardOutput);
|
|
@@ -81,7 +66,8 @@ async function runInformationalCommand(commandArguments) {
|
|
|
81
66
|
async function dispatchCommand(commandArguments) {
|
|
82
67
|
switch (commandArguments[0]) {
|
|
83
68
|
case 'check':
|
|
84
|
-
|
|
69
|
+
// SARIF driver에만 필요하므로 메타데이터 부재는 json 보고서를 막지 않는다.
|
|
70
|
+
return runCheckCommand(commandArguments, readTextFile, writeTextFile, undefined, await readPackageVersion());
|
|
85
71
|
case 'graph':
|
|
86
72
|
return runGraphCommand(commandArguments, readTextFile);
|
|
87
73
|
case 'diff':
|