isthmus-cli 0.2.0 → 0.3.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.
@@ -1,38 +1,175 @@
1
1
  import { BridgeFactsValidationError, parseBridgeFactsDocument, } from "../exchange/parse.js";
2
2
  import { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
3
+ import { applyBaseline, BridgeBaselineValidationError, createBaselineDocument, encodeBaselineDocument, MAX_BASELINE_ENTRIES, parseBaselineDocument, } from "../report/baseline.js";
3
4
  import { createCheckReport, encodeCheckReport } from "../report/check-report.js";
4
5
  /** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
5
6
  export const MAX_INPUT_TEXT_LENGTH = 16 * 1024 * 1024;
6
7
  /** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
7
8
  export const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;
8
9
  /** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
9
- export async function runCheckCommand(arguments_, readTextFile) {
10
+ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, now = () => new Date()) {
10
11
  if (arguments_[0] !== 'check')
11
12
  return usageError();
12
- const options = arguments_.slice(1).filter((argument) => argument.startsWith('-'));
13
- if (options.some((option) => option !== '--strict'))
13
+ const options = parseCheckOptions(arguments_.slice(1));
14
+ if (options === undefined)
14
15
  return usageError();
15
- const isStrict = arguments_.includes('--strict');
16
- const inputPaths = arguments_.slice(1).filter((argument) => argument !== '--strict');
16
+ const { strict, baselinePath, updateBaselinePath, inputPaths } = options;
17
17
  if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {
18
18
  return usageError();
19
19
  }
20
+ if (updateBaselinePath !== undefined && writeTextFile === undefined) {
21
+ return internalError();
22
+ }
20
23
  try {
21
24
  const documents = await readBridgeDocuments(inputPaths, readTextFile);
22
25
  const joined = joinBridgeDocuments(documents);
23
26
  if (isBridgeJoinDeferred(joined))
24
27
  return bridgeJoinDeferredError();
25
- const report = createCheckReport(joined);
28
+ let report = createCheckReport(joined);
29
+ if (baselinePath !== undefined) {
30
+ const baseline = await readBaselineDocument(baselinePath, readTextFile);
31
+ report = applyBaseline(report, baseline.entries);
32
+ }
33
+ const standardOutput = encodeCheckReport(report);
34
+ if (updateBaselinePath !== undefined && writeTextFile !== undefined) {
35
+ await writeBaselineDocument(updateBaselinePath, report.issues, now(), writeTextFile);
36
+ }
26
37
  return {
27
- standardOutput: encodeCheckReport(report),
38
+ standardOutput,
28
39
  standardError: '',
29
- exitCode: isStrict && report.summary.errors > 0 ? 1 : 0,
40
+ exitCode: strict && report.summary.errors > 0 ? 1 : 0,
30
41
  };
31
42
  }
32
43
  catch (error) {
33
44
  return inputFailureResult(error) ?? internalError();
34
45
  }
35
46
  }
47
+ /** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */
48
+ function parseCheckOptions(rest) {
49
+ let strict = false;
50
+ let baselinePath;
51
+ let updateBaselinePath;
52
+ const inputPaths = [];
53
+ for (let index = 0; index < rest.length; index++) {
54
+ const argument = rest[index];
55
+ if (argument === undefined)
56
+ return undefined;
57
+ if (argument === '--strict') {
58
+ strict = true;
59
+ continue;
60
+ }
61
+ if (argument === '--baseline' || argument === '--update-baseline') {
62
+ const value = rest[index + 1];
63
+ if (value === undefined || value.startsWith('-'))
64
+ return undefined;
65
+ if (argument === '--baseline') {
66
+ if (baselinePath !== undefined)
67
+ return undefined;
68
+ baselinePath = value;
69
+ }
70
+ else {
71
+ if (updateBaselinePath !== undefined)
72
+ return undefined;
73
+ updateBaselinePath = value;
74
+ }
75
+ index++;
76
+ continue;
77
+ }
78
+ if (argument.startsWith('-'))
79
+ return undefined;
80
+ inputPaths.push(argument);
81
+ }
82
+ if (baselinePath !== undefined && updateBaselinePath !== undefined) {
83
+ return undefined;
84
+ }
85
+ return { strict, baselinePath, updateBaselinePath, inputPaths };
86
+ }
87
+ /** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */
88
+ async function readBaselineDocument(path, readTextFile) {
89
+ let text;
90
+ try {
91
+ text = await readTextFile(path);
92
+ }
93
+ catch {
94
+ throw new BridgeBaselineReadError();
95
+ }
96
+ if (text.length > MAX_INPUT_TEXT_LENGTH) {
97
+ throw new BridgeBaselineLimitError();
98
+ }
99
+ try {
100
+ return parseBaselineDocument(JSON.parse(text));
101
+ }
102
+ catch (error) {
103
+ if (isJsonParseFailure(error))
104
+ throw new BridgeBaselineJsonError();
105
+ throw error;
106
+ }
107
+ }
108
+ /**
109
+ * 사용자 입력 JSON 파싱 실패인지 확인한다.
110
+ *
111
+ * 깊은 중첩은 SyntaxError가 아니라 RangeError(스택 초과)로 실패한다.
112
+ * 둘 다 입력 탓이라 내부 오류 메시지로 오분류하지 않는다.
113
+ */
114
+ function isJsonParseFailure(error) {
115
+ return error instanceof SyntaxError || error instanceof RangeError;
116
+ }
117
+ /** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */
118
+ async function writeBaselineDocument(path, issues, generatedAt, writeTextFile) {
119
+ let document;
120
+ try {
121
+ document = createBaselineDocument(issues, generatedAt.toISOString());
122
+ }
123
+ catch (error) {
124
+ // 만들 수 없는 문서는 쓰지 않는다. 상한 초과 산출물은 다음 실행이
125
+ // 소비할 수 없어 부분 성공보다 실패가 안전하다.
126
+ if (error instanceof BridgeBaselineValidationError) {
127
+ throw new BridgeBaselineWriteLimitError();
128
+ }
129
+ throw error;
130
+ }
131
+ try {
132
+ await writeTextFile(path, encodeBaselineDocument(document));
133
+ }
134
+ catch {
135
+ throw new BridgeBaselineWriteError();
136
+ }
137
+ }
138
+ /** 베이스라인 파일을 읽지 못한 경우를 구분한다. */
139
+ class BridgeBaselineReadError extends Error {
140
+ constructor() {
141
+ super('BridgeBaselineReadError');
142
+ this.name = 'BridgeBaselineReadError';
143
+ }
144
+ }
145
+ /** 베이스라인 파일이 JSON이 아닌 경우를 구분한다. */
146
+ class BridgeBaselineJsonError extends Error {
147
+ constructor() {
148
+ super('BridgeBaselineJsonError');
149
+ this.name = 'BridgeBaselineJsonError';
150
+ }
151
+ }
152
+ /** 베이스라인 파일이 크기 상한을 넘은 경우를 구분한다. */
153
+ class BridgeBaselineLimitError extends Error {
154
+ constructor() {
155
+ super('BridgeBaselineLimitError');
156
+ this.name = 'BridgeBaselineLimitError';
157
+ }
158
+ }
159
+ /** 베이스라인 파일을 쓰지 못한 경우를 구분한다. */
160
+ class BridgeBaselineWriteError extends Error {
161
+ constructor() {
162
+ super('BridgeBaselineWriteError');
163
+ this.name = 'BridgeBaselineWriteError';
164
+ }
165
+ }
166
+ /** 쓸 베이스라인이 항목 상한을 넘는 경우를 구분한다. */
167
+ class BridgeBaselineWriteLimitError extends Error {
168
+ constructor() {
169
+ super('BridgeBaselineWriteLimitError');
170
+ this.name = 'BridgeBaselineWriteLimitError';
171
+ }
172
+ }
36
173
  /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
37
174
  export async function readBridgeDocuments(inputPaths, readTextFile) {
38
175
  const documents = [];
@@ -55,7 +192,7 @@ export async function readBridgeDocuments(inputPaths, readTextFile) {
55
192
  documents.push(parseBridgeFactsDocument(JSON.parse(text)));
56
193
  }
57
194
  catch (error) {
58
- if (error instanceof SyntaxError) {
195
+ if (isJsonParseFailure(error)) {
59
196
  throw new BridgeInputJsonError(inputPosition);
60
197
  }
61
198
  if (error instanceof BridgeFactsValidationError) {
@@ -119,6 +256,28 @@ export function inputFailureResult(error) {
119
256
  return inputFailure(`Bridge facts input ${error.inputPosition} exceeds the input size limits; `
120
257
  + 'split the extraction into smaller documents.\n');
121
258
  }
259
+ if (error instanceof BridgeBaselineReadError) {
260
+ return inputFailure('Unable to read the baseline file; check that it exists and is readable.\n');
261
+ }
262
+ if (error instanceof BridgeBaselineJsonError) {
263
+ return inputFailure('The baseline file is not valid JSON; regenerate it with '
264
+ + 'check --update-baseline.\n');
265
+ }
266
+ if (error instanceof BridgeBaselineLimitError) {
267
+ return inputFailure('The baseline file exceeds the input size limits; regenerate it with '
268
+ + 'check --update-baseline.\n');
269
+ }
270
+ if (error instanceof BridgeBaselineWriteError) {
271
+ return inputFailure('Unable to write the baseline file; check that the path is writable.\n');
272
+ }
273
+ if (error instanceof BridgeBaselineWriteLimitError) {
274
+ return inputFailure(`Cannot write a baseline with more than ${MAX_BASELINE_ENTRIES} entries; `
275
+ + 'narrow the join inputs.\n');
276
+ }
277
+ if (error instanceof BridgeBaselineValidationError) {
278
+ return inputFailure(`The baseline file is not a valid isthmus-baseline document: `
279
+ + `${error.message}\n`);
280
+ }
122
281
  if (error instanceof BridgeJoinValidationError) {
123
282
  return inputFailure(`${error.message}\n`);
124
283
  }
@@ -154,5 +313,6 @@ function usageError() {
154
313
  }
155
314
  /** check 명령의 한 줄 사용법이다. */
156
315
  export const checkUsage = 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '
157
- + '[more...] [--strict]';
316
+ + '[more...] [--strict] [--baseline <isthmus-baseline.json>] '
317
+ + '[--update-baseline <isthmus-baseline.json>]';
158
318
  //# 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 { 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 inputFailureResult(error) ?? 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 [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 (error instanceof SyntaxError) {\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 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]';\n"]}
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,EACL,aAAa,EACb,6BAA6B,EAC7B,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAWjF,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,EAC1B,aAA6B,EAC7B,MAAa,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;IAE7B,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,UAAU,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,UAAU,EAAE,CAAC;IAC/C,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACzE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QACxE,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,kBAAkB,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QACpE,OAAO,aAAa,EAAE,CAAC;IACzB,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,IAAI,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YACxE,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,kBAAkB,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACpE,MAAM,qBAAqB,CACzB,kBAAkB,EAClB,MAAM,CAAC,MAAM,EACb,GAAG,EAAE,EACL,aAAa,CACd,CAAC;QACJ,CAAC;QACD,OAAO;YACL,cAAc;YACd,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACtD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,iDAAiD;AACjD,SAAS,iBAAiB,CACxB,IAAuB;IAOvB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,YAAgC,CAAC;IACrC,IAAI,kBAAsC,CAAC;IAC3C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7C,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC5B,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,mBAAmB,EAAE,CAAC;YAClE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,SAAS,CAAC;YACnE,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC9B,IAAI,YAAY,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC;gBACjD,YAAY,GAAG,KAAK,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,IAAI,kBAAkB,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC;gBACvD,kBAAkB,GAAG,KAAK,CAAC;YAC7B,CAAC;YACD,KAAK,EAAE,CAAC;YACR,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QAC/C,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,YAAY,KAAK,SAAS,IAAI,kBAAkB,KAAK,SAAS,EAAE,CAAC;QACnE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC;AAClE,CAAC;AAED,2CAA2C;AAC3C,KAAK,UAAU,oBAAoB,CACjC,IAAY,EACZ,YAA0B;IAE1B,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,uBAAuB,EAAE,CAAC;IACtC,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACxC,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACvC,CAAC;IACD,IAAI,CAAC;QACH,OAAO,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,kBAAkB,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;QACnE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,KAAK,YAAY,WAAW,IAAI,KAAK,YAAY,UAAU,CAAC;AACrE,CAAC;AAED,+CAA+C;AAC/C,KAAK,UAAU,qBAAqB,CAClC,IAAY,EACZ,MAA6B,EAC7B,WAAiB,EACjB,aAA4B;IAE5B,IAAI,QAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,QAAQ,GAAG,sBAAsB,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;IACvE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wCAAwC;QACxC,6BAA6B;QAC7B,IAAI,KAAK,YAAY,6BAA6B,EAAE,CAAC;YACnD,MAAM,IAAI,6BAA6B,EAAE,CAAC;QAC5C,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,IAAI,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACvC,CAAC;AACH,CAAC;AAED,gCAAgC;AAChC,MAAM,uBAAwB,SAAQ,KAAK;IACzC;QACE,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,mCAAmC;AACnC,MAAM,uBAAwB,SAAQ,KAAK;IACzC;QACE,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,oCAAoC;AACpC,MAAM,wBAAyB,SAAQ,KAAK;IAC1C;QACE,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,gCAAgC;AAChC,MAAM,wBAAyB,SAAQ,KAAK;IAC1C;QACE,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,mCAAmC;AACnC,MAAM,6BAA8B,SAAQ,KAAK;IAC/C;QACE,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;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,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,MAAM,aAAa,GAAG,KAAK,GAAG,CAAC,CAAC;QAChC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAChD,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,CAAC,aAAa,CAAC,CAAC;QACjD,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,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,oBAAoB,CAAC,aAAa,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,KAAK,YAAY,0BAA0B,EAAE,CAAC;gBAChD,MAAM,IAAI,wBAAwB,CAAC,aAAa,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,wCAAwC;AACxC,MAAM,gBAAiB,SAAQ,KAAK;IACzB,aAAa,CAAS;IAE/B,YAAY,IAAY,EAAE,aAAqB;QAC7C,KAAK,CAAC,GAAG,IAAI,aAAa,aAAa,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;CACF;AAED,6BAA6B;AAC7B,MAAM,oBAAqB,SAAQ,gBAAgB;IACjD,YAAY,aAAqB;QAC/B,KAAK,CAAC,sBAAsB,EAAE,aAAa,CAAC,CAAC;IAC/C,CAAC;CACF;AAED,iCAAiC;AACjC,MAAM,oBAAqB,SAAQ,gBAAgB;IACjD,YAAY,aAAqB;QAC/B,KAAK,CAAC,sBAAsB,EAAE,aAAa,CAAC,CAAC;IAC/C,CAAC;CACF;AAED,uCAAuC;AACvC,MAAM,wBAAyB,SAAQ,gBAAgB;IAC5C,MAAM,CAAS;IAExB,YAAY,aAAqB,EAAE,MAAc;QAC/C,KAAK,CAAC,0BAA0B,EAAE,aAAa,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,mCAAmC;AACnC,MAAM,qBAAsB,SAAQ,gBAAgB;IAClD,YAAY,aAAqB;QAC/B,KAAK,CAAC,uBAAuB,EAAE,aAAa,CAAC,CAAC;IAChD,CAAC;CACF;AAED,gDAAgD;AAChD,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;QAC1C,OAAO,YAAY,CACjB,qCAAqC,KAAK,CAAC,aAAa,IAAI;cAC1D,+CAA+C,CAClD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;QAC1C,OAAO,YAAY,CACjB,sBAAsB,KAAK,CAAC,aAAa,sBAAsB;cAC7D,+CAA+C,CAClD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;QAC9C,OAAO,YAAY,CACjB,sBAAsB,KAAK,CAAC,aAAa,6BAA6B;cACpE,aAAa,KAAK,CAAC,MAAM,IAAI,CAChC,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;QAC3C,OAAO,YAAY,CACjB,sBAAsB,KAAK,CAAC,aAAa,kCAAkC;cACzE,gDAAgD,CACnD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,uBAAuB,EAAE,CAAC;QAC7C,OAAO,YAAY,CACjB,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,uBAAuB,EAAE,CAAC;QAC7C,OAAO,YAAY,CACjB,0DAA0D;cACxD,4BAA4B,CAC/B,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;QAC9C,OAAO,YAAY,CACjB,sEAAsE;cACpE,4BAA4B,CAC/B,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;QAC9C,OAAO,YAAY,CACjB,uEAAuE,CACxE,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,6BAA6B,EAAE,CAAC;QACnD,OAAO,YAAY,CACjB,0CAA0C,oBAAoB,YAAY;cACxE,2BAA2B,CAC9B,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,6BAA6B,EAAE,CAAC;QACnD,OAAO,YAAY,CACjB,8DAA8D;cAC5D,GAAG,KAAK,CAAC,OAAO,IAAI,CACvB,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,YAAY,yBAAyB,EAAE,CAAC;QAC/C,OAAO,YAAY,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,oCAAoC;AACpC,SAAS,YAAY,CAAC,aAAqB;IACzC,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAC5D,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,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,4DAA4D;MAC5D,6CAA6C,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 {\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"]}
package/dist/cli/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises';
2
+ import { readFile, rename, rm, writeFile } from 'node:fs/promises';
3
3
  import { checkUsage, runCheckCommand, } from "./check-command.js";
4
4
  import { graphUsage, runGraphCommand } from "./graph-command.js";
5
5
  import { diffUsage, runDiffCommand } from "./diff-command.js";
@@ -29,6 +29,23 @@ Options:
29
29
  `;
30
30
  const arguments_ = process.argv.slice(2);
31
31
  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
+ };
32
49
  const informationalResult = await runInformationalCommand(arguments_);
33
50
  const result = informationalResult ?? await dispatchCommand(arguments_);
34
51
  process.stdout.write(result.standardOutput);
@@ -64,7 +81,7 @@ async function runInformationalCommand(commandArguments) {
64
81
  async function dispatchCommand(commandArguments) {
65
82
  switch (commandArguments[0]) {
66
83
  case 'check':
67
- return runCheckCommand(commandArguments, readTextFile);
84
+ return runCheckCommand(commandArguments, readTextFile, writeTextFile);
68
85
  case 'graph':
69
86
  return runGraphCommand(commandArguments, readTextFile);
70
87
  case 'diff':
@@ -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 { diffUsage, runDiffCommand } from './diff-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 ['diff', diffUsage],\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 diff Compare bridge observations before and after a change\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 'diff':\n return runDiffCommand(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, rename, rm, writeFile } 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 { diffUsage, runDiffCommand } from './diff-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 ['diff', diffUsage],\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 diff Compare bridge observations before and after a change\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');\n/**\n * 같은 디렉터리의 임시 파일에 쓰고 rename으로 교체한다.\n *\n * 중단·디스크 가득 참 중에 기존 베이스라인이 잘린 채 남으면 다음 실행이\n * 코드 2로 실패한다. rename은 같은 파일시스템 안에서 원자적이다.\n */\nconst writeTextFile = async (path: string, text: string) => {\n const temporaryPath = `${path}.${process.pid}.tmp`;\n try {\n await writeFile(temporaryPath, text, 'utf8');\n await rename(temporaryPath, path);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw error;\n }\n};\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, writeTextFile);\n case 'graph':\n return runGraphCommand(commandArguments, readTextFile);\n case 'diff':\n return runDiffCommand(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,6 +1,5 @@
1
- import { type CommandResult, type ReadTextFile } from './check-command.ts';
2
- /** 생성 시각을 테스트 가능하게 주입하는 시계다. */
3
- export type Clock = () => Date;
1
+ import { type Clock, type CommandResult, type ReadTextFile } from './check-command.ts';
2
+ export type { Clock };
4
3
  /** retentions 인자를 실행해 cartograph용 보존 문서를 반환한다. */
5
4
  export declare function runRetentionsCommand(arguments_: readonly string[], readTextFile: ReadTextFile, now: Clock, producerVersion: string): Promise<CommandResult>;
6
5
  /** retentions 명령의 한 줄 사용법이다. */
@@ -1 +1 @@
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,EACxB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,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,iCAAiC,CAAC,SAAS,CAAC,CAAC;QAC7C,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,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;YAC9C,OAAO;gBACL,cAAc,EAAE,EAAE;gBAClB,aAAa,EAAE,GAAG,KAAK,CAAC,OAAO,IAAI;gBACnC,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,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 validateCartographRetentionInputs,\n} from '../report/retentions.ts';\nimport {\n bridgeJoinDeferredError,\n internalError,\n inputFailureResult,\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 validateCartographRetentionInputs(documents);\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 if (error instanceof RetentionValidationError) {\n return {\n standardOutput: '',\n standardError: `${error.message}\\n`,\n exitCode: 2,\n };\n }\n return inputFailureResult(error) ?? internalError();\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"]}
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,EACxB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,GAIpB,MAAM,oBAAoB,CAAC;AAI5B,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,iCAAiC,CAAC,SAAS,CAAC,CAAC;QAC7C,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,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;YAC9C,OAAO;gBACL,cAAc,EAAE,EAAE;gBAClB,aAAa,EAAE,GAAG,KAAK,CAAC,OAAO,IAAI;gBACnC,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,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 validateCartographRetentionInputs,\n} from '../report/retentions.ts';\nimport {\n bridgeJoinDeferredError,\n internalError,\n inputFailureResult,\n readBridgeDocuments,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n} from './check-command.ts';\n\nexport type { Clock };\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 validateCartographRetentionInputs(documents);\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 if (error instanceof RetentionValidationError) {\n return {\n standardOutput: '',\n standardError: `${error.message}\\n`,\n exitCode: 2,\n };\n }\n return inputFailureResult(error) ?? internalError();\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"]}
@@ -64,3 +64,11 @@ export declare function parseBridgeFactsDocument(input: unknown): BridgeFactsDoc
64
64
  export declare function isCallerPlatform(platform: unknown): platform is 'dart' | 'js';
65
65
  /** 브리지 수신 측 사실을 생산하는 플랫폼인지 확인한다. */
66
66
  export declare function isReceiverPlatform(platform: unknown): platform is 'swift' | 'kotlin';
67
+ /** 값이 계약이 정한 브리지 메커니즘 이름인지 확인한다. */
68
+ export declare function isBridgeTarget(value: unknown): value is BridgeTarget;
69
+ /** 조인 키를 깨뜨리는 제어 문자가 없는 비어 있지 않은 문자열인지 확인한다. */
70
+ export declare function isSafeNonEmptyString(value: unknown): value is string;
71
+ /** ISO 계열 생성 시각으로 해석할 수 있는지 확인한다. */
72
+ export declare function isBridgeTimestamp(value: unknown): value is string;
73
+ /** 배열과 null을 제외한 JSON 객체인지 확인한다. isthmus 소유 문서 검증도 재사용한다. */
74
+ export declare function isJsonObject(value: unknown): value is Record<string, unknown>;
@@ -72,7 +72,7 @@ function normalizeFact(fact) {
72
72
  /** 문서 수준 필드가 v1 타입과 허용값을 따르는지 검증한다. */
73
73
  function validateDocumentMetadata(document) {
74
74
  validateTool(document.tool);
75
- if (!isTimestamp(document.generatedAt))
75
+ if (!isBridgeTimestamp(document.generatedAt))
76
76
  fail('Invalid generatedAt timestamp.');
77
77
  if (!bridgePlatforms.has(document.platform))
78
78
  fail('Unsupported bridge platform.');
@@ -171,6 +171,14 @@ export function isCallerPlatform(platform) {
171
171
  export function isReceiverPlatform(platform) {
172
172
  return platform === 'swift' || platform === 'kotlin';
173
173
  }
174
+ /** 값이 계약이 정한 브리지 메커니즘 이름인지 확인한다. */
175
+ export function isBridgeTarget(value) {
176
+ return bridgeTargets.has(value);
177
+ }
178
+ /** 조인 키를 깨뜨리는 제어 문자가 없는 비어 있지 않은 문자열인지 확인한다. */
179
+ export function isSafeNonEmptyString(value) {
180
+ return isNonEmptyString(value) && !controlCharacterPattern.test(value);
181
+ }
174
182
  /** 사실 위치가 상대 경로와 1부터 시작하는 줄·열을 갖는지 검증한다. */
175
183
  function validateLocation(value, index) {
176
184
  if (!isJsonObject(value) ||
@@ -209,7 +217,7 @@ function validateTool(value) {
209
217
  }
210
218
  }
211
219
  /** ISO 계열 생성 시각으로 해석할 수 있는지 확인한다. */
212
- function isTimestamp(value) {
220
+ export function isBridgeTimestamp(value) {
213
221
  if (typeof value !== 'string')
214
222
  return false;
215
223
  const match = timestampPattern.exec(value);
@@ -241,10 +249,6 @@ function daysInMonth(year, month) {
241
249
  function isNonEmptyString(value) {
242
250
  return typeof value === 'string' && value.trim().length > 0;
243
251
  }
244
- /** 조인 키를 깨뜨리는 제어 문자가 없는 비어 있지 않은 문자열인지 확인한다. */
245
- function isSafeNonEmptyString(value) {
246
- return isNonEmptyString(value) && !controlCharacterPattern.test(value);
247
- }
248
252
  /** 모든 원소가 문자열인 배열인지 확인한다. */
249
253
  function isStringArray(value) {
250
254
  return Array.isArray(value) && value.every((item) => typeof item === 'string');
@@ -299,8 +303,8 @@ const receiverFactKinds = new Set([
299
303
  const methodFactKinds = new Set(['method-invoke', 'method-handle']);
300
304
  const timestampPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u;
301
305
  const controlCharacterPattern = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/u;
302
- /** 배열과 null을 제외한 JSON 객체인지 확인한다. */
303
- function isJsonObject(value) {
306
+ /** 배열과 null을 제외한 JSON 객체인지 확인한다. isthmus 소유 문서 검증도 재사용한다. */
307
+ export function isJsonObject(value) {
304
308
  return typeof value === 'object' && value !== null && !Array.isArray(value);
305
309
  }
306
310
  //# sourceMappingURL=parse.js.map