isthmus-cli 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -5
- package/Skills/isthmus/SKILL.md +38 -49
- package/dist/cli/check-command.d.ts +2 -2
- package/dist/cli/check-command.js +66 -19
- package/dist/cli/check-command.js.map +1 -1
- package/dist/cli/diff-command.js +13 -13
- package/dist/cli/diff-command.js.map +1 -1
- package/dist/cli/graph-command.js +10 -14
- package/dist/cli/graph-command.js.map +1 -1
- package/dist/cli/query-command.js +2 -10
- package/dist/cli/query-command.js.map +1 -1
- package/dist/cli/retentions-command.js +11 -13
- package/dist/cli/retentions-command.js.map +1 -1
- package/dist/exchange/parse.d.ts +4 -0
- package/dist/exchange/parse.js +9 -1
- package/dist/exchange/parse.js.map +1 -1
- package/dist/join/join.js +85 -2
- package/dist/join/join.js.map +1 -1
- package/dist/report/check-report.d.ts +7 -2
- package/dist/report/check-report.js +91 -36
- package/dist/report/check-report.js.map +1 -1
- package/dist/report/diff.js +3 -2
- package/dist/report/diff.js.map +1 -1
- package/dist/report/retentions.d.ts +9 -1
- package/dist/report/retentions.js +37 -0
- package/dist/report/retentions.js.map +1 -1
- package/package.json +2 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isReceiverPlatform } from "../exchange/parse.js";
|
|
1
2
|
import { isBridgeJoinDeferred } from "../join/join.js";
|
|
2
3
|
import { encodeSortedJson } from "./sorted-json.js";
|
|
3
4
|
/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */
|
|
@@ -9,50 +10,104 @@ export function createCheckReport(joined) {
|
|
|
9
10
|
if (isBridgeJoinDeferred(joined)) {
|
|
10
11
|
throw new Error('Cannot create a check report from a deferred bridge join.');
|
|
11
12
|
}
|
|
13
|
+
const gaps = receiverCoverageGaps(joined.limitations);
|
|
14
|
+
const issues = [
|
|
15
|
+
...joined.unhandledInvocations.map((item) => ({
|
|
16
|
+
severity: gaps.hidesHandlers ? 'warning' : 'error',
|
|
17
|
+
code: gaps.hidesHandlers
|
|
18
|
+
? 'unhandled-invocation-unverified'
|
|
19
|
+
: 'unhandled-invocation',
|
|
20
|
+
target: item.target,
|
|
21
|
+
channel: item.channel,
|
|
22
|
+
method: item.method,
|
|
23
|
+
evidence: item.invocations,
|
|
24
|
+
})),
|
|
25
|
+
...joined.unregisteredChannelCreations.map((item) => ({
|
|
26
|
+
severity: gaps.hidesRegistrations ? 'warning' : 'error',
|
|
27
|
+
code: gaps.hidesRegistrations
|
|
28
|
+
? 'unregistered-channel-creation-unverified'
|
|
29
|
+
: 'unregistered-channel-creation',
|
|
30
|
+
target: item.target,
|
|
31
|
+
channel: item.channel,
|
|
32
|
+
evidence: item.creations,
|
|
33
|
+
})),
|
|
34
|
+
...joined.registrationsWithoutCreations.map((item) => ({
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
code: 'registration-without-creation',
|
|
37
|
+
target: item.target,
|
|
38
|
+
channel: item.channel,
|
|
39
|
+
evidence: item.registrations,
|
|
40
|
+
})),
|
|
41
|
+
...joined.handlersWithoutInvocations.map((item) => ({
|
|
42
|
+
severity: 'warning',
|
|
43
|
+
code: 'handler-without-invocation',
|
|
44
|
+
target: item.target,
|
|
45
|
+
channel: item.channel,
|
|
46
|
+
method: item.method,
|
|
47
|
+
evidence: item.handlers,
|
|
48
|
+
})),
|
|
49
|
+
];
|
|
12
50
|
return {
|
|
13
51
|
format: 'isthmus-check',
|
|
14
52
|
version: 1,
|
|
15
53
|
summary: {
|
|
16
|
-
errors:
|
|
17
|
-
|
|
18
|
-
warnings: joined.registrationsWithoutCreations.length +
|
|
19
|
-
joined.handlersWithoutInvocations.length,
|
|
54
|
+
errors: issues.filter(({ severity }) => severity === 'error').length,
|
|
55
|
+
warnings: issues.filter(({ severity }) => severity === 'warning').length,
|
|
20
56
|
matchedChannels: joined.matchedChannels.length,
|
|
21
57
|
matchedMethods: joined.matchedMethods.length,
|
|
22
58
|
},
|
|
23
|
-
issues
|
|
24
|
-
...joined.unhandledInvocations.map((item) => ({
|
|
25
|
-
severity: 'error',
|
|
26
|
-
code: 'unhandled-invocation',
|
|
27
|
-
target: item.target,
|
|
28
|
-
channel: item.channel,
|
|
29
|
-
method: item.method,
|
|
30
|
-
evidence: item.invocations,
|
|
31
|
-
})),
|
|
32
|
-
...joined.unregisteredChannelCreations.map((item) => ({
|
|
33
|
-
severity: 'error',
|
|
34
|
-
code: 'unregistered-channel-creation',
|
|
35
|
-
target: item.target,
|
|
36
|
-
channel: item.channel,
|
|
37
|
-
evidence: item.creations,
|
|
38
|
-
})),
|
|
39
|
-
...joined.registrationsWithoutCreations.map((item) => ({
|
|
40
|
-
severity: 'warning',
|
|
41
|
-
code: 'registration-without-creation',
|
|
42
|
-
target: item.target,
|
|
43
|
-
channel: item.channel,
|
|
44
|
-
evidence: item.registrations,
|
|
45
|
-
})),
|
|
46
|
-
...joined.handlersWithoutInvocations.map((item) => ({
|
|
47
|
-
severity: 'warning',
|
|
48
|
-
code: 'handler-without-invocation',
|
|
49
|
-
target: item.target,
|
|
50
|
-
channel: item.channel,
|
|
51
|
-
method: item.method,
|
|
52
|
-
evidence: item.handlers,
|
|
53
|
-
})),
|
|
54
|
-
],
|
|
59
|
+
issues,
|
|
55
60
|
limitations: joined.limitations,
|
|
56
61
|
};
|
|
57
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 확인한다.
|
|
65
|
+
*
|
|
66
|
+
* 이때 "핸들러 없는 호출"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인
|
|
67
|
+
* Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,
|
|
68
|
+
* error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.
|
|
69
|
+
*
|
|
70
|
+
* 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드
|
|
71
|
+
* 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로
|
|
72
|
+
* 수신 측 플랫폼의 한계만 본다.
|
|
73
|
+
*/
|
|
74
|
+
function receiverCoverageGaps(limitations) {
|
|
75
|
+
const messages = limitations
|
|
76
|
+
.filter(({ platform }) => isReceiverPlatform(platform))
|
|
77
|
+
.map(({ message }) => message);
|
|
78
|
+
return {
|
|
79
|
+
hidesHandlers: messages.some(startsWithAny(handlerCoverageGapPrefixes)),
|
|
80
|
+
hidesRegistrations: messages.some(startsWithAny(registrationCoverageGapPrefixes)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */
|
|
84
|
+
function startsWithAny(prefixes) {
|
|
85
|
+
return (message) => prefixes.some((prefix) => message.startsWith(prefix));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 수신 측 소스 자체가 분석되지 않아 등록과 핸들러를 모두 가리는 한계다.
|
|
89
|
+
*
|
|
90
|
+
* 알려진 접두사만 인정한다. 모르는 한계를 공백으로 넓게 해석하면 진짜 불일치가
|
|
91
|
+
* 경고로 묻힌다.
|
|
92
|
+
*/
|
|
93
|
+
const sourceCoverageGapPrefixes = [
|
|
94
|
+
'objective-c-sources:',
|
|
95
|
+
'shadowed-flutter-method-channel:',
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* 핸들러를 가릴 수 있는 한계다.
|
|
99
|
+
*
|
|
100
|
+
* `unjoined-`는 isthmus가 직접 센 값이라 생산자의 신고 개수에 의존하지 않는다.
|
|
101
|
+
*/
|
|
102
|
+
const handlerCoverageGapPrefixes = [
|
|
103
|
+
...sourceCoverageGapPrefixes,
|
|
104
|
+
'opaque-handler-bodies:',
|
|
105
|
+
'unjoined-dynamic-methods:',
|
|
106
|
+
'unjoined-unattributed-handlers:',
|
|
107
|
+
];
|
|
108
|
+
/** 채널 등록을 가릴 수 있는 한계다. */
|
|
109
|
+
const registrationCoverageGapPrefixes = [
|
|
110
|
+
...sourceCoverageGapPrefixes,
|
|
111
|
+
'unjoined-dynamic-channels:',
|
|
112
|
+
];
|
|
58
113
|
//# sourceMappingURL=check-report.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-report.js","sourceRoot":"","sources":["../../src/report/check-report.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check-report.js","sourceRoot":"","sources":["../../src/report/check-report.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeTarget } from '../exchange/parse.ts';\nimport { isReceiverPlatform } from '../exchange/parse.ts';\nimport type {\n BridgeEndpoint,\n BridgeJoinResult,\n JoinLimitation,\n} from '../join/join.ts';\nimport { isBridgeJoinDeferred } from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** check 결과 개수를 빠르게 판단할 요약이다. */\nexport interface CheckSummary {\n readonly errors: number;\n readonly warnings: number;\n readonly matchedChannels: number;\n readonly matchedMethods: number;\n}\n\n/**\n * check가 보고하는 안정적인 진단 종류다.\n *\n * `-unverified` 종류는 수신 측이 스스로 분석 공백을 신고해, 핸들러가 없는 것인지\n * 보지 못한 것인지 구분할 수 없는 경우다. 사실과 증거는 같지만 판정이 아니다.\n */\nexport type CheckIssueCode =\n | 'unhandled-invocation'\n | 'unhandled-invocation-unverified'\n | 'unregistered-channel-creation'\n | 'unregistered-channel-creation-unverified'\n | 'registration-without-creation'\n | 'handler-without-invocation';\n\n/** 삭제 판정 없이 경계 불일치 사실과 증거만 전달한다. */\nexport interface CheckIssue {\n readonly severity: 'error' | 'warning';\n readonly code: CheckIssueCode;\n readonly target: BridgeTarget;\n readonly channel: string;\n readonly method?: string;\n readonly evidence: readonly BridgeEndpoint[];\n}\n\n/** 에이전트와 CI가 소비할 check 문서다. */\nexport interface CheckReport {\n readonly format: 'isthmus-check';\n readonly version: 1;\n readonly summary: CheckSummary;\n readonly issues: readonly CheckIssue[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** check 문서를 결정적인 JSON 문자열로 인코딩한다. */\nexport function encodeCheckReport(report: CheckReport): string {\n return encodeSortedJson(report);\n}\n\n/** 조인 결과를 정책 심각도가 포함된 check 문서로 바꾼다. */\nexport function createCheckReport(joined: BridgeJoinResult): CheckReport {\n if (isBridgeJoinDeferred(joined)) {\n throw new Error('Cannot create a check report from a deferred bridge join.');\n }\n const gaps = receiverCoverageGaps(joined.limitations);\n const issues: CheckIssue[] = [\n ...joined.unhandledInvocations.map<CheckIssue>((item) => ({\n severity: gaps.hidesHandlers ? 'warning' : 'error',\n code: gaps.hidesHandlers\n ? 'unhandled-invocation-unverified'\n : 'unhandled-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.invocations,\n })),\n ...joined.unregisteredChannelCreations.map<CheckIssue>((item) => ({\n severity: gaps.hidesRegistrations ? 'warning' : 'error',\n code: gaps.hidesRegistrations\n ? 'unregistered-channel-creation-unverified'\n : 'unregistered-channel-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.creations,\n })),\n ...joined.registrationsWithoutCreations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'registration-without-creation',\n target: item.target,\n channel: item.channel,\n evidence: item.registrations,\n })),\n ...joined.handlersWithoutInvocations.map<CheckIssue>((item) => ({\n severity: 'warning',\n code: 'handler-without-invocation',\n target: item.target,\n channel: item.channel,\n method: item.method,\n evidence: item.handlers,\n })),\n ];\n return {\n format: 'isthmus-check',\n version: 1,\n summary: {\n errors: issues.filter(({ severity }) => severity === 'error').length,\n warnings: issues.filter(({ severity }) => severity === 'warning').length,\n matchedChannels: joined.matchedChannels.length,\n matchedMethods: joined.matchedMethods.length,\n },\n issues,\n limitations: joined.limitations,\n };\n}\n\n/** 수신 측이 스스로 알린 분석 공백이 무엇을 가리는지 나눈 결과다. */\ninterface ReceiverCoverageGaps {\n readonly hidesHandlers: boolean;\n readonly hidesRegistrations: boolean;\n}\n\n/**\n * 수신 측이 핸들러나 등록을 놓쳤을 수 있다고 스스로 알렸는지 확인한다.\n *\n * 이때 \"핸들러 없는 호출\"은 경계 불일치가 아니라 판정 불가다. Objective-C로 쓰인\n * Flutter 핸들러처럼 수신 측 분석에 아예 나타나지 않는 코드가 실제로 있어서,\n * error로 단정하면 이 도구가 없애려던 오탐을 이 도구가 만든다.\n *\n * 공백의 종류는 구분한다. 이름이 리터럴이 아닌 채널 등록 하나가 무관한 메서드\n * 진단까지 무르게 하면 안 된다. 호출 측 한계는 네이티브 코드를 가리지 않으므로\n * 수신 측 플랫폼의 한계만 본다.\n */\nfunction receiverCoverageGaps(\n limitations: readonly JoinLimitation[],\n): ReceiverCoverageGaps {\n const messages = limitations\n .filter(({ platform }) => isReceiverPlatform(platform))\n .map(({ message }) => message);\n return {\n hidesHandlers: messages.some(startsWithAny(handlerCoverageGapPrefixes)),\n hidesRegistrations: messages.some(\n startsWithAny(registrationCoverageGapPrefixes),\n ),\n };\n}\n\n/** 주어진 접두사 중 하나로 시작하는지 검사하는 술어를 만든다. */\nfunction startsWithAny(\n prefixes: readonly string[],\n): (message: string) => boolean {\n return (message) => prefixes.some((prefix) => message.startsWith(prefix));\n}\n\n/**\n * 수신 측 소스 자체가 분석되지 않아 등록과 핸들러를 모두 가리는 한계다.\n *\n * 알려진 접두사만 인정한다. 모르는 한계를 공백으로 넓게 해석하면 진짜 불일치가\n * 경고로 묻힌다.\n */\nconst sourceCoverageGapPrefixes = [\n 'objective-c-sources:',\n 'shadowed-flutter-method-channel:',\n];\n\n/**\n * 핸들러를 가릴 수 있는 한계다.\n *\n * `unjoined-`는 isthmus가 직접 센 값이라 생산자의 신고 개수에 의존하지 않는다.\n */\nconst handlerCoverageGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n 'opaque-handler-bodies:',\n 'unjoined-dynamic-methods:',\n 'unjoined-unattributed-handlers:',\n];\n\n/** 채널 등록을 가릴 수 있는 한계다. */\nconst registrationCoverageGapPrefixes = [\n ...sourceCoverageGapPrefixes,\n 'unjoined-dynamic-channels:',\n];\n"]}
|
package/dist/report/diff.js
CHANGED
|
@@ -7,7 +7,7 @@ export function createBridgeDiff(before, after) {
|
|
|
7
7
|
const oldJoin = joinBridgeDocuments(before);
|
|
8
8
|
const newJoin = joinBridgeDocuments(after);
|
|
9
9
|
if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {
|
|
10
|
-
throw new BridgeJoinValidationError('Cannot compare deferred bridge joins.');
|
|
10
|
+
throw new BridgeJoinValidationError('Cannot compare deferred bridge joins; split mixed bridge targets and retry.');
|
|
11
11
|
}
|
|
12
12
|
const oldReport = createCheckReport(oldJoin);
|
|
13
13
|
const newReport = createCheckReport(newJoin);
|
|
@@ -47,7 +47,8 @@ function validateSnapshots(before, after) {
|
|
|
47
47
|
JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||
|
|
48
48
|
all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||
|
|
49
49
|
(doc.target !== null && doc.target !== 'flutter'))) {
|
|
50
|
-
throw new BridgeJoinValidationError('Diff requires matching Flutter producer
|
|
50
|
+
throw new BridgeJoinValidationError('Diff requires the same project and matching Flutter dart/swift producer '
|
|
51
|
+
+ 'inventories in both snapshots; rebuild both snapshots from one checkout.');
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */
|
package/dist/report/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAC9B,MAAsC,EACtC,KAAqC;IAErC,iBAAiB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,oBAAoB,CAAC,OAAO,CAAC,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,yBAAyB,
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n) {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and retry.',\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, issueKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, issueKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter dart/swift producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction issueKey(item: Parameters<typeof logicalKey>[0] & { readonly code: string }): string {\n return JSON.stringify([item.code, logicalKey(item)]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly tool: string; readonly message: string }): string {\n return JSON.stringify([item.platform, item.tool, item.message]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BridgePlatform, BridgeSymbol } from '../exchange/parse.ts';
|
|
1
|
+
import type { BridgeFactsDocument, BridgePlatform, BridgeSymbol } from '../exchange/parse.ts';
|
|
2
2
|
import { type BridgeJoinResult } from '../join/join.ts';
|
|
3
3
|
/** cartograph가 보존할 Swift 선언 식별자다. */
|
|
4
4
|
export interface RetentionSymbol extends BridgeSymbol {
|
|
@@ -37,6 +37,14 @@ export interface CartographRetentionsDocument {
|
|
|
37
37
|
export declare class RetentionValidationError extends Error {
|
|
38
38
|
constructor(message: string);
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.
|
|
42
|
+
*
|
|
43
|
+
* cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는
|
|
44
|
+
* 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로
|
|
45
|
+
* 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateCartographRetentionInputs(documents: readonly BridgeFactsDocument[]): void;
|
|
40
48
|
/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */
|
|
41
49
|
export declare function encodeCartographRetentionsDocument(document: CartographRetentionsDocument): string;
|
|
42
50
|
/** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */
|
|
@@ -7,6 +7,19 @@ export class RetentionValidationError extends Error {
|
|
|
7
7
|
this.name = 'RetentionValidationError';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.
|
|
12
|
+
*
|
|
13
|
+
* cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는
|
|
14
|
+
* 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로
|
|
15
|
+
* 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.
|
|
16
|
+
*/
|
|
17
|
+
export function validateCartographRetentionInputs(documents) {
|
|
18
|
+
if (documents.some(({ platform }) => platform === 'swift'))
|
|
19
|
+
return;
|
|
20
|
+
throw new RetentionValidationError('Retentions for cartograph require at least one swift bridge facts document; '
|
|
21
|
+
+ 'run a swift producer for the receiver side.');
|
|
22
|
+
}
|
|
10
23
|
/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */
|
|
11
24
|
export function encodeCartographRetentionsDocument(document) {
|
|
12
25
|
return encodeSortedJson(document);
|
|
@@ -16,6 +29,7 @@ export function createCartographRetentionsDocument(joined, generatedAt, producer
|
|
|
16
29
|
if (isBridgeJoinDeferred(joined)) {
|
|
17
30
|
throw new RetentionValidationError('Cannot create retentions from a deferred bridge join.');
|
|
18
31
|
}
|
|
32
|
+
rejectUnresolvedSwiftHandlers(joined);
|
|
19
33
|
return {
|
|
20
34
|
format: 'external-retentions',
|
|
21
35
|
version: 0,
|
|
@@ -24,6 +38,29 @@ export function createCartographRetentionsDocument(joined, generatedAt, producer
|
|
|
24
38
|
retentions: collectCartographRetentions(joined),
|
|
25
39
|
};
|
|
26
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.
|
|
43
|
+
*
|
|
44
|
+
* 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한
|
|
45
|
+
* 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,
|
|
46
|
+
* 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.
|
|
47
|
+
*/
|
|
48
|
+
function rejectUnresolvedSwiftHandlers(joined) {
|
|
49
|
+
const unresolved = new Set();
|
|
50
|
+
for (const method of joined.matchedMethods) {
|
|
51
|
+
for (const handler of method.handlers) {
|
|
52
|
+
if (handler.platform !== 'swift' || handler.symbol !== undefined)
|
|
53
|
+
continue;
|
|
54
|
+
const { path, line, column } = handler.location;
|
|
55
|
+
unresolved.add(`${path}\u0000${line}\u0000${column}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (unresolved.size === 0)
|
|
59
|
+
return;
|
|
60
|
+
throw new RetentionValidationError(`Cannot produce retention evidence for ${unresolved.size} matched swift `
|
|
61
|
+
+ 'handlers without a symbol; regenerate the swift document with a producer '
|
|
62
|
+
+ 'that attaches handler symbols.');
|
|
63
|
+
}
|
|
27
64
|
/** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */
|
|
28
65
|
function collectCartographRetentions(joined) {
|
|
29
66
|
const retentions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BridgeFactsDocument,\n BridgePlatform,\n BridgeSymbol,\n} from '../exchange/parse.ts';\nimport {\n isBridgeJoinDeferred,\n type BridgeJoinResult,\n} from '../join/join.ts';\nimport { encodeSortedJson } from './sorted-json.ts';\n\n/** cartograph가 보존할 Swift 선언 식별자다. */\nexport interface RetentionSymbol extends BridgeSymbol {\n readonly usr?: string;\n}\n\n/** 언어 경계 너머 호출자의 증거 위치다. */\nexport interface RetentionCaller {\n readonly platform: BridgePlatform;\n readonly path: string;\n readonly line: number;\n}\n\n/** 보존 판단을 설명할 채널·메서드·호출자 근거다. */\nexport interface RetentionEvidence {\n readonly channel: string;\n readonly method: string;\n readonly caller: RetentionCaller;\n}\n\n/** cartograph 외부 보존 근거 하나다. */\nexport interface ExternalRetention {\n readonly symbol: RetentionSymbol;\n readonly reason: 'bridge';\n readonly evidence: RetentionEvidence;\n}\n\n/** cartograph가 읽는 external-retentions 버전 0 문서다. */\nexport interface CartographRetentionsDocument {\n readonly format: 'external-retentions';\n readonly version: 0;\n readonly producedBy: Readonly<{ name: 'isthmus'; version: string }>;\n readonly generatedAt: string;\n readonly retentions: readonly ExternalRetention[];\n}\n\n/** 불완전한 조인으로 보존 결정을 만들 수 없음을 나타낸다. */\nexport class RetentionValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RetentionValidationError';\n }\n}\n\n/**\n * cartograph가 소비할 수 있는 수신 측 문서가 입력에 있는지 검증한다.\n *\n * cartograph는 Swift 심볼만 보존한다. Swift 문서가 없는 입력은 조인 자체는\n * 성공하므로, 검증하지 않으면 보존할 근거가 없다는 사실이 빈 목록과 코드 0으로\n * 사라진다. 사실이 없는 Swift 문서도 그 플랫폼을 분석했다는 근거로 인정한다.\n */\nexport function validateCartographRetentionInputs(\n documents: readonly BridgeFactsDocument[],\n): void {\n if (documents.some(({ platform }) => platform === 'swift')) return;\n throw new RetentionValidationError(\n 'Retentions for cartograph require at least one swift bridge facts document; '\n + 'run a swift producer for the receiver side.',\n );\n}\n\n/** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */\nexport function encodeCartographRetentionsDocument(\n document: CartographRetentionsDocument,\n): string {\n return encodeSortedJson(document);\n}\n\n/** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */\nexport function createCartographRetentionsDocument(\n joined: BridgeJoinResult,\n generatedAt: string,\n producerVersion: string,\n): CartographRetentionsDocument {\n if (isBridgeJoinDeferred(joined)) {\n throw new RetentionValidationError(\n 'Cannot create retentions from a deferred bridge join.',\n );\n }\n rejectUnresolvedSwiftHandlers(joined);\n return {\n format: 'external-retentions',\n version: 0,\n producedBy: { name: 'isthmus', version: producerVersion },\n generatedAt,\n retentions: collectCartographRetentions(joined),\n };\n}\n\n/**\n * 심볼이 없어 보존 근거로 바꿀 수 없는 매치 Swift 핸들러를 거부한다.\n *\n * 교환 계약에서 `symbol`은 선택 필드다. 호출자가 있는데도 근거를 만들지 못한\n * 핸들러를 조용히 빼면 cartograph는 그 핸들러를 계속 미사용으로 보고하고,\n * 소비자는 살아 있는 코드를 지운다. 부분 보존 문서 대신 실패를 돌려준다.\n */\nfunction rejectUnresolvedSwiftHandlers(joined: BridgeJoinResult): void {\n const unresolved = new Set<string>();\n for (const method of joined.matchedMethods) {\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.symbol !== undefined) continue;\n const { path, line, column } = handler.location;\n unresolved.add(`${path}\\u0000${line}\\u0000${column}`);\n }\n }\n if (unresolved.size === 0) return;\n throw new RetentionValidationError(\n `Cannot produce retention evidence for ${unresolved.size} matched swift `\n + 'handlers without a symbol; regenerate the swift document with a producer '\n + 'that attaches handler symbols.',\n );\n}\n\n/** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */\nfunction collectCartographRetentions(\n joined: BridgeJoinResult,\n): ExternalRetention[] {\n const retentions: ExternalRetention[] = [];\n const seen = new Set<string>();\n for (const method of joined.matchedMethods) {\n const caller = method.invocations[0];\n if (caller === undefined) continue;\n for (const handler of method.handlers) {\n if (handler.platform !== 'swift' || handler.symbol === undefined) continue;\n const symbolKey = handler.symbol.usr === undefined\n ? `name:${handler.symbol.qualifiedName}`\n : `usr:${handler.symbol.usr}`;\n const retentionKey = `${symbolKey}\\u0000${method.channel}\\u0000${method.method}`;\n if (seen.has(retentionKey)) continue;\n seen.add(retentionKey);\n retentions.push({\n symbol: handler.symbol,\n reason: 'bridge',\n evidence: {\n channel: method.channel,\n method: method.method,\n caller: {\n platform: caller.platform,\n path: caller.location.path,\n line: caller.location.line,\n },\n },\n });\n }\n }\n return retentions;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "isthmus-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Join bridge facts across cross-platform application boundaries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
7
|
-
"react-native",
|
|
8
7
|
"platform-channel",
|
|
9
8
|
"bridge",
|
|
10
9
|
"static-analysis",
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
"verify:build": "node scripts/verify-build-contract.mjs",
|
|
44
43
|
"verify:cli": "npm run verify:build && node scripts/verify-cli-contract.mjs",
|
|
45
44
|
"verify:package": "npm run verify:build && node scripts/verify-package-contract.mjs",
|
|
46
|
-
"verify": "npm run typecheck && npm test && npm run test:phase0:join && npm run verify:cli &&
|
|
45
|
+
"verify": "npm run typecheck && npm test && npm run test:phase0:join && npm run verify:build && node scripts/verify-cli-contract.mjs && node scripts/verify-package-contract.mjs",
|
|
47
46
|
"prepublishOnly": "npm run verify"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|