isthmus-cli 0.6.0 → 0.7.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.
Files changed (99) hide show
  1. package/README.ko.md +135 -31
  2. package/README.md +134 -16
  3. package/Skills/isthmus/SKILL.md +17 -7
  4. package/compatibility.json +10 -0
  5. package/dist/cli/check-command.d.ts +2 -2
  6. package/dist/cli/check-command.js +22 -9
  7. package/dist/cli/check-command.js.map +1 -1
  8. package/dist/cli/command-support.d.ts +19 -1
  9. package/dist/cli/command-support.js +37 -8
  10. package/dist/cli/command-support.js.map +1 -1
  11. package/dist/cli/diff-command.js +4 -3
  12. package/dist/cli/diff-command.js.map +1 -1
  13. package/dist/cli/doctor-command.d.ts +11 -0
  14. package/dist/cli/doctor-command.js +128 -0
  15. package/dist/cli/doctor-command.js.map +1 -0
  16. package/dist/cli/extract-js-command.d.ts +33 -0
  17. package/dist/cli/extract-js-command.js +233 -0
  18. package/dist/cli/extract-js-command.js.map +1 -0
  19. package/dist/cli/graph-command.js +13 -6
  20. package/dist/cli/graph-command.js.map +1 -1
  21. package/dist/cli/init-command.d.ts +11 -0
  22. package/dist/cli/init-command.js +115 -0
  23. package/dist/cli/init-command.js.map +1 -0
  24. package/dist/cli/main.js +52 -1
  25. package/dist/cli/main.js.map +1 -1
  26. package/dist/cli/mcp-server.d.ts +16 -0
  27. package/dist/cli/mcp-server.js +534 -0
  28. package/dist/cli/mcp-server.js.map +1 -0
  29. package/dist/cli/query-command.js +13 -6
  30. package/dist/cli/query-command.js.map +1 -1
  31. package/dist/cli/retentions-command.js +14 -7
  32. package/dist/cli/retentions-command.js.map +1 -1
  33. package/dist/cli/serve-command.d.ts +13 -0
  34. package/dist/cli/serve-command.js +43 -0
  35. package/dist/cli/serve-command.js.map +1 -0
  36. package/dist/exchange/capture-config.d.ts +18 -0
  37. package/dist/exchange/capture-config.js +100 -0
  38. package/dist/exchange/capture-config.js.map +1 -0
  39. package/dist/exchange/messages.d.ts +9 -24
  40. package/dist/exchange/messages.js +25 -55
  41. package/dist/exchange/messages.js.map +1 -1
  42. package/dist/exchange/parse.d.ts +61 -0
  43. package/dist/exchange/parse.js +115 -12
  44. package/dist/exchange/parse.js.map +1 -1
  45. package/dist/extract/js-document.d.ts +22 -0
  46. package/dist/extract/js-document.js +230 -0
  47. package/dist/extract/js-document.js.map +1 -0
  48. package/dist/extract/js-scan.d.ts +93 -0
  49. package/dist/extract/js-scan.js +1205 -0
  50. package/dist/extract/js-scan.js.map +1 -0
  51. package/dist/extract/lexer.d.ts +26 -0
  52. package/dist/extract/lexer.js +316 -0
  53. package/dist/extract/lexer.js.map +1 -0
  54. package/dist/join/join.d.ts +66 -1
  55. package/dist/join/join.js +171 -1
  56. package/dist/join/join.js.map +1 -1
  57. package/dist/join/messages.d.ts +7 -4
  58. package/dist/join/messages.js +15 -8
  59. package/dist/join/messages.js.map +1 -1
  60. package/dist/report/check-report.d.ts +20 -3
  61. package/dist/report/check-report.js +240 -8
  62. package/dist/report/check-report.js.map +1 -1
  63. package/dist/report/codequality.d.ts +45 -0
  64. package/dist/report/codequality.js +74 -0
  65. package/dist/report/codequality.js.map +1 -0
  66. package/dist/report/diff.d.ts +25 -3
  67. package/dist/report/diff.js +91 -11
  68. package/dist/report/diff.js.map +1 -1
  69. package/dist/report/graph.d.ts +10 -4
  70. package/dist/report/graph.js +60 -6
  71. package/dist/report/graph.js.map +1 -1
  72. package/dist/report/preflight-view.js +28 -1
  73. package/dist/report/preflight-view.js.map +1 -1
  74. package/dist/report/preflight.d.ts +4 -4
  75. package/dist/report/preflight.js +81 -46
  76. package/dist/report/preflight.js.map +1 -1
  77. package/dist/report/query.d.ts +11 -4
  78. package/dist/report/query.js +84 -17
  79. package/dist/report/query.js.map +1 -1
  80. package/dist/report/retentions.d.ts +17 -5
  81. package/dist/report/retentions.js +85 -8
  82. package/dist/report/retentions.js.map +1 -1
  83. package/dist/report/rules.d.ts +16 -0
  84. package/dist/report/rules.js +30 -0
  85. package/dist/report/rules.js.map +1 -0
  86. package/dist/report/sarif.d.ts +2 -8
  87. package/dist/report/sarif.js +2 -10
  88. package/dist/report/sarif.js.map +1 -1
  89. package/docs/BRIDGE-EVENTS.md +87 -0
  90. package/docs/BRIDGE-MESSAGES.md +23 -8
  91. package/docs/GRAPH-EXCHANGE.md +141 -14
  92. package/docs/IMPACT.md +1 -1
  93. package/docs/MCP.md +62 -0
  94. package/docs/PREFLIGHT.md +24 -8
  95. package/docs/RUNTIME.md +2 -2
  96. package/docs/TOOLCHAIN.md +5 -5
  97. package/package.json +5 -2
  98. package/scripts/capture-preflight.mjs +32 -44
  99. package/scripts/run-child.mjs +1 -0
@@ -1,9 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
2
+ import { emptyBridgeJoinResult, isBridgeJoinDeferred, joinBridgeDocuments, MAX_DOCUMENTS_PER_JOIN, } from "../join/join.js";
3
+ import { joinMessageBridges } from "../join/messages.js";
3
4
  import { applyBaseline, baselineEntryKey, BridgeBaselineValidationError, createBaselineDocument, encodeBaselineDocument, MAX_BASELINE_ENTRIES, parseBaselineDocument, } from "../report/baseline.js";
4
5
  import { createCheckReport, encodeCheckReport } from "../report/check-report.js";
6
+ import { createCodeQualityFindings, encodeCodeQualityReport, } from "../report/codequality.js";
5
7
  import { createSarifLog, encodeSarifLog, } from "../report/sarif.js";
6
- import { bridgeJoinDeferredError, inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, readBridgeDocuments, } from "./command-support.js";
8
+ import { bridgeJoinDeferredError, inputFailure, inputFailureResult, internalError, isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, readBridgeInputs, } from "./command-support.js";
7
9
  import { parseCommandArguments } from "./parse-arguments.js";
8
10
  /** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */
9
11
  export async function runCheckCommand(arguments_, readTextFile, writeTextFile, now = () => new Date(), producerVersion) {
@@ -20,19 +22,29 @@ export async function runCheckCommand(arguments_, readTextFile, writeTextFile, n
20
22
  return internalError();
21
23
  }
22
24
  try {
23
- const documents = await readBridgeDocuments(inputPaths, readTextFile);
24
- const joined = joinBridgeDocuments(documents);
25
+ const { bridges, messages } = await readBridgeInputs(inputPaths, readTextFile);
26
+ // v1 사실이 없어도 v2 전용 입력은 빈 결과 위에서 보고한다. project는
27
+ // 어느 쪽에 있든 하나여야 하며 joinMessageBridges가 일치를 검증한다.
28
+ const project = bridges[0]?.project ?? messages[0].project;
29
+ const joined = bridges.length > 0
30
+ ? joinBridgeDocuments(bridges)
31
+ : emptyBridgeJoinResult();
25
32
  if (isBridgeJoinDeferred(joined)) {
26
- return bridgeJoinDeferredError(joined.observedFacts, documents.length);
33
+ return bridgeJoinDeferredError(joined.observedFacts, bridges.length);
27
34
  }
28
- let report = createCheckReport(joined);
35
+ const messageJoin = messages.length > 0
36
+ ? joinMessageBridges(messages, project)
37
+ : undefined;
38
+ let report = createCheckReport(joined, messageJoin);
29
39
  if (baselinePath !== undefined) {
30
40
  const baseline = await readBaselineDocument(baselinePath, readTextFile);
31
41
  report = applyBaseline(report, baseline.entries);
32
42
  }
33
43
  const standardOutput = format === 'sarif'
34
44
  ? encodeSarifLog(createSarifLog(report, producerVersion, issueFingerprint))
35
- : encodeCheckReport(report);
45
+ : format === 'codequality'
46
+ ? encodeCodeQualityReport(createCodeQualityFindings(report, issueFingerprint))
47
+ : encodeCheckReport(report);
36
48
  if (updateBaselinePath !== undefined && writeTextFile !== undefined) {
37
49
  await writeBaselineDocument(updateBaselinePath, report.issues, now(), writeTextFile);
38
50
  }
@@ -53,7 +65,8 @@ function parseCheckOptions(rest) {
53
65
  if (parsed === undefined)
54
66
  return undefined;
55
67
  const format = parsed.valueFlags.get('--format');
56
- if (format !== undefined && format !== 'json' && format !== 'sarif') {
68
+ if (format !== undefined && format !== 'json' && format !== 'sarif'
69
+ && format !== 'codequality') {
57
70
  return undefined;
58
71
  }
59
72
  const baselinePath = parsed.valueFlags.get('--baseline');
@@ -188,7 +201,7 @@ function usageError() {
188
201
  }
189
202
  /** check 명령의 한 줄 사용법이다. */
190
203
  export const checkUsage = 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '
191
- + '[more...] [--strict] [--format json|sarif] '
204
+ + '[more...] [--strict] [--format json|sarif|codequality] '
192
205
  + '[--baseline <isthmus-baseline.json>] '
193
206
  + '[--update-baseline <isthmus-baseline.json>]';
194
207
  /** 논리 이슈 키의 SHA-256 지문이다. SARIF partialFingerprints에 쓴다. */
@@ -1 +1 @@
1
- {"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport {\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport {\n applyBaseline,\n baselineEntryKey,\n BridgeBaselineValidationError,\n createBaselineDocument,\n encodeBaselineDocument,\n MAX_BASELINE_ENTRIES,\n parseBaselineDocument,\n type BaselineDocument,\n} from '../report/baseline.ts';\nimport type { CheckIssue } from '../report/check-report.ts';\nimport { createCheckReport, encodeCheckReport } from '../report/check-report.ts';\nimport {\n createSarifLog,\n encodeSarifLog,\n} from '../report/sarif.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailure,\n inputFailureResult,\n internalError,\n isJsonParseFailure,\n MAX_INPUT_TEXT_LENGTH,\n readBridgeDocuments,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n type WriteTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\nexport type { Clock, CommandResult, ReadTextFile, WriteTextFile };\n\n/** check가 내는 보고서 형식이다. SARIF는 isthmus 소유의 additive 출력이다. */\nexport type CheckOutputFormat = 'json' | 'sarif';\n\n/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */\nexport async function runCheckCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n writeTextFile?: WriteTextFile,\n now: Clock = () => new Date(),\n producerVersion?: string,\n): Promise<CommandResult> {\n if (arguments_[0] !== 'check') return usageError();\n const options = parseCheckOptions(arguments_.slice(1));\n if (options === undefined) return usageError();\n const { strict, format, baselinePath, updateBaselinePath, inputPaths } = options;\n if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n if (updateBaselinePath !== undefined && writeTextFile === undefined) {\n return internalError();\n }\n try {\n const documents = await readBridgeDocuments(inputPaths, readTextFile);\n const joined = joinBridgeDocuments(documents);\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, documents.length);\n }\n let report = createCheckReport(joined);\n if (baselinePath !== undefined) {\n const baseline = await readBaselineDocument(baselinePath, readTextFile);\n report = applyBaseline(report, baseline.entries);\n }\n const standardOutput = format === 'sarif'\n ? encodeSarifLog(createSarifLog(report, producerVersion, issueFingerprint))\n : encodeCheckReport(report);\n if (updateBaselinePath !== undefined && writeTextFile !== undefined) {\n await writeBaselineDocument(\n updateBaselinePath,\n report.issues,\n now(),\n writeTextFile,\n );\n }\n return {\n standardOutput,\n standardError: '',\n exitCode: strict && report.summary.errors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? baselineFailureResult(error)\n ?? internalError();\n }\n}\n\n/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */\nfunction parseCheckOptions(\n rest: readonly string[],\n): {\n strict: boolean;\n format: CheckOutputFormat;\n baselinePath: string | undefined;\n updateBaselinePath: string | undefined;\n inputPaths: string[];\n} | undefined {\n const parsed = parseCommandArguments(\n rest,\n ['--format', '--baseline', '--update-baseline'],\n ['--strict'],\n );\n if (parsed === undefined) return undefined;\n const format = parsed.valueFlags.get('--format');\n if (format !== undefined && format !== 'json' && format !== 'sarif') {\n return undefined;\n }\n const baselinePath = parsed.valueFlags.get('--baseline');\n const updateBaselinePath = parsed.valueFlags.get('--update-baseline');\n if (baselinePath !== undefined && updateBaselinePath !== undefined) {\n return undefined;\n }\n return {\n strict: parsed.booleanFlags.has('--strict'),\n format: format ?? 'json',\n baselinePath,\n updateBaselinePath,\n inputPaths: [...parsed.positionals],\n };\n}\n\n/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */\nasync function readBaselineDocument(\n path: string,\n readTextFile: ReadTextFile,\n): Promise<BaselineDocument> {\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeBaselineReadError();\n }\n if (text.length > MAX_INPUT_TEXT_LENGTH) {\n throw new BridgeBaselineLimitError();\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) throw new BridgeBaselineJsonError();\n throw error;\n }\n return parseBaselineDocument(parsed);\n}\n\n/** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */\nasync function writeBaselineDocument(\n path: string,\n issues: readonly CheckIssue[],\n generatedAt: Date,\n writeTextFile: WriteTextFile,\n): Promise<void> {\n let document: BaselineDocument;\n try {\n document = createBaselineDocument(issues, generatedAt.toISOString());\n } catch (error) {\n // 만들 수 없는 문서는 쓰지 않는다. 상한 초과 산출물은 다음 실행이\n // 소비할 수 없어 부분 성공보다 실패가 안전하다.\n if (error instanceof BridgeBaselineValidationError) {\n throw new BridgeBaselineWriteLimitError();\n }\n throw error;\n }\n // 인코딩 결함은 쓰기 실패가 아니라 내부 오류로 분류되어야 한다.\n const encoded = encodeBaselineDocument(document);\n try {\n await writeTextFile(path, encoded);\n } catch {\n throw new BridgeBaselineWriteError();\n }\n}\n\n/** 베이스라인 파일을 읽지 못한 경우를 구분한다. */\nclass BridgeBaselineReadError extends Error {\n constructor() {\n super('BridgeBaselineReadError');\n this.name = 'BridgeBaselineReadError';\n }\n}\n\n/** 베이스라인 파일이 JSON이 아닌 경우를 구분한다. */\nclass BridgeBaselineJsonError extends Error {\n constructor() {\n super('BridgeBaselineJsonError');\n this.name = 'BridgeBaselineJsonError';\n }\n}\n\n/** 베이스라인 파일이 크기 상한을 넘은 경우를 구분한다. */\nclass BridgeBaselineLimitError extends Error {\n constructor() {\n super('BridgeBaselineLimitError');\n this.name = 'BridgeBaselineLimitError';\n }\n}\n\n/** 베이스라인 파일을 쓰지 못한 경우를 구분한다. */\nclass BridgeBaselineWriteError extends Error {\n constructor() {\n super('BridgeBaselineWriteError');\n this.name = 'BridgeBaselineWriteError';\n }\n}\n\n/** 쓸 베이스라인이 항목 상한을 넘는 경우를 구분한다. */\nclass BridgeBaselineWriteLimitError extends Error {\n constructor() {\n super('BridgeBaselineWriteLimitError');\n this.name = 'BridgeBaselineWriteLimitError';\n }\n}\n\n/** check 고유의 baseline 실패를 원인별 해결 방향을 담은 코드 2로 바꾼다. */\nfunction baselineFailureResult(error: unknown): CommandResult | undefined {\n if (error instanceof BridgeBaselineReadError) {\n return inputFailure(\n 'Unable to read the baseline file; check that it exists and is readable.\\n',\n );\n }\n if (error instanceof BridgeBaselineJsonError) {\n return inputFailure(\n 'The baseline file is not valid JSON; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineLimitError) {\n return inputFailure(\n 'The baseline file exceeds the input size limits; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteError) {\n return inputFailure(\n 'Unable to write the baseline file; check that the path is writable.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteLimitError) {\n return inputFailure(\n `Cannot write a baseline with more than ${MAX_BASELINE_ENTRIES} entries; `\n + 'narrow the join inputs.\\n',\n );\n }\n if (error instanceof BridgeBaselineValidationError) {\n return inputFailure(\n `The baseline file is not a valid isthmus-baseline document: `\n + `${error.message}\\n`,\n );\n }\n return undefined;\n}\n\n/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */\nfunction usageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${checkUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** check 명령의 한 줄 사용법이다. */\nexport const checkUsage =\n 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--strict] [--format json|sarif] '\n + '[--baseline <isthmus-baseline.json>] '\n + '[--update-baseline <isthmus-baseline.json>]';\n\n/** 논리 이슈 키의 SHA-256 지문이다. SARIF partialFingerprints에 쓴다. */\nfunction issueFingerprint(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');\n}\n"]}
1
+ {"version":3,"file":"check-command.js","sourceRoot":"","sources":["../../src/cli/check-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\n\nimport {\n emptyBridgeJoinResult,\n isBridgeJoinDeferred,\n joinBridgeDocuments,\n MAX_DOCUMENTS_PER_JOIN,\n} from '../join/join.ts';\nimport { joinMessageBridges } from '../join/messages.ts';\nimport {\n applyBaseline,\n baselineEntryKey,\n BridgeBaselineValidationError,\n createBaselineDocument,\n encodeBaselineDocument,\n MAX_BASELINE_ENTRIES,\n parseBaselineDocument,\n type BaselineDocument,\n} from '../report/baseline.ts';\nimport type { CheckIssue } from '../report/check-report.ts';\nimport { createCheckReport, encodeCheckReport } from '../report/check-report.ts';\nimport {\n createCodeQualityFindings,\n encodeCodeQualityReport,\n} from '../report/codequality.ts';\nimport {\n createSarifLog,\n encodeSarifLog,\n} from '../report/sarif.ts';\nimport {\n bridgeJoinDeferredError,\n inputFailure,\n inputFailureResult,\n internalError,\n isJsonParseFailure,\n MAX_INPUT_TEXT_LENGTH,\n readBridgeInputs,\n type Clock,\n type CommandResult,\n type ReadTextFile,\n type WriteTextFile,\n} from './command-support.ts';\nimport { parseCommandArguments } from './parse-arguments.ts';\n\nexport type { Clock, CommandResult, ReadTextFile, WriteTextFile };\n\n/** check가 내는 보고서 형식이다. SARIF·Code Quality는 isthmus 소유의 additive 출력이다. */\nexport type CheckOutputFormat = 'json' | 'sarif' | 'codequality';\n\n/** check 인자를 실행해 프로세스에 독립적인 결과를 반환한다. */\nexport async function runCheckCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n writeTextFile?: WriteTextFile,\n now: Clock = () => new Date(),\n producerVersion?: string,\n): Promise<CommandResult> {\n if (arguments_[0] !== 'check') return usageError();\n const options = parseCheckOptions(arguments_.slice(1));\n if (options === undefined) return usageError();\n const { strict, format, baselinePath, updateBaselinePath, inputPaths } = options;\n if (inputPaths.length < 2 || inputPaths.length > MAX_DOCUMENTS_PER_JOIN) {\n return usageError();\n }\n if (updateBaselinePath !== undefined && writeTextFile === undefined) {\n return internalError();\n }\n try {\n const { bridges, messages } = await readBridgeInputs(inputPaths, readTextFile);\n // v1 사실이 없어도 v2 전용 입력은 빈 결과 위에서 보고한다. project는\n // 어느 쪽에 있든 하나여야 하며 joinMessageBridges가 일치를 검증한다.\n const project = bridges[0]?.project ?? messages[0]!.project;\n const joined = bridges.length > 0\n ? joinBridgeDocuments(bridges)\n : emptyBridgeJoinResult();\n if (isBridgeJoinDeferred(joined)) {\n return bridgeJoinDeferredError(joined.observedFacts, bridges.length);\n }\n const messageJoin = messages.length > 0\n ? joinMessageBridges(messages, project)\n : undefined;\n let report = createCheckReport(joined, messageJoin);\n if (baselinePath !== undefined) {\n const baseline = await readBaselineDocument(baselinePath, readTextFile);\n report = applyBaseline(report, baseline.entries);\n }\n const standardOutput = format === 'sarif'\n ? encodeSarifLog(createSarifLog(report, producerVersion, issueFingerprint))\n : format === 'codequality'\n ? encodeCodeQualityReport(\n createCodeQualityFindings(report, issueFingerprint),\n )\n : encodeCheckReport(report);\n if (updateBaselinePath !== undefined && writeTextFile !== undefined) {\n await writeBaselineDocument(\n updateBaselinePath,\n report.issues,\n now(),\n writeTextFile,\n );\n }\n return {\n standardOutput,\n standardError: '',\n exitCode: strict && report.summary.errors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? baselineFailureResult(error)\n ?? internalError();\n }\n}\n\n/** check 플래그와 값·입력 경로를 분리한다. 잘못되면 undefined다. */\nfunction parseCheckOptions(\n rest: readonly string[],\n): {\n strict: boolean;\n format: CheckOutputFormat;\n baselinePath: string | undefined;\n updateBaselinePath: string | undefined;\n inputPaths: string[];\n} | undefined {\n const parsed = parseCommandArguments(\n rest,\n ['--format', '--baseline', '--update-baseline'],\n ['--strict'],\n );\n if (parsed === undefined) return undefined;\n const format = parsed.valueFlags.get('--format');\n if (\n format !== undefined && format !== 'json' && format !== 'sarif'\n && format !== 'codequality'\n ) {\n return undefined;\n }\n const baselinePath = parsed.valueFlags.get('--baseline');\n const updateBaselinePath = parsed.valueFlags.get('--update-baseline');\n if (baselinePath !== undefined && updateBaselinePath !== undefined) {\n return undefined;\n }\n return {\n strict: parsed.booleanFlags.has('--strict'),\n format: format ?? 'json',\n baselinePath,\n updateBaselinePath,\n inputPaths: [...parsed.positionals],\n };\n}\n\n/** 베이스라인 파일을 읽고 크기 상한 안에서 검증된 문서로 파싱한다. */\nasync function readBaselineDocument(\n path: string,\n readTextFile: ReadTextFile,\n): Promise<BaselineDocument> {\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeBaselineReadError();\n }\n if (text.length > MAX_INPUT_TEXT_LENGTH) {\n throw new BridgeBaselineLimitError();\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) throw new BridgeBaselineJsonError();\n throw error;\n }\n return parseBaselineDocument(parsed);\n}\n\n/** 현재 이슈 전체를 결정적 베이스라인 문서로 써서 해결된 항목을 정리한다. */\nasync function writeBaselineDocument(\n path: string,\n issues: readonly CheckIssue[],\n generatedAt: Date,\n writeTextFile: WriteTextFile,\n): Promise<void> {\n let document: BaselineDocument;\n try {\n document = createBaselineDocument(issues, generatedAt.toISOString());\n } catch (error) {\n // 만들 수 없는 문서는 쓰지 않는다. 상한 초과 산출물은 다음 실행이\n // 소비할 수 없어 부분 성공보다 실패가 안전하다.\n if (error instanceof BridgeBaselineValidationError) {\n throw new BridgeBaselineWriteLimitError();\n }\n throw error;\n }\n // 인코딩 결함은 쓰기 실패가 아니라 내부 오류로 분류되어야 한다.\n const encoded = encodeBaselineDocument(document);\n try {\n await writeTextFile(path, encoded);\n } catch {\n throw new BridgeBaselineWriteError();\n }\n}\n\n/** 베이스라인 파일을 읽지 못한 경우를 구분한다. */\nclass BridgeBaselineReadError extends Error {\n constructor() {\n super('BridgeBaselineReadError');\n this.name = 'BridgeBaselineReadError';\n }\n}\n\n/** 베이스라인 파일이 JSON이 아닌 경우를 구분한다. */\nclass BridgeBaselineJsonError extends Error {\n constructor() {\n super('BridgeBaselineJsonError');\n this.name = 'BridgeBaselineJsonError';\n }\n}\n\n/** 베이스라인 파일이 크기 상한을 넘은 경우를 구분한다. */\nclass BridgeBaselineLimitError extends Error {\n constructor() {\n super('BridgeBaselineLimitError');\n this.name = 'BridgeBaselineLimitError';\n }\n}\n\n/** 베이스라인 파일을 쓰지 못한 경우를 구분한다. */\nclass BridgeBaselineWriteError extends Error {\n constructor() {\n super('BridgeBaselineWriteError');\n this.name = 'BridgeBaselineWriteError';\n }\n}\n\n/** 쓸 베이스라인이 항목 상한을 넘는 경우를 구분한다. */\nclass BridgeBaselineWriteLimitError extends Error {\n constructor() {\n super('BridgeBaselineWriteLimitError');\n this.name = 'BridgeBaselineWriteLimitError';\n }\n}\n\n/** check 고유의 baseline 실패를 원인별 해결 방향을 담은 코드 2로 바꾼다. */\nfunction baselineFailureResult(error: unknown): CommandResult | undefined {\n if (error instanceof BridgeBaselineReadError) {\n return inputFailure(\n 'Unable to read the baseline file; check that it exists and is readable.\\n',\n );\n }\n if (error instanceof BridgeBaselineJsonError) {\n return inputFailure(\n 'The baseline file is not valid JSON; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineLimitError) {\n return inputFailure(\n 'The baseline file exceeds the input size limits; regenerate it with '\n + 'check --update-baseline.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteError) {\n return inputFailure(\n 'Unable to write the baseline file; check that the path is writable.\\n',\n );\n }\n if (error instanceof BridgeBaselineWriteLimitError) {\n return inputFailure(\n `Cannot write a baseline with more than ${MAX_BASELINE_ENTRIES} entries; `\n + 'narrow the join inputs.\\n',\n );\n }\n if (error instanceof BridgeBaselineValidationError) {\n return inputFailure(\n `The baseline file is not a valid isthmus-baseline document: `\n + `${error.message}\\n`,\n );\n }\n return undefined;\n}\n\n/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */\nfunction usageError(): CommandResult {\n return {\n standardOutput: '',\n standardError: `${checkUsage}\\n`,\n exitCode: 64,\n };\n}\n\n/** check 명령의 한 줄 사용법이다. */\nexport const checkUsage =\n 'Usage: isthmus check <bridge-facts.json> <bridge-facts.json> '\n + '[more...] [--strict] [--format json|sarif|codequality] '\n + '[--baseline <isthmus-baseline.json>] '\n + '[--update-baseline <isthmus-baseline.json>]';\n\n/** 논리 이슈 키의 SHA-256 지문이다. SARIF partialFingerprints에 쓴다. */\nfunction issueFingerprint(issue: {\n readonly code: string;\n readonly target: string;\n readonly channel: string;\n readonly method?: string;\n}): string {\n return createHash('sha256').update(baselineEntryKey(issue)).digest('hex');\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { BridgeFactsDocument } from '../exchange/parse.ts';
2
+ import type { BridgeMessageDocument } from '../exchange/messages.ts';
2
3
  /**
3
4
  * 모든 분석 하위 명령이 공유하는 CLI 인프라다.
4
5
  *
@@ -10,6 +11,18 @@ import type { BridgeFactsDocument } from '../exchange/parse.ts';
10
11
  export type ReadTextFile = (path: string) => Promise<string>;
11
12
  /** 파일 경로에 UTF-8 텍스트를 쓰는 주입 경계다. */
12
13
  export type WriteTextFile = (path: string, text: string) => Promise<void>;
14
+ /** 경로 종류다. 없는 경로를 빈 결과로 오인하지 않게 `missing`을 구분한다. */
15
+ export type PathKind = 'file' | 'directory' | 'missing';
16
+ /**
17
+ * doctor·init이 쓰는 최소 파일시스템 경계다.
18
+ *
19
+ * 제품은 producer를 실행하지 않는다. 이 경계는 설정 JSON의 경로 존재와
20
+ * project 정규화만 확인한다.
21
+ */
22
+ export interface CaptureFileSystem {
23
+ statPath(path: string): Promise<PathKind>;
24
+ realPath(path: string): Promise<string>;
25
+ }
13
26
  /** 생성 시각을 테스트 가능하게 주입하는 시계다. */
14
27
  export type Clock = () => Date;
15
28
  /** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */
@@ -22,8 +35,13 @@ export interface CommandResult {
22
35
  export declare const MAX_INPUT_TEXT_LENGTH: number;
23
36
  /** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
24
37
  export declare const MAX_TOTAL_INPUT_TEXT_LENGTH: number;
25
- /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
38
+ /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 v1 교환 문서로 파싱한다. */
26
39
  export declare function readBridgeDocuments(inputPaths: readonly string[], readTextFile: ReadTextFile, initialTextLength?: number): Promise<BridgeFactsDocument[]>;
40
+ /** v1 MethodChannel과 v2 Basic/Event 문서를 한 번에 읽어 버전별로 나눈다. */
41
+ export declare function readBridgeInputs(inputPaths: readonly string[], readTextFile: ReadTextFile, initialTextLength?: number): Promise<{
42
+ bridges: BridgeFactsDocument[];
43
+ messages: BridgeMessageDocument[];
44
+ }>;
27
45
  /**
28
46
  * 사용자 입력 JSON 파싱 실패인지 확인한다.
29
47
  *
@@ -1,12 +1,13 @@
1
- import { BridgeFactsValidationError, parseBridgeFactsDocument, } from "../exchange/parse.js";
1
+ import { BridgeFactsValidationError, isJsonObject, parseBridgeFactsDocument, } from "../exchange/parse.js";
2
+ import { parseMessageBridgeDocument } from "../exchange/messages.js";
2
3
  import { BridgeJoinValidationError } from "../join/join.js";
3
4
  /** 한 입력 파일에서 허용하는 최대 UTF-16 문자열 길이다. */
4
5
  export const MAX_INPUT_TEXT_LENGTH = 16 * 1024 * 1024;
5
6
  /** 한 명령에서 허용하는 전체 UTF-16 입력 문자열 길이다. */
6
7
  export const MAX_TOTAL_INPUT_TEXT_LENGTH = 64 * 1024 * 1024;
7
- /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */
8
- export async function readBridgeDocuments(inputPaths, readTextFile, initialTextLength = 0) {
9
- const documents = [];
8
+ /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 JSON 값으로 만든다. */
9
+ async function readJsonInputs(inputPaths, readTextFile, initialTextLength) {
10
+ const inputs = [];
10
11
  let totalTextLength = initialTextLength;
11
12
  for (const [index, path] of inputPaths.entries()) {
12
13
  const inputPosition = index + 1;
@@ -24,9 +25,8 @@ export async function readBridgeDocuments(inputPaths, readTextFile, initialTextL
24
25
  }
25
26
  // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가
26
27
  // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.
27
- let parsed;
28
28
  try {
29
- parsed = JSON.parse(text);
29
+ inputs.push({ position: inputPosition, value: JSON.parse(text) });
30
30
  }
31
31
  catch (error) {
32
32
  if (isJsonParseFailure(error)) {
@@ -34,18 +34,47 @@ export async function readBridgeDocuments(inputPaths, readTextFile, initialTextL
34
34
  }
35
35
  throw error;
36
36
  }
37
+ }
38
+ return inputs;
39
+ }
40
+ /** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 v1 교환 문서로 파싱한다. */
41
+ export async function readBridgeDocuments(inputPaths, readTextFile, initialTextLength = 0) {
42
+ const documents = [];
43
+ for (const { position, value } of await readJsonInputs(inputPaths, readTextFile, initialTextLength)) {
37
44
  try {
38
- documents.push(parseBridgeFactsDocument(parsed));
45
+ documents.push(parseBridgeFactsDocument(value));
39
46
  }
40
47
  catch (error) {
41
48
  if (error instanceof BridgeFactsValidationError) {
42
- throw new BridgeInputContractError(inputPosition, error.message);
49
+ throw new BridgeInputContractError(position, error.message);
43
50
  }
44
51
  throw error;
45
52
  }
46
53
  }
47
54
  return documents;
48
55
  }
56
+ /** v1 MethodChannel과 v2 Basic/Event 문서를 한 번에 읽어 버전별로 나눈다. */
57
+ export async function readBridgeInputs(inputPaths, readTextFile, initialTextLength = 0) {
58
+ const bridges = [];
59
+ const messages = [];
60
+ for (const { position, value } of await readJsonInputs(inputPaths, readTextFile, initialTextLength)) {
61
+ try {
62
+ if (isJsonObject(value) && value.version === 2) {
63
+ messages.push(parseMessageBridgeDocument(value));
64
+ }
65
+ else {
66
+ bridges.push(parseBridgeFactsDocument(value));
67
+ }
68
+ }
69
+ catch (error) {
70
+ if (error instanceof BridgeFactsValidationError) {
71
+ throw new BridgeInputContractError(position, error.message);
72
+ }
73
+ throw error;
74
+ }
75
+ }
76
+ return { bridges, messages };
77
+ }
49
78
  /**
50
79
  * 사용자 입력 JSON 파싱 실패인지 확인한다.
51
80
  *
@@ -1 +1 @@
1
- {"version":3,"file":"command-support.js","sourceRoot":"","sources":["../../src/cli/command-support.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument } from '../exchange/parse.ts';\nimport {\n BridgeFactsValidationError,\n parseBridgeFactsDocument,\n} from '../exchange/parse.ts';\nimport { BridgeJoinValidationError } from '../join/join.ts';\n\n/**\n * 모든 분석 하위 명령이 공유하는 CLI 인프라다.\n *\n * 입력 문서 읽기·예산, 입력 실패의 분류와 메시지, 프로세스 경계 결과 형태를\n * 한 곳에 모아 check-command의 편중을 떼어낸다. baseline 같은 명령 고유\n * 오류는 각 명령이 자신의 실패 매퍼를 덧붙이는 방식으로 확장한다.\n */\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/** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */\nexport interface CommandResult {\n readonly standardOutput: string;\n readonly standardError: string;\n readonly exitCode: 0 | 1 | 2 | 64;\n}\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/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 교환 문서로 파싱한다. */\nexport async function readBridgeDocuments(\n inputPaths: readonly string[],\n readTextFile: ReadTextFile,\n initialTextLength = 0,\n): Promise<BridgeFactsDocument[]> {\n const documents: BridgeFactsDocument[] = [];\n let totalTextLength = initialTextLength;\n for (const [index, path] of inputPaths.entries()) {\n const inputPosition = index + 1;\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeInputReadError(inputPosition);\n }\n totalTextLength += text.length;\n if (\n text.length > MAX_INPUT_TEXT_LENGTH ||\n totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH\n ) {\n throw new BridgeInputLimitError(inputPosition);\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) {\n throw new BridgeInputJsonError(inputPosition);\n }\n throw error;\n }\n try {\n documents.push(parseBridgeFactsDocument(parsed));\n } catch (error) {\n if (error instanceof BridgeFactsValidationError) {\n throw new BridgeInputContractError(inputPosition, error.message);\n }\n throw error;\n }\n }\n return documents;\n}\n\n/**\n * 사용자 입력 JSON 파싱 실패인지 확인한다.\n *\n * 깊은 중첩은 SyntaxError가 아니라 RangeError(스택 초과)로 실패한다.\n * 둘 다 입력 탓이라 내부 오류 메시지로 오분류하지 않는다.\n */\nexport function isJsonParseFailure(error: unknown): boolean {\n return error instanceof SyntaxError || error instanceof RangeError;\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 결과로 바꾼다.\n\n명령 고유의 입력(baseline 등)은 각 명령이 자신의 매퍼를 이 뒤에 붙인다. */\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로 내보낸다. */\nexport function inputFailure(standardError: string): CommandResult {\n return { standardOutput: '', standardError, exitCode: 2 };\n}\n\n/**\n * 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다.\n *\n * 관찰량을 숫자 보간으로 함께 알린다. 보류된 조인은 문서를 출력하지 않으므로,\n * 이 숫자가 \"몇 개를 봤는데 못 조인했는지\"가 도달하는 유일한 경로다.\n */\nexport function bridgeJoinDeferredError(\n observedFacts: number,\n documentCount: number,\n): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Bridge facts could not be joined; split mixed bridge targets and retry. '\n + `The inputs observed ${observedFacts} facts across ${documentCount} documents.\\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"]}
1
+ {"version":3,"file":"command-support.js","sourceRoot":"","sources":["../../src/cli/command-support.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument } from '../exchange/parse.ts';\nimport {\n BridgeFactsValidationError,\n isJsonObject,\n parseBridgeFactsDocument,\n} from '../exchange/parse.ts';\nimport type { BridgeMessageDocument } from '../exchange/messages.ts';\nimport { parseMessageBridgeDocument } from '../exchange/messages.ts';\nimport { BridgeJoinValidationError } from '../join/join.ts';\n\n/**\n * 모든 분석 하위 명령이 공유하는 CLI 인프라다.\n *\n * 입력 문서 읽기·예산, 입력 실패의 분류와 메시지, 프로세스 경계 결과 형태를\n * 한 곳에 모아 check-command의 편중을 떼어낸다. baseline 같은 명령 고유\n * 오류는 각 명령이 자신의 실패 매퍼를 덧붙이는 방식으로 확장한다.\n */\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/** 경로 종류다. 없는 경로를 빈 결과로 오인하지 않게 `missing`을 구분한다. */\nexport type PathKind = 'file' | 'directory' | 'missing';\n\n/**\n * doctor·init이 쓰는 최소 파일시스템 경계다.\n *\n * 제품은 producer를 실행하지 않는다. 이 경계는 설정 JSON의 경로 존재와\n * project 정규화만 확인한다.\n */\nexport interface CaptureFileSystem {\n statPath(path: string): Promise<PathKind>;\n realPath(path: string): Promise<string>;\n}\n\n/** 생성 시각을 테스트 가능하게 주입하는 시계다. */\nexport type Clock = () => Date;\n\n/** CLI가 프로세스 경계에 쓸 출력과 종료 코드다. */\nexport interface CommandResult {\n readonly standardOutput: string;\n readonly standardError: string;\n readonly exitCode: 0 | 1 | 2 | 64;\n}\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/** 한 입력 파일의 JSON 값과 1부터 시작하는 순서를 보존한다. */\ninterface ParsedInput {\n readonly position: number;\n readonly value: unknown;\n}\n\n/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 JSON 값으로 만든다. */\nasync function readJsonInputs(\n inputPaths: readonly string[],\n readTextFile: ReadTextFile,\n initialTextLength: number,\n): Promise<ParsedInput[]> {\n const inputs: ParsedInput[] = [];\n let totalTextLength = initialTextLength;\n for (const [index, path] of inputPaths.entries()) {\n const inputPosition = index + 1;\n let text: string;\n try {\n text = await readTextFile(path);\n } catch {\n throw new BridgeInputReadError(inputPosition);\n }\n totalTextLength += text.length;\n if (\n text.length > MAX_INPUT_TEXT_LENGTH ||\n totalTextLength > MAX_TOTAL_INPUT_TEXT_LENGTH\n ) {\n throw new BridgeInputLimitError(inputPosition);\n }\n // JSON 구문 오류와 문서 검증 실패를 같은 try로 감싸면 검증기의 예외가\n // 구문 오류로 오분류된다. 파싱과 검증을 나눠 분류 경계를 지킨다.\n try {\n inputs.push({ position: inputPosition, value: JSON.parse(text) });\n } catch (error) {\n if (isJsonParseFailure(error)) {\n throw new BridgeInputJsonError(inputPosition);\n }\n throw error;\n }\n }\n return inputs;\n}\n\n/** 입력을 순서대로 읽어 파일 수·메모리 상한 안에서 v1 교환 문서로 파싱한다. */\nexport async function readBridgeDocuments(\n inputPaths: readonly string[],\n readTextFile: ReadTextFile,\n initialTextLength = 0,\n): Promise<BridgeFactsDocument[]> {\n const documents: BridgeFactsDocument[] = [];\n for (const { position, value } of await readJsonInputs(\n inputPaths,\n readTextFile,\n initialTextLength,\n )) {\n try {\n documents.push(parseBridgeFactsDocument(value));\n } catch (error) {\n if (error instanceof BridgeFactsValidationError) {\n throw new BridgeInputContractError(position, error.message);\n }\n throw error;\n }\n }\n return documents;\n}\n\n/** v1 MethodChannel과 v2 Basic/Event 문서를 한 번에 읽어 버전별로 나눈다. */\nexport async function readBridgeInputs(\n inputPaths: readonly string[],\n readTextFile: ReadTextFile,\n initialTextLength = 0,\n): Promise<{\n bridges: BridgeFactsDocument[];\n messages: BridgeMessageDocument[];\n}> {\n const bridges: BridgeFactsDocument[] = [];\n const messages: BridgeMessageDocument[] = [];\n for (const { position, value } of await readJsonInputs(\n inputPaths,\n readTextFile,\n initialTextLength,\n )) {\n try {\n if (isJsonObject(value) && value.version === 2) {\n messages.push(parseMessageBridgeDocument(value));\n } else {\n bridges.push(parseBridgeFactsDocument(value));\n }\n } catch (error) {\n if (error instanceof BridgeFactsValidationError) {\n throw new BridgeInputContractError(position, error.message);\n }\n throw error;\n }\n }\n return { bridges, messages };\n}\n\n/**\n * 사용자 입력 JSON 파싱 실패인지 확인한다.\n *\n * 깊은 중첩은 SyntaxError가 아니라 RangeError(스택 초과)로 실패한다.\n * 둘 다 입력 탓이라 내부 오류 메시지로 오분류하지 않는다.\n */\nexport function isJsonParseFailure(error: unknown): boolean {\n return error instanceof SyntaxError || error instanceof RangeError;\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 결과로 바꾼다.\n\n명령 고유의 입력(baseline 등)은 각 명령이 자신의 매퍼를 이 뒤에 붙인다. */\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로 내보낸다. */\nexport function inputFailure(standardError: string): CommandResult {\n return { standardOutput: '', standardError, exitCode: 2 };\n}\n\n/**\n * 전체 조인 보류를 깨끗한 결과와 구분하는 코드 2 결과로 바꾼다.\n *\n * 관찰량을 숫자 보간으로 함께 알린다. 보류된 조인은 문서를 출력하지 않으므로,\n * 이 숫자가 \"몇 개를 봤는데 못 조인했는지\"가 도달하는 유일한 경로다.\n */\nexport function bridgeJoinDeferredError(\n observedFacts: number,\n documentCount: number,\n): CommandResult {\n return {\n standardOutput: '',\n standardError:\n 'Bridge facts could not be joined; split mixed bridge targets and retry. '\n + `The inputs observed ${observedFacts} facts across ${documentCount} documents.\\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"]}
@@ -1,7 +1,7 @@
1
1
  import { MAX_DOCUMENTS_PER_JOIN } from "../join/join.js";
2
2
  import { createBridgeDiff } from "../report/diff.js";
3
3
  import { encodeSortedJson } from "../report/sorted-json.js";
4
- import { inputFailureResult, internalError, readBridgeDocuments, } from "./command-support.js";
4
+ import { inputFailureResult, internalError, readBridgeInputs, } from "./command-support.js";
5
5
  /** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */
6
6
  export async function runDiffCommand(arguments_, readTextFile) {
7
7
  const strictFlags = arguments_.filter((argument) => argument === '--strict');
@@ -21,8 +21,9 @@ export async function runDiffCommand(arguments_, readTextFile) {
21
21
  return { standardOutput: '', standardError: `${diffUsage}\n`, exitCode: 64 };
22
22
  }
23
23
  try {
24
- const documents = await readBridgeDocuments(paths, readTextFile);
25
- const report = createBridgeDiff(documents.slice(0, beforePaths.length), documents.slice(beforePaths.length));
24
+ const before = await readBridgeInputs(beforePaths, readTextFile);
25
+ const after = await readBridgeInputs(afterPaths, readTextFile);
26
+ const report = createBridgeDiff(before.bridges, after.bridges, before.messages.length > 0 ? before.messages : undefined, after.messages.length > 0 ? after.messages : undefined);
26
27
  return {
27
28
  standardOutput: encodeSortedJson(report),
28
29
  standardError: '',
@@ -1 +1 @@
1
- {"version":3,"file":"diff-command.js","sourceRoot":"","sources":["../../src/cli/diff-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAE9B,qDAAqD;AACrD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAA6B,EAC7B,YAA0B;IAE1B,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,UAAU,CAAC,CAAC;IAC9C,IACE,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;QAClB,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU;QACtB,WAAW,CAAC,MAAM,GAAG,CAAC;QACtB,SAAS,GAAG,CAAC;QACb,WAAW,CAAC,MAAM,GAAG,CAAC;QACtB,UAAU,CAAC,MAAM,GAAG,CAAC;QACrB,KAAK,CAAC,MAAM,GAAG,sBAAsB;QACrC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EACtE,CAAC;QACD,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,SAAS,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,gBAAgB,CAC7B,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EACtC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CACpC,CAAC;QACF,OAAO;YACL,cAAc,EAAE,gBAAgB,CAAC,MAAM,CAAC;YACxC,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAClF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GACpB,kEAAkE;MAChE,uDAAuD,CAAC","sourcesContent":["import { MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { createBridgeDiff } from '../report/diff.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport {\n inputFailureResult,\n internalError,\n readBridgeDocuments,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\n\n/** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */\nexport async function runDiffCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const strictFlags = arguments_.filter((argument) => argument === '--strict');\n const args = arguments_.filter((argument) => argument !== '--strict');\n const separator = args.indexOf('--after');\n const beforePaths = args.slice(2, separator);\n const afterPaths = args.slice(separator + 1);\n const paths = [...beforePaths, ...afterPaths];\n if (\n args[0] !== 'diff' ||\n args[1] !== '--before' ||\n strictFlags.length > 1 ||\n separator < 4 ||\n beforePaths.length < 2 ||\n afterPaths.length < 2 ||\n paths.length > MAX_DOCUMENTS_PER_JOIN ||\n paths.some((path) => path.trim().length === 0 || path.startsWith('-'))\n ) {\n return { standardOutput: '', standardError: `${diffUsage}\\n`, exitCode: 64 };\n }\n try {\n const documents = await readBridgeDocuments(paths, readTextFile);\n const report = createBridgeDiff(\n documents.slice(0, beforePaths.length),\n documents.slice(beforePaths.length),\n );\n return {\n standardOutput: encodeSortedJson(report),\n standardError: '',\n exitCode: strictFlags.length === 1 && report.summary.introducedErrors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** 각 snapshot은 caller와 receiver 문서를 함께 제공해야 한다. */\nexport const diffUsage =\n 'Usage: isthmus diff --before <dart.json> <swift.json> [more...] '\n + '--after <dart.json> <swift.json> [more...] [--strict]';\n"]}
1
+ {"version":3,"file":"diff-command.js","sourceRoot":"","sources":["../../src/cli/diff-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,gBAAgB,GAGjB,MAAM,sBAAsB,CAAC;AAE9B,qDAAqD;AACrD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAA6B,EAC7B,YAA0B;IAE1B,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,EAAE,GAAG,UAAU,CAAC,CAAC;IAC9C,IACE,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;QAClB,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU;QACtB,WAAW,CAAC,MAAM,GAAG,CAAC;QACtB,SAAS,GAAG,CAAC;QACb,WAAW,CAAC,MAAM,GAAG,CAAC;QACtB,UAAU,CAAC,MAAM,GAAG,CAAC;QACrB,KAAK,CAAC,MAAM,GAAG,sBAAsB;QACrC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EACtE,CAAC;QACD,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,SAAS,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,gBAAgB,CAC7B,MAAM,CAAC,OAAO,EACd,KAAK,CAAC,OAAO,EACb,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EACxD,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CACvD,CAAC;QACF,OAAO;YACL,cAAc,EAAE,gBAAgB,CAAC,MAAM,CAAC;YACxC,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAClF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GACpB,kEAAkE;MAChE,uDAAuD,CAAC","sourcesContent":["import { MAX_DOCUMENTS_PER_JOIN } from '../join/join.ts';\nimport { createBridgeDiff } from '../report/diff.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport {\n inputFailureResult,\n internalError,\n readBridgeInputs,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\n\n/** 전후 교환 파일을 하나의 입력 예산으로 읽어 새 경계 오류만 CI 실패로 표시한다. */\nexport async function runDiffCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n): Promise<CommandResult> {\n const strictFlags = arguments_.filter((argument) => argument === '--strict');\n const args = arguments_.filter((argument) => argument !== '--strict');\n const separator = args.indexOf('--after');\n const beforePaths = args.slice(2, separator);\n const afterPaths = args.slice(separator + 1);\n const paths = [...beforePaths, ...afterPaths];\n if (\n args[0] !== 'diff' ||\n args[1] !== '--before' ||\n strictFlags.length > 1 ||\n separator < 4 ||\n beforePaths.length < 2 ||\n afterPaths.length < 2 ||\n paths.length > MAX_DOCUMENTS_PER_JOIN ||\n paths.some((path) => path.trim().length === 0 || path.startsWith('-'))\n ) {\n return { standardOutput: '', standardError: `${diffUsage}\\n`, exitCode: 64 };\n }\n try {\n const before = await readBridgeInputs(beforePaths, readTextFile);\n const after = await readBridgeInputs(afterPaths, readTextFile);\n const report = createBridgeDiff(\n before.bridges,\n after.bridges,\n before.messages.length > 0 ? before.messages : undefined,\n after.messages.length > 0 ? after.messages : undefined,\n );\n return {\n standardOutput: encodeSortedJson(report),\n standardError: '',\n exitCode: strictFlags.length === 1 && report.summary.introducedErrors > 0 ? 1 : 0,\n };\n } catch (error) {\n return inputFailureResult(error) ?? internalError();\n }\n}\n\n/** 각 snapshot은 caller와 receiver 문서를 함께 제공해야 한다. */\nexport const diffUsage =\n 'Usage: isthmus diff --before <dart.json> <swift.json> [more...] '\n + '--after <dart.json> <swift.json> [more...] [--strict]';\n"]}
@@ -0,0 +1,11 @@
1
+ import { type CaptureFileSystem, type CommandResult, type ReadTextFile } from './command-support.ts';
2
+ /**
3
+ * capture 설정 JSON을 검증하고 참조한 실행 파일이 실제로 있는지 확인한다.
4
+ *
5
+ * producer를 실행하거나 버전을 묻지 않는다 — 제품은 JSON만 읽는다. 여기서
6
+ * 확인하는 것은 `scripts/capture-preflight.mjs`가 실행할 명령의 첫 토큰이
7
+ * 존재하는 파일(또는 PATH 위의 실행 파일)인지뿐이다.
8
+ */
9
+ export declare function runDoctorCommand(arguments_: readonly string[], readTextFile: ReadTextFile, fileSystem: CaptureFileSystem, cwd: string, pathValue?: string): Promise<CommandResult>;
10
+ /** doctor 명령의 한 줄 사용법이다. */
11
+ export declare const doctorUsage = "Usage: isthmus doctor <capture.json>";
@@ -0,0 +1,128 @@
1
+ import { isAbsolute, join, resolve } from 'node:path';
2
+ import { CaptureConfigValidationError, validateCaptureConfig } from "../exchange/capture-config.js";
3
+ import { isJsonObject, isSafeNonEmptyString } from "../exchange/parse.js";
4
+ import { encodeSortedJson } from "../report/sorted-json.js";
5
+ import { inputFailure, isJsonParseFailure, MAX_INPUT_TEXT_LENGTH, } from "./command-support.js";
6
+ /**
7
+ * capture 설정 JSON을 검증하고 참조한 실행 파일이 실제로 있는지 확인한다.
8
+ *
9
+ * producer를 실행하거나 버전을 묻지 않는다 — 제품은 JSON만 읽는다. 여기서
10
+ * 확인하는 것은 `scripts/capture-preflight.mjs`가 실행할 명령의 첫 토큰이
11
+ * 존재하는 파일(또는 PATH 위의 실행 파일)인지뿐이다.
12
+ */
13
+ export async function runDoctorCommand(arguments_, readTextFile, fileSystem, cwd, pathValue = '') {
14
+ if (arguments_[0] !== 'doctor')
15
+ return usageError();
16
+ const rest = arguments_.slice(1);
17
+ if (rest.length !== 1 || rest[0].startsWith('--'))
18
+ return usageError();
19
+ const configPath = rest[0];
20
+ let text;
21
+ try {
22
+ text = await readTextFile(configPath);
23
+ }
24
+ catch {
25
+ return inputFailure('Unable to read the capture config; check that the file exists and is readable.\n');
26
+ }
27
+ if (text.length > MAX_INPUT_TEXT_LENGTH) {
28
+ return inputFailure('The capture config exceeds the input size limits.\n');
29
+ }
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(text);
33
+ }
34
+ catch (error) {
35
+ if (isJsonParseFailure(error)) {
36
+ return inputFailure('The capture config is not valid JSON; regenerate it with isthmus init.\n');
37
+ }
38
+ throw error;
39
+ }
40
+ if (!isJsonObject(parsed) || !isSafeNonEmptyString(parsed.project)) {
41
+ return inputFailure('The capture config must be a JSON object with a project path.\n');
42
+ }
43
+ let project;
44
+ try {
45
+ project = await fileSystem.realPath(parsed.project);
46
+ }
47
+ catch {
48
+ return inputFailure('The capture config project path does not exist; point it at the app root.\n');
49
+ }
50
+ try {
51
+ validateCaptureConfig(parsed, project);
52
+ }
53
+ catch (error) {
54
+ if (error instanceof CaptureConfigValidationError) {
55
+ return inputFailure(`The capture config violates the workflow contract: ${error.message}\n`);
56
+ }
57
+ throw error;
58
+ }
59
+ const pathEntries = pathValue.split(':').filter((entry) => entry.length > 0);
60
+ const checks = [];
61
+ const names = [
62
+ ...producerNames(parsed),
63
+ ...objectCommands(parsed.messages, parsed),
64
+ ...objectCommands(parsed.events, parsed),
65
+ ...parsed.prepare.map((command, index) => ({
66
+ name: `prepare-${index + 1}`,
67
+ command,
68
+ })),
69
+ ];
70
+ for (const { name, command } of names) {
71
+ const resolved = await resolveExecutable(command[0], project, fileSystem, pathEntries);
72
+ checks.push({
73
+ name,
74
+ status: resolved === undefined ? 'missing' : 'ok',
75
+ command,
76
+ ...(resolved === undefined ? {} : { resolved }),
77
+ });
78
+ }
79
+ const report = {
80
+ format: 'isthmus-doctor',
81
+ version: 1,
82
+ status: checks.every(({ status }) => status === 'ok') ? 'ok' : 'incomplete',
83
+ project,
84
+ checks,
85
+ };
86
+ return {
87
+ standardOutput: encodeSortedJson(report),
88
+ standardError: '',
89
+ exitCode: report.status === 'ok' ? 0 : 1,
90
+ };
91
+ }
92
+ /** 설정에 선언된 기본 producer 명령과 이름을 순서대로 모은다. */
93
+ function producerNames(config) {
94
+ return [
95
+ { name: 'dartograph', command: config.dartograph },
96
+ ...['cartograph', 'kartograph']
97
+ .filter((name) => config[name] !== undefined)
98
+ .map((name) => ({ name, command: config[name] })),
99
+ ];
100
+ }
101
+ /** messages·events의 사용자 지정 명령만 검사한다. `true`는 기본 명령 재사용이다. */
102
+ function objectCommands(value, config) {
103
+ if (value === undefined || value === true || !isJsonObject(value))
104
+ return [];
105
+ return Object.entries(value)
106
+ .filter(([name, command]) => config[name] !== undefined && Array.isArray(command))
107
+ .map(([name, command]) => ({ name, command: command }));
108
+ }
109
+ /** 첫 토큰이 존재하는 파일이거나 PATH 위의 실행 파일인지 확인한다. */
110
+ async function resolveExecutable(token, project, fileSystem, pathEntries) {
111
+ if (token.includes('/') || token.includes('\\')) {
112
+ const candidate = isAbsolute(token) ? token : resolve(project, token);
113
+ return (await fileSystem.statPath(candidate)) === 'file' ? candidate : undefined;
114
+ }
115
+ for (const entry of pathEntries) {
116
+ const candidate = join(entry, token);
117
+ if ((await fileSystem.statPath(candidate)) === 'file')
118
+ return candidate;
119
+ }
120
+ return undefined;
121
+ }
122
+ /** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */
123
+ function usageError() {
124
+ return { standardOutput: '', standardError: `${doctorUsage}\n`, exitCode: 64 };
125
+ }
126
+ /** doctor 명령의 한 줄 사용법이다. */
127
+ export const doctorUsage = 'Usage: isthmus doctor <capture.json>';
128
+ //# sourceMappingURL=doctor-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-command.js","sourceRoot":"","sources":["../../src/cli/doctor-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, join, resolve } from 'node:path';\n\nimport { CaptureConfigValidationError, validateCaptureConfig } from '../exchange/capture-config.ts';\nimport { isJsonObject, isSafeNonEmptyString } from '../exchange/parse.ts';\nimport { encodeSortedJson } from '../report/sorted-json.ts';\nimport {\n inputFailure,\n isJsonParseFailure,\n MAX_INPUT_TEXT_LENGTH,\n type CaptureFileSystem,\n type CommandResult,\n type ReadTextFile,\n} from './command-support.ts';\n\n/** doctor가 확인한 명령 하나의 결과다. */\ninterface DoctorCheck {\n readonly name: string;\n readonly status: 'ok' | 'missing';\n readonly command: readonly string[];\n readonly resolved?: string;\n}\n\n/** doctor가 내는 기계 판독 보고서다. */\ninterface DoctorReport {\n readonly format: 'isthmus-doctor';\n readonly version: 1;\n readonly status: 'ok' | 'incomplete';\n readonly project: string;\n readonly checks: readonly DoctorCheck[];\n}\n\n/**\n * capture 설정 JSON을 검증하고 참조한 실행 파일이 실제로 있는지 확인한다.\n *\n * producer를 실행하거나 버전을 묻지 않는다 — 제품은 JSON만 읽는다. 여기서\n * 확인하는 것은 `scripts/capture-preflight.mjs`가 실행할 명령의 첫 토큰이\n * 존재하는 파일(또는 PATH 위의 실행 파일)인지뿐이다.\n */\nexport async function runDoctorCommand(\n arguments_: readonly string[],\n readTextFile: ReadTextFile,\n fileSystem: CaptureFileSystem,\n cwd: string,\n pathValue: string = '',\n): Promise<CommandResult> {\n if (arguments_[0] !== 'doctor') return usageError();\n const rest = arguments_.slice(1);\n if (rest.length !== 1 || rest[0]!.startsWith('--')) return usageError();\n const configPath = rest[0]!;\n\n let text: string;\n try {\n text = await readTextFile(configPath);\n } catch {\n return inputFailure(\n 'Unable to read the capture config; check that the file exists and is readable.\\n',\n );\n }\n if (text.length > MAX_INPUT_TEXT_LENGTH) {\n return inputFailure('The capture config exceeds the input size limits.\\n');\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n if (isJsonParseFailure(error)) {\n return inputFailure(\n 'The capture config is not valid JSON; regenerate it with isthmus init.\\n',\n );\n }\n throw error;\n }\n if (!isJsonObject(parsed) || !isSafeNonEmptyString(parsed.project)) {\n return inputFailure('The capture config must be a JSON object with a project path.\\n');\n }\n let project: string;\n try {\n project = await fileSystem.realPath(parsed.project);\n } catch {\n return inputFailure(\n 'The capture config project path does not exist; point it at the app root.\\n',\n );\n }\n try {\n validateCaptureConfig(parsed, project);\n } catch (error) {\n if (error instanceof CaptureConfigValidationError) {\n return inputFailure(`The capture config violates the workflow contract: ${error.message}\\n`);\n }\n throw error;\n }\n\n const pathEntries = pathValue.split(':').filter((entry) => entry.length > 0);\n const checks: DoctorCheck[] = [];\n const names = [\n ...producerNames(parsed),\n ...objectCommands(parsed.messages, parsed),\n ...objectCommands(parsed.events, parsed),\n ...(parsed.prepare as readonly (readonly string[])[]).map((command, index) => ({\n name: `prepare-${index + 1}`,\n command,\n })),\n ];\n for (const { name, command } of names) {\n const resolved = await resolveExecutable(command[0]!, project, fileSystem, pathEntries);\n checks.push({\n name,\n status: resolved === undefined ? 'missing' : 'ok',\n command,\n ...(resolved === undefined ? {} : { resolved }),\n });\n }\n const report: DoctorReport = {\n format: 'isthmus-doctor',\n version: 1,\n status: checks.every(({ status }) => status === 'ok') ? 'ok' : 'incomplete',\n project,\n checks,\n };\n return {\n standardOutput: encodeSortedJson(report),\n standardError: '',\n exitCode: report.status === 'ok' ? 0 : 1,\n };\n}\n\n/** 설정에 선언된 기본 producer 명령과 이름을 순서대로 모은다. */\nfunction producerNames(\n config: Record<string, unknown>,\n): ReadonlyArray<{ name: string; command: readonly string[] }> {\n return [\n { name: 'dartograph', command: config.dartograph as readonly string[] },\n ...(['cartograph', 'kartograph'] as const)\n .filter((name) => config[name] !== undefined)\n .map((name) => ({ name, command: config[name] as readonly string[] })),\n ];\n}\n\n/** messages·events의 사용자 지정 명령만 검사한다. `true`는 기본 명령 재사용이다. */\nfunction objectCommands(\n value: unknown,\n config: Record<string, unknown>,\n): ReadonlyArray<{ name: string; command: readonly string[] }> {\n if (value === undefined || value === true || !isJsonObject(value)) return [];\n return Object.entries(value)\n .filter(([name, command]) => config[name] !== undefined && Array.isArray(command))\n .map(([name, command]) => ({ name, command: command as readonly string[] }));\n}\n\n/** 첫 토큰이 존재하는 파일이거나 PATH 위의 실행 파일인지 확인한다. */\nasync function resolveExecutable(\n token: string,\n project: string,\n fileSystem: CaptureFileSystem,\n pathEntries: readonly string[],\n): Promise<string | undefined> {\n if (token.includes('/') || token.includes('\\\\')) {\n const candidate = isAbsolute(token) ? token : resolve(project, token);\n return (await fileSystem.statPath(candidate)) === 'file' ? candidate : undefined;\n }\n for (const entry of pathEntries) {\n const candidate = join(entry, token);\n if ((await fileSystem.statPath(candidate)) === 'file') return candidate;\n }\n return undefined;\n}\n\n/** 잘못된 CLI 호출을 경로 없는 사용법과 코드 64로 바꾼다. */\nfunction usageError(): CommandResult {\n return { standardOutput: '', standardError: `${doctorUsage}\\n`, exitCode: 64 };\n}\n\n/** doctor 명령의 한 줄 사용법이다. */\nexport const doctorUsage =\n 'Usage: isthmus doctor <capture.json>';"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `isthmus extract-js` — React Native 호출 측 bridge-facts 생산자다.
3
+ *
4
+ * JS/TS 파일(또는 그 파일을 담은 디렉터리)을 토큰 스캔해 정적 모듈·컴포넌트
5
+ * 이름과 바인딩 해석이 끝난 메서드 호출을 bridge-facts v1 문서로 낸다.
6
+ * 해석이 멈춘 곳은 limitations로만 보고한다 — 문서를 거짓으로 완전하게
7
+ * 만들지 않는다.
8
+ */
9
+ import { type Clock, type CommandResult, type ReadTextFile } from './command-support.ts';
10
+ /** 디렉터리 항목 — 워커가 경로 종류를 다시 질의하지 않게 타입을 함께 준다. */
11
+ export interface JsDirectoryEntry {
12
+ readonly name: string;
13
+ readonly isFile: boolean;
14
+ readonly isDirectory: boolean;
15
+ }
16
+ /** extract-js가 필요로 하는 최소 파일시스템 표면이다. */
17
+ export interface ExtractJsFileSystem {
18
+ /** 경로 종류를 돌려준다 — 없으면 'missing'. */
19
+ readonly statPath: (path: string) => Promise<'file' | 'directory' | 'missing'>;
20
+ /** 디렉터리의 바로 아래 항목들을 나열한다. */
21
+ readonly listDirectory: (path: string) => Promise<readonly JsDirectoryEntry[]>;
22
+ /** 심볼릭 링크·`..`를 풀어 절대 경로로 만든다. */
23
+ readonly realPath: (path: string) => Promise<string>;
24
+ }
25
+ /**
26
+ * extract-js 인자를 실행해 프로세스에 독립적인 결과를 반환한다.
27
+ *
28
+ * 성공 시 정렬된 bridge-facts JSON을 stdout에 쓴다. 입력 실패는 원인 종류와
29
+ * 해결 방향을 담은 코드 2, 잘못된 호출은 사용법과 코드 64다.
30
+ */
31
+ export declare function runExtractJsCommand(arguments_: readonly string[], fileSystem: ExtractJsFileSystem, readSource: ReadTextFile, now?: Clock, version?: string): Promise<CommandResult>;
32
+ /** extract-js 명령의 한 줄 사용법이다. */
33
+ export declare const extractJsUsage = "Usage: isthmus extract-js <file-or-dir> [more...] [--project <dir>]";