isthmus-cli 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/Skills/isthmus/SKILL.md +47 -0
  4. package/dist/cli/check-command.d.ts +12 -0
  5. package/dist/cli/check-command.js +48 -0
  6. package/dist/cli/check-command.js.map +1 -0
  7. package/dist/cli/graph-command.d.ts +5 -0
  8. package/dist/cli/graph-command.js +62 -0
  9. package/dist/cli/graph-command.js.map +1 -0
  10. package/dist/cli/main.d.ts +2 -0
  11. package/dist/cli/main.js +111 -0
  12. package/dist/cli/main.js.map +1 -0
  13. package/dist/cli/query-command.d.ts +5 -0
  14. package/dist/cli/query-command.js +48 -0
  15. package/dist/cli/query-command.js.map +1 -0
  16. package/dist/cli/retentions-command.d.ts +7 -0
  17. package/dist/cli/retentions-command.js +55 -0
  18. package/dist/cli/retentions-command.js.map +1 -0
  19. package/dist/compare.d.ts +2 -0
  20. package/dist/compare.js +9 -0
  21. package/dist/compare.js.map +1 -0
  22. package/dist/exchange/parse.d.ts +48 -0
  23. package/dist/exchange/parse.js +148 -0
  24. package/dist/exchange/parse.js.map +1 -0
  25. package/dist/join/join.d.ts +59 -0
  26. package/dist/join/join.js +218 -0
  27. package/dist/join/join.js.map +1 -0
  28. package/dist/report/check-report.d.ts +32 -0
  29. package/dist/report/check-report.js +46 -0
  30. package/dist/report/check-report.js.map +1 -0
  31. package/dist/report/graph.d.ts +30 -0
  32. package/dist/report/graph.js +143 -0
  33. package/dist/report/graph.js.map +1 -0
  34. package/dist/report/query.d.ts +28 -0
  35. package/dist/report/query.js +112 -0
  36. package/dist/report/query.js.map +1 -0
  37. package/dist/report/retentions.d.ts +39 -0
  38. package/dist/report/retentions.js +49 -0
  39. package/dist/report/retentions.js.map +1 -0
  40. package/dist/report/sorted-json.d.ts +2 -0
  41. package/dist/report/sorted-json.js +16 -0
  42. package/dist/report/sorted-json.js.map +1 -0
  43. package/package.json +51 -0
@@ -0,0 +1,143 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { encodeSortedJson } from "./sorted-json.js";
3
+ /** 경계 그래프를 요청한 출력 형식으로 렌더링한다. */
4
+ export function renderBridgeGraph(graph, format) {
5
+ if (format === 'json')
6
+ return encodeSortedJson(graph);
7
+ if (format === 'dot')
8
+ return renderDot(graph);
9
+ return renderMermaid(graph);
10
+ }
11
+ /** Mermaid flowchart 문법으로 경계 그래프를 렌더링한다. */
12
+ function renderMermaid(graph) {
13
+ const identifiers = new Map(graph.nodes.map((node, index) => [node.id, `n${index}`]));
14
+ const lines = ['flowchart LR'];
15
+ for (const node of graph.nodes) {
16
+ const label = `${mermaidText(node.platform)}<br/>${mermaidText(locationLabel(node.location))}`;
17
+ lines.push(` ${identifiers.get(node.id)}["${label}"]`);
18
+ }
19
+ for (const edge of graph.edges) {
20
+ const from = identifiers.get(edge.from);
21
+ const to = identifiers.get(edge.to);
22
+ lines.push(` ${from} -->|"${mermaidText(edgeLabel(edge))}"| ${to}`);
23
+ }
24
+ return `${lines.join('\n')}\n`;
25
+ }
26
+ /** Mermaid 인용 라벨에서 제어 문자를 HTML 엔터티로 바꾼다. */
27
+ function mermaidText(value) {
28
+ return value
29
+ .replaceAll('&', '&amp;')
30
+ .replaceAll('"', '&quot;')
31
+ .replaceAll('<', '&lt;')
32
+ .replaceAll('>', '&gt;')
33
+ .replaceAll('|', '&#124;')
34
+ .replaceAll('\r', '&#13;')
35
+ .replaceAll('\n', '&#10;');
36
+ }
37
+ /** Graphviz DOT 문법으로 경계 그래프를 렌더링한다. */
38
+ function renderDot(graph) {
39
+ const lines = ['digraph isthmus {', ' rankdir=LR;'];
40
+ for (const node of graph.nodes) {
41
+ const label = `${node.platform}\n${locationLabel(node.location)}`;
42
+ lines.push(` ${dotString(node.id)} [label=${dotString(label)}];`);
43
+ }
44
+ for (const edge of graph.edges) {
45
+ lines.push(` ${dotString(edge.from)} -> ${dotString(edge.to)} `
46
+ + `[label=${dotString(edgeLabel(edge))}];`);
47
+ }
48
+ lines.push('}');
49
+ return `${lines.join('\n')}\n`;
50
+ }
51
+ /** DOT 문자열 리터럴로 안전하게 이스케이프한다. */
52
+ function dotString(value) {
53
+ return JSON.stringify(value);
54
+ }
55
+ /** 위치를 사람이 읽을 수 있는 경로:줄:열로 만든다. */
56
+ function locationLabel(location) {
57
+ return `${location.path}:${location.line}:${location.column}`;
58
+ }
59
+ /** 간선 종류와 문자열 키를 한 줄 라벨로 만든다. */
60
+ function edgeLabel(edge) {
61
+ const key = edge.method === undefined
62
+ ? edge.channel
63
+ : `${edge.channel}#${edge.method}`;
64
+ return `${edge.kind} ${key}`;
65
+ }
66
+ /** 조인 결과의 매치만 경계 그래프로 바꾼다. */
67
+ export function createBridgeGraph(joined) {
68
+ const nodes = new Map();
69
+ const edges = [];
70
+ addChannelEdges(joined, nodes, edges);
71
+ addMethodEdges(joined, nodes, edges);
72
+ return {
73
+ format: 'isthmus-graph',
74
+ version: 1,
75
+ nodes: [...nodes.values()].sort((left, right) => compareStrings(left.id, right.id)),
76
+ edges: edges.sort(compareEdges),
77
+ limitations: joined.limitations,
78
+ };
79
+ }
80
+ /** 매치된 채널의 생성→등록 간선을 추가한다. */
81
+ function addChannelEdges(joined, nodes, edges) {
82
+ for (const match of joined.matchedChannels) {
83
+ for (const creation of match.creations) {
84
+ for (const registration of match.registrations) {
85
+ const from = addNode(nodes, creation);
86
+ const to = addNode(nodes, registration);
87
+ edges.push({
88
+ from,
89
+ to,
90
+ kind: 'channel',
91
+ target: match.target,
92
+ channel: match.channel,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ }
98
+ /** 매치된 메서드의 호출→핸들러 간선을 추가한다. */
99
+ function addMethodEdges(joined, nodes, edges) {
100
+ for (const match of joined.matchedMethods) {
101
+ for (const invocation of match.invocations) {
102
+ for (const handler of match.handlers) {
103
+ const from = addNode(nodes, invocation);
104
+ const to = addNode(nodes, handler);
105
+ edges.push({
106
+ from,
107
+ to,
108
+ kind: 'method',
109
+ target: match.target,
110
+ channel: match.channel,
111
+ method: match.method,
112
+ });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ /** 증거 위치를 그래프 노드로 추가하고 안정 ID를 돌려준다. */
118
+ function addNode(nodes, endpoint) {
119
+ const id = endpointId(endpoint);
120
+ if (!nodes.has(id)) {
121
+ nodes.set(id, endpoint.symbol === undefined
122
+ ? { id, platform: endpoint.platform, location: endpoint.location }
123
+ : {
124
+ id,
125
+ platform: endpoint.platform,
126
+ location: endpoint.location,
127
+ symbol: endpoint.symbol,
128
+ });
129
+ }
130
+ return id;
131
+ }
132
+ /** 플랫폼과 소스 위치로 실행 간 안정적인 노드 ID를 만든다. */
133
+ function endpointId(endpoint) {
134
+ const { path, line, column } = endpoint.location;
135
+ return `${endpoint.platform}:${path}:${line}:${column}`;
136
+ }
137
+ /** 간선을 종류·from·to 순으로 고정한다. */
138
+ function compareEdges(left, right) {
139
+ return (compareStrings(left.kind, right.kind) ||
140
+ compareStrings(left.from, right.from) ||
141
+ compareStrings(left.to, right.to));
142
+ }
143
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/report/graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { BridgeEndpoint, BridgeJoinResult, JoinLimitation } from '../join/join.ts';
2
+ /** query가 식별한 채널 또는 메서드 키다. */
3
+ export interface BridgeQuerySubject {
4
+ readonly name: string;
5
+ readonly qualifiedName: string;
6
+ readonly kind: 'channel' | 'method';
7
+ }
8
+ /** 한 브리지 키에서 본 호출 측과 수신 측 증거다. */
9
+ export interface BridgeQueryResult {
10
+ readonly subject: BridgeQuerySubject;
11
+ readonly usedBy: readonly BridgeEndpoint[];
12
+ readonly dependsOn: readonly BridgeEndpoint[];
13
+ }
14
+ /** cartograph query와 같은 상태 외피를 쓰는 브리지 질의 문서다. */
15
+ export interface BridgeQueryDocument {
16
+ readonly status: 'found' | 'ambiguous' | 'notFound';
17
+ readonly requested: string;
18
+ readonly level: 'bridge';
19
+ readonly limitations: readonly JoinLimitation[];
20
+ readonly result?: BridgeQueryResult;
21
+ readonly candidates?: ReadonlyArray<{
22
+ readonly qualifiedName: string;
23
+ }>;
24
+ }
25
+ /** query 문서를 결정적인 JSON 문자열로 인코딩한다. */
26
+ export declare function encodeBridgeQuery(document: BridgeQueryDocument): string;
27
+ /** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */
28
+ export declare function createBridgeQuery(joined: BridgeJoinResult, requested: string): BridgeQueryDocument;
@@ -0,0 +1,112 @@
1
+ import { compareStrings } from "../compare.js";
2
+ import { encodeSortedJson } from "./sorted-json.js";
3
+ /** query 문서를 결정적인 JSON 문자열로 인코딩한다. */
4
+ export function encodeBridgeQuery(document) {
5
+ return encodeSortedJson(document);
6
+ }
7
+ /** 조인된 브리지에서 채널 또는 메서드 문자열을 질의한다. */
8
+ export function createBridgeQuery(joined, requested) {
9
+ const channels = channelResults(joined, requested);
10
+ if (channels.length > 1) {
11
+ return ambiguousQuery(joined, requested, channels);
12
+ }
13
+ const channel = channels[0];
14
+ if (channel !== undefined) {
15
+ return {
16
+ status: 'found',
17
+ requested,
18
+ level: 'bridge',
19
+ limitations: joined.limitations,
20
+ result: channel,
21
+ };
22
+ }
23
+ const methods = methodResults(joined, requested);
24
+ if (methods.length > 1) {
25
+ return ambiguousQuery(joined, requested, methods);
26
+ }
27
+ const method = methods[0];
28
+ if (method !== undefined) {
29
+ return {
30
+ status: 'found',
31
+ requested,
32
+ level: 'bridge',
33
+ limitations: joined.limitations,
34
+ result: method,
35
+ };
36
+ }
37
+ return {
38
+ status: 'notFound',
39
+ requested,
40
+ level: 'bridge',
41
+ limitations: joined.limitations,
42
+ };
43
+ }
44
+ /** 여러 논리 키가 같은 요청과 맞을 때 선택 가능한 정규화 이름을 돌려준다. */
45
+ function ambiguousQuery(joined, requested, results) {
46
+ return {
47
+ status: 'ambiguous',
48
+ requested,
49
+ level: 'bridge',
50
+ limitations: joined.limitations,
51
+ candidates: results
52
+ .map(({ subject }) => ({ qualifiedName: subject.qualifiedName }))
53
+ .sort((left, right) => compareStrings(left.qualifiedName, right.qualifiedName)),
54
+ };
55
+ }
56
+ /** 요청 문자열과 정확히 같은 논리 메서드 결과를 만든다. */
57
+ function methodResults(joined, requested) {
58
+ const matched = joined.matchedMethods
59
+ .map(({ target, channel, method, invocations, handlers }) => makeMethodResult(target, channel, method, invocations, handlers))
60
+ .filter((result) => matchesRequested(result, requested));
61
+ const unhandled = joined.unhandledInvocations
62
+ .map(({ target, channel, method, invocations }) => makeMethodResult(target, channel, method, invocations, []))
63
+ .filter((result) => matchesRequested(result, requested));
64
+ const handlers = joined.handlersWithoutInvocations
65
+ .map(({ target, channel, method, handlers }) => makeMethodResult(target, channel, method, [], handlers))
66
+ .filter((result) => matchesRequested(result, requested));
67
+ return [...matched, ...unhandled, ...handlers];
68
+ }
69
+ /** 메서드 키와 양쪽 증거를 query result로 바꾼다. */
70
+ function makeMethodResult(target, channel, method, usedBy, dependsOn) {
71
+ return {
72
+ subject: {
73
+ name: method,
74
+ qualifiedName: `${target}:${encodeSubjectComponent(channel)}#${encodeSubjectComponent(method)}`,
75
+ kind: 'method',
76
+ },
77
+ usedBy,
78
+ dependsOn,
79
+ };
80
+ }
81
+ /** 요청 문자열과 정확히 같은 논리 채널 결과를 만든다. */
82
+ function channelResults(joined, requested) {
83
+ const matched = joined.matchedChannels
84
+ .map(({ target, channel, creations, registrations }) => makeQueryResult(target, channel, 'channel', creations, registrations))
85
+ .filter((result) => matchesRequested(result, requested));
86
+ const unregistered = joined.unregisteredChannelCreations
87
+ .map(({ target, channel, creations }) => makeQueryResult(target, channel, 'channel', creations, []))
88
+ .filter((result) => matchesRequested(result, requested));
89
+ return [...matched, ...unregistered];
90
+ }
91
+ /** 짧은 이름 또는 모호성 해소용 qualifiedName과 정확히 일치하는지 확인한다. */
92
+ function matchesRequested(result, requested) {
93
+ return (result.subject.name === requested ||
94
+ result.subject.qualifiedName === requested);
95
+ }
96
+ /** 조인 키와 양쪽 증거를 query result 골격으로 바꾼다. */
97
+ function makeQueryResult(target, name, kind, usedBy, dependsOn) {
98
+ return {
99
+ subject: {
100
+ name,
101
+ qualifiedName: `${target}:${encodeSubjectComponent(name)}`,
102
+ kind,
103
+ },
104
+ usedBy,
105
+ dependsOn,
106
+ };
107
+ }
108
+ /** qualifiedName 구분자와 이스케이프 문자를 가역적인 퍼센트 표기로 바꾼다. */
109
+ function encodeSubjectComponent(value) {
110
+ return value.replaceAll('%', '%25').replaceAll('#', '%23');
111
+ }
112
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/report/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { BridgePlatform, BridgeSymbol } from '../exchange/parse.ts';
2
+ import type { BridgeJoinResult } from '../join/join.ts';
3
+ /** cartograph가 보존할 Swift 선언 식별자다. */
4
+ export interface RetentionSymbol extends BridgeSymbol {
5
+ readonly usr?: string;
6
+ }
7
+ /** 언어 경계 너머 호출자의 증거 위치다. */
8
+ export interface RetentionCaller {
9
+ readonly platform: BridgePlatform;
10
+ readonly path: string;
11
+ readonly line: number;
12
+ }
13
+ /** 보존 판단을 설명할 채널·메서드·호출자 근거다. */
14
+ export interface RetentionEvidence {
15
+ readonly channel: string;
16
+ readonly method: string;
17
+ readonly caller: RetentionCaller;
18
+ }
19
+ /** cartograph 외부 보존 근거 하나다. */
20
+ export interface ExternalRetention {
21
+ readonly symbol: RetentionSymbol;
22
+ readonly reason: 'bridge';
23
+ readonly evidence: RetentionEvidence;
24
+ }
25
+ /** cartograph가 읽는 external-retentions 버전 0 문서다. */
26
+ export interface CartographRetentionsDocument {
27
+ readonly format: 'external-retentions';
28
+ readonly version: 0;
29
+ readonly producedBy: Readonly<{
30
+ name: 'isthmus';
31
+ version: string;
32
+ }>;
33
+ readonly generatedAt: string;
34
+ readonly retentions: readonly ExternalRetention[];
35
+ }
36
+ /** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */
37
+ export declare function encodeCartographRetentionsDocument(document: CartographRetentionsDocument): string;
38
+ /** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */
39
+ export declare function createCartographRetentionsDocument(joined: BridgeJoinResult, generatedAt: string, producerVersion: string): CartographRetentionsDocument;
@@ -0,0 +1,49 @@
1
+ import { encodeSortedJson } from "./sorted-json.js";
2
+ /** cartograph 보존 문서를 결정적인 JSON으로 인코딩한다. */
3
+ export function encodeCartographRetentionsDocument(document) {
4
+ return encodeSortedJson(document);
5
+ }
6
+ /** 매치된 브리지 메서드를 cartograph 보존 근거로 바꾼다. */
7
+ export function createCartographRetentionsDocument(joined, generatedAt, producerVersion) {
8
+ return {
9
+ format: 'external-retentions',
10
+ version: 0,
11
+ producedBy: { name: 'isthmus', version: producerVersion },
12
+ generatedAt,
13
+ retentions: collectCartographRetentions(joined),
14
+ };
15
+ }
16
+ /** 매치별 Dart 호출자와 Swift 심볼을 cartograph 근거로 결합한다. */
17
+ function collectCartographRetentions(joined) {
18
+ const retentions = [];
19
+ const seen = new Set();
20
+ for (const method of joined.matchedMethods) {
21
+ const caller = method.invocations[0];
22
+ if (caller === undefined)
23
+ continue;
24
+ for (const handler of method.handlers) {
25
+ if (handler.platform !== 'swift' || handler.symbol === undefined)
26
+ continue;
27
+ const symbolKey = handler.symbol.usr ?? `name:${handler.symbol.qualifiedName}`;
28
+ const retentionKey = `${symbolKey}\u0000${method.channel}\u0000${method.method}`;
29
+ if (seen.has(retentionKey))
30
+ continue;
31
+ seen.add(retentionKey);
32
+ retentions.push({
33
+ symbol: handler.symbol,
34
+ reason: 'bridge',
35
+ evidence: {
36
+ channel: method.channel,
37
+ method: method.method,
38
+ caller: {
39
+ platform: caller.platform,
40
+ path: caller.location.path,
41
+ line: caller.location.line,
42
+ },
43
+ },
44
+ });
45
+ }
46
+ }
47
+ return retentions;
48
+ }
49
+ //# sourceMappingURL=retentions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retentions.js","sourceRoot":"","sources":["../../src/report/retentions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAqCpD,2CAA2C;AAC3C,MAAM,UAAU,kCAAkC,CAChD,QAAsC;IAEtC,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,kCAAkC,CAChD,MAAwB,EACxB,WAAmB,EACnB,eAAuB;IAEvB,OAAO;QACL,MAAM,EAAE,qBAAqB;QAC7B,OAAO,EAAE,CAAC;QACV,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE;QACzD,WAAW;QACX,UAAU,EAAE,2BAA2B,CAAC,MAAM,CAAC;KAChD,CAAC;AACJ,CAAC;AAED,mDAAmD;AACnD,SAAS,2BAA2B,CAClC,MAAwB;IAExB,MAAM,UAAU,GAAwB,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;gBAAE,SAAS;YAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,QAAQ,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC/E,MAAM,YAAY,GAAG,GAAG,SAAS,SAAS,MAAM,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC;YACjF,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;gBAAE,SAAS;YACrC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE;oBACR,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,MAAM,EAAE;wBACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;wBACzB,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;wBAC1B,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;qBAC3B;iBACF;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** JSON 객체 키를 모든 깊이에서 정렬하고 마지막 개행을 붙인다. */
2
+ export declare function encodeSortedJson(value: unknown): string;
@@ -0,0 +1,16 @@
1
+ import { compareStrings } from "../compare.js";
2
+ /** JSON 객체 키를 모든 깊이에서 정렬하고 마지막 개행을 붙인다. */
3
+ export function encodeSortedJson(value) {
4
+ return `${JSON.stringify(sortJson(value), null, 2)}\n`;
5
+ }
6
+ /** JSON 배열 순서는 보존하고 객체 키만 재귀 정렬한다. */
7
+ function sortJson(value) {
8
+ if (Array.isArray(value))
9
+ return value.map(sortJson);
10
+ if (typeof value !== 'object' || value === null)
11
+ return value;
12
+ return Object.fromEntries(Object.entries(value)
13
+ .sort(([left], [right]) => compareStrings(left, right))
14
+ .map(([key, item]) => [key, sortJson(item)]));
15
+ }
16
+ //# sourceMappingURL=sorted-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sorted-json.js","sourceRoot":"","sources":["../../src/report/sorted-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,2CAA2C;AAC3C,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAED,sCAAsC;AACtC,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SAClB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;SACtD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/C,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "isthmus-cli",
3
+ "version": "0.1.0",
4
+ "description": "Join bridge facts across cross-platform application boundaries.",
5
+ "keywords": [
6
+ "flutter",
7
+ "react-native",
8
+ "platform-channel",
9
+ "bridge",
10
+ "static-analysis",
11
+ "dead-code"
12
+ ],
13
+ "type": "module",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ictechgy/isthmus.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/ictechgy/isthmus/issues"
21
+ },
22
+ "homepage": "https://github.com/ictechgy/isthmus#readme",
23
+ "engines": {
24
+ "node": ">=22.18.0"
25
+ },
26
+ "bin": {
27
+ "isthmus": "dist/cli/main.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "Skills",
32
+ "LICENSE",
33
+ "README.md"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "node scripts/run-tests.mjs",
42
+ "verify:cli": "npm run build && node scripts/verify-cli-contract.mjs",
43
+ "verify:package": "node scripts/verify-package-contract.mjs",
44
+ "verify": "npm run typecheck && npm test && npm run verify:cli && npm run verify:package",
45
+ "prepublishOnly": "npm run verify"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "22.19.7",
49
+ "typescript": "5.9.3"
50
+ }
51
+ }