kmp-api-lookup-mcp 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.
- package/README.md +219 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +33 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/index.d.ts +12 -0
- package/dist/indexer/index.js +245 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/search-utils.d.ts +10 -0
- package/dist/search-utils.js +81 -0
- package/dist/search-utils.js.map +1 -0
- package/dist/server/createServer.d.ts +11 -0
- package/dist/server/createServer.js +30 -0
- package/dist/server/createServer.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lookupDetail.d.ts +3 -0
- package/dist/server/lookupDetail.js +295 -0
- package/dist/server/lookupDetail.js.map +1 -0
- package/dist/server/lookupService.d.ts +34 -0
- package/dist/server/lookupService.js +822 -0
- package/dist/server/lookupService.js.map +1 -0
- package/dist/server/metadataInspector.d.ts +14 -0
- package/dist/server/metadataInspector.js +288 -0
- package/dist/server/metadataInspector.js.map +1 -0
- package/dist/storage/index.d.ts +58 -0
- package/dist/storage/index.js +554 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +488 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +287 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# kmp-api-lookup-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for fast lookup of Kotlin/Native iOS klib APIs.
|
|
4
|
+
|
|
5
|
+
The server indexes local Kotlin/Native platform klibs into a persistent SQLite database and exposes a compact MCP API for symbol lookup and index maintenance.
|
|
6
|
+
|
|
7
|
+
## Current Scope
|
|
8
|
+
|
|
9
|
+
- TypeScript npm ESM MCP server over stdio
|
|
10
|
+
- Persistent SQLite cache in the user cache directory
|
|
11
|
+
- Discovery of Kotlin/Native prebuilt installations via `KONAN_HOME`, `~/.konan`, or an explicit path
|
|
12
|
+
- Manual index rebuild from `klib dump-metadata-signatures`
|
|
13
|
+
- On-demand enrichment from `klib dump-metadata` for full Kotlin signatures, class hierarchy, and imports
|
|
14
|
+
- Structured JSON MCP responses with short text summaries
|
|
15
|
+
|
|
16
|
+
## Implemented Tools
|
|
17
|
+
|
|
18
|
+
### `lookup_symbol`
|
|
19
|
+
|
|
20
|
+
Resolve a Kotlin/Native Apple platform class, member, or top-level platform alias/constant into a compact development card.
|
|
21
|
+
|
|
22
|
+
Input:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"query": "AVPlayer",
|
|
27
|
+
"frameworks": ["AVFoundation"],
|
|
28
|
+
"detail": "compact",
|
|
29
|
+
"queryKind": "auto"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Behavior:
|
|
34
|
+
|
|
35
|
+
- A class query like `AVPlayer` returns one class card with:
|
|
36
|
+
- full Kotlin class signature
|
|
37
|
+
- superclass and implemented interfaces
|
|
38
|
+
- all constructors, instance methods, and class methods when `detail` is omitted or set to `compact`, grouped by member name to reduce output size
|
|
39
|
+
- a separate `properties` list in compact mode with explicit `accessors.getter` and `accessors.setter` flags when matching getter/setter methods exist for that property
|
|
40
|
+
- compact mode removes only duplicated property accessor methods; it does not trim unrelated methods from the class surface
|
|
41
|
+
- the full direct member set, ObjC bridge extension members, and `Meta` class members when `detail` is set to `full`
|
|
42
|
+
- `requiredImports` for code generation
|
|
43
|
+
- A member query like `AVPlayer.play` or `play` returns a compact grouped card with overload signatures and imports.
|
|
44
|
+
- Exact top-level platform aliases and constants like `AVPlayerStatus`, `AVLayerVideoGravity`, or `AVPlayerItemDidPlayToEndTimeNotification` resolve to package-scoped member cards instead of degrading into fuzzy class matches.
|
|
45
|
+
- If the query is ambiguous, the tool returns a short alternatives list instead of dumping raw search rows.
|
|
46
|
+
- Output intentionally omits noisy fields like DB paths, internal IDs, raw metadata dumps, match stages, and installation paths.
|
|
47
|
+
- `detail` defaults to `compact`. Use `"detail": "full"` only when you really need the entire class surface.
|
|
48
|
+
|
|
49
|
+
### `get_klib_index_status`
|
|
50
|
+
|
|
51
|
+
Return a compact index summary.
|
|
52
|
+
|
|
53
|
+
Input:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Output includes:
|
|
60
|
+
|
|
61
|
+
- `ready`
|
|
62
|
+
- discovered Kotlin/Native versions and targets
|
|
63
|
+
- indexed datasets with counts
|
|
64
|
+
- aggregate symbol counts
|
|
65
|
+
- `lastRebuildAt`
|
|
66
|
+
|
|
67
|
+
### `rebuild_klib_index`
|
|
68
|
+
|
|
69
|
+
Build or refresh the SQLite index from local klibs.
|
|
70
|
+
|
|
71
|
+
Input:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"kotlinVersion": "2.2.21",
|
|
76
|
+
"target": "ios_simulator_arm64",
|
|
77
|
+
"frameworks": ["Foundation", "UIKit"],
|
|
78
|
+
"force": false,
|
|
79
|
+
"dryRun": false,
|
|
80
|
+
"cleanBefore": true
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Rules:
|
|
85
|
+
|
|
86
|
+
- `kotlinVersion` and `konanHome` are optional, but you may provide at most one of them.
|
|
87
|
+
- If both are omitted, the latest discovered local Kotlin/Native installation is used.
|
|
88
|
+
- If `target` is omitted, the server prefers `ios_simulator_arm64`, then `ios_arm64`, then `ios_x64`.
|
|
89
|
+
- If `frameworks` is omitted, the rebuild covers all frameworks for the selected target.
|
|
90
|
+
- `dryRun=true` computes the rebuild plan without writing to SQLite.
|
|
91
|
+
- `force=true` ignores freshness checks.
|
|
92
|
+
- `cleanBefore=true` removes existing rows for the affected frameworks before writing fresh records.
|
|
93
|
+
|
|
94
|
+
## Storage Layout
|
|
95
|
+
|
|
96
|
+
The server stores data outside the repository.
|
|
97
|
+
|
|
98
|
+
- SQLite DB: user cache dir + `klib-index.sqlite`
|
|
99
|
+
- Service metadata: user cache dir + `state.json`
|
|
100
|
+
|
|
101
|
+
Typical cache locations:
|
|
102
|
+
|
|
103
|
+
- macOS: `~/Library/Caches/kmp-api-lookup-mcp/`
|
|
104
|
+
- Linux: `${XDG_CACHE_HOME:-~/.cache}/kmp-api-lookup-mcp/`
|
|
105
|
+
- Windows: `%LOCALAPPDATA%/kmp-api-lookup-mcp/`
|
|
106
|
+
|
|
107
|
+
## Discovery Rules
|
|
108
|
+
|
|
109
|
+
Installations are discovered in this order:
|
|
110
|
+
|
|
111
|
+
1. Explicit `konanHome` argument when a tool provides it
|
|
112
|
+
2. `KONAN_HOME`
|
|
113
|
+
3. `~/.konan/kotlin-native-prebuilt-*`
|
|
114
|
+
|
|
115
|
+
Each installation is validated by checking for:
|
|
116
|
+
|
|
117
|
+
- `bin/klib`
|
|
118
|
+
- `klib/platform/`
|
|
119
|
+
|
|
120
|
+
## MCP Configuration
|
|
121
|
+
|
|
122
|
+
### Run From Source
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"kmp-api-lookup": {
|
|
128
|
+
"command": "node",
|
|
129
|
+
"args": ["/absolute/path/to/kmp-api-lookup-mcp/dist/index.js"]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Optional environment override:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"kmp-api-lookup": {
|
|
141
|
+
"command": "node",
|
|
142
|
+
"args": ["/absolute/path/to/kmp-api-lookup-mcp/dist/index.js"],
|
|
143
|
+
"env": {
|
|
144
|
+
"KONAN_HOME": "/Users/you/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.21"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Run As Installed Binary
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"kmp-api-lookup": {
|
|
157
|
+
"command": "kmp-api-lookup-mcp"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
### Scripts
|
|
166
|
+
|
|
167
|
+
- `npm run dev` starts the server from TypeScript sources
|
|
168
|
+
- `npm run build` compiles to `dist/`
|
|
169
|
+
- `npm start` runs the compiled server
|
|
170
|
+
- `npm run typecheck` runs TypeScript type checking
|
|
171
|
+
- `npm test` runs Vitest
|
|
172
|
+
- `npm run test:watch` starts Vitest in watch mode
|
|
173
|
+
|
|
174
|
+
### Local Workflow
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm install
|
|
178
|
+
npm run typecheck
|
|
179
|
+
npm run build
|
|
180
|
+
npm test
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Publishing
|
|
184
|
+
|
|
185
|
+
npm publication is handled by GitHub Actions.
|
|
186
|
+
|
|
187
|
+
- Push a tag in the form `vX.Y.Z` where `X.Y.Z` matches the `version` in `package.json`.
|
|
188
|
+
- The `Publish Package` workflow validates the package and publishes it to npm.
|
|
189
|
+
- The npm package must be configured for trusted publishing from the `SuLG-ik/kmp-api-lookup-mcp` GitHub repository.
|
|
190
|
+
- See [PUBLISHING.md](./PUBLISHING.md) for the one-time npm setup and the exact release steps.
|
|
191
|
+
|
|
192
|
+
## Test Coverage
|
|
193
|
+
|
|
194
|
+
The current test suite covers:
|
|
195
|
+
|
|
196
|
+
- MCP tool registration
|
|
197
|
+
- `dump-metadata-signatures` line parsing
|
|
198
|
+
- SQLite storage and search behavior on synthetic fixtures
|
|
199
|
+
- server runtime creation
|
|
200
|
+
|
|
201
|
+
## Project Structure
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
.
|
|
205
|
+
├── src/
|
|
206
|
+
│ ├── index.ts
|
|
207
|
+
│ ├── config/
|
|
208
|
+
│ ├── indexer/
|
|
209
|
+
│ ├── server/
|
|
210
|
+
│ ├── storage/
|
|
211
|
+
│ ├── tools/
|
|
212
|
+
│ ├── search-utils.ts
|
|
213
|
+
│ └── types.ts
|
|
214
|
+
├── test/
|
|
215
|
+
├── package.json
|
|
216
|
+
├── tsconfig.json
|
|
217
|
+
├── tsconfig.build.json
|
|
218
|
+
└── vitest.config.ts
|
|
219
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function loadAppConfig(overrides = {}) {
|
|
4
|
+
const cacheDir = overrides.cacheDir ?? path.join(resolveBaseCacheDir(), 'kmp-api-lookup-mcp');
|
|
5
|
+
return {
|
|
6
|
+
serverName: overrides.serverName ?? 'kmp-api-lookup-mcp',
|
|
7
|
+
version: overrides.version ?? '0.1.0',
|
|
8
|
+
cacheDir,
|
|
9
|
+
dbPath: overrides.dbPath ?? path.join(cacheDir, 'klib-index.sqlite'),
|
|
10
|
+
metadataPath: overrides.metadataPath ?? path.join(cacheDir, 'state.json'),
|
|
11
|
+
konanScanRoot: overrides.konanScanRoot ?? path.join(os.homedir(), '.konan'),
|
|
12
|
+
defaultSearchLimit: overrides.defaultSearchLimit ?? 20,
|
|
13
|
+
defaultMatchMode: overrides.defaultMatchMode ?? 'auto',
|
|
14
|
+
defaultIncludeMetaClasses: overrides.defaultIncludeMetaClasses ?? false,
|
|
15
|
+
defaultIncludeRawSignature: overrides.defaultIncludeRawSignature ?? false,
|
|
16
|
+
storageDriver: overrides.storageDriver ?? 'better-sqlite3',
|
|
17
|
+
freshnessStrategy: overrides.freshnessStrategy
|
|
18
|
+
?? 'version + target + selected frameworks + source directory mtime',
|
|
19
|
+
autoIndexing: overrides.autoIndexing ?? 'manual-error',
|
|
20
|
+
searchTargetFallback: overrides.searchTargetFallback ?? 'all-indexed-targets',
|
|
21
|
+
searchVersionFallback: overrides.searchVersionFallback ?? 'latest-indexed-version',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function resolveBaseCacheDir() {
|
|
25
|
+
if (process.platform === 'darwin') {
|
|
26
|
+
return path.join(os.homedir(), 'Library', 'Caches');
|
|
27
|
+
}
|
|
28
|
+
if (process.platform === 'win32') {
|
|
29
|
+
return process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
|
|
30
|
+
}
|
|
31
|
+
return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,MAAM,UAAU,aAAa,CAAC,YAAgC,EAAE;IAC9D,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,oBAAoB,CAAC,CAAC;IAE9F,OAAO;QACL,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,oBAAoB;QACxD,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,OAAO;QACrC,QAAQ;QACR,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC;QACpE,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;QACzE,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC;QAC3E,kBAAkB,EAAE,SAAS,CAAC,kBAAkB,IAAI,EAAE;QACtD,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,IAAI,MAAM;QACtD,yBAAyB,EAAE,SAAS,CAAC,yBAAyB,IAAI,KAAK;QACvE,0BAA0B,EAAE,SAAS,CAAC,0BAA0B,IAAI,KAAK;QACzE,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,gBAAgB;QAC1D,iBAAiB,EACf,SAAS,CAAC,iBAAiB;eACxB,iEAAiE;QACtE,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,cAAc;QACtD,oBAAoB,EAAE,SAAS,CAAC,oBAAoB,IAAI,qBAAqB;QAC7E,qBAAqB,EAAE,SAAS,CAAC,qBAAqB,IAAI,wBAAwB;KACnF,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB;IAC1B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/server';
|
|
3
|
+
import { createServerRuntime } from './server/index.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const runtime = createServerRuntime();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
let isShuttingDown = false;
|
|
8
|
+
const shutdown = async (signal) => {
|
|
9
|
+
if (isShuttingDown) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
isShuttingDown = true;
|
|
13
|
+
console.error(`Received ${signal}, shutting down kmp-api-lookup-mcp...`);
|
|
14
|
+
await runtime.close();
|
|
15
|
+
};
|
|
16
|
+
process.on('SIGINT', () => {
|
|
17
|
+
void shutdown('SIGINT').finally(() => process.exit(0));
|
|
18
|
+
});
|
|
19
|
+
process.on('SIGTERM', () => {
|
|
20
|
+
void shutdown('SIGTERM').finally(() => process.exit(0));
|
|
21
|
+
});
|
|
22
|
+
await runtime.server.connect(transport);
|
|
23
|
+
console.error('kmp-api-lookup-mcp server running on stdio');
|
|
24
|
+
}
|
|
25
|
+
await main().catch((error) => {
|
|
26
|
+
console.error('Fatal error while starting kmp-api-lookup-mcp:', error);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;QACvD,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QAED,cAAc,GAAG,IAAI,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,uCAAuC,CAAC,CAAC;QACzE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,KAAK,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,KAAK,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACpC,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AppConfig } from '../config/index.js';
|
|
2
|
+
import type { DiscoveredInstallation, FrameworkSource, ParsedSignatureRecord } from '../types.js';
|
|
3
|
+
export declare function discoverKotlinNativeInstallations(config: AppConfig, explicitKonanHome?: string, options?: {
|
|
4
|
+
onlyExplicit?: boolean;
|
|
5
|
+
}): Promise<DiscoveredInstallation[]>;
|
|
6
|
+
export declare function listFrameworkSources(installation: DiscoveredInstallation, target: string, requestedFrameworks?: string[]): Promise<FrameworkSource[]>;
|
|
7
|
+
export declare function parseSignatureLine(line: string): ParsedSignatureRecord | null;
|
|
8
|
+
export declare function dumpFrameworkSignatures(installation: DiscoveredInstallation, framework: FrameworkSource): Promise<{
|
|
9
|
+
lineCount: number;
|
|
10
|
+
records: ParsedSignatureRecord[];
|
|
11
|
+
}>;
|
|
12
|
+
export declare function dumpFrameworkMetadata(installation: DiscoveredInstallation, framework: FrameworkSource): Promise<string[]>;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { access, readdir, realpath, stat } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { compactSelector, compareVersionStrings, normalizeSelector, uniqueSorted } from '../search-utils.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const FRAMEWORK_PREFIX = 'org.jetbrains.kotlin.native.platform.';
|
|
8
|
+
export async function discoverKotlinNativeInstallations(config, explicitKonanHome, options = {}) {
|
|
9
|
+
const candidates = new Map();
|
|
10
|
+
const installations = new Map();
|
|
11
|
+
const addCandidate = (candidatePath, source) => {
|
|
12
|
+
const resolvedPath = path.resolve(candidatePath);
|
|
13
|
+
const sources = candidates.get(resolvedPath) ?? new Set();
|
|
14
|
+
sources.add(source);
|
|
15
|
+
candidates.set(resolvedPath, sources);
|
|
16
|
+
};
|
|
17
|
+
if (explicitKonanHome) {
|
|
18
|
+
addCandidate(explicitKonanHome, 'explicit');
|
|
19
|
+
}
|
|
20
|
+
if (!options.onlyExplicit) {
|
|
21
|
+
if (process.env.KONAN_HOME) {
|
|
22
|
+
addCandidate(process.env.KONAN_HOME, 'env');
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const scanEntries = await readdir(config.konanScanRoot, { withFileTypes: true });
|
|
26
|
+
for (const entry of scanEntries) {
|
|
27
|
+
if (entry.isDirectory() && entry.name.startsWith('kotlin-native-prebuilt-')) {
|
|
28
|
+
addCandidate(path.join(config.konanScanRoot, entry.name), 'scan');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore missing ~/.konan roots.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const [candidatePath, sources] of candidates.entries()) {
|
|
37
|
+
const installation = await inspectKonanHome(candidatePath, [...sources]);
|
|
38
|
+
if (!installation) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const existing = installations.get(installation.konanHome);
|
|
42
|
+
if (existing) {
|
|
43
|
+
installations.set(installation.konanHome, {
|
|
44
|
+
...existing,
|
|
45
|
+
sources: uniqueSorted([...existing.sources, ...installation.sources]),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
installations.set(installation.konanHome, installation);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...installations.values()].sort((left, right) => {
|
|
53
|
+
const versionOrder = compareVersionStrings(right.kotlinVersion, left.kotlinVersion);
|
|
54
|
+
if (versionOrder !== 0) {
|
|
55
|
+
return versionOrder;
|
|
56
|
+
}
|
|
57
|
+
return left.konanHome.localeCompare(right.konanHome);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export async function listFrameworkSources(installation, target, requestedFrameworks) {
|
|
61
|
+
const targetRoot = path.join(installation.platformRootPath, target);
|
|
62
|
+
const entries = await readdir(targetRoot, { withFileTypes: true });
|
|
63
|
+
const availableFrameworks = new Map();
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (!entry.isDirectory() || !entry.name.startsWith(FRAMEWORK_PREFIX)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const frameworkName = entry.name.slice(FRAMEWORK_PREFIX.length);
|
|
69
|
+
availableFrameworks.set(frameworkName, path.join(targetRoot, entry.name));
|
|
70
|
+
}
|
|
71
|
+
const selectedFrameworks = requestedFrameworks ?? [...availableFrameworks.keys()];
|
|
72
|
+
const missingFrameworks = selectedFrameworks.filter((framework) => !availableFrameworks.has(framework));
|
|
73
|
+
if (missingFrameworks.length > 0) {
|
|
74
|
+
throw new Error(`Frameworks not found for target ${target}: ${missingFrameworks.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
const frameworkSources = [];
|
|
77
|
+
for (const frameworkName of uniqueSorted(selectedFrameworks)) {
|
|
78
|
+
const directoryPath = availableFrameworks.get(frameworkName);
|
|
79
|
+
if (!directoryPath) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const frameworkStat = await stat(directoryPath);
|
|
83
|
+
frameworkSources.push({
|
|
84
|
+
name: frameworkName,
|
|
85
|
+
directoryPath,
|
|
86
|
+
sourceMtimeMs: frameworkStat.mtimeMs,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return frameworkSources;
|
|
90
|
+
}
|
|
91
|
+
export function parseSignatureLine(line) {
|
|
92
|
+
const trimmedLine = line.trim();
|
|
93
|
+
if (!trimmedLine) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const classMatch = trimmedLine.match(/^(?<container>[^|]+)\|null\[(?<version>\d+)\]$/);
|
|
97
|
+
if (classMatch?.groups) {
|
|
98
|
+
const [packageName, symbolPath] = classMatch.groups.container.split('/', 2);
|
|
99
|
+
if (!packageName || !symbolPath) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const framework = packageName.startsWith('platform.')
|
|
103
|
+
? packageName.slice('platform.'.length)
|
|
104
|
+
: packageName;
|
|
105
|
+
const memberName = symbolPath.includes('.') ? symbolPath.slice(symbolPath.lastIndexOf('.') + 1) : symbolPath;
|
|
106
|
+
return {
|
|
107
|
+
framework,
|
|
108
|
+
packageName,
|
|
109
|
+
className: symbolPath,
|
|
110
|
+
memberName,
|
|
111
|
+
memberSearchName: memberName,
|
|
112
|
+
memberKind: 'class',
|
|
113
|
+
declarationForm: 'class',
|
|
114
|
+
objcSelector: null,
|
|
115
|
+
objcSelectorNormalized: null,
|
|
116
|
+
objcSelectorCompact: null,
|
|
117
|
+
rawSignature: trimmedLine,
|
|
118
|
+
isMetaClass: symbolPath.endsWith('Meta'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const propertyMatch = trimmedLine.match(/^(?<container>[^|]+)\|\{\}(?<property>[^\[]+)\[(?<version>\d+)\]$/);
|
|
122
|
+
if (propertyMatch?.groups) {
|
|
123
|
+
const [packageName, symbolPath] = propertyMatch.groups.container.split('/', 2);
|
|
124
|
+
if (!packageName || !symbolPath) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const dotIndex = symbolPath.lastIndexOf('.');
|
|
128
|
+
const className = dotIndex >= 0 ? symbolPath.slice(0, dotIndex) : null;
|
|
129
|
+
const memberName = propertyMatch.groups.property;
|
|
130
|
+
if (memberName.startsWith('<get-') || memberName.startsWith('<set-')) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const framework = packageName.startsWith('platform.')
|
|
134
|
+
? packageName.slice('platform.'.length)
|
|
135
|
+
: packageName;
|
|
136
|
+
return {
|
|
137
|
+
framework,
|
|
138
|
+
packageName,
|
|
139
|
+
className,
|
|
140
|
+
memberName,
|
|
141
|
+
memberSearchName: memberName,
|
|
142
|
+
memberKind: 'member',
|
|
143
|
+
declarationForm: 'direct_member',
|
|
144
|
+
objcSelector: null,
|
|
145
|
+
objcSelectorNormalized: null,
|
|
146
|
+
objcSelectorCompact: null,
|
|
147
|
+
rawSignature: trimmedLine,
|
|
148
|
+
isMetaClass: className?.endsWith('Meta') ?? false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const lineMatch = trimmedLine.match(/^(?<container>[^|]+)\|(?:(?<receiver>.+)\.)?objc:(?<selector>[^\[]+)\[(?<version>\d+)\]$/);
|
|
152
|
+
if (!lineMatch?.groups) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const [packageName, symbolPath] = lineMatch.groups.container.split('/', 2);
|
|
156
|
+
if (!packageName || !symbolPath) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const dotIndex = symbolPath.lastIndexOf('.');
|
|
160
|
+
const className = lineMatch.groups.receiver ?? (dotIndex >= 0 ? symbolPath.slice(0, dotIndex) : null);
|
|
161
|
+
const memberName = dotIndex >= 0 ? symbolPath.slice(dotIndex + 1) : symbolPath;
|
|
162
|
+
if (memberName.startsWith('<get-') || memberName.startsWith('<set-')) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const normalizedSelector = lineMatch.groups.selector.replace(/#Constructor$/, '');
|
|
166
|
+
const framework = packageName.startsWith('platform.')
|
|
167
|
+
? packageName.slice('platform.'.length)
|
|
168
|
+
: packageName;
|
|
169
|
+
return {
|
|
170
|
+
framework,
|
|
171
|
+
packageName,
|
|
172
|
+
className,
|
|
173
|
+
memberName,
|
|
174
|
+
memberSearchName: memberName === '<init>' ? 'init' : memberName,
|
|
175
|
+
memberKind: memberName === '<init>' ? 'constructor' : 'member',
|
|
176
|
+
declarationForm: lineMatch.groups.receiver ? 'objc_bridge_extension' : 'direct_member',
|
|
177
|
+
objcSelector: normalizedSelector,
|
|
178
|
+
objcSelectorNormalized: normalizeSelector(normalizedSelector),
|
|
179
|
+
objcSelectorCompact: compactSelector(normalizedSelector),
|
|
180
|
+
rawSignature: trimmedLine,
|
|
181
|
+
isMetaClass: className?.endsWith('Meta') ?? false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export async function dumpFrameworkSignatures(installation, framework) {
|
|
185
|
+
try {
|
|
186
|
+
const { stdout } = await execFileAsync(installation.klibBinaryPath, ['dump-metadata-signatures', framework.directoryPath], {
|
|
187
|
+
encoding: 'utf8',
|
|
188
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
189
|
+
});
|
|
190
|
+
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
191
|
+
return {
|
|
192
|
+
lineCount: lines.length,
|
|
193
|
+
records: lines
|
|
194
|
+
.map((line) => parseSignatureLine(line))
|
|
195
|
+
.filter((record) => record !== null),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
200
|
+
throw new Error(`Failed to dump metadata signatures for ${framework.name}: ${message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function dumpFrameworkMetadata(installation, framework) {
|
|
204
|
+
try {
|
|
205
|
+
const { stdout } = await execFileAsync(installation.klibBinaryPath, ['dump-metadata', framework.directoryPath, '-print-signatures', 'true'], {
|
|
206
|
+
encoding: 'utf8',
|
|
207
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
208
|
+
});
|
|
209
|
+
return stdout.split(/\r?\n/);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
213
|
+
throw new Error(`Failed to dump metadata for ${framework.name}: ${message}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function inspectKonanHome(konanHome, sources) {
|
|
217
|
+
try {
|
|
218
|
+
const resolvedKonanHome = await realpath(konanHome);
|
|
219
|
+
const klibBinaryPath = path.join(resolvedKonanHome, 'bin', 'klib');
|
|
220
|
+
const platformRootPath = path.join(resolvedKonanHome, 'klib', 'platform');
|
|
221
|
+
await access(klibBinaryPath);
|
|
222
|
+
await access(platformRootPath);
|
|
223
|
+
const targetEntries = await readdir(platformRootPath, { withFileTypes: true });
|
|
224
|
+
return {
|
|
225
|
+
kotlinVersion: parseVersionFromKonanHome(resolvedKonanHome),
|
|
226
|
+
konanHome: resolvedKonanHome,
|
|
227
|
+
klibBinaryPath,
|
|
228
|
+
platformRootPath,
|
|
229
|
+
availableTargets: uniqueSorted(targetEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)),
|
|
230
|
+
sources: uniqueSorted(sources),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function parseVersionFromKonanHome(konanHome) {
|
|
238
|
+
const baseName = path.basename(konanHome);
|
|
239
|
+
const versionMatch = baseName.match(/-(\d+\.\d+\.\d+(?:[-A-Za-z0-9.]+)?)$/);
|
|
240
|
+
if (versionMatch) {
|
|
241
|
+
return versionMatch[1];
|
|
242
|
+
}
|
|
243
|
+
return baseName.replace(/^kotlin-native-prebuilt-/, '');
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/indexer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function normalizeText(value: string): string;
|
|
2
|
+
export declare function normalizeSelector(value: string | null | undefined): string | null;
|
|
3
|
+
export declare function compactSelector(value: string | null | undefined): string | null;
|
|
4
|
+
export declare function tokenizeSearchText(value: string): string[];
|
|
5
|
+
export declare function buildFtsQuery(value: string): string | null;
|
|
6
|
+
export declare function escapeLikePattern(value: string): string;
|
|
7
|
+
export declare function uniqueSorted(values: readonly string[]): string[];
|
|
8
|
+
export declare function compareVersionStrings(left: string, right: string): number;
|
|
9
|
+
export declare function pickLatestVersion(values: readonly string[]): string | null;
|
|
10
|
+
export declare function toIsoNow(): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function normalizeText(value) {
|
|
2
|
+
return value.trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
export function normalizeSelector(value) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return normalizeText(value);
|
|
9
|
+
}
|
|
10
|
+
export function compactSelector(value) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return normalizeText(value).replace(/:/g, '');
|
|
15
|
+
}
|
|
16
|
+
export function tokenizeSearchText(value) {
|
|
17
|
+
const withCamelCaseSplit = value.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
|
18
|
+
const normalized = withCamelCaseSplit
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
21
|
+
.trim();
|
|
22
|
+
if (!normalized) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return [...new Set(normalized.split(/\s+/).filter(Boolean))];
|
|
26
|
+
}
|
|
27
|
+
export function buildFtsQuery(value) {
|
|
28
|
+
const tokens = tokenizeSearchText(value);
|
|
29
|
+
if (tokens.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return tokens.map((token) => `${token}*`).join(' OR ');
|
|
33
|
+
}
|
|
34
|
+
export function escapeLikePattern(value) {
|
|
35
|
+
return value.replace(/[\\%_]/g, '\\$&');
|
|
36
|
+
}
|
|
37
|
+
export function uniqueSorted(values) {
|
|
38
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
39
|
+
}
|
|
40
|
+
export function compareVersionStrings(left, right) {
|
|
41
|
+
const leftParts = splitVersion(left);
|
|
42
|
+
const rightParts = splitVersion(right);
|
|
43
|
+
const maxLength = Math.max(leftParts.numbers.length, rightParts.numbers.length);
|
|
44
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
45
|
+
const leftNumber = leftParts.numbers[index] ?? 0;
|
|
46
|
+
const rightNumber = rightParts.numbers[index] ?? 0;
|
|
47
|
+
if (leftNumber !== rightNumber) {
|
|
48
|
+
return leftNumber - rightNumber;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!leftParts.suffix && rightParts.suffix) {
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
if (leftParts.suffix && !rightParts.suffix) {
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
return leftParts.suffix.localeCompare(rightParts.suffix);
|
|
58
|
+
}
|
|
59
|
+
export function pickLatestVersion(values) {
|
|
60
|
+
if (values.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return [...values].sort((left, right) => compareVersionStrings(right, left))[0] ?? null;
|
|
64
|
+
}
|
|
65
|
+
export function toIsoNow() {
|
|
66
|
+
return new Date().toISOString();
|
|
67
|
+
}
|
|
68
|
+
function splitVersion(value) {
|
|
69
|
+
const match = value.match(/(\d+(?:\.\d+)*)(.*)/);
|
|
70
|
+
if (!match) {
|
|
71
|
+
return {
|
|
72
|
+
numbers: [],
|
|
73
|
+
suffix: value.toLowerCase(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
numbers: match[1].split('.').map((part) => Number.parseInt(part, 10)),
|
|
78
|
+
suffix: match[2].replace(/^[^a-z0-9]+/i, '').toLowerCase(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=search-utils.js.map
|