isthmus-cli 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -20
- package/Skills/isthmus/SKILL.md +10 -2
- package/dist/cli/check-command.d.ts +11 -0
- package/dist/cli/check-command.js +59 -7
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/graph-command.js +13 -9
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/main.js +10 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/query-command.js +6 -7
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.js +12 -9
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/exchange/parse.d.ts +2 -0
- package/dist/exchange/parse.js +27 -3
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.d.ts +9 -0
- package/dist/join/join.js +38 -10
- package/dist/join/join.js.map +1 -1
- package/dist/report/check-report.d.ts +1 -1
- package/dist/report/check-report.js +13 -1
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/graph.js +5 -1
- package/dist/report/graph.js.map +1 -1
- package/dist/report/query.js +7 -1
- package/dist/report/query.js.map +1 -1
- package/dist/report/retentions.js +3 -1
- package/dist/report/retentions.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# isthmus
|
|
2
2
|
|
|
3
|
-
크로스플랫폼 앱에서 **언어 경계를 넘는 호출**을 그래프로 잇는 도구.
|
|
3
|
+
크로스플랫폼 앱에서 **언어 경계를 넘는 호출**을 그래프로 잇는 도구.
|
|
4
|
+
[cartograph](https://github.com/ictechgy/cartograph)(Swift) · kartograph(Kotlin, 예정) ·
|
|
5
|
+
[dartograph](https://github.com/ictechgy/dartograph)(Dart)가 각자 그린 지도를 하나로 붙인다.
|
|
4
6
|
|
|
5
7
|
이름은 지협(isthmus) — 두 땅덩어리를 잇는 좁은 육교. 지도에서 다리를 뜻하는 말이다.
|
|
6
8
|
|
|
@@ -9,20 +11,25 @@
|
|
|
9
11
|
React Native 나 Flutter 앱의 네이티브 코드는 JS/Dart 가 **문자열 이름으로** 부른다. `MethodChannel('com.example/camera')`, `NativeModules.CameraModule`. 컴파일러 인덱스는 이 문자열을 못 본다. 그래서:
|
|
10
12
|
|
|
11
13
|
- cartograph 는 Flutter 가 부르는 Swift 핸들러를 **미사용** 이라고 한다 — 오탐
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
+
- 기존 언어별 분석만으로는 "Dart가 `invokeMethod('takePhoto')`를 부르는데 Swift 쪽에
|
|
15
|
+
그 핸들러가 없다"를 **빌드 전에** 잡기 어렵다 — 런타임 크래시
|
|
16
|
+
- 같은 이유로 "이 채널은 Swift에는 있는데 Dart 어디서도 안 부른다"는 교차 경계
|
|
17
|
+
사실을 언어별 도구 하나만으로는 판단하기 어렵다
|
|
14
18
|
|
|
15
19
|
isthmus 는 각 언어 도구가 내보낸 **브리지 사실**(채널 이름 · 메서드 이름 · 등록 지점 · 호출 지점)을 문자열 키로 조인해서, 경계를 넘는 간선을 만들고 위 세 가지를 답한다. 그리고 그 결과를 cartograph/kartograph 에 **보존 근거로 돌려준다** — "Swift `CameraHandler.takePhoto` 는 `lib/camera.dart:42` 가 채널 `com.example/camera` 로 부르므로 보존".
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
isthmus는 조사한 도구들이 언어별로 나눠 보던 이 교차 경계를 조인한다.
|
|
18
22
|
|
|
19
23
|
## 상태
|
|
20
24
|
|
|
21
|
-
**0.1.
|
|
25
|
+
**0.1.3.** bridge-facts 버전 1 파서와 `check`, `query`, `graph`,
|
|
22
26
|
cartograph용 외부 보존 근거 왕복을 구현했다. 외부 입력·혼합 target·그래프 크기와
|
|
23
27
|
Dart/Swift Phase 0 추출 경계를 fail-closed로 강화했다. 다음 단계는 실제 Flutter 앱
|
|
24
28
|
도그푸딩과 React Native 지원이다.
|
|
25
29
|
|
|
30
|
+
현재 cartograph는 Swift fact를 생산하지만 Dart producer는 아직 정식 릴리스 전이다.
|
|
31
|
+
저장소의 Phase 0 Dart 추출기는 형식 검증용 임시 구현이며 운영 도구로 배포되지 않는다.
|
|
32
|
+
|
|
26
33
|
| 문서 | 내용 |
|
|
27
34
|
|---|---|
|
|
28
35
|
| [`docs/PRD.md`](docs/PRD.md) | 무엇을 · 누구를 위해 · 어디까지 |
|
|
@@ -44,6 +51,8 @@ isthmus 자체는 작다. 무거운 일(각 언어의 해석)은 자매 도구
|
|
|
44
51
|
|
|
45
52
|
## 설치
|
|
46
53
|
|
|
54
|
+
Node.js 22.18.0 이상이 필요하다.
|
|
55
|
+
|
|
47
56
|
전역 설치 후 CLI 이름 `isthmus`로 실행한다.
|
|
48
57
|
|
|
49
58
|
```bash
|
|
@@ -59,33 +68,31 @@ npx isthmus-cli --help
|
|
|
59
68
|
|
|
60
69
|
`npx isthmus`는 이름이 같은 다른 package를 설치하므로 사용하지 않는다.
|
|
61
70
|
|
|
62
|
-
##
|
|
71
|
+
## 사용
|
|
63
72
|
|
|
64
73
|
isthmus는 자매 도구를 직접 실행하지 않는다. 각 도구가 만든 JSON 파일을 전달한다.
|
|
65
74
|
|
|
66
75
|
```bash
|
|
67
|
-
|
|
68
|
-
npm run build
|
|
69
|
-
node dist/cli/main.js check dart-bridges.json swift-bridges.json
|
|
76
|
+
isthmus check dart-bridges.json swift-bridges.json
|
|
70
77
|
```
|
|
71
78
|
|
|
72
79
|
전체 명령과 현재 package 버전은 다음처럼 확인한다.
|
|
73
80
|
|
|
74
81
|
```bash
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
isthmus --help
|
|
83
|
+
isthmus --version
|
|
77
84
|
```
|
|
78
85
|
|
|
79
86
|
CI에서 브리지 오류가 있으면 실패시키려면 `--strict`를 붙인다.
|
|
80
87
|
|
|
81
88
|
```bash
|
|
82
|
-
|
|
89
|
+
isthmus check dart-bridges.json swift-bridges.json --strict
|
|
83
90
|
```
|
|
84
91
|
|
|
85
92
|
매치된 Swift 핸들러를 cartograph 보존 근거로 돌려주려면:
|
|
86
93
|
|
|
87
94
|
```bash
|
|
88
|
-
|
|
95
|
+
isthmus retentions \
|
|
89
96
|
dart-bridges.json swift-bridges.json \
|
|
90
97
|
--for cartograph > external-retentions.json
|
|
91
98
|
|
|
@@ -97,15 +104,15 @@ cartograph dead --external-retentions external-retentions.json
|
|
|
97
104
|
채널이나 메서드가 경계 반대편의 어느 위치와 연결되는지 조회하려면:
|
|
98
105
|
|
|
99
106
|
```bash
|
|
100
|
-
|
|
107
|
+
isthmus query takePhoto dart-bridges.json swift-bridges.json
|
|
101
108
|
```
|
|
102
109
|
|
|
103
110
|
경계 간선만 JSON, Graphviz DOT, Mermaid로 출력하려면:
|
|
104
111
|
|
|
105
112
|
```bash
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
isthmus graph dart-bridges.json swift-bridges.json
|
|
114
|
+
isthmus graph dart-bridges.json swift-bridges.json --format dot
|
|
115
|
+
isthmus graph dart-bridges.json swift-bridges.json --format mermaid
|
|
109
116
|
```
|
|
110
117
|
|
|
111
118
|
`query`는 같은 메서드가 여러 채널에 있으면 후보를 반환하고 임의로 고르지 않는다.
|
|
@@ -114,13 +121,16 @@ node dist/cli/main.js graph dart-bridges.json swift-bridges.json --format mermai
|
|
|
114
121
|
주석으로 보존한다. 증거의 Cartesian 곱이 100,000개 간선을 넘으면 메모리 폭증을 막기
|
|
115
122
|
위해 입력 오류로 종료한다.
|
|
116
123
|
|
|
117
|
-
출력은 `isthmus-check` 버전 1 JSON이며 다음
|
|
124
|
+
출력은 `isthmus-check` 버전 1 JSON이며 다음 네 사실을 보고한다.
|
|
118
125
|
|
|
119
126
|
- `unhandled-invocation` (error): 호출은 있지만 네이티브 핸들러가 없음
|
|
120
127
|
- `unregistered-channel-creation` (error): 호출 측 채널 생성은 있지만 네이티브 등록이 없음
|
|
128
|
+
- `registration-without-creation` (warning): 네이티브 채널 등록은 있지만 호출 측 생성이 없음
|
|
121
129
|
- `handler-without-invocation` (warning): 네이티브 핸들러는 있지만 호출 측 사용이 없음
|
|
122
130
|
|
|
123
|
-
모든 이슈는 관찰된 위치를 `evidence`로 제공한다. 동적 이름,
|
|
131
|
+
모든 이슈는 관찰된 위치를 `evidence`로 제공한다. 동적 이름, 해석하지 못한 receiver나
|
|
132
|
+
handler 본문, USR 누락, 입력 생성 시각 차이, 혼합 target은 `limitations`에 출처와 함께
|
|
133
|
+
남긴다. 이 도구는 삭제 가능 여부를 판정하지 않는다.
|
|
124
134
|
|
|
125
135
|
| 종료 코드 | 의미 |
|
|
126
136
|
|---|---|
|
|
@@ -129,13 +139,16 @@ node dist/cli/main.js graph dart-bridges.json swift-bridges.json --format mermai
|
|
|
129
139
|
| `2` | 파일 읽기, JSON, 교환 계약, 보류된 조인 등 도구 실패 |
|
|
130
140
|
| `64` | 잘못된 명령·옵션·입력 개수 또는 `query`의 `notFound`·`ambiguous` |
|
|
131
141
|
|
|
132
|
-
|
|
142
|
+
저장소 checkout에서 개발할 때는 먼저 `npm ci`를 실행한다. 개발 검증은 타입 체크,
|
|
143
|
+
제품 코드 90% 커버리지, clean build, 실제 CLI·package 계약을 함께 실행한다.
|
|
133
144
|
|
|
134
145
|
```bash
|
|
135
146
|
npm run verify
|
|
136
147
|
```
|
|
137
148
|
|
|
138
149
|
로컬 cartograph 코퍼스와 외부 보존 근거 왕복을 검증하려면 빌드된 cartograph 바이너리와 fixture 루트를 넘긴다.
|
|
150
|
+
이 검증은 외부 바이너리와 코퍼스가 필요하므로 `npm run verify`와 공개 CI에는 포함되지
|
|
151
|
+
않으며 릴리스 전에 수동으로 실행한다.
|
|
139
152
|
|
|
140
153
|
```bash
|
|
141
154
|
node scripts/verify-cartograph-roundtrip.mjs \
|
package/Skills/isthmus/SKILL.md
CHANGED
|
@@ -15,6 +15,10 @@ another platform may call.
|
|
|
15
15
|
|
|
16
16
|
Obtain current `bridge-facts` JSON from both sides, then run:
|
|
17
17
|
|
|
18
|
+
As of isthmus 0.1.1, cartograph produces the Swift facts. The Dart producer is not yet
|
|
19
|
+
released; use the repository's Phase 0 extractor only as an interim source.
|
|
20
|
+
Do not fabricate a missing facts file or treat an older file as current evidence.
|
|
21
|
+
|
|
18
22
|
```bash
|
|
19
23
|
isthmus query <channel-or-method> <bridge-facts.json> <bridge-facts.json> [more...]
|
|
20
24
|
```
|
|
@@ -39,9 +43,13 @@ For Swift handlers, produce external retention evidence and use the same inputs:
|
|
|
39
43
|
|
|
40
44
|
```bash
|
|
41
45
|
isthmus retentions <bridge-facts.json> <bridge-facts.json> --for cartograph \
|
|
42
|
-
> external-retentions.json
|
|
43
|
-
|
|
46
|
+
> external-retentions.json &&
|
|
47
|
+
test -s external-retentions.json &&
|
|
48
|
+
cartograph dead --external-retentions external-retentions.json
|
|
44
49
|
```
|
|
45
50
|
|
|
51
|
+
Only continue when every command exits 0. A failed or empty retentions file is not evidence
|
|
52
|
+
that a native handler is unused; remove the incomplete output and regenerate both facts files.
|
|
53
|
+
|
|
46
54
|
Do not commit generated retention files when they contain local source paths unless the
|
|
47
55
|
project has explicitly chosen to version them.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { type BridgeFactsDocument } from '../exchange/parse.ts';
|
|
1
2
|
/** 파일 경로를 받아 UTF-8 텍스트를 읽는 주입 경계다. */
|
|
2
3
|
export type ReadTextFile = (path: string) => Promise<string>;
|
|
4
|
+
/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
|
|
5
|
+
export declare const MAX_INPUT_TEXT_LENGTH: number;
|
|
6
|
+
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
7
|
+
export declare const MAX_TOTAL_INPUT_TEXT_LENGTH: number;
|
|
3
8
|
/** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */
|
|
4
9
|
export interface CommandResult {
|
|
5
10
|
readonly standardOutput: string;
|
|
@@ -8,7 +13,13 @@ export interface CommandResult {
|
|
|
8
13
|
}
|
|
9
14
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
10
15
|
export declare function runCheckCommand(arguments_: readonly string[], readTextFile: ReadTextFile): Promise<CommandResult>;
|
|
16
|
+
/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
|
|
17
|
+
export declare function readBridgeDocuments(inputPaths: readonly string[], readTextFile: ReadTextFile): Promise<BridgeFactsDocument[]>;
|
|
18
|
+
/** 외부 입력에서 기대하는 실패 종류인지 확인한다. */
|
|
19
|
+
export declare function isExpectedInputError(error: unknown): boolean;
|
|
11
20
|
/** 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다. */
|
|
12
21
|
export declare function bridgeJoinDeferredError(): CommandResult;
|
|
22
|
+
/** 내부 결함을 입력 탓으로 돌리지 않는 경로 없는 코드 2 결과다. */
|
|
23
|
+
export declare function internalError(): CommandResult;
|
|
13
24
|
/** check 명령의 한 줄 사용법이다. */
|
|
14
25
|
export declare const checkUsage: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { parseBridgeFactsDocument } from "../exchange/parse.js";
|
|
2
|
-
import { isBridgeJoinDeferred, joinBridgeDocuments } from "../join/join.js";
|
|
1
|
+
import { BridgeFactsValidationError, parseBridgeFactsDocument, } from "../exchange/parse.js";
|
|
2
|
+
import { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
3
3
|
import { createCheckReport, encodeCheckReport } from "../report/check-report.js";
|
|
4
|
+
/** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
|
|
5
|
+
export const MAX_INPUT_TEXT_LENGTH = 16 * 1024 * 1024;
|
|
6
|
+
/** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
|
|
7
|
+
export const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;
|
|
4
8
|
/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
|
|
5
9
|
export async function runCheckCommand(arguments_, readTextFile) {
|
|
6
10
|
if (arguments_[0] !== 'check')
|
|
@@ -10,11 +14,11 @@ export async function runCheckCommand(arguments_, readTextFile) {
|
|
|
10
14
|
return usageError();
|
|
11
15
|
const isStrict = arguments_.includes('--strict');
|
|
12
16
|
const inputPaths = arguments_.slice(1).filter((argument) => argument !== '--strict');
|
|
13
|
-
if (inputPaths.length < 2)
|
|
17
|
+
if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {
|
|
14
18
|
return usageError();
|
|
19
|
+
}
|
|
15
20
|
try {
|
|
16
|
-
const
|
|
17
|
-
const documents = texts.map((text) => parseBridgeFactsDocument(JSON.parse(text)));
|
|
21
|
+
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
18
22
|
const joined = joinBridgeDocuments(documents);
|
|
19
23
|
if (isBridgeJoinDeferred(joined))
|
|
20
24
|
return bridgeJoinDeferredError();
|
|
@@ -25,9 +29,49 @@ export async function runCheckCommand(arguments_, readTextFile) {
|
|
|
25
29
|
exitCode: isStrict && report.summary.errors > 0 ? 1 : 0,
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
|
-
catch {
|
|
29
|
-
return inputError();
|
|
32
|
+
catch (error) {
|
|
33
|
+
return isExpectedInputError(error) ? inputError() : internalError();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
|
|
37
|
+
export async function readBridgeDocuments(inputPaths, readTextFile) {
|
|
38
|
+
const documents = [];
|
|
39
|
+
let totalTextLength = 0;
|
|
40
|
+
for (const path of inputPaths) {
|
|
41
|
+
let text;
|
|
42
|
+
try {
|
|
43
|
+
text = await readTextFile(path);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new BridgeInputError();
|
|
47
|
+
}
|
|
48
|
+
totalTextLength += text.length;
|
|
49
|
+
if (text.length > MAX_INPUT_TEXT_LENGTH ||
|
|
50
|
+
totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH) {
|
|
51
|
+
throw new BridgeInputLimitError();
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
documents.push(parseBridgeFactsDocument(JSON.parse(text)));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error instanceof SyntaxError ||
|
|
58
|
+
error instanceof BridgeFactsValidationError) {
|
|
59
|
+
throw new BridgeInputError();
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
30
63
|
}
|
|
64
|
+
return documents;
|
|
65
|
+
}
|
|
66
|
+
/** 입력 텍스트가 CLI 메모리 예산을 넘었음을 나타낸다. */
|
|
67
|
+
class BridgeInputError extends Error {
|
|
68
|
+
}
|
|
69
|
+
/** 입력 텍스트의 크기만 계약을 넘은 경우를 구분한다. */
|
|
70
|
+
class BridgeInputLimitError extends BridgeInputError {
|
|
71
|
+
}
|
|
72
|
+
/** 외부 입력에서 기대하는 실패 종류인지 확인한다. */
|
|
73
|
+
export function isExpectedInputError(error) {
|
|
74
|
+
return error instanceof BridgeInputError || error instanceof BridgeJoinValidationError;
|
|
31
75
|
}
|
|
32
76
|
/** 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다. */
|
|
33
77
|
export function bridgeJoinDeferredError() {
|
|
@@ -45,6 +89,14 @@ function inputError() {
|
|
|
45
89
|
exitCode: 2,
|
|
46
90
|
};
|
|
47
91
|
}
|
|
92
|
+
/** 내부 결함을 입력 탓으로 돌리지 않는 경로 없는 코드 2 결과다. */
|
|
93
|
+
export function internalError() {
|
|
94
|
+
return {
|
|
95
|
+
standardOutput: '',
|
|
96
|
+
standardError: 'Internal isthmus error; retry with a current version.\n',
|
|
97
|
+
exitCode: 2,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
48
100
|
/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */
|
|
49
101
|
function usageError() {
|
|
50
102
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAKjF,wCAAwC;AACxC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtD,wCAAwC;AACxC,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAS5D,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAA6B,EAC7B,YAA0B;IAE1B,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,UAAU,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,UAAU,CAAC;QAAE,OAAO,UAAU,EAAE,CAAC;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACrF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QACxE,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,oBAAoB,CAAC,MAAM,CAAC;YAAE,OAAO,uBAAuB,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO;YACL,cAAc,EAAE,iBAAiB,CAAC,MAAM,CAAC;YACzC,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACxD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACtE,CAAC;AACH,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAA6B,EAC7B,YAA0B;IAE1B,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,gBAAgB,EAAE,CAAC;QAC/B,CAAC;QACD,eAAe,IAAI,IAAI,CAAC,MAAM,CAAC;QAC/B,IACE,IAAI,CAAC,MAAM,GAAG,qBAAqB;YACnC,eAAe,GAAG,2BAA2B,EAC7C,CAAC;YACD,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACpC,CAAC;QACD,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK,YAAY,WAAW;gBAC5B,KAAK,YAAY,0BAA0B,EAC3C,CAAC;gBACD,MAAM,IAAI,gBAAgB,EAAE,CAAC;YAC/B,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qCAAqC;AACrC,MAAM,gBAAiB,SAAQ,KAAK;CAAG;AAEvC,mCAAmC;AACnC,MAAM,qBAAsB,SAAQ,gBAAgB;CAAG;AAEvD,iCAAiC;AACjC,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,OAAO,KAAK,YAAY,gBAAgB,IAAI,KAAK,YAAY,yBAAyB,CAAC;AACzF,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,uBAAuB;IACrC,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EACX,2EAA2E;QAC7E,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,2CAA2C;AAC3C,SAAS,UAAU;IACjB,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EACX,mEAAmE;QACrE,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,yDAAyD;QACxE,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,yCAAyC;AACzC,SAAS,UAAU;IACjB,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,GAAG,UAAU,IAAI;QAChC,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,2BAA2B;AAC3B,MAAM,CAAC,MAAM,UAAU,GACrB,+DAA+D;MAC7D,sBAAsB,CAAC","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 { createCheckReport, encodeCheckReport } from '../report/check-report.ts';\n\n/** 파일 경로를 받아 UTF-8 텍스트를 읽는 주입 경계다. */\nexport type ReadTextFile = (path: string) => Promise<string>;\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): Promise<CommandResult> {\n if (arguments_[0] !== 'check') return usageError();\n const options = arguments_.slice(1).filter((argument) => argument.startsWith('-'));\n if (options.some((option) => option !== '--strict')) return usageError();\n const isStrict = arguments_.includes('--strict');\n const inputPaths = arguments_.slice(1).filter((argument) => argument !== '--strict');\n if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) return bridgeJoinDeferredError();\n const report = createCheckReport(joined);\n return {\n standardOutput: encodeCheckReport(report),\n standardError: '',\n exitCode: isStrict && report.summary.errors > 0 ? 1 : 0,\n };\n } catch (error) {\n return isExpectedInputError(error) ? inputError() : internalError();\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 path of inputPaths) {\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeInputError();\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();\n }\n try {\n documents.push(parseBridgeFactsDocument(JSON.parse(text)));\n } catch (error) {\n if (\n error instanceof SyntaxError ||\n error instanceof BridgeFactsValidationError\n ) {\n throw new BridgeInputError();\n }\n throw error;\n }\n }\n return documents;\n}\n\n/** 입력 텍스트가 CLI 메모리 예산을 넘었음을 나타낸다. */\nclass BridgeInputError extends Error {}\n\n/** 입력 텍스트의 크기만 계약을 넘은 경우를 구분한다. */\nclass BridgeInputLimitError extends BridgeInputError {}\n\n/** 외부 입력에서 기대하는 실패 종류인지 확인한다. */\nexport function isExpectedInputError(error: unknown): boolean {\n return error instanceof BridgeInputError || error instanceof BridgeJoinValidationError;\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/** 파일·JSON·계약 오류를 민감정보 없는 코드 2 결과로 바꾼다. */\nfunction inputError(): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Unable to read or validate bridge facts; check the input files.\\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]';\n"]}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { bridgeJoinDeferredError, } from "./check-command.js";
|
|
1
|
+
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
2
|
+
import { BridgeGraphLimitError, BridgeGraphValidationError, createBridgeGraph, renderBridgeGraph, } from "../report/graph.js";
|
|
3
|
+
import { bridgeJoinDeferredError, internalError, isExpectedInputError, readBridgeDocuments, } from "./check-command.js";
|
|
5
4
|
/** graph 인자를 실행해 경계 그래프와 종료 코드를 반환한다. */
|
|
6
5
|
export async function runGraphCommand(arguments_, readTextFile) {
|
|
7
6
|
const options = parseGraphArguments(arguments_);
|
|
8
7
|
if (options === undefined)
|
|
9
8
|
return graphUsageError();
|
|
10
9
|
try {
|
|
11
|
-
const
|
|
12
|
-
const documents = texts.map((text) => parseBridgeFactsDocument(JSON.parse(text)));
|
|
10
|
+
const documents = await readBridgeDocuments(options.inputPaths, readTextFile);
|
|
13
11
|
const joined = joinBridgeDocuments(documents);
|
|
14
12
|
if (isBridgeJoinDeferred(joined))
|
|
15
13
|
return bridgeJoinDeferredError();
|
|
@@ -20,8 +18,12 @@ export async function runGraphCommand(arguments_, readTextFile) {
|
|
|
20
18
|
exitCode: 0,
|
|
21
19
|
};
|
|
22
20
|
}
|
|
23
|
-
catch {
|
|
24
|
-
return
|
|
21
|
+
catch (error) {
|
|
22
|
+
return isExpectedInputError(error) ||
|
|
23
|
+
error instanceof BridgeGraphLimitError ||
|
|
24
|
+
error instanceof BridgeGraphValidationError
|
|
25
|
+
? graphInputError()
|
|
26
|
+
: internalError();
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
/** graph의 입력 파일과 선택 출력 형식을 검증한다. */
|
|
@@ -30,7 +32,9 @@ function parseGraphArguments(arguments_) {
|
|
|
30
32
|
return undefined;
|
|
31
33
|
const formatIndex = arguments_.indexOf('--format');
|
|
32
34
|
const inputPaths = arguments_.slice(1, formatIndex < 0 ? undefined : formatIndex);
|
|
33
|
-
if (inputPaths.length < 2 ||
|
|
35
|
+
if (inputPaths.length < 2 ||
|
|
36
|
+
inputPaths.length > MAX_DOCUMENTS_PER_JOIN ||
|
|
37
|
+
inputPaths.some((path) => path.startsWith('-'))) {
|
|
34
38
|
return undefined;
|
|
35
39
|
}
|
|
36
40
|
if (formatIndex < 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-command.js","sourceRoot":"","sources":["../../src/cli/graph-command.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"graph-command.js","sourceRoot":"","sources":["../../src/cli/graph-command.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n BridgeGraphLimitError,\n BridgeGraphValidationError,\n createBridgeGraph,\n renderBridgeGraph,\n} from '../report/graph.ts';\nimport {\n bridgeJoinDeferredError,\n internalError,\n isExpectedInputError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './check-command.ts';\n\n/** graph 인자를 실행해 경계 그래프와 종료 코드를 반환한다. */\nexport async function runGraphCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const options = parseGraphArguments(arguments_);\n if (options === undefined) return graphUsageError();\n try {\n const documents = await readBridgeDocuments(options.inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) return bridgeJoinDeferredError();\n const graph = createBridgeGraph(joined);\n return {\n standardOutput: renderBridgeGraph(graph, options.format),\n standardError: '',\n exitCode: 0,\n };\n } catch (error) {\n return isExpectedInputError(error) ||\n error instanceof BridgeGraphLimitError ||\n error instanceof BridgeGraphValidationError\n ? graphInputError()\n : internalError();\n }\n}\n\n/** graph의 입력 파일과 선택 출력 형식을 검증한다. */\nfunction parseGraphArguments(arguments_: readonly string[]): GraphOptions | undefined {\n if (arguments_[0] !== 'graph') return undefined;\n const formatIndex = arguments_.indexOf('--format');\n const inputPaths = arguments_.slice(1, formatIndex < 0 ? undefined : formatIndex);\n if (\n inputPaths.length < 2 ||\n inputPaths.length > MAX_DOCUMENTS_PER_JOIN ||\n inputPaths.some((path) => path.startsWith('-'))\n ) {\n return undefined;\n }\n if (formatIndex < 0) return { inputPaths, format: 'json' };\n if (formatIndex !== arguments_.length - 2) return undefined;\n const format = arguments_[formatIndex + 1];\n return isGraphFormat(format) ? { inputPaths, format } : undefined;\n}\n\n/** 지원하는 그래프 형식인지 확인한다. */\nfunction isGraphFormat(value: string | undefined): value is GraphFormat {\n return value === 'json' || value === 'dot' || value === 'mermaid';\n}\n\n/** graph 사용 오류를 경로 없는 코드 64로 바꾼다. */\nfunction graphUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${graphUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** graph 입력 실패를 경로 없는 코드 2로 바꾼다. */\nfunction graphInputError(): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Unable to read or validate bridge facts; check the input files.\\n',\n exitCode: 2,\n };\n}\n\n/** 검증된 graph 명령 옵션이다. */\ninterface GraphOptions {\n readonly inputPaths: readonly string[];\n readonly format: GraphFormat;\n}\n\ntype GraphFormat = 'json' | 'dot' | 'mermaid';\n\n/** graph 명령의 한 줄 사용법이다. */\nexport const graphUsage =\n 'Usage: isthmus graph <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--format json|dot|mermaid]';\n"]}
|
package/dist/cli/main.js
CHANGED
|
@@ -4,6 +4,8 @@ import { checkUsage, runCheckCommand, } from "./check-command.js";
|
|
|
4
4
|
import { graphUsage, runGraphCommand } from "./graph-command.js";
|
|
5
5
|
import { queryUsage, runQueryCommand } from "./query-command.js";
|
|
6
6
|
import { retentionUsage, runRetentionsCommand, } from "./retentions-command.js";
|
|
7
|
+
process.stdout.on('error', handleStreamError);
|
|
8
|
+
process.stderr.on('error', handleStreamError);
|
|
7
9
|
const commandUsages = new Map([
|
|
8
10
|
['check', checkUsage],
|
|
9
11
|
['graph', graphUsage],
|
|
@@ -29,6 +31,14 @@ const result = informationalResult ?? await dispatchCommand(arguments_);
|
|
|
29
31
|
process.stdout.write(result.standardOutput);
|
|
30
32
|
process.stderr.write(result.standardError);
|
|
31
33
|
process.exitCode = result.exitCode;
|
|
34
|
+
/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */
|
|
35
|
+
function handleStreamError(error) {
|
|
36
|
+
if (error.code === 'EPIPE') {
|
|
37
|
+
process.exitCode = 0;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
32
42
|
/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */
|
|
33
43
|
async function runInformationalCommand(commandArguments) {
|
|
34
44
|
const first = commandArguments[0];
|
package/dist/cli/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport {\n checkUsage,\n runCheckCommand,\n type CommandResult,\n} from './check-command.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['query', queryUsage],\n ['retentions', retentionUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n graph Render matched boundary edges\n retentions Produce external retention evidence\n\nOptions:\n -h, --help Show help\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n const first = commandArguments[0];\n if (\n commandArguments.length === 1 &&\n (first === '--help' || first === '-h')\n ) {\n return successfulText(rootHelp);\n }\n if (commandArguments.length === 1 && first === '--version') {\n return readVersion();\n }\n if (\n commandArguments.length === 2 &&\n (commandArguments[1] === '--help' || commandArguments[1] === '-h')\n ) {\n const usage = first === undefined ? undefined : commandUsages.get(first);\n if (usage !== undefined) return successfulText(`${usage}\\n`);\n }\n return undefined;\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n return runCheckCommand(commandArguments, readTextFile);\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from 'node:fs/promises';\n\nimport {\n checkUsage,\n runCheckCommand,\n type CommandResult,\n} from './check-command.ts';\nimport { graphUsage, runGraphCommand } from './graph-command.ts';\nimport { queryUsage, runQueryCommand } from './query-command.ts';\nimport {\n retentionUsage,\n runRetentionsCommand,\n} from './retentions-command.ts';\n\nprocess.stdout.on('error', handleStreamError);\nprocess.stderr.on('error', handleStreamError);\n\nconst commandUsages = new Map([\n ['check', checkUsage],\n ['graph', graphUsage],\n ['query', queryUsage],\n ['retentions', retentionUsage],\n]);\n\nconst rootHelp = `Usage: isthmus <command> [options]\n\nCommands:\n check Report unmatched bridge calls and handlers\n query Find both sides of a channel or method\n graph Render matched boundary edges\n retentions Produce external retention evidence\n\nOptions:\n -h, --help Show help\n --version Show version\n`;\n\nconst arguments_ = process.argv.slice(2);\nconst readTextFile = (path: string) => readFile(path, 'utf8');\nconst informationalResult = await runInformationalCommand(arguments_);\nconst result = informationalResult ?? await dispatchCommand(arguments_);\n\nprocess.stdout.write(result.standardOutput);\nprocess.stderr.write(result.standardError);\nprocess.exitCode = result.exitCode;\n\n/** 파이프 소비자가 먼저 닫힌 정상 상황만 조용히 종료한다. */\nfunction handleStreamError(error: NodeJS.ErrnoException): void {\n if (error.code === 'EPIPE') {\n process.exitCode = 0;\n return;\n }\n throw error;\n}\n\n/** 도움말과 버전처럼 입력 문서가 필요 없는 명령을 실행한다. */\nasync function runInformationalCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult | undefined> {\n const first = commandArguments[0];\n if (\n commandArguments.length === 1 &&\n (first === '--help' || first === '-h')\n ) {\n return successfulText(rootHelp);\n }\n if (commandArguments.length === 1 && first === '--version') {\n return readVersion();\n }\n if (\n commandArguments.length === 2 &&\n (commandArguments[1] === '--help' || commandArguments[1] === '-h')\n ) {\n const usage = first === undefined ? undefined : commandUsages.get(first);\n if (usage !== undefined) return successfulText(`${usage}\\n`);\n }\n return undefined;\n}\n\n/** 알려진 분석 하위 명령으로만 라우팅한다. */\nasync function dispatchCommand(\n commandArguments: readonly string[],\n): Promise<CommandResult> {\n switch (commandArguments[0]) {\n case 'check':\n return runCheckCommand(commandArguments, readTextFile);\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'query':\n return runQueryCommand(commandArguments, readTextFile);\n case 'retentions': {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : runRetentionsCommand(\n commandArguments,\n readTextFile,\n () => new Date(),\n version,\n );\n }\n default:\n return {\n standardOutput: '',\n standardError: rootHelp,\n exitCode: 64,\n };\n }\n}\n\n/** 설치된 package.json을 단일 출처로 사용해 버전을 출력한다. */\nasync function readVersion(): Promise<CommandResult> {\n const version = await readPackageVersion();\n return version === undefined\n ? packageMetadataError()\n : successfulText(`${version}\\n`);\n}\n\n/** 설치된 package.json에서 검증된 버전 문자열을 읽는다. */\nasync function readPackageVersion(): Promise<string | undefined> {\n try {\n const text = await readFile(new URL('../../package.json', import.meta.url), 'utf8');\n const document: unknown = JSON.parse(text);\n return isVersionDocument(document) ? document.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** package metadata가 비어 있지 않은 버전을 제공하는지 확인한다. */\nfunction isVersionDocument(value: unknown): value is { readonly version: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n typeof value.version === 'string' &&\n value.version.length > 0\n );\n}\n\n/** stdout 텍스트와 성공 종료 코드를 만든다. */\nfunction successfulText(standardOutput: string): CommandResult {\n return { standardOutput, standardError: '', exitCode: 0 };\n}\n\n/** package metadata 실패를 민감정보 없는 코드 2로 바꾼다. */\nfunction packageMetadataError(): CommandResult {\n return {\n standardOutput: '',\n standardError: 'Unable to read package metadata.\\n',\n exitCode: 2,\n };\n}\n"]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isBridgeJoinDeferred, joinBridgeDocuments } from "../join/join.js";
|
|
1
|
+
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
3
2
|
import { createBridgeQuery, encodeBridgeQuery } from "../report/query.js";
|
|
4
|
-
import { bridgeJoinDeferredError, } from "./check-command.js";
|
|
3
|
+
import { bridgeJoinDeferredError, internalError, isExpectedInputError, readBridgeDocuments, } from "./check-command.js";
|
|
5
4
|
/** query 인자를 실행해 bridge 질의 JSON과 종료 코드를 반환한다. */
|
|
6
5
|
export async function runQueryCommand(arguments_, readTextFile) {
|
|
7
6
|
const requested = arguments_[1];
|
|
@@ -10,12 +9,12 @@ export async function runQueryCommand(arguments_, readTextFile) {
|
|
|
10
9
|
requested === undefined ||
|
|
11
10
|
requested.trim().length === 0 ||
|
|
12
11
|
inputPaths.length < 2 ||
|
|
12
|
+
inputPaths.length > MAX_DOCUMENTS_PER_JOIN ||
|
|
13
13
|
inputPaths.some((path) => path.startsWith('-'))) {
|
|
14
14
|
return queryUsageError();
|
|
15
15
|
}
|
|
16
16
|
try {
|
|
17
|
-
const
|
|
18
|
-
const documents = texts.map((text) => parseBridgeFactsDocument(JSON.parse(text)));
|
|
17
|
+
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
19
18
|
const joined = joinBridgeDocuments(documents);
|
|
20
19
|
if (isBridgeJoinDeferred(joined))
|
|
21
20
|
return bridgeJoinDeferredError();
|
|
@@ -26,8 +25,8 @@ export async function runQueryCommand(arguments_, readTextFile) {
|
|
|
26
25
|
exitCode: query.status === 'found' ? 0 : 64,
|
|
27
26
|
};
|
|
28
27
|
}
|
|
29
|
-
catch {
|
|
30
|
-
return queryInputError();
|
|
28
|
+
catch (error) {
|
|
29
|
+
return isExpectedInputError(error) ? queryInputError() : internalError();
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
32
|
/** 입력 실패를 경로 없는 도구 실패 2로 바꾼다. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-command.js","sourceRoot":"","sources":["../../src/cli/query-command.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"query-command.js","sourceRoot":"","sources":["../../src/cli/query-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GAGpB,MAAM,oBAAoB,CAAC;AAE5B,iDAAiD;AACjD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAA6B,EAC7B,YAA0B;IAE1B,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvC,IACE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO;QACzB,SAAS,KAAK,SAAS;QACvB,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAC7B,UAAU,CAAC,MAAM,GAAG,CAAC;QACrB,UAAU,CAAC,MAAM,GAAG,sBAAsB;QAC1C,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAC/C,CAAC;QACD,OAAO,eAAe,EAAE,CAAC;IAC3B,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,oBAAoB,CAAC,MAAM,CAAC;YAAE,OAAO,uBAAuB,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO;YACL,cAAc,EAAE,iBAAiB,CAAC,KAAK,CAAC;YACxC,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;SAC5C,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,iCAAiC;AACjC,SAAS,eAAe;IACtB,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EACX,mEAAmE;QACrE,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,qCAAqC;AACrC,SAAS,eAAe;IACtB,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,GAAG,UAAU,IAAI;QAChC,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,2BAA2B;AAC3B,MAAM,CAAC,MAAM,UAAU,GACrB,+DAA+D;MAC7D,+BAA+B,CAAC","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport { createBridgeQuery, encodeBridgeQuery } from '../report/query.ts';\nimport {\n bridgeJoinDeferredError,\n internalError,\n isExpectedInputError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './check-command.ts';\n\n/** query 인자를 실행해 bridge 질의 JSON과 종료 코드를 반환한다. */\nexport async function runQueryCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const requested = arguments_[1];\n const inputPaths = arguments_.slice(2);\n if (\n arguments_[0] !== 'query' ||\n requested === undefined ||\n requested.trim().length === 0 ||\n inputPaths.length < 2 ||\n inputPaths.length > MAX_DOCUMENTS_PER_JOIN ||\n inputPaths.some((path) => path.startsWith('-'))\n ) {\n return queryUsageError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) return bridgeJoinDeferredError();\n const query = createBridgeQuery(joined, requested);\n return {\n standardOutput: encodeBridgeQuery(query),\n standardError: '',\n exitCode: query.status === 'found' ? 0 : 64,\n };\n } catch (error) {\n return isExpectedInputError(error) ? queryInputError() : internalError();\n }\n}\n\n/** 입력 실패를 경로 없는 도구 실패 2로 바꾼다. */\nfunction queryInputError(): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Unable to read or validate bridge facts; check the input files.\\n',\n exitCode: 2,\n };\n}\n\n/** 잘못된 query 호출을 사용법과 코드 64로 바꾼다. */\nfunction queryUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${queryUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** query 명령의 한 줄 사용법이다. */\nexport const queryUsage =\n 'Usage: isthmus query <channel-or-method> <bridge-facts.json> '\n + '<bridge-facts.json> [more...]';\n"]}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { bridgeJoinDeferredError, } from "./check-command.js";
|
|
1
|
+
import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
|
|
2
|
+
import { createCartographRetentionsDocument, encodeCartographRetentionsDocument, RetentionValidationError, } from "../report/retentions.js";
|
|
3
|
+
import { bridgeJoinDeferredError, internalError, isExpectedInputError, readBridgeDocuments, } from "./check-command.js";
|
|
5
4
|
/** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */
|
|
6
5
|
export async function runRetentionsCommand(arguments_, readTextFile, now, producerVersion) {
|
|
7
6
|
const inputPaths = retentionInputPaths(arguments_);
|
|
8
7
|
if (inputPaths === undefined)
|
|
9
8
|
return retentionUsageError();
|
|
10
9
|
try {
|
|
11
|
-
const
|
|
12
|
-
const documents = texts.map((text) => parseBridgeFactsDocument(JSON.parse(text)));
|
|
10
|
+
const documents = await readBridgeDocuments(inputPaths, readTextFile);
|
|
13
11
|
const joined = joinBridgeDocuments(documents);
|
|
14
12
|
if (isBridgeJoinDeferred(joined))
|
|
15
13
|
return bridgeJoinDeferredError();
|
|
@@ -20,8 +18,10 @@ export async function runRetentionsCommand(arguments_, readTextFile, now, produc
|
|
|
20
18
|
exitCode: 0,
|
|
21
19
|
};
|
|
22
20
|
}
|
|
23
|
-
catch {
|
|
24
|
-
return
|
|
21
|
+
catch (error) {
|
|
22
|
+
return isExpectedInputError(error) || error instanceof RetentionValidationError
|
|
23
|
+
? retentionInputError()
|
|
24
|
+
: internalError();
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
/** 입력 실패를 경로 없는 도구 실패 2로 바꾼다. */
|
|
@@ -42,7 +42,10 @@ function retentionInputPaths(arguments_) {
|
|
|
42
42
|
if (arguments_[forIndex + 1] !== 'cartograph')
|
|
43
43
|
return undefined;
|
|
44
44
|
const paths = arguments_.slice(1, forIndex);
|
|
45
|
-
return paths.
|
|
45
|
+
return paths.length > MAX_DOCUMENTS_PER_JOIN ||
|
|
46
|
+
paths.some((path) => path.startsWith('-'))
|
|
47
|
+
? undefined
|
|
48
|
+
: paths;
|
|
46
49
|
}
|
|
47
50
|
/** 잘못된 retentions 호출을 사용법과 코드 64로 바꾼다. */
|
|
48
51
|
function retentionUsageError() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retentions-command.js","sourceRoot":"","sources":["../../src/cli/retentions-command.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"retentions-command.js","sourceRoot":"","sources":["../../src/cli/retentions-command.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kCAAkC,EAClC,kCAAkC,EAClC,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GAGpB,MAAM,oBAAoB,CAAC;AAK5B,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAA6B,EAC7B,YAA0B,EAC1B,GAAU,EACV,eAAuB;IAEvB,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,mBAAmB,EAAE,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,oBAAoB,CAAC,MAAM,CAAC;YAAE,OAAO,uBAAuB,EAAE,CAAC;QACnE,MAAM,UAAU,GAAG,kCAAkC,CACnD,MAAM,EACN,GAAG,EAAE,CAAC,WAAW,EAAE,EACnB,eAAe,CAChB,CAAC;QACF,OAAO;YACL,cAAc,EAAE,kCAAkC,CAAC,UAAU,CAAC;YAC9D,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,CAAC;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,oBAAoB,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,wBAAwB;YAC7E,CAAC,CAAC,mBAAmB,EAAE;YACvB,CAAC,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAED,iCAAiC;AACjC,SAAS,mBAAmB;IAC1B,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EACX,mEAAmE;QACrE,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,uCAAuC;AACvC,SAAS,mBAAmB,CAAC,UAA6B;IACxD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,YAAY,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACrE,IAAI,QAAQ,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACzD,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC;IAChE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,MAAM,GAAG,sBAAsB;QAC1C,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC1C,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AAED,0CAA0C;AAC1C,SAAS,mBAAmB;IAC1B,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,GAAG,cAAc,IAAI;QACpC,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GACzB,oEAAoE;MAClE,4BAA4B,CAAC","sourcesContent":["import {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n createCartographRetentionsDocument,\n encodeCartographRetentionsDocument,\n RetentionValidationError,\n} from '../report/retentions.ts';\nimport {\n bridgeJoinDeferredError,\n internalError,\n isExpectedInputError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './check-command.ts';\n\n/** 생성 시각을 테스트 가능하게 주입하는 시계다. */\nexport type Clock = () => Date;\n\n/** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */\nexport async function runRetentionsCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n now: Clock,\n producerVersion: string,\n): Promise<CommandResult> {\n const inputPaths = retentionInputPaths(arguments_);\n if (inputPaths === undefined) return retentionUsageError();\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) return bridgeJoinDeferredError();\n const retentions = createCartographRetentionsDocument(\n joined,\n now().toISOString(),\n producerVersion,\n );\n return {\n standardOutput: encodeCartographRetentionsDocument(retentions),\n standardError: '',\n exitCode: 0,\n };\n } catch (error) {\n return isExpectedInputError(error) || error instanceof RetentionValidationError\n ? retentionInputError()\n : internalError();\n }\n}\n\n/** 입력 실패를 경로 없는 도구 실패 2로 바꾼다. */\nfunction retentionInputError(): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Unable to read or validate bridge facts; check the input files.\\n',\n exitCode: 2,\n };\n}\n\n/** 지원 대상과 최소 입력 수를 검증해 파일 경로만 돌려준다. */\nfunction retentionInputPaths(arguments_: readonly string[]): string[] | undefined {\n const forIndex = arguments_.indexOf('--for');\n if (arguments_[0] !== 'retentions' || forIndex < 3) return undefined;\n if (forIndex !== arguments_.length - 2) return undefined;\n if (arguments_[forIndex + 1] !== 'cartograph') return undefined;\n const paths = arguments_.slice(1, forIndex);\n return paths.length > MAX_DOCUMENTS_PER_JOIN ||\n paths.some((path) => path.startsWith('-'))\n ? undefined\n : paths;\n}\n\n/** 잘못된 retentions 호출을 사용법과 코드 64로 바꾼다. */\nfunction retentionUsageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${retentionUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** retentions 명령의 한 줄 사용법이다. */\nexport const retentionUsage =\n 'Usage: isthmus retentions <bridge-facts.json> <bridge-facts.json> '\n + '[more...] --for cartograph';\n"]}
|
package/dist/exchange/parse.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type BridgePlatform = 'dart' | 'swift' | 'kotlin' | 'js';
|
|
|
4
4
|
export type BridgeTarget = 'flutter' | 'react-native' | 'capacitor';
|
|
5
5
|
/** 교환 형식의 사실 종류다. */
|
|
6
6
|
export type BridgeFactKind = 'channel-create' | 'channel-register' | 'method-invoke' | 'method-handle' | 'module-export' | 'module-import' | 'component-export' | 'component-require';
|
|
7
|
+
/** 한 생산 문서가 담을 수 있는 최대 사실 수다. */
|
|
8
|
+
export declare const MAX_FACTS_PER_DOCUMENT = 100000;
|
|
7
9
|
/** 사실의 프로젝트 상대 소스 위치다. */
|
|
8
10
|
export interface BridgeLocation {
|
|
9
11
|
readonly path: string;
|