githits 0.1.11 → 0.2.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/GEMINI.md +13 -5
- package/README.md +14 -8
- package/commands/example.md +23 -0
- package/commands/help.md +8 -3
- package/commands/search.md +6 -4
- package/dist/cli.js +3203 -5373
- package/dist/index.js +1 -1
- package/dist/shared/chunk-67qnnqby.js +11 -0
- package/dist/shared/chunk-szjytcvw.js +6 -0
- package/dist/shared/chunk-ykr7x8jp.js +4436 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/commands/example.md +23 -0
- package/plugins/claude/commands/help.md +8 -3
- package/plugins/claude/commands/search.md +6 -4
- package/plugins/claude/skills/search/SKILL.md +4 -1
- package/skills/search/SKILL.md +4 -1
- package/dist/shared/chunk-hyfjv9fg.js +0 -4
|
@@ -0,0 +1,4436 @@
|
|
|
1
|
+
import {
|
|
2
|
+
version
|
|
3
|
+
} from "./chunk-szjytcvw.js";
|
|
4
|
+
|
|
5
|
+
// src/services/auth-service.ts
|
|
6
|
+
import { createServer } from "node:http";
|
|
7
|
+
|
|
8
|
+
// src/auth/pkce.ts
|
|
9
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
10
|
+
function generateCodeVerifier() {
|
|
11
|
+
return randomBytes(32).toString("base64url");
|
|
12
|
+
}
|
|
13
|
+
function generateCodeChallenge(verifier) {
|
|
14
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
15
|
+
}
|
|
16
|
+
function generateState() {
|
|
17
|
+
return randomBytes(32).toString("hex");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/services/auth-service.ts
|
|
21
|
+
class AuthServiceImpl {
|
|
22
|
+
async discoverEndpoints(mcpBaseUrl) {
|
|
23
|
+
const url = `${mcpBaseUrl}/.well-known/oauth-authorization-server`;
|
|
24
|
+
const response = await fetch(url);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to discover OAuth endpoints: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
const authorizationEndpoint = data.authorization_endpoint;
|
|
30
|
+
const tokenEndpoint = data.token_endpoint;
|
|
31
|
+
const registrationEndpoint = data.registration_endpoint;
|
|
32
|
+
if (!authorizationEndpoint || !tokenEndpoint || !registrationEndpoint) {
|
|
33
|
+
throw new Error("OAuth metadata missing required endpoints");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
authorizationEndpoint,
|
|
37
|
+
tokenEndpoint,
|
|
38
|
+
registrationEndpoint
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async registerClient(params) {
|
|
42
|
+
const response = await fetch(params.registrationEndpoint, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
client_name: "GitHits CLI",
|
|
47
|
+
redirect_uris: [params.redirectUri],
|
|
48
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
49
|
+
response_types: ["code"],
|
|
50
|
+
token_endpoint_auth_method: "client_secret_post"
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const error = await response.text();
|
|
55
|
+
throw new Error(`Client registration failed: ${error}`);
|
|
56
|
+
}
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
if (!data.client_id || !data.client_secret) {
|
|
59
|
+
throw new Error("Client registration response missing required fields");
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
clientId: data.client_id,
|
|
63
|
+
clientSecret: data.client_secret
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
generatePkceParams() {
|
|
67
|
+
const verifier = generateCodeVerifier();
|
|
68
|
+
return {
|
|
69
|
+
verifier,
|
|
70
|
+
challenge: generateCodeChallenge(verifier),
|
|
71
|
+
state: generateState()
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
buildAuthUrl(params) {
|
|
75
|
+
const url = new URL(params.authorizationEndpoint);
|
|
76
|
+
url.searchParams.set("response_type", "code");
|
|
77
|
+
url.searchParams.set("client_id", params.clientId);
|
|
78
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
79
|
+
url.searchParams.set("state", params.state);
|
|
80
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
81
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
82
|
+
return url.toString();
|
|
83
|
+
}
|
|
84
|
+
startCallbackServer(port, expectedState) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let callbackHandled = false;
|
|
87
|
+
let resolved = false;
|
|
88
|
+
let closeTimer;
|
|
89
|
+
const server = createServer((req, res) => {
|
|
90
|
+
const url = new URL(req.url ?? "", `http://127.0.0.1:${port}`);
|
|
91
|
+
if (url.pathname === "/favicon.ico") {
|
|
92
|
+
res.writeHead(204);
|
|
93
|
+
res.end();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (url.pathname !== "/callback") {
|
|
97
|
+
if (callbackHandled) {
|
|
98
|
+
sendHtmlResponse(res, 200, successHtml("Authentication already completed."));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
sendHtmlResponse(res, 404, errorHtml("Invalid callback path.", "Run `githits login` to start authentication."));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const code = url.searchParams.get("code");
|
|
105
|
+
const state = url.searchParams.get("state");
|
|
106
|
+
const error = url.searchParams.get("error");
|
|
107
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
108
|
+
const evaluation = evaluateCallback({
|
|
109
|
+
code,
|
|
110
|
+
state,
|
|
111
|
+
error,
|
|
112
|
+
errorDescription,
|
|
113
|
+
expectedState
|
|
114
|
+
});
|
|
115
|
+
callbackHandled = true;
|
|
116
|
+
sendHtmlResponse(res, evaluation.statusCode, evaluation.html);
|
|
117
|
+
if (!resolved) {
|
|
118
|
+
resolved = true;
|
|
119
|
+
resolve(evaluation.result);
|
|
120
|
+
}
|
|
121
|
+
if (closeTimer)
|
|
122
|
+
clearTimeout(closeTimer);
|
|
123
|
+
closeTimer = setTimeout(() => closeServer(server), 1500);
|
|
124
|
+
});
|
|
125
|
+
server.listen(port, "127.0.0.1");
|
|
126
|
+
server.on("error", (err) => {
|
|
127
|
+
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async exchangeCodeForTokens(params) {
|
|
132
|
+
const body = new URLSearchParams({
|
|
133
|
+
grant_type: "authorization_code",
|
|
134
|
+
client_id: params.clientId,
|
|
135
|
+
client_secret: params.clientSecret,
|
|
136
|
+
code: params.code,
|
|
137
|
+
code_verifier: params.codeVerifier,
|
|
138
|
+
redirect_uri: params.redirectUri
|
|
139
|
+
});
|
|
140
|
+
const response = await fetch(params.tokenEndpoint, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
143
|
+
body: body.toString()
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const error = await response.text();
|
|
147
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
148
|
+
}
|
|
149
|
+
return parseTokenResponse(await response.json());
|
|
150
|
+
}
|
|
151
|
+
async refreshAccessToken(params) {
|
|
152
|
+
const body = new URLSearchParams({
|
|
153
|
+
grant_type: "refresh_token",
|
|
154
|
+
client_id: params.clientId,
|
|
155
|
+
client_secret: params.clientSecret,
|
|
156
|
+
refresh_token: params.refreshToken
|
|
157
|
+
});
|
|
158
|
+
const response = await fetch(params.tokenEndpoint, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
161
|
+
body: body.toString()
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
const error = await response.text();
|
|
165
|
+
throw new Error(`Token refresh failed: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
return parseTokenResponse(await response.json());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function parseTokenResponse(data) {
|
|
171
|
+
const d = data;
|
|
172
|
+
if (!d.access_token || !d.refresh_token) {
|
|
173
|
+
throw new Error("Token response missing required fields");
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
accessToken: d.access_token,
|
|
177
|
+
refreshToken: d.refresh_token,
|
|
178
|
+
expiresIn: d.expires_in || 3600
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function successHtml(title = "Authentication successful") {
|
|
182
|
+
return `<!DOCTYPE html>
|
|
183
|
+
<html><head><title>GitHits CLI</title>
|
|
184
|
+
<style>
|
|
185
|
+
body {
|
|
186
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
align-items: center;
|
|
190
|
+
height: 100vh;
|
|
191
|
+
margin: 0;
|
|
192
|
+
background: radial-gradient(circle at center center, #4d3648, #3a2835, #261a22, #0d1117);
|
|
193
|
+
}
|
|
194
|
+
.card {
|
|
195
|
+
text-align: center;
|
|
196
|
+
background: rgba(13, 17, 23, 0.75);
|
|
197
|
+
padding: 3rem;
|
|
198
|
+
border-radius: 16px;
|
|
199
|
+
border: 1px solid rgba(244, 11, 166, 0.35);
|
|
200
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
201
|
+
max-width: 720px;
|
|
202
|
+
}
|
|
203
|
+
h1 {
|
|
204
|
+
color: #f40ba6;
|
|
205
|
+
margin-bottom: 0.75rem;
|
|
206
|
+
font-size: 3rem;
|
|
207
|
+
font-weight: 700;
|
|
208
|
+
}
|
|
209
|
+
p {
|
|
210
|
+
color: #f385a5;
|
|
211
|
+
font-size: 1.1rem;
|
|
212
|
+
margin: 0;
|
|
213
|
+
}
|
|
214
|
+
</style>
|
|
215
|
+
</head>
|
|
216
|
+
<body>
|
|
217
|
+
<div class="card">
|
|
218
|
+
<h1>${escapeHtml(title)}</h1>
|
|
219
|
+
<p>You can close this window and return to the terminal.</p>
|
|
220
|
+
</div>
|
|
221
|
+
</body></html>`;
|
|
222
|
+
}
|
|
223
|
+
function evaluateCallback(input) {
|
|
224
|
+
if (input.error) {
|
|
225
|
+
const message = input.errorDescription ? `${input.error}: ${input.errorDescription}` : input.error;
|
|
226
|
+
return {
|
|
227
|
+
statusCode: 200,
|
|
228
|
+
html: errorHtml(message, "Run `githits login` to try again."),
|
|
229
|
+
result: { type: "oauth_error", message }
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (input.code && input.state) {
|
|
233
|
+
if (input.state !== input.expectedState) {
|
|
234
|
+
return {
|
|
235
|
+
statusCode: 400,
|
|
236
|
+
html: errorHtml("Authentication failed security validation (state mismatch)", "Run `githits login` to try again."),
|
|
237
|
+
result: {
|
|
238
|
+
type: "state_mismatch",
|
|
239
|
+
message: "Security validation failed (state mismatch)"
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
statusCode: 200,
|
|
245
|
+
html: successHtml(),
|
|
246
|
+
result: { type: "success", code: input.code, state: input.state }
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
statusCode: 400,
|
|
251
|
+
html: errorHtml("Authentication callback was missing required parameters", "Run `githits login` to try again."),
|
|
252
|
+
result: {
|
|
253
|
+
type: "invalid_callback",
|
|
254
|
+
message: "Authentication callback missing required parameters"
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function errorHtml(error, nextStep) {
|
|
259
|
+
const nextStepHtml = nextStep ? `<p>${escapeHtml(nextStep)}</p>` : "";
|
|
260
|
+
return `<!DOCTYPE html>
|
|
261
|
+
<html><head><title>GitHits CLI</title>
|
|
262
|
+
<style>
|
|
263
|
+
body {
|
|
264
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
265
|
+
display: flex;
|
|
266
|
+
justify-content: center;
|
|
267
|
+
align-items: center;
|
|
268
|
+
height: 100vh;
|
|
269
|
+
margin: 0;
|
|
270
|
+
background: radial-gradient(circle at center center, #4d3648, #3a2835, #261a22, #0d1117);
|
|
271
|
+
}
|
|
272
|
+
.card {
|
|
273
|
+
text-align: center;
|
|
274
|
+
background: rgba(13, 17, 23, 0.75);
|
|
275
|
+
padding: 3rem;
|
|
276
|
+
border-radius: 16px;
|
|
277
|
+
border: 1px solid rgba(239, 68, 68, 0.35);
|
|
278
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
279
|
+
max-width: 720px;
|
|
280
|
+
}
|
|
281
|
+
h1 {
|
|
282
|
+
color: #ef4444;
|
|
283
|
+
margin-bottom: 0.75rem;
|
|
284
|
+
font-size: 3rem;
|
|
285
|
+
font-weight: 700;
|
|
286
|
+
}
|
|
287
|
+
p {
|
|
288
|
+
color: #f385a5;
|
|
289
|
+
font-size: 1.1rem;
|
|
290
|
+
margin: 0;
|
|
291
|
+
}
|
|
292
|
+
</style>
|
|
293
|
+
</head>
|
|
294
|
+
<body>
|
|
295
|
+
<div class="card">
|
|
296
|
+
<h1>Authentication failed</h1>
|
|
297
|
+
<p>${escapeHtml(error)}</p>
|
|
298
|
+
${nextStepHtml}
|
|
299
|
+
</div>
|
|
300
|
+
</body></html>`;
|
|
301
|
+
}
|
|
302
|
+
function sendHtmlResponse(res, statusCode, html) {
|
|
303
|
+
res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
|
|
304
|
+
res.end(html);
|
|
305
|
+
}
|
|
306
|
+
function closeServer(server) {
|
|
307
|
+
server.close();
|
|
308
|
+
}
|
|
309
|
+
function escapeHtml(text) {
|
|
310
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
311
|
+
}
|
|
312
|
+
// src/services/auth-storage.ts
|
|
313
|
+
var CONFIG_DIR = ".githits";
|
|
314
|
+
var AUTH_FILE = "auth.json";
|
|
315
|
+
var CLIENT_FILE = "client.json";
|
|
316
|
+
var DIR_MODE = 448;
|
|
317
|
+
var FILE_MODE = 384;
|
|
318
|
+
|
|
319
|
+
class AuthStorageImpl {
|
|
320
|
+
fs;
|
|
321
|
+
configDir;
|
|
322
|
+
authPath;
|
|
323
|
+
clientPath;
|
|
324
|
+
constructor(fs, configDir) {
|
|
325
|
+
this.fs = fs;
|
|
326
|
+
this.configDir = configDir ?? fs.joinPath(fs.getHomeDir(), CONFIG_DIR);
|
|
327
|
+
this.authPath = fs.joinPath(this.configDir, AUTH_FILE);
|
|
328
|
+
this.clientPath = fs.joinPath(this.configDir, CLIENT_FILE);
|
|
329
|
+
}
|
|
330
|
+
getStorageLocation() {
|
|
331
|
+
return this.configDir;
|
|
332
|
+
}
|
|
333
|
+
async loadTokens(baseUrl) {
|
|
334
|
+
const stored = await this.loadAuthFile();
|
|
335
|
+
if (!stored)
|
|
336
|
+
return null;
|
|
337
|
+
return stored.tokens[normalizeBaseUrl(baseUrl)] ?? null;
|
|
338
|
+
}
|
|
339
|
+
async saveTokens(baseUrl, data) {
|
|
340
|
+
const stored = await this.loadAuthFile() ?? {
|
|
341
|
+
version: 1,
|
|
342
|
+
tokens: {}
|
|
343
|
+
};
|
|
344
|
+
stored.tokens[normalizeBaseUrl(baseUrl)] = data;
|
|
345
|
+
await this.fs.ensureDir(this.configDir, DIR_MODE);
|
|
346
|
+
await this.fs.writeFile(this.authPath, JSON.stringify(stored, null, 2), FILE_MODE);
|
|
347
|
+
}
|
|
348
|
+
async clearTokens(baseUrl) {
|
|
349
|
+
const stored = await this.loadAuthFile();
|
|
350
|
+
if (!stored)
|
|
351
|
+
return;
|
|
352
|
+
delete stored.tokens[normalizeBaseUrl(baseUrl)];
|
|
353
|
+
if (Object.keys(stored.tokens).length === 0) {
|
|
354
|
+
await this.fs.deleteFile(this.authPath);
|
|
355
|
+
} else {
|
|
356
|
+
await this.fs.writeFile(this.authPath, JSON.stringify(stored, null, 2), FILE_MODE);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async loadClient(baseUrl) {
|
|
360
|
+
const stored = await this.loadClientFile();
|
|
361
|
+
if (!stored)
|
|
362
|
+
return null;
|
|
363
|
+
return stored.clients[normalizeBaseUrl(baseUrl)] ?? null;
|
|
364
|
+
}
|
|
365
|
+
async clearClient(baseUrl) {
|
|
366
|
+
const stored = await this.loadClientFile();
|
|
367
|
+
if (!stored)
|
|
368
|
+
return;
|
|
369
|
+
delete stored.clients[normalizeBaseUrl(baseUrl)];
|
|
370
|
+
if (Object.keys(stored.clients).length === 0) {
|
|
371
|
+
await this.fs.deleteFile(this.clientPath);
|
|
372
|
+
} else {
|
|
373
|
+
await this.fs.writeFile(this.clientPath, JSON.stringify(stored, null, 2), FILE_MODE);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async saveClient(baseUrl, data) {
|
|
377
|
+
const stored = await this.loadClientFile() ?? {
|
|
378
|
+
version: 1,
|
|
379
|
+
clients: {}
|
|
380
|
+
};
|
|
381
|
+
stored.clients[normalizeBaseUrl(baseUrl)] = data;
|
|
382
|
+
await this.fs.ensureDir(this.configDir, DIR_MODE);
|
|
383
|
+
await this.fs.writeFile(this.clientPath, JSON.stringify(stored, null, 2), FILE_MODE);
|
|
384
|
+
}
|
|
385
|
+
async loadAuthFile() {
|
|
386
|
+
if (!await this.fs.exists(this.authPath))
|
|
387
|
+
return null;
|
|
388
|
+
try {
|
|
389
|
+
const content = await this.fs.readFile(this.authPath);
|
|
390
|
+
const data = JSON.parse(content);
|
|
391
|
+
if (data.version !== 1 || !data.tokens)
|
|
392
|
+
return null;
|
|
393
|
+
return data;
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async loadClientFile() {
|
|
399
|
+
if (!await this.fs.exists(this.clientPath))
|
|
400
|
+
return null;
|
|
401
|
+
try {
|
|
402
|
+
const content = await this.fs.readFile(this.clientPath);
|
|
403
|
+
const data = JSON.parse(content);
|
|
404
|
+
if (data.version !== 1 || !data.clients)
|
|
405
|
+
return null;
|
|
406
|
+
return data;
|
|
407
|
+
} catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function normalizeBaseUrl(url) {
|
|
413
|
+
return url.replace(/\/+$/, "");
|
|
414
|
+
}
|
|
415
|
+
// src/services/browser-service.ts
|
|
416
|
+
import open from "open";
|
|
417
|
+
|
|
418
|
+
class BrowserServiceImpl {
|
|
419
|
+
async open(url) {
|
|
420
|
+
await open(url);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// src/services/chunking-keyring-service.ts
|
|
424
|
+
var WINDOWS_MAX_ENTRY_SIZE = 1200;
|
|
425
|
+
var CHUNKED_PREFIX = "CHUNKED:";
|
|
426
|
+
var MAX_CHUNK_COUNT = 100;
|
|
427
|
+
function chunkKey(account, writeId, index) {
|
|
428
|
+
return `${account}:chunk:${writeId}:${index}`;
|
|
429
|
+
}
|
|
430
|
+
function parseChunkedSentinel(value) {
|
|
431
|
+
if (!value.startsWith(CHUNKED_PREFIX))
|
|
432
|
+
return null;
|
|
433
|
+
const rest = value.slice(CHUNKED_PREFIX.length);
|
|
434
|
+
const colonIndex = rest.indexOf(":");
|
|
435
|
+
if (colonIndex === -1)
|
|
436
|
+
return null;
|
|
437
|
+
const writeId = rest.slice(0, colonIndex);
|
|
438
|
+
if (writeId.length === 0)
|
|
439
|
+
return null;
|
|
440
|
+
const countStr = rest.slice(colonIndex + 1);
|
|
441
|
+
const count = Number(countStr);
|
|
442
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
443
|
+
return null;
|
|
444
|
+
return { writeId, count };
|
|
445
|
+
}
|
|
446
|
+
function splitIntoChunks(value, maxSize) {
|
|
447
|
+
if (value.length === 0)
|
|
448
|
+
return [""];
|
|
449
|
+
const chunks = [];
|
|
450
|
+
for (let offset = 0;offset < value.length; offset += maxSize) {
|
|
451
|
+
chunks.push(value.slice(offset, offset + maxSize));
|
|
452
|
+
}
|
|
453
|
+
return chunks;
|
|
454
|
+
}
|
|
455
|
+
function generateWriteId() {
|
|
456
|
+
let id;
|
|
457
|
+
do {
|
|
458
|
+
id = Math.random().toString(36).slice(2, 8);
|
|
459
|
+
} while (id.length < 6);
|
|
460
|
+
return id;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
class ChunkingKeyringService {
|
|
464
|
+
inner;
|
|
465
|
+
maxEntrySize;
|
|
466
|
+
constructor(inner, maxEntrySize = WINDOWS_MAX_ENTRY_SIZE) {
|
|
467
|
+
this.inner = inner;
|
|
468
|
+
this.maxEntrySize = maxEntrySize;
|
|
469
|
+
}
|
|
470
|
+
getPassword(service, account) {
|
|
471
|
+
const value = this.inner.getPassword(service, account);
|
|
472
|
+
if (value === null)
|
|
473
|
+
return null;
|
|
474
|
+
if (!value.startsWith(CHUNKED_PREFIX))
|
|
475
|
+
return value;
|
|
476
|
+
const sentinel = parseChunkedSentinel(value);
|
|
477
|
+
if (sentinel === null)
|
|
478
|
+
return null;
|
|
479
|
+
const chunks = [];
|
|
480
|
+
for (let i = 0;i < sentinel.count; i++) {
|
|
481
|
+
const chunk = this.inner.getPassword(service, chunkKey(account, sentinel.writeId, i));
|
|
482
|
+
if (chunk === null) {
|
|
483
|
+
console.error(`Warning: Incomplete chunked keychain entry for "${account}" (missing chunk ${i} of ${sentinel.count}). Treating as missing.`);
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
chunks.push(chunk);
|
|
487
|
+
}
|
|
488
|
+
return chunks.join("");
|
|
489
|
+
}
|
|
490
|
+
setPassword(service, account, password) {
|
|
491
|
+
const oldValue = this.readOldSentinel(service, account);
|
|
492
|
+
if (password.length <= this.maxEntrySize) {
|
|
493
|
+
this.inner.setPassword(service, account, password);
|
|
494
|
+
} else {
|
|
495
|
+
const chunks = splitIntoChunks(password, this.maxEntrySize);
|
|
496
|
+
if (chunks.length > MAX_CHUNK_COUNT) {
|
|
497
|
+
throw new Error(`Value requires ${chunks.length} chunks, exceeding maximum of ${MAX_CHUNK_COUNT}. ` + `This likely indicates a bug — credential data should not be this large.`);
|
|
498
|
+
}
|
|
499
|
+
const writeId = generateWriteId();
|
|
500
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
501
|
+
this.inner.setPassword(service, chunkKey(account, writeId, i), chunk);
|
|
502
|
+
}
|
|
503
|
+
this.inner.setPassword(service, account, `${CHUNKED_PREFIX}${writeId}:${chunks.length}`);
|
|
504
|
+
}
|
|
505
|
+
if (oldValue !== null) {
|
|
506
|
+
this.deleteChunkEntries(service, account, oldValue);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
deletePassword(service, account) {
|
|
510
|
+
const oldValue = this.readOldSentinel(service, account);
|
|
511
|
+
if (oldValue !== null) {
|
|
512
|
+
this.deleteChunkEntries(service, account, oldValue);
|
|
513
|
+
}
|
|
514
|
+
return this.inner.deletePassword(service, account);
|
|
515
|
+
}
|
|
516
|
+
readOldSentinel(service, account) {
|
|
517
|
+
try {
|
|
518
|
+
const value = this.inner.getPassword(service, account);
|
|
519
|
+
if (value === null)
|
|
520
|
+
return null;
|
|
521
|
+
return parseChunkedSentinel(value);
|
|
522
|
+
} catch {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
deleteChunkEntries(service, account, sentinel) {
|
|
527
|
+
for (let i = 0;i < sentinel.count; i++) {
|
|
528
|
+
try {
|
|
529
|
+
this.inner.deletePassword(service, chunkKey(account, sentinel.writeId, i));
|
|
530
|
+
} catch {}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// src/services/code-navigation-capability.ts
|
|
535
|
+
function getCodeNavigationCapability(token) {
|
|
536
|
+
if (!token)
|
|
537
|
+
return "unknown";
|
|
538
|
+
const payload = parseJwtPayload(token);
|
|
539
|
+
if (!payload)
|
|
540
|
+
return "unknown";
|
|
541
|
+
const featureFlags = extractFeatureFlags(payload);
|
|
542
|
+
if (!featureFlags)
|
|
543
|
+
return "unknown";
|
|
544
|
+
return featureFlags.includes("code_navigation") ? "enabled" : "disabled";
|
|
545
|
+
}
|
|
546
|
+
function extractFeatureFlags(payload) {
|
|
547
|
+
if (isStringArray(payload.feature_flags)) {
|
|
548
|
+
return payload.feature_flags;
|
|
549
|
+
}
|
|
550
|
+
if (payload.claims && isStringArray(payload.claims.feature_flags)) {
|
|
551
|
+
return payload.claims.feature_flags;
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
function isStringArray(value) {
|
|
556
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
557
|
+
}
|
|
558
|
+
function parseJwtPayload(token) {
|
|
559
|
+
const parts = token.split(".");
|
|
560
|
+
if (parts.length < 2)
|
|
561
|
+
return null;
|
|
562
|
+
try {
|
|
563
|
+
const decoded = Buffer.from(toBase64(parts[1] ?? ""), "base64").toString("utf8");
|
|
564
|
+
const parsed = JSON.parse(decoded);
|
|
565
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
return parsed;
|
|
569
|
+
} catch {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function toBase64(base64Url) {
|
|
574
|
+
const normalized = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
575
|
+
const padding = normalized.length % 4;
|
|
576
|
+
if (padding === 0)
|
|
577
|
+
return normalized;
|
|
578
|
+
return normalized.padEnd(normalized.length + (4 - padding), "=");
|
|
579
|
+
}
|
|
580
|
+
// src/services/code-navigation-service.ts
|
|
581
|
+
import { z } from "zod";
|
|
582
|
+
|
|
583
|
+
// src/shared/debug-log.ts
|
|
584
|
+
function debugLog(area, payload) {
|
|
585
|
+
if (!isAreaEnabled(area))
|
|
586
|
+
return;
|
|
587
|
+
const line = {
|
|
588
|
+
ts: new Date().toISOString(),
|
|
589
|
+
area,
|
|
590
|
+
...payload
|
|
591
|
+
};
|
|
592
|
+
let text;
|
|
593
|
+
try {
|
|
594
|
+
text = JSON.stringify(line);
|
|
595
|
+
} catch {
|
|
596
|
+
text = JSON.stringify({
|
|
597
|
+
ts: line.ts,
|
|
598
|
+
area,
|
|
599
|
+
error: "debug-log payload not serialisable"
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
process.stderr.write(`${text}
|
|
603
|
+
`);
|
|
604
|
+
}
|
|
605
|
+
function isDebugAreaEnabled(area) {
|
|
606
|
+
return isAreaEnabled(area);
|
|
607
|
+
}
|
|
608
|
+
function isAreaEnabled(area) {
|
|
609
|
+
const raw = process.env.GITHITS_DEBUG;
|
|
610
|
+
if (!raw || raw === "")
|
|
611
|
+
return false;
|
|
612
|
+
const scopes = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
613
|
+
if (scopes.includes(area))
|
|
614
|
+
return true;
|
|
615
|
+
if (isExplicitOnlyArea(area))
|
|
616
|
+
return false;
|
|
617
|
+
return scopes.includes("*");
|
|
618
|
+
}
|
|
619
|
+
function isExplicitOnlyArea(area) {
|
|
620
|
+
return area === "code-nav-wire";
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/shared/request-headers.ts
|
|
624
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
625
|
+
var MAX_HEADER_BYTES = 256;
|
|
626
|
+
var SESSION_ENV_VARS = [
|
|
627
|
+
"TERM_SESSION_ID",
|
|
628
|
+
"ITERM_SESSION_ID",
|
|
629
|
+
"WEZTERM_PANE",
|
|
630
|
+
"KITTY_PID",
|
|
631
|
+
"ALACRITTY_SOCKET",
|
|
632
|
+
"WT_SESSION",
|
|
633
|
+
"VSCODE_PID",
|
|
634
|
+
"SUPERSET_PANE_ID",
|
|
635
|
+
"SUPERSET_WORKSPACE_ID",
|
|
636
|
+
"STARSHIP_SESSION_KEY",
|
|
637
|
+
"SSH_CONNECTION"
|
|
638
|
+
];
|
|
639
|
+
var cachedSessionId;
|
|
640
|
+
function resolveRawSessionId(env = process.env, ppid = process.ppid) {
|
|
641
|
+
for (const key of SESSION_ENV_VARS) {
|
|
642
|
+
const value = env[key];
|
|
643
|
+
if (value && value.trim().length > 0) {
|
|
644
|
+
return value.trim();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (typeof ppid === "number" && !Number.isNaN(ppid) && ppid > 0) {
|
|
648
|
+
return String(ppid);
|
|
649
|
+
}
|
|
650
|
+
return randomUUID();
|
|
651
|
+
}
|
|
652
|
+
function getSessionId(env, ppid) {
|
|
653
|
+
if (cachedSessionId !== undefined && env === undefined && ppid === undefined) {
|
|
654
|
+
return cachedSessionId;
|
|
655
|
+
}
|
|
656
|
+
const raw = resolveRawSessionId(env, ppid);
|
|
657
|
+
const hashed = hashValue(raw);
|
|
658
|
+
if (env === undefined && ppid === undefined) {
|
|
659
|
+
cachedSessionId = hashed;
|
|
660
|
+
}
|
|
661
|
+
return hashed;
|
|
662
|
+
}
|
|
663
|
+
function hashValue(input) {
|
|
664
|
+
return createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
665
|
+
}
|
|
666
|
+
var AGENT_PROBES = [
|
|
667
|
+
{ envVar: "OPENCODE", name: "opencode" },
|
|
668
|
+
{ envVar: "CLAUDECODE", name: "claude-code" },
|
|
669
|
+
{ envVar: "CURSOR_TRACE_ID", name: "cursor" },
|
|
670
|
+
{ envVar: "WINDSURF_CONFIG_DIR", name: "windsurf" },
|
|
671
|
+
{ envVar: "ZED_TERM", name: "zed" },
|
|
672
|
+
{ envVar: "VSCODE_PID", name: "vscode" }
|
|
673
|
+
];
|
|
674
|
+
function parseAgentString(raw) {
|
|
675
|
+
const trimmed = raw.trim();
|
|
676
|
+
if (trimmed.length === 0)
|
|
677
|
+
return;
|
|
678
|
+
const slashIndex = trimmed.indexOf("/");
|
|
679
|
+
if (slashIndex === -1)
|
|
680
|
+
return { name: trimmed };
|
|
681
|
+
const name = trimmed.slice(0, slashIndex);
|
|
682
|
+
const ver = trimmed.slice(slashIndex + 1);
|
|
683
|
+
if (name.length === 0)
|
|
684
|
+
return;
|
|
685
|
+
return { name, version: ver || undefined };
|
|
686
|
+
}
|
|
687
|
+
function formatAgentInfo(info) {
|
|
688
|
+
return info.version ? `${info.name}/${info.version}` : info.name;
|
|
689
|
+
}
|
|
690
|
+
function resolveAgentInfo(env = process.env) {
|
|
691
|
+
const explicit = env.GITHITS_AGENT;
|
|
692
|
+
if (explicit && explicit.trim().length > 0) {
|
|
693
|
+
return parseAgentString(explicit);
|
|
694
|
+
}
|
|
695
|
+
for (const probe of AGENT_PROBES) {
|
|
696
|
+
const value = env[probe.envVar];
|
|
697
|
+
if (value && value.trim().length > 0) {
|
|
698
|
+
return { name: probe.name };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
var currentAgentInfo;
|
|
704
|
+
var agentInfoInitialized = false;
|
|
705
|
+
var agentInfoExplicitlySet = false;
|
|
706
|
+
var mcpClientVersionProvider;
|
|
707
|
+
function setMcpClientVersionProvider(provider) {
|
|
708
|
+
mcpClientVersionProvider = provider;
|
|
709
|
+
}
|
|
710
|
+
function getAgentInfo(env) {
|
|
711
|
+
if (agentInfoExplicitlySet) {
|
|
712
|
+
return currentAgentInfo;
|
|
713
|
+
}
|
|
714
|
+
if (mcpClientVersionProvider) {
|
|
715
|
+
try {
|
|
716
|
+
const fromProvider = mcpClientVersionProvider();
|
|
717
|
+
if (fromProvider && fromProvider.name.trim().length > 0) {
|
|
718
|
+
return fromProvider;
|
|
719
|
+
}
|
|
720
|
+
} catch {}
|
|
721
|
+
}
|
|
722
|
+
if (!agentInfoInitialized || env !== undefined) {
|
|
723
|
+
currentAgentInfo = resolveAgentInfo(env);
|
|
724
|
+
if (env === undefined) {
|
|
725
|
+
agentInfoInitialized = true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return currentAgentInfo;
|
|
729
|
+
}
|
|
730
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
731
|
+
function sanitizeHeaderValue(value) {
|
|
732
|
+
if (value === undefined || value === null || typeof value !== "string") {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const cleaned = value.replace(CONTROL_CHARS, "").trim();
|
|
736
|
+
if (cleaned.length === 0)
|
|
737
|
+
return;
|
|
738
|
+
if (Buffer.byteLength(cleaned, "utf8") > MAX_HEADER_BYTES)
|
|
739
|
+
return;
|
|
740
|
+
return cleaned;
|
|
741
|
+
}
|
|
742
|
+
var BASE_CLIENT_NAME = "githits-cli";
|
|
743
|
+
var clientName = BASE_CLIENT_NAME;
|
|
744
|
+
function setClientMode(mode) {
|
|
745
|
+
clientName = `${BASE_CLIENT_NAME}/${mode}`;
|
|
746
|
+
}
|
|
747
|
+
function buildClientHeaders(env, ppid) {
|
|
748
|
+
try {
|
|
749
|
+
const headers = {};
|
|
750
|
+
const name = sanitizeHeaderValue(clientName);
|
|
751
|
+
if (name) {
|
|
752
|
+
headers["x-githits-client-name"] = name;
|
|
753
|
+
}
|
|
754
|
+
const clientVersion = sanitizeHeaderValue(version);
|
|
755
|
+
if (clientVersion) {
|
|
756
|
+
headers["x-githits-client-version"] = clientVersion;
|
|
757
|
+
}
|
|
758
|
+
const agentInfo = getAgentInfo(env);
|
|
759
|
+
if (agentInfo) {
|
|
760
|
+
const agentValue = sanitizeHeaderValue(formatAgentInfo(agentInfo));
|
|
761
|
+
if (agentValue) {
|
|
762
|
+
headers["x-githits-agent"] = agentValue;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const sessionId = sanitizeHeaderValue(getSessionId(env, ppid));
|
|
766
|
+
if (sessionId) {
|
|
767
|
+
headers["x-githits-session-id"] = sessionId;
|
|
768
|
+
}
|
|
769
|
+
return headers;
|
|
770
|
+
} catch {
|
|
771
|
+
return {};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/shared/pkgseer-graphql.ts
|
|
776
|
+
class PkgseerTransportError extends Error {
|
|
777
|
+
constructor(message, options) {
|
|
778
|
+
super(message, options);
|
|
779
|
+
this.name = "PkgseerTransportError";
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function baseUrl(endpointUrl) {
|
|
783
|
+
return endpointUrl.replace(/\/+$/, "");
|
|
784
|
+
}
|
|
785
|
+
async function postPkgseerGraphql(request) {
|
|
786
|
+
const fetchFn = request.fetchFn ?? globalThis.fetch;
|
|
787
|
+
const userAgent = request.userAgent ?? `githits-cli/${version}`;
|
|
788
|
+
let response;
|
|
789
|
+
try {
|
|
790
|
+
response = await fetchFn(`${baseUrl(request.endpointUrl)}/api/graphql`, {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: {
|
|
793
|
+
...buildClientHeaders(),
|
|
794
|
+
Authorization: `Bearer ${request.token}`,
|
|
795
|
+
"Content-Type": "application/json",
|
|
796
|
+
"User-Agent": userAgent
|
|
797
|
+
},
|
|
798
|
+
body: JSON.stringify({
|
|
799
|
+
query: request.query,
|
|
800
|
+
variables: request.variables
|
|
801
|
+
})
|
|
802
|
+
});
|
|
803
|
+
} catch (cause) {
|
|
804
|
+
debugLog("pkg-graphql", {
|
|
805
|
+
event: "transport-error",
|
|
806
|
+
errorName: cause instanceof Error ? cause.name : typeof cause,
|
|
807
|
+
hasCause: true
|
|
808
|
+
});
|
|
809
|
+
throw new PkgseerTransportError("Network request failed before a response was received. Caller should re-wrap with a domain-specific message.", { cause });
|
|
810
|
+
}
|
|
811
|
+
const responseBody = await response.text().catch(() => "");
|
|
812
|
+
const parsedBody = parseJsonOrNull(responseBody);
|
|
813
|
+
return {
|
|
814
|
+
status: response.status,
|
|
815
|
+
responseBody,
|
|
816
|
+
parsedBody
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function parseJsonOrNull(body) {
|
|
820
|
+
if (!body)
|
|
821
|
+
return null;
|
|
822
|
+
try {
|
|
823
|
+
return JSON.parse(body);
|
|
824
|
+
} catch {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/shared/telemetry.ts
|
|
830
|
+
import { writeSync } from "node:fs";
|
|
831
|
+
var ENABLED_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
832
|
+
function isTelemetryEnabled(env = process.env) {
|
|
833
|
+
const raw = env.GITHITS_TELEMETRY?.trim().toLowerCase();
|
|
834
|
+
if (!raw)
|
|
835
|
+
return false;
|
|
836
|
+
return ENABLED_VALUES.has(raw);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
class TelemetryCollector {
|
|
840
|
+
enabled;
|
|
841
|
+
now;
|
|
842
|
+
write;
|
|
843
|
+
sessionStartMs;
|
|
844
|
+
spans = [];
|
|
845
|
+
activeSpans = new Map;
|
|
846
|
+
nextId = 1;
|
|
847
|
+
flushed = false;
|
|
848
|
+
constructor(options = {}) {
|
|
849
|
+
this.enabled = isTelemetryEnabled(options.env);
|
|
850
|
+
this.now = options.now ?? (() => globalThis.performance.now());
|
|
851
|
+
this.write = options.write ?? ((text) => writeSync(process.stderr.fd, text));
|
|
852
|
+
this.sessionStartMs = this.now();
|
|
853
|
+
}
|
|
854
|
+
isEnabled() {
|
|
855
|
+
return this.enabled;
|
|
856
|
+
}
|
|
857
|
+
startSpan(name, attributes) {
|
|
858
|
+
if (!this.enabled)
|
|
859
|
+
return;
|
|
860
|
+
const span = {
|
|
861
|
+
id: this.nextId++,
|
|
862
|
+
name,
|
|
863
|
+
startMs: this.now(),
|
|
864
|
+
attributes: sanitiseAttributes(attributes)
|
|
865
|
+
};
|
|
866
|
+
this.spans.push(span);
|
|
867
|
+
this.activeSpans.set(span.id, span);
|
|
868
|
+
return { id: span.id };
|
|
869
|
+
}
|
|
870
|
+
endSpan(handle, attributes) {
|
|
871
|
+
if (!this.enabled || !handle)
|
|
872
|
+
return;
|
|
873
|
+
const span = this.activeSpans.get(handle.id);
|
|
874
|
+
if (!span || span.endMs !== undefined)
|
|
875
|
+
return;
|
|
876
|
+
span.endMs = this.now();
|
|
877
|
+
span.attributes = mergeAttributes(span.attributes, attributes);
|
|
878
|
+
this.activeSpans.delete(handle.id);
|
|
879
|
+
}
|
|
880
|
+
flush(exitCode = 0) {
|
|
881
|
+
if (!this.enabled || this.flushed)
|
|
882
|
+
return;
|
|
883
|
+
const nowMs = this.now();
|
|
884
|
+
for (const span of this.activeSpans.values()) {
|
|
885
|
+
if (span.endMs !== undefined)
|
|
886
|
+
continue;
|
|
887
|
+
span.endMs = nowMs;
|
|
888
|
+
span.endedAtExit = true;
|
|
889
|
+
}
|
|
890
|
+
this.activeSpans.clear();
|
|
891
|
+
this.write(formatTelemetryReport(this.spans, this.sessionStartMs, nowMs, exitCode));
|
|
892
|
+
this.flushed = true;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
async function withTelemetrySpan(name, operation, attributes) {
|
|
896
|
+
const handle = telemetryCollector.startSpan(name, attributes);
|
|
897
|
+
try {
|
|
898
|
+
const result = await operation();
|
|
899
|
+
telemetryCollector.endSpan(handle);
|
|
900
|
+
return result;
|
|
901
|
+
} catch (error) {
|
|
902
|
+
telemetryCollector.endSpan(handle, { error: true });
|
|
903
|
+
throw error;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function withTelemetrySpanSync(name, operation, attributes) {
|
|
907
|
+
const handle = telemetryCollector.startSpan(name, attributes);
|
|
908
|
+
try {
|
|
909
|
+
const result = operation();
|
|
910
|
+
telemetryCollector.endSpan(handle);
|
|
911
|
+
return result;
|
|
912
|
+
} catch (error) {
|
|
913
|
+
telemetryCollector.endSpan(handle, { error: true });
|
|
914
|
+
throw error;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function startTelemetrySpan(name, attributes) {
|
|
918
|
+
return telemetryCollector.startSpan(name, attributes);
|
|
919
|
+
}
|
|
920
|
+
function endTelemetrySpan(handle, attributes) {
|
|
921
|
+
telemetryCollector.endSpan(handle, attributes);
|
|
922
|
+
}
|
|
923
|
+
function flushTelemetry(exitCode = 0) {
|
|
924
|
+
telemetryCollector.flush(exitCode);
|
|
925
|
+
}
|
|
926
|
+
var telemetryCollector = new TelemetryCollector;
|
|
927
|
+
function sanitiseAttributes(attributes) {
|
|
928
|
+
if (!attributes)
|
|
929
|
+
return;
|
|
930
|
+
const entries = Object.entries(attributes).filter(([, value]) => value !== undefined);
|
|
931
|
+
if (entries.length === 0)
|
|
932
|
+
return;
|
|
933
|
+
return Object.fromEntries(entries);
|
|
934
|
+
}
|
|
935
|
+
function mergeAttributes(initial, extra) {
|
|
936
|
+
if (!initial && !extra)
|
|
937
|
+
return;
|
|
938
|
+
return sanitiseAttributes({
|
|
939
|
+
...initial ?? {},
|
|
940
|
+
...extra ?? {}
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
function formatTelemetryReport(spans, sessionStartMs, sessionEndMs, exitCode) {
|
|
944
|
+
const lines = [
|
|
945
|
+
"[githits telemetry]",
|
|
946
|
+
`exit: ${exitCode}`,
|
|
947
|
+
`total: ${formatMs(sessionEndMs - sessionStartMs)}`
|
|
948
|
+
];
|
|
949
|
+
const orderedSpans = [...spans].sort((left, right) => {
|
|
950
|
+
if (left.startMs !== right.startMs) {
|
|
951
|
+
return left.startMs - right.startMs;
|
|
952
|
+
}
|
|
953
|
+
return left.id - right.id;
|
|
954
|
+
});
|
|
955
|
+
for (const span of orderedSpans) {
|
|
956
|
+
const endMs = span.endMs ?? sessionEndMs;
|
|
957
|
+
const details = [`start +${formatMs(span.startMs - sessionStartMs)}`];
|
|
958
|
+
if (span.endedAtExit) {
|
|
959
|
+
details.push("ended-at-exit");
|
|
960
|
+
}
|
|
961
|
+
const attrs = formatAttributes(span.attributes);
|
|
962
|
+
if (attrs) {
|
|
963
|
+
details.push(attrs);
|
|
964
|
+
}
|
|
965
|
+
lines.push(`- ${span.name}: ${formatMs(endMs - span.startMs)} (${details.join(", ")})`);
|
|
966
|
+
}
|
|
967
|
+
return `${lines.join(`
|
|
968
|
+
`)}
|
|
969
|
+
`;
|
|
970
|
+
}
|
|
971
|
+
function formatAttributes(attributes) {
|
|
972
|
+
if (!attributes)
|
|
973
|
+
return "";
|
|
974
|
+
return Object.entries(attributes).map(([key, value]) => `${key}=${String(value)}`).join(" ");
|
|
975
|
+
}
|
|
976
|
+
function formatMs(value) {
|
|
977
|
+
return `${value.toFixed(1)}ms`;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/services/githits-service.ts
|
|
981
|
+
class AuthenticationError extends Error {
|
|
982
|
+
constructor(message) {
|
|
983
|
+
super(message);
|
|
984
|
+
this.name = "AuthenticationError";
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function parseDetail(body) {
|
|
988
|
+
if (!body)
|
|
989
|
+
return;
|
|
990
|
+
try {
|
|
991
|
+
const parsed = JSON.parse(body);
|
|
992
|
+
if (typeof parsed.detail === "string")
|
|
993
|
+
return parsed.detail;
|
|
994
|
+
} catch {
|
|
995
|
+
return body;
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
class GitHitsServiceImpl {
|
|
1001
|
+
apiUrl;
|
|
1002
|
+
token;
|
|
1003
|
+
constructor(apiUrl, token) {
|
|
1004
|
+
this.apiUrl = apiUrl;
|
|
1005
|
+
this.token = token;
|
|
1006
|
+
}
|
|
1007
|
+
async search(params) {
|
|
1008
|
+
return withTelemetrySpan("githits.search.request", async () => {
|
|
1009
|
+
const response = await fetch(`${this.apiUrl}/search`, {
|
|
1010
|
+
method: "POST",
|
|
1011
|
+
headers: this.headers(),
|
|
1012
|
+
body: JSON.stringify({
|
|
1013
|
+
query: params.query,
|
|
1014
|
+
language: params.language,
|
|
1015
|
+
license_mode: params.licenseMode ?? "strict",
|
|
1016
|
+
include_explanation: params.includeExplanation ?? false
|
|
1017
|
+
})
|
|
1018
|
+
});
|
|
1019
|
+
if (!response.ok) {
|
|
1020
|
+
throw await this.createError(response);
|
|
1021
|
+
}
|
|
1022
|
+
return response.text();
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
async getLanguages() {
|
|
1026
|
+
return withTelemetrySpan("githits.languages.request", async () => {
|
|
1027
|
+
const response = await fetch(`${this.apiUrl}/languages`, {
|
|
1028
|
+
headers: this.headers()
|
|
1029
|
+
});
|
|
1030
|
+
if (!response.ok) {
|
|
1031
|
+
throw await this.createError(response);
|
|
1032
|
+
}
|
|
1033
|
+
return response.json();
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
async submitFeedback(params) {
|
|
1037
|
+
return withTelemetrySpan("githits.feedback.request", async () => {
|
|
1038
|
+
const response = await fetch(`${this.apiUrl}/feedbacks`, {
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
headers: this.headers(),
|
|
1041
|
+
body: JSON.stringify({
|
|
1042
|
+
solution_id: params.solutionId,
|
|
1043
|
+
accepted: params.accepted,
|
|
1044
|
+
feedback_text: params.feedbackText ?? null
|
|
1045
|
+
})
|
|
1046
|
+
});
|
|
1047
|
+
if (!response.ok) {
|
|
1048
|
+
throw await this.createError(response);
|
|
1049
|
+
}
|
|
1050
|
+
return { success: true, message: "Feedback submitted successfully" };
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
headers() {
|
|
1054
|
+
return {
|
|
1055
|
+
...buildClientHeaders(),
|
|
1056
|
+
Authorization: `Bearer ${this.token}`,
|
|
1057
|
+
"Content-Type": "application/json",
|
|
1058
|
+
"User-Agent": `githits-cli/${version}`
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
async createError(response) {
|
|
1062
|
+
const status = response.status;
|
|
1063
|
+
const body = await response.text().catch(() => "");
|
|
1064
|
+
switch (status) {
|
|
1065
|
+
case 401:
|
|
1066
|
+
return new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
1067
|
+
case 403:
|
|
1068
|
+
return new Error("Access denied.");
|
|
1069
|
+
case 404:
|
|
1070
|
+
return new Error(parseDetail(body) || "Resource not found.");
|
|
1071
|
+
default: {
|
|
1072
|
+
if (status >= 500) {
|
|
1073
|
+
const detail = body ? `: ${body}` : "";
|
|
1074
|
+
return new Error(`Server error (${status})${detail}`);
|
|
1075
|
+
}
|
|
1076
|
+
return new Error(body || `Request failed with status ${status}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/services/execute-with-token-refresh.ts
|
|
1083
|
+
async function executeWithTokenRefresh(options) {
|
|
1084
|
+
const token = await options.getToken();
|
|
1085
|
+
if (!token) {
|
|
1086
|
+
throw new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
return await options.executeWithToken(token);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
if (!options.shouldRefresh(error)) {
|
|
1092
|
+
throw error;
|
|
1093
|
+
}
|
|
1094
|
+
const refreshedToken = await options.forceRefresh();
|
|
1095
|
+
if (!refreshedToken) {
|
|
1096
|
+
throw error;
|
|
1097
|
+
}
|
|
1098
|
+
return options.executeWithToken(refreshedToken);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/services/code-navigation-service.ts
|
|
1103
|
+
class CodeNavigationAccessError extends Error {
|
|
1104
|
+
constructor(message) {
|
|
1105
|
+
super(message);
|
|
1106
|
+
this.name = "CodeNavigationAccessError";
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
class CodeNavigationGraphQLError extends Error {
|
|
1111
|
+
code;
|
|
1112
|
+
constructor(message, code) {
|
|
1113
|
+
super(message);
|
|
1114
|
+
this.code = code;
|
|
1115
|
+
this.name = "CodeNavigationGraphQLError";
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
class CodeNavigationIndexingError extends Error {
|
|
1120
|
+
indexingRef;
|
|
1121
|
+
availableVersions;
|
|
1122
|
+
constructor(message, indexingRef, availableVersions) {
|
|
1123
|
+
super(message);
|
|
1124
|
+
this.indexingRef = indexingRef;
|
|
1125
|
+
this.availableVersions = availableVersions;
|
|
1126
|
+
this.name = "CodeNavigationIndexingError";
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
class CodeNavigationUnresolvableError extends Error {
|
|
1131
|
+
constructor(message) {
|
|
1132
|
+
super(message);
|
|
1133
|
+
this.name = "CodeNavigationUnresolvableError";
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
class MalformedCodeNavigationResponseError extends Error {
|
|
1138
|
+
constructor(message) {
|
|
1139
|
+
super(message);
|
|
1140
|
+
this.name = "MalformedCodeNavigationResponseError";
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
class CodeNavigationTargetNotFoundError extends Error {
|
|
1145
|
+
availableVersions;
|
|
1146
|
+
constructor(message, availableVersions) {
|
|
1147
|
+
super(message);
|
|
1148
|
+
this.availableVersions = availableVersions;
|
|
1149
|
+
this.name = "CodeNavigationTargetNotFoundError";
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
class CodeNavigationFileNotFoundError extends Error {
|
|
1154
|
+
filePath;
|
|
1155
|
+
constructor(message, filePath) {
|
|
1156
|
+
super(message);
|
|
1157
|
+
this.filePath = filePath;
|
|
1158
|
+
this.name = "CodeNavigationFileNotFoundError";
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
class CodeNavigationVersionNotFoundError extends Error {
|
|
1163
|
+
packageName;
|
|
1164
|
+
requestedVersion;
|
|
1165
|
+
latestIndexed;
|
|
1166
|
+
availableVersions;
|
|
1167
|
+
constructor(message, packageName, requestedVersion, latestIndexed, availableVersions) {
|
|
1168
|
+
super(message);
|
|
1169
|
+
this.packageName = packageName;
|
|
1170
|
+
this.requestedVersion = requestedVersion;
|
|
1171
|
+
this.latestIndexed = latestIndexed;
|
|
1172
|
+
this.availableVersions = availableVersions;
|
|
1173
|
+
this.name = "CodeNavigationVersionNotFoundError";
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
class CodeNavigationValidationError extends Error {
|
|
1178
|
+
constructor(message) {
|
|
1179
|
+
super(message);
|
|
1180
|
+
this.name = "CodeNavigationValidationError";
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
class CodeNavigationFeatureFlagRequiredError extends Error {
|
|
1185
|
+
constructor(message) {
|
|
1186
|
+
super(message);
|
|
1187
|
+
this.name = "CodeNavigationFeatureFlagRequiredError";
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
class CodeNavigationNetworkError extends Error {
|
|
1192
|
+
constructor(message, options) {
|
|
1193
|
+
super(message, options);
|
|
1194
|
+
this.name = "CodeNavigationNetworkError";
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
class CodeNavigationBackendError extends Error {
|
|
1199
|
+
status;
|
|
1200
|
+
graphqlCode;
|
|
1201
|
+
retryable;
|
|
1202
|
+
constructor(message, status, graphqlCode, retryable) {
|
|
1203
|
+
super(message);
|
|
1204
|
+
this.status = status;
|
|
1205
|
+
this.graphqlCode = graphqlCode;
|
|
1206
|
+
this.retryable = retryable;
|
|
1207
|
+
this.name = "CodeNavigationBackendError";
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
var UNIFIED_SEARCH_QUERY = `
|
|
1211
|
+
query UnifiedSearch(
|
|
1212
|
+
$targets: [SearchPackageInput!]!
|
|
1213
|
+
$query: String!
|
|
1214
|
+
$sources: [DiscoverySearchSource!]
|
|
1215
|
+
$filters: DiscoverySearchFiltersInput
|
|
1216
|
+
$allowPartialResults: Boolean
|
|
1217
|
+
$limit: Int
|
|
1218
|
+
$offset: Int
|
|
1219
|
+
$waitTimeoutMs: Int
|
|
1220
|
+
) {
|
|
1221
|
+
search(
|
|
1222
|
+
targets: $targets
|
|
1223
|
+
query: $query
|
|
1224
|
+
sources: $sources
|
|
1225
|
+
filters: $filters
|
|
1226
|
+
allowPartialResults: $allowPartialResults
|
|
1227
|
+
limit: $limit
|
|
1228
|
+
offset: $offset
|
|
1229
|
+
waitTimeoutMs: $waitTimeoutMs
|
|
1230
|
+
) {
|
|
1231
|
+
completed
|
|
1232
|
+
searchRef
|
|
1233
|
+
result {
|
|
1234
|
+
query
|
|
1235
|
+
queryWarnings
|
|
1236
|
+
sources
|
|
1237
|
+
results {
|
|
1238
|
+
id
|
|
1239
|
+
resultType
|
|
1240
|
+
targetLabel
|
|
1241
|
+
title
|
|
1242
|
+
summary
|
|
1243
|
+
score
|
|
1244
|
+
highlights {
|
|
1245
|
+
title
|
|
1246
|
+
summary
|
|
1247
|
+
}
|
|
1248
|
+
locator {
|
|
1249
|
+
registry
|
|
1250
|
+
packageName
|
|
1251
|
+
version
|
|
1252
|
+
pageId
|
|
1253
|
+
sourceKind
|
|
1254
|
+
sourceUrl
|
|
1255
|
+
repoUrl
|
|
1256
|
+
gitRef
|
|
1257
|
+
requestedRef
|
|
1258
|
+
filePath
|
|
1259
|
+
startLine
|
|
1260
|
+
endLine
|
|
1261
|
+
fileContentHash
|
|
1262
|
+
symbolRef
|
|
1263
|
+
qualifiedPath
|
|
1264
|
+
kind
|
|
1265
|
+
category
|
|
1266
|
+
language
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
page {
|
|
1270
|
+
offset
|
|
1271
|
+
limit
|
|
1272
|
+
returned
|
|
1273
|
+
hasMore
|
|
1274
|
+
}
|
|
1275
|
+
partialResults
|
|
1276
|
+
sourceStatus {
|
|
1277
|
+
source
|
|
1278
|
+
targetLabel
|
|
1279
|
+
indexingStatus
|
|
1280
|
+
codeIndexState
|
|
1281
|
+
resultCount
|
|
1282
|
+
appliedFilters
|
|
1283
|
+
ignoredFilters
|
|
1284
|
+
incompatibleFilters
|
|
1285
|
+
appliedQueryFeatures
|
|
1286
|
+
ignoredQueryFeatures
|
|
1287
|
+
incompatibleQueryFeatures
|
|
1288
|
+
note
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
progress {
|
|
1292
|
+
searchRef
|
|
1293
|
+
status
|
|
1294
|
+
targetsTotal
|
|
1295
|
+
targetsReady
|
|
1296
|
+
elapsedMs
|
|
1297
|
+
query
|
|
1298
|
+
queryWarnings
|
|
1299
|
+
sources
|
|
1300
|
+
expiresAt
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}`;
|
|
1304
|
+
var UNIFIED_SEARCH_STATUS_QUERY = `
|
|
1305
|
+
query UnifiedSearchStatus($searchRef: String!, $includeResults: Boolean!) {
|
|
1306
|
+
discoverySearchProgress(searchRef: $searchRef, includeResults: $includeResults) {
|
|
1307
|
+
searchRef
|
|
1308
|
+
status
|
|
1309
|
+
targetsTotal
|
|
1310
|
+
targetsReady
|
|
1311
|
+
elapsedMs
|
|
1312
|
+
query
|
|
1313
|
+
queryWarnings
|
|
1314
|
+
sources
|
|
1315
|
+
expiresAt
|
|
1316
|
+
results {
|
|
1317
|
+
query
|
|
1318
|
+
queryWarnings
|
|
1319
|
+
sources
|
|
1320
|
+
results {
|
|
1321
|
+
id
|
|
1322
|
+
resultType
|
|
1323
|
+
targetLabel
|
|
1324
|
+
title
|
|
1325
|
+
summary
|
|
1326
|
+
score
|
|
1327
|
+
highlights {
|
|
1328
|
+
title
|
|
1329
|
+
summary
|
|
1330
|
+
}
|
|
1331
|
+
locator {
|
|
1332
|
+
registry
|
|
1333
|
+
packageName
|
|
1334
|
+
version
|
|
1335
|
+
pageId
|
|
1336
|
+
sourceKind
|
|
1337
|
+
sourceUrl
|
|
1338
|
+
repoUrl
|
|
1339
|
+
gitRef
|
|
1340
|
+
requestedRef
|
|
1341
|
+
filePath
|
|
1342
|
+
startLine
|
|
1343
|
+
endLine
|
|
1344
|
+
fileContentHash
|
|
1345
|
+
symbolRef
|
|
1346
|
+
qualifiedPath
|
|
1347
|
+
kind
|
|
1348
|
+
category
|
|
1349
|
+
language
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
page {
|
|
1353
|
+
offset
|
|
1354
|
+
limit
|
|
1355
|
+
returned
|
|
1356
|
+
hasMore
|
|
1357
|
+
}
|
|
1358
|
+
partialResults
|
|
1359
|
+
sourceStatus {
|
|
1360
|
+
source
|
|
1361
|
+
targetLabel
|
|
1362
|
+
indexingStatus
|
|
1363
|
+
codeIndexState
|
|
1364
|
+
resultCount
|
|
1365
|
+
appliedFilters
|
|
1366
|
+
ignoredFilters
|
|
1367
|
+
incompatibleFilters
|
|
1368
|
+
appliedQueryFeatures
|
|
1369
|
+
ignoredQueryFeatures
|
|
1370
|
+
incompatibleQueryFeatures
|
|
1371
|
+
note
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}`;
|
|
1376
|
+
function debugUnifiedSearchRequest(variables) {
|
|
1377
|
+
if (!isDebugAreaEnabled("code-nav"))
|
|
1378
|
+
return;
|
|
1379
|
+
const serialised = serialiseForDebug(variables);
|
|
1380
|
+
const filters = asRecord(serialised.filters);
|
|
1381
|
+
debugLog("code-nav", {
|
|
1382
|
+
event: "request",
|
|
1383
|
+
operation: "search",
|
|
1384
|
+
targetCount: Array.isArray(serialised.targets) ? serialised.targets.length : 0,
|
|
1385
|
+
sources: Array.isArray(serialised.sources) ? serialised.sources : [],
|
|
1386
|
+
hasFilters: filters !== undefined,
|
|
1387
|
+
filterKeys: filters ? Object.keys(filters).sort() : [],
|
|
1388
|
+
fileIntent: filters && typeof filters.fileIntent === "string" ? filters.fileIntent : "omitted",
|
|
1389
|
+
allowPartialResults: serialised.allowPartialResults === true,
|
|
1390
|
+
presentVariableKeys: Object.keys(serialised).sort(),
|
|
1391
|
+
hasLimit: typeof serialised.limit === "number",
|
|
1392
|
+
hasOffset: typeof serialised.offset === "number",
|
|
1393
|
+
waitTimeoutMs: typeof serialised.waitTimeoutMs === "number" ? serialised.waitTimeoutMs : undefined
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
function debugGraphqlWireRequest(operation, graphqlQuery, variables) {
|
|
1397
|
+
if (!isDebugAreaEnabled("code-nav-wire"))
|
|
1398
|
+
return;
|
|
1399
|
+
debugLog("code-nav-wire", {
|
|
1400
|
+
event: "wire-request",
|
|
1401
|
+
operation,
|
|
1402
|
+
graphqlQuery,
|
|
1403
|
+
variables: serialiseForDebug(variables)
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
function serialiseForDebug(value) {
|
|
1407
|
+
try {
|
|
1408
|
+
const text = JSON.stringify(value);
|
|
1409
|
+
if (!text)
|
|
1410
|
+
return {};
|
|
1411
|
+
const parsed = JSON.parse(text);
|
|
1412
|
+
return asRecord(parsed) ?? {};
|
|
1413
|
+
} catch {
|
|
1414
|
+
return {};
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function asRecord(value) {
|
|
1418
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1419
|
+
return value;
|
|
1420
|
+
}
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
var availableVersionSchema = z.object({
|
|
1424
|
+
version: z.string().nullable().optional(),
|
|
1425
|
+
ref: z.string()
|
|
1426
|
+
});
|
|
1427
|
+
var unifiedSearchSourceSchema = z.enum(["AUTO", "DOCS", "CODE", "SYMBOL"]);
|
|
1428
|
+
var unifiedSearchResultTypeSchema = z.enum([
|
|
1429
|
+
"DOCUMENTATION_PAGE",
|
|
1430
|
+
"REPOSITORY_SYMBOL",
|
|
1431
|
+
"REPOSITORY_CODE",
|
|
1432
|
+
"REPOSITORY_DOC"
|
|
1433
|
+
]);
|
|
1434
|
+
var unifiedSearchLocatorSchema = z.object({
|
|
1435
|
+
registry: z.string().nullable().optional(),
|
|
1436
|
+
packageName: z.string().nullable().optional(),
|
|
1437
|
+
version: z.string().nullable().optional(),
|
|
1438
|
+
pageId: z.string().nullable().optional(),
|
|
1439
|
+
sourceKind: z.string().nullable().optional(),
|
|
1440
|
+
sourceUrl: z.string().nullable().optional(),
|
|
1441
|
+
repoUrl: z.string().nullable().optional(),
|
|
1442
|
+
gitRef: z.string().nullable().optional(),
|
|
1443
|
+
requestedRef: z.string().nullable().optional(),
|
|
1444
|
+
filePath: z.string().nullable().optional(),
|
|
1445
|
+
startLine: z.number().int().nullable().optional(),
|
|
1446
|
+
endLine: z.number().int().nullable().optional(),
|
|
1447
|
+
fileContentHash: z.string().nullable().optional(),
|
|
1448
|
+
symbolRef: z.string().nullable().optional(),
|
|
1449
|
+
qualifiedPath: z.string().nullable().optional(),
|
|
1450
|
+
kind: z.string().nullable().optional(),
|
|
1451
|
+
category: z.string().nullable().optional(),
|
|
1452
|
+
language: z.string().nullable().optional()
|
|
1453
|
+
});
|
|
1454
|
+
var unifiedSearchHitSchema = z.object({
|
|
1455
|
+
id: z.string(),
|
|
1456
|
+
resultType: unifiedSearchResultTypeSchema,
|
|
1457
|
+
targetLabel: z.string(),
|
|
1458
|
+
title: z.string().nullable().optional(),
|
|
1459
|
+
summary: z.string().nullable().optional(),
|
|
1460
|
+
score: z.number().nullable().optional(),
|
|
1461
|
+
highlights: z.object({
|
|
1462
|
+
title: z.array(z.tuple([z.number().int(), z.number().int()])).nullable().optional(),
|
|
1463
|
+
summary: z.array(z.tuple([z.number().int(), z.number().int()])).nullable().optional()
|
|
1464
|
+
}).nullable().optional(),
|
|
1465
|
+
locator: unifiedSearchLocatorSchema
|
|
1466
|
+
});
|
|
1467
|
+
var unifiedSearchPageInfoSchema = z.object({
|
|
1468
|
+
offset: z.number().int(),
|
|
1469
|
+
limit: z.number().int(),
|
|
1470
|
+
returned: z.number().int(),
|
|
1471
|
+
hasMore: z.boolean()
|
|
1472
|
+
});
|
|
1473
|
+
var unifiedSearchSourceStatusSchema = z.object({
|
|
1474
|
+
source: unifiedSearchSourceSchema,
|
|
1475
|
+
targetLabel: z.string(),
|
|
1476
|
+
indexingStatus: z.string().nullable().optional(),
|
|
1477
|
+
codeIndexState: z.string().nullable().optional(),
|
|
1478
|
+
resultCount: z.number().int().nullable().optional(),
|
|
1479
|
+
appliedFilters: z.array(z.string()),
|
|
1480
|
+
ignoredFilters: z.array(z.string()),
|
|
1481
|
+
incompatibleFilters: z.array(z.string()),
|
|
1482
|
+
appliedQueryFeatures: z.array(z.string()),
|
|
1483
|
+
ignoredQueryFeatures: z.array(z.string()),
|
|
1484
|
+
incompatibleQueryFeatures: z.array(z.string()),
|
|
1485
|
+
note: z.string().nullable().optional()
|
|
1486
|
+
});
|
|
1487
|
+
var unifiedSearchResultSchema = z.object({
|
|
1488
|
+
query: z.string(),
|
|
1489
|
+
queryWarnings: z.array(z.string()),
|
|
1490
|
+
sources: z.array(unifiedSearchSourceSchema),
|
|
1491
|
+
results: z.array(unifiedSearchHitSchema),
|
|
1492
|
+
page: unifiedSearchPageInfoSchema,
|
|
1493
|
+
partialResults: z.boolean(),
|
|
1494
|
+
sourceStatus: z.array(unifiedSearchSourceStatusSchema)
|
|
1495
|
+
});
|
|
1496
|
+
var unifiedSearchSessionStatusSchema = z.enum([
|
|
1497
|
+
"PENDING",
|
|
1498
|
+
"INDEXING",
|
|
1499
|
+
"SEARCHING",
|
|
1500
|
+
"COMPLETED",
|
|
1501
|
+
"TIMEOUT",
|
|
1502
|
+
"FAILED"
|
|
1503
|
+
]);
|
|
1504
|
+
var unifiedSearchProgressSchema = z.object({
|
|
1505
|
+
searchRef: z.string(),
|
|
1506
|
+
status: unifiedSearchSessionStatusSchema,
|
|
1507
|
+
targetsTotal: z.number().int(),
|
|
1508
|
+
targetsReady: z.number().int(),
|
|
1509
|
+
elapsedMs: z.number().int(),
|
|
1510
|
+
query: z.string(),
|
|
1511
|
+
queryWarnings: z.array(z.string()),
|
|
1512
|
+
sources: z.array(unifiedSearchSourceSchema),
|
|
1513
|
+
expiresAt: z.string().nullable().optional(),
|
|
1514
|
+
results: unifiedSearchResultSchema.nullable().optional()
|
|
1515
|
+
});
|
|
1516
|
+
var asyncUnifiedSearchResultSchema = z.object({
|
|
1517
|
+
completed: z.boolean(),
|
|
1518
|
+
searchRef: z.string().nullable().optional(),
|
|
1519
|
+
result: unifiedSearchResultSchema.nullable().optional(),
|
|
1520
|
+
progress: unifiedSearchProgressSchema.nullable().optional()
|
|
1521
|
+
});
|
|
1522
|
+
var graphQLErrorSchema = z.object({
|
|
1523
|
+
message: z.string(),
|
|
1524
|
+
extensions: z.record(z.string(), z.unknown()).optional()
|
|
1525
|
+
});
|
|
1526
|
+
var navigationResolutionSchema = z.object({
|
|
1527
|
+
requestedVersion: z.string().nullable().optional(),
|
|
1528
|
+
requestedRef: z.string().nullable().optional(),
|
|
1529
|
+
resolvedRef: z.string().nullable().optional(),
|
|
1530
|
+
commitSha: z.string().nullable().optional()
|
|
1531
|
+
}).nullable().optional();
|
|
1532
|
+
var navigationDiagnosticsSchema = z.object({
|
|
1533
|
+
hint: z.string().nullable().optional()
|
|
1534
|
+
}).nullable().optional();
|
|
1535
|
+
var repoFileEntrySchema = z.object({
|
|
1536
|
+
path: z.string(),
|
|
1537
|
+
name: z.string().nullable().optional(),
|
|
1538
|
+
language: z.string().nullable().optional(),
|
|
1539
|
+
fileType: z.string().nullable().optional(),
|
|
1540
|
+
byteSize: z.number().int().nullable().optional()
|
|
1541
|
+
});
|
|
1542
|
+
var listRepoFilesResponseSchema = z.object({
|
|
1543
|
+
files: z.array(repoFileEntrySchema),
|
|
1544
|
+
total: z.number().int(),
|
|
1545
|
+
hasMore: z.boolean(),
|
|
1546
|
+
indexedVersion: z.string().nullable().optional(),
|
|
1547
|
+
resolution: navigationResolutionSchema,
|
|
1548
|
+
diagnostics: navigationDiagnosticsSchema,
|
|
1549
|
+
codeIndexState: z.string(),
|
|
1550
|
+
indexingRef: z.string().nullable().optional(),
|
|
1551
|
+
availableVersions: z.array(availableVersionSchema).nullable().optional()
|
|
1552
|
+
});
|
|
1553
|
+
var listRepoFilesGraphQLResponseSchema = z.object({
|
|
1554
|
+
data: z.object({
|
|
1555
|
+
listRepoFiles: listRepoFilesResponseSchema.nullable().optional()
|
|
1556
|
+
}).nullable().optional(),
|
|
1557
|
+
errors: z.array(graphQLErrorSchema).optional()
|
|
1558
|
+
});
|
|
1559
|
+
var LIST_REPO_FILES_QUERY = `
|
|
1560
|
+
query ListRepoFiles(
|
|
1561
|
+
$registry: Registry
|
|
1562
|
+
$packageName: String
|
|
1563
|
+
$repoUrl: String
|
|
1564
|
+
$gitRef: String
|
|
1565
|
+
$version: String
|
|
1566
|
+
$pathPrefix: String
|
|
1567
|
+
$limit: Int
|
|
1568
|
+
$waitTimeoutMs: Int
|
|
1569
|
+
) {
|
|
1570
|
+
listRepoFiles(
|
|
1571
|
+
registry: $registry
|
|
1572
|
+
packageName: $packageName
|
|
1573
|
+
repoUrl: $repoUrl
|
|
1574
|
+
gitRef: $gitRef
|
|
1575
|
+
version: $version
|
|
1576
|
+
pathPrefix: $pathPrefix
|
|
1577
|
+
limit: $limit
|
|
1578
|
+
waitTimeoutMs: $waitTimeoutMs
|
|
1579
|
+
) {
|
|
1580
|
+
files {
|
|
1581
|
+
path
|
|
1582
|
+
name
|
|
1583
|
+
language
|
|
1584
|
+
fileType
|
|
1585
|
+
byteSize
|
|
1586
|
+
}
|
|
1587
|
+
total
|
|
1588
|
+
hasMore
|
|
1589
|
+
indexedVersion
|
|
1590
|
+
resolution {
|
|
1591
|
+
requestedVersion
|
|
1592
|
+
requestedRef
|
|
1593
|
+
resolvedRef
|
|
1594
|
+
commitSha
|
|
1595
|
+
}
|
|
1596
|
+
diagnostics {
|
|
1597
|
+
hint
|
|
1598
|
+
}
|
|
1599
|
+
codeIndexState
|
|
1600
|
+
indexingRef
|
|
1601
|
+
availableVersions {
|
|
1602
|
+
version
|
|
1603
|
+
ref
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}`;
|
|
1607
|
+
var codeContextResponseSchema = z.object({
|
|
1608
|
+
content: z.string().nullable().optional(),
|
|
1609
|
+
filePath: z.string().nullable().optional(),
|
|
1610
|
+
language: z.string().nullable().optional(),
|
|
1611
|
+
totalLines: z.number().int().nullable().optional(),
|
|
1612
|
+
startLine: z.number().int().nullable().optional(),
|
|
1613
|
+
endLine: z.number().int().nullable().optional(),
|
|
1614
|
+
repoUrl: z.string().nullable().optional(),
|
|
1615
|
+
gitRef: z.string().nullable().optional(),
|
|
1616
|
+
isBinary: z.boolean().nullable().optional(),
|
|
1617
|
+
codeIndexState: z.string(),
|
|
1618
|
+
indexingRef: z.string().nullable().optional()
|
|
1619
|
+
});
|
|
1620
|
+
var fetchCodeContextGraphQLResponseSchema = z.object({
|
|
1621
|
+
data: z.object({
|
|
1622
|
+
fetchCodeContext: codeContextResponseSchema.nullable().optional()
|
|
1623
|
+
}).nullable().optional(),
|
|
1624
|
+
errors: z.array(graphQLErrorSchema).optional()
|
|
1625
|
+
});
|
|
1626
|
+
var FETCH_CODE_CONTEXT_QUERY = `
|
|
1627
|
+
query FetchCodeContext(
|
|
1628
|
+
$registry: Registry
|
|
1629
|
+
$packageName: String
|
|
1630
|
+
$repoUrl: String
|
|
1631
|
+
$gitRef: String
|
|
1632
|
+
$version: String
|
|
1633
|
+
$filePath: String!
|
|
1634
|
+
$startLine: Int
|
|
1635
|
+
$endLine: Int
|
|
1636
|
+
$waitTimeoutMs: Int
|
|
1637
|
+
) {
|
|
1638
|
+
fetchCodeContext(
|
|
1639
|
+
registry: $registry
|
|
1640
|
+
packageName: $packageName
|
|
1641
|
+
repoUrl: $repoUrl
|
|
1642
|
+
gitRef: $gitRef
|
|
1643
|
+
version: $version
|
|
1644
|
+
filePath: $filePath
|
|
1645
|
+
startLine: $startLine
|
|
1646
|
+
endLine: $endLine
|
|
1647
|
+
waitTimeoutMs: $waitTimeoutMs
|
|
1648
|
+
) {
|
|
1649
|
+
content
|
|
1650
|
+
filePath
|
|
1651
|
+
language
|
|
1652
|
+
totalLines
|
|
1653
|
+
startLine
|
|
1654
|
+
endLine
|
|
1655
|
+
repoUrl
|
|
1656
|
+
gitRef
|
|
1657
|
+
isBinary
|
|
1658
|
+
codeIndexState
|
|
1659
|
+
indexingRef
|
|
1660
|
+
}
|
|
1661
|
+
}`;
|
|
1662
|
+
var grepRepoMatchSchema = z.object({
|
|
1663
|
+
filePath: z.string(),
|
|
1664
|
+
line: z.number().int(),
|
|
1665
|
+
matchStartByte: z.number().int(),
|
|
1666
|
+
matchEndByte: z.number().int(),
|
|
1667
|
+
lineContent: z.string(),
|
|
1668
|
+
contextBefore: z.array(z.string()).nullable().optional(),
|
|
1669
|
+
contextAfter: z.array(z.string()).nullable().optional(),
|
|
1670
|
+
fileContentHash: z.string().nullable().optional(),
|
|
1671
|
+
fileIntent: z.string().nullable().optional(),
|
|
1672
|
+
symbolRowId: z.string().nullable().optional(),
|
|
1673
|
+
symbol: z.object({
|
|
1674
|
+
symbolRef: z.string().optional(),
|
|
1675
|
+
name: z.string().optional(),
|
|
1676
|
+
qualifiedPath: z.string().nullable().optional(),
|
|
1677
|
+
kind: z.string().nullable().optional(),
|
|
1678
|
+
category: z.string().nullable().optional(),
|
|
1679
|
+
arity: z.number().int().nullable().optional(),
|
|
1680
|
+
isPublic: z.boolean().nullable().optional(),
|
|
1681
|
+
filePath: z.string().nullable().optional(),
|
|
1682
|
+
startLine: z.number().int().nullable().optional(),
|
|
1683
|
+
endLine: z.number().int().nullable().optional(),
|
|
1684
|
+
code: z.string().nullable().optional(),
|
|
1685
|
+
callerCount: z.number().int().nullable().optional(),
|
|
1686
|
+
contentHash: z.string().nullable().optional(),
|
|
1687
|
+
parentSymbolRef: z.string().nullable().optional(),
|
|
1688
|
+
parentPath: z.string().nullable().optional()
|
|
1689
|
+
}).nullable().optional()
|
|
1690
|
+
});
|
|
1691
|
+
var grepRepoResponseSchema = z.object({
|
|
1692
|
+
matches: z.array(grepRepoMatchSchema),
|
|
1693
|
+
nextCursor: z.string().nullable().optional(),
|
|
1694
|
+
hasMore: z.boolean(),
|
|
1695
|
+
truncatedReason: z.enum([
|
|
1696
|
+
"NONE",
|
|
1697
|
+
"MAX_MATCHES",
|
|
1698
|
+
"MAX_MATCHES_PER_FILE",
|
|
1699
|
+
"DEADLINE"
|
|
1700
|
+
]),
|
|
1701
|
+
routeTaken: z.enum(["SINGLE_FILE", "CONTENT_INDEX"]).nullable().optional(),
|
|
1702
|
+
filesScanned: z.number().int(),
|
|
1703
|
+
filesInScope: z.number().int(),
|
|
1704
|
+
binaryFilesSkipped: z.number().int(),
|
|
1705
|
+
filesTooLargeSkipped: z.number().int(),
|
|
1706
|
+
totalMatches: z.number().int(),
|
|
1707
|
+
uniqueFilesMatched: z.number().int(),
|
|
1708
|
+
indexedVersion: z.string().nullable().optional(),
|
|
1709
|
+
resolution: navigationResolutionSchema,
|
|
1710
|
+
codeIndexState: z.string(),
|
|
1711
|
+
indexingRef: z.string().nullable().optional(),
|
|
1712
|
+
availableVersions: z.array(availableVersionSchema).nullable().optional()
|
|
1713
|
+
});
|
|
1714
|
+
var grepRepoGraphQLResponseSchema = z.object({
|
|
1715
|
+
data: z.object({
|
|
1716
|
+
grepRepo: grepRepoResponseSchema.nullable().optional()
|
|
1717
|
+
}).nullable().optional(),
|
|
1718
|
+
errors: z.array(graphQLErrorSchema).optional()
|
|
1719
|
+
});
|
|
1720
|
+
var GREP_REPO_SYMBOL_SELECTIONS = {
|
|
1721
|
+
symbol_ref: "symbolRef",
|
|
1722
|
+
name: "name",
|
|
1723
|
+
qualified_path: "qualifiedPath",
|
|
1724
|
+
kind: "kind",
|
|
1725
|
+
category: "category",
|
|
1726
|
+
arity: "arity",
|
|
1727
|
+
is_public: "isPublic",
|
|
1728
|
+
file_path: "filePath",
|
|
1729
|
+
start_line: "startLine",
|
|
1730
|
+
end_line: "endLine",
|
|
1731
|
+
code: "code",
|
|
1732
|
+
caller_count: "callerCount",
|
|
1733
|
+
content_hash: "contentHash",
|
|
1734
|
+
parent_symbol_ref: "parentSymbolRef",
|
|
1735
|
+
parent_path: "parentPath"
|
|
1736
|
+
};
|
|
1737
|
+
function buildGrepRepoQuery(symbolFields) {
|
|
1738
|
+
const symbolSelection = (symbolFields ?? []).map((field) => GREP_REPO_SYMBOL_SELECTIONS[field]).filter((field) => Boolean(field)).filter((field, index, fields) => fields.indexOf(field) === index).join(`
|
|
1739
|
+
`);
|
|
1740
|
+
const symbolBlock = symbolSelection.length > 0 ? `
|
|
1741
|
+
symbol {
|
|
1742
|
+
${symbolSelection}
|
|
1743
|
+
}` : "";
|
|
1744
|
+
return `
|
|
1745
|
+
query GrepRepo(
|
|
1746
|
+
$registry: Registry
|
|
1747
|
+
$packageName: String
|
|
1748
|
+
$repoUrl: String
|
|
1749
|
+
$gitRef: String
|
|
1750
|
+
$version: String
|
|
1751
|
+
$waitTimeoutMs: Int
|
|
1752
|
+
$pattern: String!
|
|
1753
|
+
$patternType: GrepPatternType
|
|
1754
|
+
$caseSensitive: Boolean
|
|
1755
|
+
$pathSelectors: [GrepPathSelectorInput!]
|
|
1756
|
+
$extensions: [String!]
|
|
1757
|
+
$excludeDocFiles: Boolean
|
|
1758
|
+
$excludeTestFiles: Boolean
|
|
1759
|
+
$allowUnscoped: Boolean
|
|
1760
|
+
$contextLinesBefore: Int
|
|
1761
|
+
$contextLinesAfter: Int
|
|
1762
|
+
$maxMatches: Int
|
|
1763
|
+
$maxMatchesPerFile: Int
|
|
1764
|
+
$cursor: String
|
|
1765
|
+
$symbolFields: [String!]
|
|
1766
|
+
) {
|
|
1767
|
+
grepRepo(
|
|
1768
|
+
registry: $registry
|
|
1769
|
+
packageName: $packageName
|
|
1770
|
+
repoUrl: $repoUrl
|
|
1771
|
+
gitRef: $gitRef
|
|
1772
|
+
version: $version
|
|
1773
|
+
waitTimeoutMs: $waitTimeoutMs
|
|
1774
|
+
pattern: $pattern
|
|
1775
|
+
patternType: $patternType
|
|
1776
|
+
caseSensitive: $caseSensitive
|
|
1777
|
+
pathSelectors: $pathSelectors
|
|
1778
|
+
extensions: $extensions
|
|
1779
|
+
excludeDocFiles: $excludeDocFiles
|
|
1780
|
+
excludeTestFiles: $excludeTestFiles
|
|
1781
|
+
allowUnscoped: $allowUnscoped
|
|
1782
|
+
contextLinesBefore: $contextLinesBefore
|
|
1783
|
+
contextLinesAfter: $contextLinesAfter
|
|
1784
|
+
maxMatches: $maxMatches
|
|
1785
|
+
maxMatchesPerFile: $maxMatchesPerFile
|
|
1786
|
+
cursor: $cursor
|
|
1787
|
+
symbolFields: $symbolFields
|
|
1788
|
+
) {
|
|
1789
|
+
matches {
|
|
1790
|
+
filePath
|
|
1791
|
+
line
|
|
1792
|
+
matchStartByte
|
|
1793
|
+
matchEndByte
|
|
1794
|
+
lineContent
|
|
1795
|
+
contextBefore
|
|
1796
|
+
contextAfter
|
|
1797
|
+
fileContentHash
|
|
1798
|
+
fileIntent
|
|
1799
|
+
symbolRowId${symbolBlock}
|
|
1800
|
+
}
|
|
1801
|
+
nextCursor
|
|
1802
|
+
totalMatches
|
|
1803
|
+
hasMore
|
|
1804
|
+
truncatedReason
|
|
1805
|
+
routeTaken
|
|
1806
|
+
filesScanned
|
|
1807
|
+
filesInScope
|
|
1808
|
+
binaryFilesSkipped
|
|
1809
|
+
filesTooLargeSkipped
|
|
1810
|
+
uniqueFilesMatched
|
|
1811
|
+
indexedVersion
|
|
1812
|
+
resolution {
|
|
1813
|
+
requestedVersion
|
|
1814
|
+
requestedRef
|
|
1815
|
+
resolvedRef
|
|
1816
|
+
commitSha
|
|
1817
|
+
}
|
|
1818
|
+
codeIndexState
|
|
1819
|
+
indexingRef
|
|
1820
|
+
availableVersions {
|
|
1821
|
+
version
|
|
1822
|
+
ref
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}`;
|
|
1826
|
+
}
|
|
1827
|
+
var unifiedSearchGraphQLResponseSchema = z.object({
|
|
1828
|
+
data: z.object({
|
|
1829
|
+
search: asyncUnifiedSearchResultSchema.nullable().optional()
|
|
1830
|
+
}).nullable().optional(),
|
|
1831
|
+
errors: z.array(graphQLErrorSchema).optional()
|
|
1832
|
+
});
|
|
1833
|
+
var unifiedSearchStatusGraphQLResponseSchema = z.object({
|
|
1834
|
+
data: z.object({
|
|
1835
|
+
discoverySearchProgress: unifiedSearchProgressSchema.nullable().optional()
|
|
1836
|
+
}).nullable().optional(),
|
|
1837
|
+
errors: z.array(graphQLErrorSchema).optional()
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
class CodeNavigationServiceImpl {
|
|
1841
|
+
codeNavigationUrl;
|
|
1842
|
+
tokenProvider;
|
|
1843
|
+
fetchFn;
|
|
1844
|
+
constructor(codeNavigationUrl, tokenProvider, fetchFn = globalThis.fetch) {
|
|
1845
|
+
this.codeNavigationUrl = codeNavigationUrl;
|
|
1846
|
+
this.tokenProvider = tokenProvider;
|
|
1847
|
+
this.fetchFn = fetchFn;
|
|
1848
|
+
}
|
|
1849
|
+
async search(params) {
|
|
1850
|
+
return executeWithTokenRefresh({
|
|
1851
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
1852
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
1853
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
1854
|
+
executeWithToken: (token) => this.executeUnifiedSearch(token, params)
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
async searchStatus(searchRef) {
|
|
1858
|
+
return executeWithTokenRefresh({
|
|
1859
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
1860
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
1861
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
1862
|
+
executeWithToken: (token) => this.executeUnifiedSearchStatus(token, searchRef)
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
async executeUnifiedSearch(token, params) {
|
|
1866
|
+
if (params.targets.length === 0) {
|
|
1867
|
+
throw new CodeNavigationValidationError("At least one search target is required.");
|
|
1868
|
+
}
|
|
1869
|
+
let response;
|
|
1870
|
+
const variables = {
|
|
1871
|
+
targets: params.targets.map((target) => ({
|
|
1872
|
+
registry: target.registry,
|
|
1873
|
+
name: target.packageName,
|
|
1874
|
+
version: target.version,
|
|
1875
|
+
repoUrl: target.repoUrl,
|
|
1876
|
+
gitRef: target.gitRef
|
|
1877
|
+
})),
|
|
1878
|
+
query: params.query,
|
|
1879
|
+
sources: params.sources,
|
|
1880
|
+
filters: params.filters,
|
|
1881
|
+
allowPartialResults: params.allowPartialResults ?? false,
|
|
1882
|
+
limit: params.limit,
|
|
1883
|
+
offset: params.offset,
|
|
1884
|
+
waitTimeoutMs: params.waitTimeoutMs
|
|
1885
|
+
};
|
|
1886
|
+
debugUnifiedSearchRequest(variables);
|
|
1887
|
+
debugGraphqlWireRequest("search", UNIFIED_SEARCH_QUERY, variables);
|
|
1888
|
+
try {
|
|
1889
|
+
response = await postPkgseerGraphql({
|
|
1890
|
+
endpointUrl: this.codeNavigationUrl,
|
|
1891
|
+
token,
|
|
1892
|
+
query: UNIFIED_SEARCH_QUERY,
|
|
1893
|
+
variables,
|
|
1894
|
+
fetchFn: this.fetchFn
|
|
1895
|
+
});
|
|
1896
|
+
} catch (cause) {
|
|
1897
|
+
if (cause instanceof PkgseerTransportError) {
|
|
1898
|
+
throw new CodeNavigationNetworkError("Could not reach the code navigation service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
1899
|
+
}
|
|
1900
|
+
throw cause;
|
|
1901
|
+
}
|
|
1902
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1903
|
+
throw this.createHttpError(response);
|
|
1904
|
+
}
|
|
1905
|
+
const parsed = unifiedSearchGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
1906
|
+
if (!parsed.success) {
|
|
1907
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
1908
|
+
}
|
|
1909
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
1910
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
1911
|
+
}
|
|
1912
|
+
const data = parsed.data.data?.search;
|
|
1913
|
+
if (!data) {
|
|
1914
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
1915
|
+
}
|
|
1916
|
+
return this.normaliseUnifiedSearchOutcome(data);
|
|
1917
|
+
}
|
|
1918
|
+
async executeUnifiedSearchStatus(token, searchRef) {
|
|
1919
|
+
let response;
|
|
1920
|
+
try {
|
|
1921
|
+
response = await postPkgseerGraphql({
|
|
1922
|
+
endpointUrl: this.codeNavigationUrl,
|
|
1923
|
+
token,
|
|
1924
|
+
query: UNIFIED_SEARCH_STATUS_QUERY,
|
|
1925
|
+
variables: {
|
|
1926
|
+
searchRef,
|
|
1927
|
+
includeResults: true
|
|
1928
|
+
},
|
|
1929
|
+
fetchFn: this.fetchFn
|
|
1930
|
+
});
|
|
1931
|
+
} catch (cause) {
|
|
1932
|
+
if (cause instanceof PkgseerTransportError) {
|
|
1933
|
+
throw new CodeNavigationNetworkError("Could not reach the code navigation service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
1934
|
+
}
|
|
1935
|
+
throw cause;
|
|
1936
|
+
}
|
|
1937
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1938
|
+
throw this.createHttpError(response);
|
|
1939
|
+
}
|
|
1940
|
+
const parsed = unifiedSearchStatusGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
1941
|
+
if (!parsed.success) {
|
|
1942
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
1943
|
+
}
|
|
1944
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
1945
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
1946
|
+
}
|
|
1947
|
+
const data = parsed.data.data?.discoverySearchProgress;
|
|
1948
|
+
if (!data) {
|
|
1949
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
1950
|
+
}
|
|
1951
|
+
const progress = this.normaliseUnifiedSearchProgress(data);
|
|
1952
|
+
const result = data.results ? this.normaliseUnifiedSearchResult(data.results) : undefined;
|
|
1953
|
+
if (result && progress.status === "COMPLETED") {
|
|
1954
|
+
return {
|
|
1955
|
+
state: "completed",
|
|
1956
|
+
completed: true,
|
|
1957
|
+
searchRef: progress.searchRef,
|
|
1958
|
+
result,
|
|
1959
|
+
progress
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
return {
|
|
1963
|
+
state: "incomplete",
|
|
1964
|
+
completed: false,
|
|
1965
|
+
searchRef: progress.searchRef,
|
|
1966
|
+
result,
|
|
1967
|
+
progress
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
createHttpError(response) {
|
|
1971
|
+
const status = response.status;
|
|
1972
|
+
const detail = parseDetail2(response.responseBody);
|
|
1973
|
+
if (status === 401) {
|
|
1974
|
+
return new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
1975
|
+
}
|
|
1976
|
+
if (status === 403) {
|
|
1977
|
+
return new CodeNavigationAccessError(detail ?? "Code navigation access denied.");
|
|
1978
|
+
}
|
|
1979
|
+
if (status >= 500) {
|
|
1980
|
+
return new CodeNavigationBackendError(detail ? `Server error (${status}): ${detail}` : `Server error (${status})`, status);
|
|
1981
|
+
}
|
|
1982
|
+
return new CodeNavigationBackendError(detail ?? `Request failed with status ${status}`, status);
|
|
1983
|
+
}
|
|
1984
|
+
createGraphQLError(errors) {
|
|
1985
|
+
const message = errors.map((error) => error.message).join(", ");
|
|
1986
|
+
const extensions = getPrimaryExtensions(errors);
|
|
1987
|
+
const code = typeof extensions?.code === "string" ? extensions.code : undefined;
|
|
1988
|
+
const retryable = typeof extensions?.retryable === "boolean" ? extensions.retryable : undefined;
|
|
1989
|
+
const indexingRef = getGraphQLIndexingRef(errors);
|
|
1990
|
+
switch (code) {
|
|
1991
|
+
case "PACKAGE_INDEXING":
|
|
1992
|
+
return new CodeNavigationIndexingError(this.createIndexingMessage(indexingRef), indexingRef, parseAvailableVersions(extensions));
|
|
1993
|
+
case "GREP_PATTERN_TOO_SHORT":
|
|
1994
|
+
case "GREP_PATTERN_TOO_LONG":
|
|
1995
|
+
case "GREP_PATTERN_INVALID":
|
|
1996
|
+
case "GREP_INVALID_REGEX":
|
|
1997
|
+
case "GREP_UNSUPPORTED_PATTERN":
|
|
1998
|
+
case "GREP_PATTERN_TOO_UNSELECTIVE":
|
|
1999
|
+
case "GREP_SCOPE_REQUIRED":
|
|
2000
|
+
case "GREP_SELECTOR_INVALID":
|
|
2001
|
+
case "GREP_CURSOR_INVALID":
|
|
2002
|
+
case "GREP_CONTEXT_TOO_LARGE":
|
|
2003
|
+
case "GREP_CONTEXT_NEGATIVE":
|
|
2004
|
+
case "GREP_MAX_MATCHES_TOO_LARGE":
|
|
2005
|
+
case "GREP_MAX_MATCHES_INVALID":
|
|
2006
|
+
return new CodeNavigationValidationError(message);
|
|
2007
|
+
case "VERSION_NOT_FOUND":
|
|
2008
|
+
return new CodeNavigationVersionNotFoundError(message, typeof extensions?.package === "string" ? extensions.package : undefined, typeof extensions?.requested_version === "string" ? extensions.requested_version : undefined, typeof extensions?.latest_indexed === "string" ? extensions.latest_indexed : undefined, parseAvailableVersions(extensions));
|
|
2009
|
+
case "NOT_FOUND":
|
|
2010
|
+
case "PACKAGE_NOT_FOUND":
|
|
2011
|
+
case "NO_REPOSITORY_URL":
|
|
2012
|
+
return new CodeNavigationTargetNotFoundError(message);
|
|
2013
|
+
case "FILE_NOT_FOUND":
|
|
2014
|
+
return new CodeNavigationFileNotFoundError(message, typeof extensions?.file_path === "string" ? extensions.file_path : typeof extensions?.filePath === "string" ? extensions.filePath : undefined);
|
|
2015
|
+
case "UNSUPPORTED_REGISTRY":
|
|
2016
|
+
case "VALIDATION_ERROR":
|
|
2017
|
+
return new CodeNavigationValidationError(message);
|
|
2018
|
+
case "FEATURE_FLAG_REQUIRED":
|
|
2019
|
+
return new CodeNavigationFeatureFlagRequiredError(message);
|
|
2020
|
+
case "UNAUTHORIZED":
|
|
2021
|
+
return new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
2022
|
+
case "FORBIDDEN":
|
|
2023
|
+
return new CodeNavigationAccessError("Code navigation access denied. This feature may not be enabled for your account.");
|
|
2024
|
+
case "UPSTREAM_ERROR":
|
|
2025
|
+
case "TIMEOUT":
|
|
2026
|
+
case "RATE_LIMITED":
|
|
2027
|
+
case "GREP_FILE_TOO_LARGE":
|
|
2028
|
+
case "GREP_TIMEOUT":
|
|
2029
|
+
case "GREP_SERVICE_UNAVAILABLE":
|
|
2030
|
+
case "GREP_FAILED":
|
|
2031
|
+
case "GREP_INDEX_NOT_AVAILABLE":
|
|
2032
|
+
case "INTERNAL_ERROR":
|
|
2033
|
+
case "UNKNOWN_ERROR":
|
|
2034
|
+
return new CodeNavigationBackendError(message, undefined, code, retryable);
|
|
2035
|
+
default:
|
|
2036
|
+
break;
|
|
2037
|
+
}
|
|
2038
|
+
if (code === undefined) {
|
|
2039
|
+
if (isAuthMessage(message)) {
|
|
2040
|
+
return new CodeNavigationAccessError("Code navigation access denied. This feature may not be enabled for your account.");
|
|
2041
|
+
}
|
|
2042
|
+
if (isUnresolvableMessage(message)) {
|
|
2043
|
+
return new CodeNavigationUnresolvableError(message);
|
|
2044
|
+
}
|
|
2045
|
+
if (isTargetNotFoundMessage(message)) {
|
|
2046
|
+
return new CodeNavigationTargetNotFoundError(message);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return new CodeNavigationBackendError(message, undefined, code, retryable);
|
|
2050
|
+
}
|
|
2051
|
+
createIndexingMessage(indexingRef) {
|
|
2052
|
+
const base = "Target is still indexing. Indexing usually completes within 30 seconds. Retry this request, or pass a longer wait timeout (CLI: `--wait 60000`, MCP: `wait_timeout_ms: 60000`) to block until ready.";
|
|
2053
|
+
if (indexingRef) {
|
|
2054
|
+
return `${base} Indexing reference: ${indexingRef}.`;
|
|
2055
|
+
}
|
|
2056
|
+
return base;
|
|
2057
|
+
}
|
|
2058
|
+
normaliseUnifiedSearchOutcome(data) {
|
|
2059
|
+
const progress = data.progress ? this.normaliseUnifiedSearchProgress(data.progress) : undefined;
|
|
2060
|
+
if (data.completed) {
|
|
2061
|
+
if (!data.result) {
|
|
2062
|
+
throw new MalformedCodeNavigationResponseError("Completed unified search response missing result payload.");
|
|
2063
|
+
}
|
|
2064
|
+
return {
|
|
2065
|
+
state: "completed",
|
|
2066
|
+
completed: true,
|
|
2067
|
+
searchRef: data.searchRef ?? undefined,
|
|
2068
|
+
result: this.normaliseUnifiedSearchResult(data.result),
|
|
2069
|
+
progress
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
const searchRef = data.searchRef ?? progress?.searchRef;
|
|
2073
|
+
if (!searchRef) {
|
|
2074
|
+
throw new MalformedCodeNavigationResponseError("Incomplete unified search response missing search reference.");
|
|
2075
|
+
}
|
|
2076
|
+
const result = data.result ? this.normaliseUnifiedSearchResult(data.result) : undefined;
|
|
2077
|
+
return {
|
|
2078
|
+
state: "incomplete",
|
|
2079
|
+
completed: false,
|
|
2080
|
+
searchRef,
|
|
2081
|
+
result,
|
|
2082
|
+
progress
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
normaliseUnifiedSearchResult(result) {
|
|
2086
|
+
return {
|
|
2087
|
+
query: result.query,
|
|
2088
|
+
queryWarnings: result.queryWarnings,
|
|
2089
|
+
sources: result.sources,
|
|
2090
|
+
results: result.results.map((entry) => ({
|
|
2091
|
+
id: entry.id,
|
|
2092
|
+
resultType: entry.resultType,
|
|
2093
|
+
targetLabel: entry.targetLabel,
|
|
2094
|
+
title: entry.title ?? undefined,
|
|
2095
|
+
summary: entry.summary ?? undefined,
|
|
2096
|
+
score: entry.score ?? undefined,
|
|
2097
|
+
highlights: entry.highlights ? {
|
|
2098
|
+
title: entry.highlights.title ?? undefined,
|
|
2099
|
+
summary: entry.highlights.summary ?? undefined
|
|
2100
|
+
} : undefined,
|
|
2101
|
+
locator: {
|
|
2102
|
+
registry: entry.locator.registry ?? undefined,
|
|
2103
|
+
packageName: entry.locator.packageName ?? undefined,
|
|
2104
|
+
version: entry.locator.version ?? undefined,
|
|
2105
|
+
pageId: entry.locator.pageId ?? undefined,
|
|
2106
|
+
sourceKind: entry.locator.sourceKind ?? undefined,
|
|
2107
|
+
sourceUrl: entry.locator.sourceUrl ?? undefined,
|
|
2108
|
+
repoUrl: entry.locator.repoUrl ?? undefined,
|
|
2109
|
+
gitRef: entry.locator.gitRef ?? undefined,
|
|
2110
|
+
requestedRef: entry.locator.requestedRef ?? undefined,
|
|
2111
|
+
filePath: entry.locator.filePath ?? undefined,
|
|
2112
|
+
startLine: entry.locator.startLine ?? undefined,
|
|
2113
|
+
endLine: entry.locator.endLine ?? undefined,
|
|
2114
|
+
fileContentHash: entry.locator.fileContentHash ?? undefined,
|
|
2115
|
+
symbolRef: entry.locator.symbolRef ?? undefined,
|
|
2116
|
+
qualifiedPath: entry.locator.qualifiedPath ?? undefined,
|
|
2117
|
+
kind: entry.locator.kind ?? undefined,
|
|
2118
|
+
category: entry.locator.category ?? undefined,
|
|
2119
|
+
language: entry.locator.language ?? undefined
|
|
2120
|
+
}
|
|
2121
|
+
})),
|
|
2122
|
+
page: {
|
|
2123
|
+
offset: result.page.offset,
|
|
2124
|
+
limit: result.page.limit,
|
|
2125
|
+
returned: result.page.returned,
|
|
2126
|
+
hasMore: result.page.hasMore
|
|
2127
|
+
},
|
|
2128
|
+
partialResults: result.partialResults,
|
|
2129
|
+
sourceStatus: result.sourceStatus.map((entry) => ({
|
|
2130
|
+
source: entry.source,
|
|
2131
|
+
targetLabel: entry.targetLabel,
|
|
2132
|
+
indexingStatus: entry.indexingStatus ?? undefined,
|
|
2133
|
+
codeIndexState: entry.codeIndexState ?? undefined,
|
|
2134
|
+
resultCount: entry.resultCount ?? undefined,
|
|
2135
|
+
appliedFilters: entry.appliedFilters,
|
|
2136
|
+
ignoredFilters: entry.ignoredFilters,
|
|
2137
|
+
incompatibleFilters: entry.incompatibleFilters,
|
|
2138
|
+
appliedQueryFeatures: entry.appliedQueryFeatures,
|
|
2139
|
+
ignoredQueryFeatures: entry.ignoredQueryFeatures,
|
|
2140
|
+
incompatibleQueryFeatures: entry.incompatibleQueryFeatures,
|
|
2141
|
+
note: entry.note ?? undefined
|
|
2142
|
+
}))
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
normaliseUnifiedSearchProgress(progress) {
|
|
2146
|
+
return {
|
|
2147
|
+
searchRef: progress.searchRef,
|
|
2148
|
+
status: progress.status,
|
|
2149
|
+
targetsTotal: progress.targetsTotal,
|
|
2150
|
+
targetsReady: progress.targetsReady,
|
|
2151
|
+
elapsedMs: progress.elapsedMs,
|
|
2152
|
+
query: progress.query,
|
|
2153
|
+
queryWarnings: progress.queryWarnings,
|
|
2154
|
+
sources: progress.sources,
|
|
2155
|
+
expiresAt: progress.expiresAt ?? undefined
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
throwIfIndexing(data) {
|
|
2159
|
+
if (data.codeIndexState === "INDEXING") {
|
|
2160
|
+
throw new CodeNavigationIndexingError(this.createIndexingMessage(data.indexingRef ?? undefined), data.indexingRef ?? undefined, data.availableVersions?.map((entry) => ({
|
|
2161
|
+
version: entry.version ?? undefined,
|
|
2162
|
+
ref: entry.ref
|
|
2163
|
+
})));
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
async listFiles(params) {
|
|
2167
|
+
return executeWithTokenRefresh({
|
|
2168
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
2169
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
2170
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
2171
|
+
executeWithToken: (token) => this.executeListFiles(token, params)
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
async executeListFiles(token, params) {
|
|
2175
|
+
let response;
|
|
2176
|
+
try {
|
|
2177
|
+
response = await postPkgseerGraphql({
|
|
2178
|
+
endpointUrl: this.codeNavigationUrl,
|
|
2179
|
+
token,
|
|
2180
|
+
query: LIST_REPO_FILES_QUERY,
|
|
2181
|
+
variables: {
|
|
2182
|
+
registry: params.target.registry,
|
|
2183
|
+
packageName: params.target.packageName,
|
|
2184
|
+
repoUrl: params.target.repoUrl,
|
|
2185
|
+
gitRef: params.target.gitRef,
|
|
2186
|
+
version: params.target.version,
|
|
2187
|
+
pathPrefix: params.pathPrefix,
|
|
2188
|
+
limit: params.limit,
|
|
2189
|
+
waitTimeoutMs: params.waitTimeoutMs
|
|
2190
|
+
},
|
|
2191
|
+
fetchFn: this.fetchFn
|
|
2192
|
+
});
|
|
2193
|
+
} catch (cause) {
|
|
2194
|
+
if (cause instanceof PkgseerTransportError) {
|
|
2195
|
+
throw new CodeNavigationNetworkError("Could not reach the code navigation service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
2196
|
+
}
|
|
2197
|
+
throw cause;
|
|
2198
|
+
}
|
|
2199
|
+
if (response.status < 200 || response.status >= 300) {
|
|
2200
|
+
throw this.createHttpError(response);
|
|
2201
|
+
}
|
|
2202
|
+
const parsed = listRepoFilesGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
2203
|
+
if (!parsed.success) {
|
|
2204
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2205
|
+
}
|
|
2206
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
2207
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
2208
|
+
}
|
|
2209
|
+
const data = parsed.data.data?.listRepoFiles;
|
|
2210
|
+
if (!data) {
|
|
2211
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2212
|
+
}
|
|
2213
|
+
this.throwIfIndexing(data);
|
|
2214
|
+
return {
|
|
2215
|
+
files: data.files.map((entry) => ({
|
|
2216
|
+
path: entry.path,
|
|
2217
|
+
name: entry.name ?? undefined,
|
|
2218
|
+
language: entry.language ?? undefined,
|
|
2219
|
+
fileType: entry.fileType ?? undefined,
|
|
2220
|
+
byteSize: entry.byteSize ?? undefined
|
|
2221
|
+
})),
|
|
2222
|
+
total: data.total,
|
|
2223
|
+
hasMore: data.hasMore,
|
|
2224
|
+
indexedVersion: data.indexedVersion ?? undefined,
|
|
2225
|
+
resolution: data.resolution ? {
|
|
2226
|
+
requestedVersion: data.resolution.requestedVersion ?? undefined,
|
|
2227
|
+
requestedRef: data.resolution.requestedRef ?? undefined,
|
|
2228
|
+
resolvedRef: data.resolution.resolvedRef ?? undefined,
|
|
2229
|
+
commitSha: data.resolution.commitSha ?? undefined
|
|
2230
|
+
} : undefined,
|
|
2231
|
+
hint: data.diagnostics?.hint ?? undefined
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
async readFile(params) {
|
|
2235
|
+
return executeWithTokenRefresh({
|
|
2236
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
2237
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
2238
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
2239
|
+
executeWithToken: (token) => this.executeReadFile(token, params)
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
async executeReadFile(token, params) {
|
|
2243
|
+
let response;
|
|
2244
|
+
try {
|
|
2245
|
+
response = await postPkgseerGraphql({
|
|
2246
|
+
endpointUrl: this.codeNavigationUrl,
|
|
2247
|
+
token,
|
|
2248
|
+
query: FETCH_CODE_CONTEXT_QUERY,
|
|
2249
|
+
variables: {
|
|
2250
|
+
registry: params.target.registry,
|
|
2251
|
+
packageName: params.target.packageName,
|
|
2252
|
+
repoUrl: params.target.repoUrl,
|
|
2253
|
+
gitRef: params.target.gitRef,
|
|
2254
|
+
version: params.target.version,
|
|
2255
|
+
filePath: params.filePath,
|
|
2256
|
+
startLine: params.startLine,
|
|
2257
|
+
endLine: params.endLine,
|
|
2258
|
+
waitTimeoutMs: params.waitTimeoutMs
|
|
2259
|
+
},
|
|
2260
|
+
fetchFn: this.fetchFn
|
|
2261
|
+
});
|
|
2262
|
+
} catch (cause) {
|
|
2263
|
+
if (cause instanceof PkgseerTransportError) {
|
|
2264
|
+
throw new CodeNavigationNetworkError("Could not reach the code navigation service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
2265
|
+
}
|
|
2266
|
+
throw cause;
|
|
2267
|
+
}
|
|
2268
|
+
if (response.status < 200 || response.status >= 300) {
|
|
2269
|
+
throw this.createHttpError(response);
|
|
2270
|
+
}
|
|
2271
|
+
const parsed = fetchCodeContextGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
2272
|
+
if (!parsed.success) {
|
|
2273
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2274
|
+
}
|
|
2275
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
2276
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
2277
|
+
}
|
|
2278
|
+
const data = parsed.data.data?.fetchCodeContext;
|
|
2279
|
+
if (!data) {
|
|
2280
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2281
|
+
}
|
|
2282
|
+
this.throwIfIndexing({
|
|
2283
|
+
codeIndexState: data.codeIndexState,
|
|
2284
|
+
indexingRef: data.indexingRef
|
|
2285
|
+
});
|
|
2286
|
+
return {
|
|
2287
|
+
filePath: data.filePath ?? undefined,
|
|
2288
|
+
language: data.language ?? undefined,
|
|
2289
|
+
totalLines: data.totalLines ?? undefined,
|
|
2290
|
+
startLine: data.startLine ?? undefined,
|
|
2291
|
+
endLine: data.endLine ?? undefined,
|
|
2292
|
+
content: data.content ?? undefined,
|
|
2293
|
+
isBinary: data.isBinary ?? undefined
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
async grepRepo(params) {
|
|
2297
|
+
return executeWithTokenRefresh({
|
|
2298
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
2299
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
2300
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
2301
|
+
executeWithToken: (token) => this.executeGrepRepo(token, params)
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
async executeGrepRepo(token, params) {
|
|
2305
|
+
let response;
|
|
2306
|
+
try {
|
|
2307
|
+
response = await postPkgseerGraphql({
|
|
2308
|
+
endpointUrl: this.codeNavigationUrl,
|
|
2309
|
+
token,
|
|
2310
|
+
query: buildGrepRepoQuery(params.symbolFields),
|
|
2311
|
+
variables: {
|
|
2312
|
+
registry: params.target.registry,
|
|
2313
|
+
packageName: params.target.packageName,
|
|
2314
|
+
repoUrl: params.target.repoUrl,
|
|
2315
|
+
gitRef: params.target.gitRef,
|
|
2316
|
+
version: params.target.version,
|
|
2317
|
+
waitTimeoutMs: params.waitTimeoutMs,
|
|
2318
|
+
pattern: params.pattern,
|
|
2319
|
+
patternType: params.patternType,
|
|
2320
|
+
caseSensitive: params.caseSensitive,
|
|
2321
|
+
pathSelectors: params.pathSelectors?.map((entry) => ({
|
|
2322
|
+
kind: entry.kind,
|
|
2323
|
+
value: entry.value
|
|
2324
|
+
})),
|
|
2325
|
+
extensions: params.extensions,
|
|
2326
|
+
excludeDocFiles: params.excludeDocFiles,
|
|
2327
|
+
excludeTestFiles: params.excludeTestFiles,
|
|
2328
|
+
allowUnscoped: params.allowUnscoped,
|
|
2329
|
+
contextLinesBefore: params.contextLinesBefore,
|
|
2330
|
+
contextLinesAfter: params.contextLinesAfter,
|
|
2331
|
+
maxMatches: params.maxMatches,
|
|
2332
|
+
maxMatchesPerFile: params.maxMatchesPerFile,
|
|
2333
|
+
cursor: params.cursor,
|
|
2334
|
+
symbolFields: params.symbolFields
|
|
2335
|
+
},
|
|
2336
|
+
fetchFn: this.fetchFn
|
|
2337
|
+
});
|
|
2338
|
+
} catch (cause) {
|
|
2339
|
+
if (cause instanceof PkgseerTransportError) {
|
|
2340
|
+
throw new CodeNavigationNetworkError("Could not reach the code navigation service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
2341
|
+
}
|
|
2342
|
+
throw cause;
|
|
2343
|
+
}
|
|
2344
|
+
if (response.status < 200 || response.status >= 300) {
|
|
2345
|
+
throw this.createHttpError(response);
|
|
2346
|
+
}
|
|
2347
|
+
const parsed = grepRepoGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
2348
|
+
if (!parsed.success) {
|
|
2349
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2350
|
+
}
|
|
2351
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
2352
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
2353
|
+
}
|
|
2354
|
+
const data = parsed.data.data?.grepRepo;
|
|
2355
|
+
if (!data) {
|
|
2356
|
+
throw new MalformedCodeNavigationResponseError("Malformed response from code navigation service.");
|
|
2357
|
+
}
|
|
2358
|
+
this.throwIfIndexing(data);
|
|
2359
|
+
return {
|
|
2360
|
+
matches: data.matches.map((entry) => ({
|
|
2361
|
+
filePath: entry.filePath,
|
|
2362
|
+
line: entry.line,
|
|
2363
|
+
matchStartByte: entry.matchStartByte,
|
|
2364
|
+
matchEndByte: entry.matchEndByte,
|
|
2365
|
+
lineContent: entry.lineContent,
|
|
2366
|
+
contextBefore: entry.contextBefore ?? undefined,
|
|
2367
|
+
contextAfter: entry.contextAfter ?? undefined,
|
|
2368
|
+
fileContentHash: entry.fileContentHash ?? undefined,
|
|
2369
|
+
fileIntent: entry.fileIntent ?? undefined,
|
|
2370
|
+
symbolRowId: entry.symbolRowId ?? undefined,
|
|
2371
|
+
symbol: entry.symbol ? {
|
|
2372
|
+
symbolRef: entry.symbol.symbolRef,
|
|
2373
|
+
name: entry.symbol.name,
|
|
2374
|
+
qualifiedPath: entry.symbol.qualifiedPath ?? undefined,
|
|
2375
|
+
kind: entry.symbol.kind ?? undefined,
|
|
2376
|
+
category: entry.symbol.category ?? undefined,
|
|
2377
|
+
arity: entry.symbol.arity ?? undefined,
|
|
2378
|
+
isPublic: entry.symbol.isPublic ?? undefined,
|
|
2379
|
+
filePath: entry.symbol.filePath ?? undefined,
|
|
2380
|
+
startLine: entry.symbol.startLine ?? undefined,
|
|
2381
|
+
endLine: entry.symbol.endLine ?? undefined,
|
|
2382
|
+
code: entry.symbol.code ?? undefined,
|
|
2383
|
+
callerCount: entry.symbol.callerCount ?? undefined,
|
|
2384
|
+
contentHash: entry.symbol.contentHash ?? undefined,
|
|
2385
|
+
parentSymbolRef: entry.symbol.parentSymbolRef ?? undefined,
|
|
2386
|
+
parentPath: entry.symbol.parentPath ?? undefined
|
|
2387
|
+
} : undefined
|
|
2388
|
+
})),
|
|
2389
|
+
nextCursor: data.nextCursor ?? undefined,
|
|
2390
|
+
hasMore: data.hasMore,
|
|
2391
|
+
truncatedReason: data.truncatedReason,
|
|
2392
|
+
routeTaken: data.routeTaken ?? undefined,
|
|
2393
|
+
filesScanned: data.filesScanned,
|
|
2394
|
+
filesInScope: data.filesInScope,
|
|
2395
|
+
binaryFilesSkipped: data.binaryFilesSkipped,
|
|
2396
|
+
filesTooLargeSkipped: data.filesTooLargeSkipped,
|
|
2397
|
+
totalMatches: data.totalMatches,
|
|
2398
|
+
uniqueFilesMatched: data.uniqueFilesMatched,
|
|
2399
|
+
indexedVersion: data.indexedVersion ?? undefined,
|
|
2400
|
+
resolution: data.resolution ? {
|
|
2401
|
+
requestedVersion: data.resolution.requestedVersion ?? undefined,
|
|
2402
|
+
requestedRef: data.resolution.requestedRef ?? undefined,
|
|
2403
|
+
resolvedRef: data.resolution.resolvedRef ?? undefined,
|
|
2404
|
+
commitSha: data.resolution.commitSha ?? undefined
|
|
2405
|
+
} : undefined
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
function parseDetail2(body) {
|
|
2410
|
+
if (!body)
|
|
2411
|
+
return;
|
|
2412
|
+
try {
|
|
2413
|
+
const parsed = JSON.parse(body);
|
|
2414
|
+
if (typeof parsed.detail === "string")
|
|
2415
|
+
return parsed.detail;
|
|
2416
|
+
if (typeof parsed.error === "string")
|
|
2417
|
+
return parsed.error;
|
|
2418
|
+
} catch {
|
|
2419
|
+
return body;
|
|
2420
|
+
}
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
function getPrimaryExtensions(errors) {
|
|
2424
|
+
for (const error of errors) {
|
|
2425
|
+
if (error.extensions && Object.keys(error.extensions).length > 0) {
|
|
2426
|
+
return error.extensions;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
function getGraphQLIndexingRef(errors) {
|
|
2432
|
+
for (const error of errors) {
|
|
2433
|
+
const indexingRef = error.extensions?.indexing_ref ?? error.extensions?.indexingRef;
|
|
2434
|
+
if (typeof indexingRef === "string")
|
|
2435
|
+
return indexingRef;
|
|
2436
|
+
}
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
function parseAvailableVersions(extensions) {
|
|
2440
|
+
const raw = extensions?.available_versions ?? extensions?.availableVersions;
|
|
2441
|
+
if (!Array.isArray(raw))
|
|
2442
|
+
return;
|
|
2443
|
+
const parsed = [];
|
|
2444
|
+
for (const item of raw) {
|
|
2445
|
+
if (item && typeof item === "object" && "ref" in item) {
|
|
2446
|
+
const entry = item;
|
|
2447
|
+
if (typeof entry.ref === "string") {
|
|
2448
|
+
parsed.push({
|
|
2449
|
+
ref: entry.ref,
|
|
2450
|
+
version: typeof entry.version === "string" ? entry.version : undefined
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
return parsed.length > 0 ? parsed : undefined;
|
|
2456
|
+
}
|
|
2457
|
+
function isAuthMessage(message) {
|
|
2458
|
+
const lower = message.toLowerCase();
|
|
2459
|
+
return lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("permission") || lower.includes("authentication");
|
|
2460
|
+
}
|
|
2461
|
+
function isTargetNotFoundMessage(message) {
|
|
2462
|
+
const lower = message.toLowerCase();
|
|
2463
|
+
return lower.includes("not found") || lower.includes("unknown package") || lower.includes("no such package") || lower.includes("does not exist");
|
|
2464
|
+
}
|
|
2465
|
+
function isUnresolvableMessage(message) {
|
|
2466
|
+
const lower = message.toLowerCase();
|
|
2467
|
+
return lower.includes("could not resolve") || lower.includes("cannot resolve");
|
|
2468
|
+
}
|
|
2469
|
+
// src/services/config.ts
|
|
2470
|
+
var DEFAULT_MCP_URL = "https://mcp.githits.com";
|
|
2471
|
+
var DEFAULT_API_URL = "https://api.githits.com";
|
|
2472
|
+
var DEFAULT_CODE_NAV_URL = "https://pkgseer.dev";
|
|
2473
|
+
function getMcpUrl() {
|
|
2474
|
+
return process.env.GITHITS_MCP_URL ?? DEFAULT_MCP_URL;
|
|
2475
|
+
}
|
|
2476
|
+
function getApiUrl() {
|
|
2477
|
+
return process.env.GITHITS_API_URL ?? DEFAULT_API_URL;
|
|
2478
|
+
}
|
|
2479
|
+
function getCodeNavigationUrl() {
|
|
2480
|
+
const explicitUrl = process.env.GITHITS_CODE_NAV_URL ?? process.env.PKGSEER_URL;
|
|
2481
|
+
if (explicitUrl) {
|
|
2482
|
+
return explicitUrl;
|
|
2483
|
+
}
|
|
2484
|
+
const usingDefaultEnvironment = getMcpUrl() === DEFAULT_MCP_URL && getApiUrl() === DEFAULT_API_URL;
|
|
2485
|
+
return usingDefaultEnvironment ? DEFAULT_CODE_NAV_URL : undefined;
|
|
2486
|
+
}
|
|
2487
|
+
function getEnvApiToken() {
|
|
2488
|
+
return process.env.GITHITS_API_TOKEN;
|
|
2489
|
+
}
|
|
2490
|
+
function isCodeNavigationCliOverrideEnabled() {
|
|
2491
|
+
const raw = process.env.GITHITS_CODE_NAVIGATION;
|
|
2492
|
+
if (!raw)
|
|
2493
|
+
return false;
|
|
2494
|
+
const normalized = raw.toLowerCase();
|
|
2495
|
+
return normalized !== "0" && normalized !== "false" && normalized !== "";
|
|
2496
|
+
}
|
|
2497
|
+
// src/services/exec-service.ts
|
|
2498
|
+
import { spawn } from "node:child_process";
|
|
2499
|
+
|
|
2500
|
+
class ExecServiceImpl {
|
|
2501
|
+
async exec(command, args) {
|
|
2502
|
+
return new Promise((resolve, reject) => {
|
|
2503
|
+
const child = spawn(command, args, {
|
|
2504
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2505
|
+
env: { ...process.env },
|
|
2506
|
+
...process.platform === "win32" && { shell: true }
|
|
2507
|
+
});
|
|
2508
|
+
const stdoutChunks = [];
|
|
2509
|
+
const stderrChunks = [];
|
|
2510
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
2511
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
2512
|
+
child.on("error", (error) => {
|
|
2513
|
+
reject(error);
|
|
2514
|
+
});
|
|
2515
|
+
child.on("close", (code) => {
|
|
2516
|
+
resolve({
|
|
2517
|
+
exitCode: code ?? 1,
|
|
2518
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
2519
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8")
|
|
2520
|
+
});
|
|
2521
|
+
});
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
// src/services/filesystem-service.ts
|
|
2526
|
+
import {
|
|
2527
|
+
mkdir,
|
|
2528
|
+
readdir,
|
|
2529
|
+
readFile,
|
|
2530
|
+
rename,
|
|
2531
|
+
stat,
|
|
2532
|
+
unlink,
|
|
2533
|
+
writeFile
|
|
2534
|
+
} from "node:fs/promises";
|
|
2535
|
+
import { homedir } from "node:os";
|
|
2536
|
+
import { dirname, join } from "node:path";
|
|
2537
|
+
|
|
2538
|
+
class FileSystemServiceImpl {
|
|
2539
|
+
async readFile(path) {
|
|
2540
|
+
return readFile(path, "utf-8");
|
|
2541
|
+
}
|
|
2542
|
+
async writeFile(path, contents, mode) {
|
|
2543
|
+
await writeFile(path, contents, { mode });
|
|
2544
|
+
}
|
|
2545
|
+
async deleteFile(path) {
|
|
2546
|
+
try {
|
|
2547
|
+
await unlink(path);
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
if (error.code !== "ENOENT") {
|
|
2550
|
+
throw error;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
async exists(path) {
|
|
2555
|
+
try {
|
|
2556
|
+
await stat(path);
|
|
2557
|
+
return true;
|
|
2558
|
+
} catch {
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
async ensureDir(path, mode) {
|
|
2563
|
+
await mkdir(path, { recursive: true, mode });
|
|
2564
|
+
}
|
|
2565
|
+
getHomeDir() {
|
|
2566
|
+
return homedir();
|
|
2567
|
+
}
|
|
2568
|
+
joinPath(...segments) {
|
|
2569
|
+
return join(...segments);
|
|
2570
|
+
}
|
|
2571
|
+
getCwd() {
|
|
2572
|
+
return process.cwd();
|
|
2573
|
+
}
|
|
2574
|
+
getDirname(path) {
|
|
2575
|
+
return dirname(path);
|
|
2576
|
+
}
|
|
2577
|
+
async readdir(path) {
|
|
2578
|
+
return readdir(path);
|
|
2579
|
+
}
|
|
2580
|
+
async isDirectory(path) {
|
|
2581
|
+
try {
|
|
2582
|
+
const stats = await stat(path);
|
|
2583
|
+
return stats.isDirectory();
|
|
2584
|
+
} catch {
|
|
2585
|
+
return false;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
async atomicWriteFile(path, contents) {
|
|
2589
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
2590
|
+
let mode = 384;
|
|
2591
|
+
try {
|
|
2592
|
+
const existing = await stat(path);
|
|
2593
|
+
mode = existing.mode & 511;
|
|
2594
|
+
} catch {}
|
|
2595
|
+
try {
|
|
2596
|
+
await writeFile(tmpPath, contents, { mode });
|
|
2597
|
+
await rename(tmpPath, path);
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
try {
|
|
2600
|
+
await unlink(tmpPath);
|
|
2601
|
+
} catch {}
|
|
2602
|
+
throw error;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
// src/services/keychain-auth-storage.ts
|
|
2607
|
+
var SERVICE_NAME = "githits";
|
|
2608
|
+
var TOKEN_PREFIX = "v1:tokens:";
|
|
2609
|
+
var CLIENT_PREFIX = "v1:client:";
|
|
2610
|
+
function parseJsonOrNull2(json) {
|
|
2611
|
+
if (json === null)
|
|
2612
|
+
return null;
|
|
2613
|
+
try {
|
|
2614
|
+
const parsed = JSON.parse(json);
|
|
2615
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
2616
|
+
return null;
|
|
2617
|
+
return parsed;
|
|
2618
|
+
} catch {
|
|
2619
|
+
return null;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function isValidTokenData(data) {
|
|
2623
|
+
if (typeof data !== "object" || data === null)
|
|
2624
|
+
return false;
|
|
2625
|
+
const d = data;
|
|
2626
|
+
return typeof d.accessToken === "string" && d.accessToken.length > 0 && typeof d.refreshToken === "string" && d.refreshToken.length > 0 && typeof d.createdAt === "string" && d.createdAt.length > 0 && (d.expiresAt === null || typeof d.expiresAt === "string" && d.expiresAt.length > 0);
|
|
2627
|
+
}
|
|
2628
|
+
function isValidClientRegistration(data) {
|
|
2629
|
+
if (typeof data !== "object" || data === null)
|
|
2630
|
+
return false;
|
|
2631
|
+
const d = data;
|
|
2632
|
+
return typeof d.clientId === "string" && d.clientId.length > 0 && typeof d.clientSecret === "string" && d.clientSecret.length > 0 && typeof d.redirectUri === "string" && d.redirectUri.length > 0 && typeof d.registeredAt === "string" && d.registeredAt.length > 0;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
class KeychainAuthStorage {
|
|
2636
|
+
keyring;
|
|
2637
|
+
constructor(keyring) {
|
|
2638
|
+
this.keyring = keyring;
|
|
2639
|
+
}
|
|
2640
|
+
async loadTokens(baseUrl2) {
|
|
2641
|
+
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2642
|
+
const json = this.keyring.getPassword(SERVICE_NAME, key);
|
|
2643
|
+
const data = parseJsonOrNull2(json);
|
|
2644
|
+
if (data !== null && !isValidTokenData(data))
|
|
2645
|
+
return null;
|
|
2646
|
+
return data;
|
|
2647
|
+
}
|
|
2648
|
+
async saveTokens(baseUrl2, data) {
|
|
2649
|
+
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2650
|
+
this.keyring.setPassword(SERVICE_NAME, key, JSON.stringify(data));
|
|
2651
|
+
}
|
|
2652
|
+
async clearTokens(baseUrl2) {
|
|
2653
|
+
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2654
|
+
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2655
|
+
}
|
|
2656
|
+
async loadClient(baseUrl2) {
|
|
2657
|
+
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2658
|
+
const json = this.keyring.getPassword(SERVICE_NAME, key);
|
|
2659
|
+
const data = parseJsonOrNull2(json);
|
|
2660
|
+
if (data !== null && !isValidClientRegistration(data))
|
|
2661
|
+
return null;
|
|
2662
|
+
return data;
|
|
2663
|
+
}
|
|
2664
|
+
async saveClient(baseUrl2, data) {
|
|
2665
|
+
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2666
|
+
this.keyring.setPassword(SERVICE_NAME, key, JSON.stringify(data));
|
|
2667
|
+
}
|
|
2668
|
+
async clearClient(baseUrl2) {
|
|
2669
|
+
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2670
|
+
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2671
|
+
}
|
|
2672
|
+
getStorageLocation() {
|
|
2673
|
+
switch (process.platform) {
|
|
2674
|
+
case "darwin":
|
|
2675
|
+
return "macOS Keychain (githits)";
|
|
2676
|
+
case "win32":
|
|
2677
|
+
return "Windows Credential Manager (githits)";
|
|
2678
|
+
default:
|
|
2679
|
+
return "System keychain (githits)";
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
// src/services/keyring-service.ts
|
|
2684
|
+
import { Entry } from "@napi-rs/keyring";
|
|
2685
|
+
|
|
2686
|
+
class KeychainUnavailableError extends Error {
|
|
2687
|
+
constructor(message, cause) {
|
|
2688
|
+
super(message);
|
|
2689
|
+
this.name = "KeychainUnavailableError";
|
|
2690
|
+
this.cause = cause;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
function wrapKeyringError(error) {
|
|
2694
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2695
|
+
throw new KeychainUnavailableError(`System keychain unavailable: ${message}`, error);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
class KeyringServiceImpl {
|
|
2699
|
+
getPassword(service, account) {
|
|
2700
|
+
try {
|
|
2701
|
+
return new Entry(service, account).getPassword();
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
wrapKeyringError(error);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
setPassword(service, account, password) {
|
|
2707
|
+
try {
|
|
2708
|
+
new Entry(service, account).setPassword(password);
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
wrapKeyringError(error);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
deletePassword(service, account) {
|
|
2714
|
+
try {
|
|
2715
|
+
return new Entry(service, account).deleteCredential();
|
|
2716
|
+
} catch (error) {
|
|
2717
|
+
wrapKeyringError(error);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
// src/services/migrating-auth-storage.ts
|
|
2722
|
+
class MigratingAuthStorage {
|
|
2723
|
+
primary;
|
|
2724
|
+
legacy;
|
|
2725
|
+
onPrimaryUnavailable;
|
|
2726
|
+
primaryAvailable = true;
|
|
2727
|
+
warnedOnPrimaryFailure = false;
|
|
2728
|
+
constructor(primary, legacy, onPrimaryUnavailable = () => {}) {
|
|
2729
|
+
this.primary = primary;
|
|
2730
|
+
this.legacy = legacy;
|
|
2731
|
+
this.onPrimaryUnavailable = onPrimaryUnavailable;
|
|
2732
|
+
}
|
|
2733
|
+
async loadTokens(baseUrl2) {
|
|
2734
|
+
if (this.primaryAvailable) {
|
|
2735
|
+
try {
|
|
2736
|
+
const tokens = await this.primary.loadTokens(baseUrl2);
|
|
2737
|
+
if (tokens)
|
|
2738
|
+
return tokens;
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
if (!this.handlePrimaryFailure(error))
|
|
2741
|
+
throw error;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
const legacyTokens = await this.legacy.loadTokens(baseUrl2);
|
|
2745
|
+
if (!legacyTokens)
|
|
2746
|
+
return null;
|
|
2747
|
+
if (!this.primaryAvailable)
|
|
2748
|
+
return legacyTokens;
|
|
2749
|
+
try {
|
|
2750
|
+
await this.primary.saveTokens(baseUrl2, legacyTokens);
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
if (!this.handlePrimaryFailure(error))
|
|
2753
|
+
throw error;
|
|
2754
|
+
return legacyTokens;
|
|
2755
|
+
}
|
|
2756
|
+
try {
|
|
2757
|
+
await this.legacy.clearTokens(baseUrl2);
|
|
2758
|
+
} catch {}
|
|
2759
|
+
return legacyTokens;
|
|
2760
|
+
}
|
|
2761
|
+
async saveTokens(baseUrl2, data) {
|
|
2762
|
+
if (this.primaryAvailable) {
|
|
2763
|
+
try {
|
|
2764
|
+
await this.primary.saveTokens(baseUrl2, data);
|
|
2765
|
+
return;
|
|
2766
|
+
} catch (error) {
|
|
2767
|
+
if (!this.handlePrimaryFailure(error))
|
|
2768
|
+
throw error;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
await this.legacy.saveTokens(baseUrl2, data);
|
|
2772
|
+
}
|
|
2773
|
+
async clearTokens(baseUrl2) {
|
|
2774
|
+
let primaryError;
|
|
2775
|
+
if (this.primaryAvailable) {
|
|
2776
|
+
try {
|
|
2777
|
+
await this.primary.clearTokens(baseUrl2);
|
|
2778
|
+
} catch (error) {
|
|
2779
|
+
if (!this.handlePrimaryFailure(error)) {
|
|
2780
|
+
primaryError = error;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
try {
|
|
2785
|
+
await this.legacy.clearTokens(baseUrl2);
|
|
2786
|
+
} catch {}
|
|
2787
|
+
if (primaryError)
|
|
2788
|
+
throw primaryError;
|
|
2789
|
+
}
|
|
2790
|
+
async loadClient(baseUrl2) {
|
|
2791
|
+
if (this.primaryAvailable) {
|
|
2792
|
+
try {
|
|
2793
|
+
const client = await this.primary.loadClient(baseUrl2);
|
|
2794
|
+
if (client)
|
|
2795
|
+
return client;
|
|
2796
|
+
} catch (error) {
|
|
2797
|
+
if (!this.handlePrimaryFailure(error))
|
|
2798
|
+
throw error;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
const legacyClient = await this.legacy.loadClient(baseUrl2);
|
|
2802
|
+
if (!legacyClient)
|
|
2803
|
+
return null;
|
|
2804
|
+
if (!this.primaryAvailable)
|
|
2805
|
+
return legacyClient;
|
|
2806
|
+
try {
|
|
2807
|
+
await this.primary.saveClient(baseUrl2, legacyClient);
|
|
2808
|
+
} catch (error) {
|
|
2809
|
+
if (!this.handlePrimaryFailure(error))
|
|
2810
|
+
throw error;
|
|
2811
|
+
return legacyClient;
|
|
2812
|
+
}
|
|
2813
|
+
try {
|
|
2814
|
+
await this.legacy.clearClient(baseUrl2);
|
|
2815
|
+
} catch {}
|
|
2816
|
+
return legacyClient;
|
|
2817
|
+
}
|
|
2818
|
+
async saveClient(baseUrl2, data) {
|
|
2819
|
+
if (this.primaryAvailable) {
|
|
2820
|
+
try {
|
|
2821
|
+
await this.primary.saveClient(baseUrl2, data);
|
|
2822
|
+
return;
|
|
2823
|
+
} catch (error) {
|
|
2824
|
+
if (!this.handlePrimaryFailure(error))
|
|
2825
|
+
throw error;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
await this.legacy.saveClient(baseUrl2, data);
|
|
2829
|
+
}
|
|
2830
|
+
async clearClient(baseUrl2) {
|
|
2831
|
+
let primaryError;
|
|
2832
|
+
if (this.primaryAvailable) {
|
|
2833
|
+
try {
|
|
2834
|
+
await this.primary.clearClient(baseUrl2);
|
|
2835
|
+
} catch (error) {
|
|
2836
|
+
if (!this.handlePrimaryFailure(error)) {
|
|
2837
|
+
primaryError = error;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
try {
|
|
2842
|
+
await this.legacy.clearClient(baseUrl2);
|
|
2843
|
+
} catch {}
|
|
2844
|
+
if (primaryError)
|
|
2845
|
+
throw primaryError;
|
|
2846
|
+
}
|
|
2847
|
+
getStorageLocation() {
|
|
2848
|
+
return this.primaryAvailable ? this.primary.getStorageLocation() : this.legacy.getStorageLocation();
|
|
2849
|
+
}
|
|
2850
|
+
handlePrimaryFailure(error) {
|
|
2851
|
+
if (!(error instanceof KeychainUnavailableError)) {
|
|
2852
|
+
return false;
|
|
2853
|
+
}
|
|
2854
|
+
this.primaryAvailable = false;
|
|
2855
|
+
if (!this.warnedOnPrimaryFailure) {
|
|
2856
|
+
this.warnedOnPrimaryFailure = true;
|
|
2857
|
+
this.onPrimaryUnavailable(error);
|
|
2858
|
+
}
|
|
2859
|
+
return true;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
// src/services/package-intelligence-service.ts
|
|
2863
|
+
import { z as z2 } from "zod";
|
|
2864
|
+
|
|
2865
|
+
// src/services/promote-version-not-found.ts
|
|
2866
|
+
function promoteGenericVersionNotFound(error, params) {
|
|
2867
|
+
if (!(error instanceof PackageIntelligenceBackendError))
|
|
2868
|
+
return error;
|
|
2869
|
+
if (error.graphqlCode !== undefined)
|
|
2870
|
+
return error;
|
|
2871
|
+
const requestedVersion = pickRequestedVersion(params);
|
|
2872
|
+
if (!requestedVersion)
|
|
2873
|
+
return error;
|
|
2874
|
+
if (!/no matching version/i.test(error.message))
|
|
2875
|
+
return error;
|
|
2876
|
+
const qualifiedName = synthesizeQualifiedName(params);
|
|
2877
|
+
return new PackageIntelligenceVersionNotFoundError(error.message, qualifiedName, requestedVersion, undefined);
|
|
2878
|
+
}
|
|
2879
|
+
function pickRequestedVersion(params) {
|
|
2880
|
+
if (params.version)
|
|
2881
|
+
return params.version;
|
|
2882
|
+
if (params.fromVersion)
|
|
2883
|
+
return params.fromVersion;
|
|
2884
|
+
if (params.toVersion)
|
|
2885
|
+
return params.toVersion;
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
function synthesizeQualifiedName(params) {
|
|
2889
|
+
if (!params.registry || !params.packageName)
|
|
2890
|
+
return;
|
|
2891
|
+
return `${params.registry.toLowerCase()}:${params.packageName}`;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/services/package-intelligence-service.ts
|
|
2895
|
+
class PackageIntelligenceAccessError extends Error {
|
|
2896
|
+
constructor(message) {
|
|
2897
|
+
super(message);
|
|
2898
|
+
this.name = "PackageIntelligenceAccessError";
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
class PackageIntelligenceFeatureFlagRequiredError extends Error {
|
|
2903
|
+
constructor(message) {
|
|
2904
|
+
super(message);
|
|
2905
|
+
this.name = "PackageIntelligenceFeatureFlagRequiredError";
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
class PackageIntelligenceNetworkError extends Error {
|
|
2910
|
+
constructor(message, options) {
|
|
2911
|
+
super(message, options);
|
|
2912
|
+
this.name = "PackageIntelligenceNetworkError";
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
class PackageIntelligenceBackendError extends Error {
|
|
2917
|
+
status;
|
|
2918
|
+
graphqlCode;
|
|
2919
|
+
retryable;
|
|
2920
|
+
constructor(message, status, graphqlCode, retryable) {
|
|
2921
|
+
super(message);
|
|
2922
|
+
this.status = status;
|
|
2923
|
+
this.graphqlCode = graphqlCode;
|
|
2924
|
+
this.retryable = retryable;
|
|
2925
|
+
this.name = "PackageIntelligenceBackendError";
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
class PackageIntelligenceGraphQLError extends Error {
|
|
2930
|
+
code;
|
|
2931
|
+
constructor(message, code) {
|
|
2932
|
+
super(message);
|
|
2933
|
+
this.code = code;
|
|
2934
|
+
this.name = "PackageIntelligenceGraphQLError";
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
class PackageIntelligenceTargetNotFoundError extends Error {
|
|
2939
|
+
constructor(message) {
|
|
2940
|
+
super(message);
|
|
2941
|
+
this.name = "PackageIntelligenceTargetNotFoundError";
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
class PackageIntelligenceValidationError extends Error {
|
|
2946
|
+
constructor(message) {
|
|
2947
|
+
super(message);
|
|
2948
|
+
this.name = "PackageIntelligenceValidationError";
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
class PackageIntelligenceVersionNotFoundError extends Error {
|
|
2953
|
+
packageName;
|
|
2954
|
+
requestedVersion;
|
|
2955
|
+
availableVersions;
|
|
2956
|
+
constructor(message, packageName, requestedVersion, availableVersions) {
|
|
2957
|
+
super(message);
|
|
2958
|
+
this.packageName = packageName;
|
|
2959
|
+
this.requestedVersion = requestedVersion;
|
|
2960
|
+
this.availableVersions = availableVersions;
|
|
2961
|
+
this.name = "PackageIntelligenceVersionNotFoundError";
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
class MalformedPackageIntelligenceResponseError extends Error {
|
|
2966
|
+
constructor(message) {
|
|
2967
|
+
super(message);
|
|
2968
|
+
this.name = "MalformedPackageIntelligenceResponseError";
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
class PackageIntelligenceChangelogSourceNotFoundError extends Error {
|
|
2973
|
+
constructor(message) {
|
|
2974
|
+
super(message);
|
|
2975
|
+
this.name = "PackageIntelligenceChangelogSourceNotFoundError";
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
var githubRepositorySchema = z2.object({
|
|
2979
|
+
stargazersCount: z2.number().int().nullable().optional(),
|
|
2980
|
+
forksCount: z2.number().int().nullable().optional(),
|
|
2981
|
+
openIssuesCount: z2.number().int().nullable().optional(),
|
|
2982
|
+
archived: z2.boolean().nullable().optional(),
|
|
2983
|
+
language: z2.string().nullable().optional(),
|
|
2984
|
+
topics: z2.array(z2.string()).nullable().optional(),
|
|
2985
|
+
pushedAt: z2.string().nullable().optional()
|
|
2986
|
+
}).nullable().optional();
|
|
2987
|
+
var packageIdentitySchema = z2.object({
|
|
2988
|
+
name: z2.string().nullable().optional(),
|
|
2989
|
+
registry: z2.string().nullable().optional(),
|
|
2990
|
+
description: z2.string().nullable().optional(),
|
|
2991
|
+
latestVersion: z2.string().nullable().optional(),
|
|
2992
|
+
latestVersionPublishedAt: z2.string().nullable().optional(),
|
|
2993
|
+
homepage: z2.string().nullable().optional(),
|
|
2994
|
+
repositoryUrl: z2.string().nullable().optional(),
|
|
2995
|
+
license: z2.string().nullable().optional(),
|
|
2996
|
+
downloadsLastMonth: z2.number().int().nullable().optional(),
|
|
2997
|
+
downloadsTotal: z2.number().int().nullable().optional(),
|
|
2998
|
+
githubRepository: githubRepositorySchema
|
|
2999
|
+
});
|
|
3000
|
+
var vulnerabilityOverviewSchema = z2.object({
|
|
3001
|
+
osvId: z2.string().nullable().optional(),
|
|
3002
|
+
summary: z2.string().nullable().optional(),
|
|
3003
|
+
severityScore: z2.number().nullable().optional(),
|
|
3004
|
+
publishedAt: z2.string().nullable().optional()
|
|
3005
|
+
});
|
|
3006
|
+
var packageSecurityOverviewSchema = z2.object({
|
|
3007
|
+
vulnerabilityCount: z2.number().int().nullable().optional(),
|
|
3008
|
+
hasCurrentVulnerabilities: z2.boolean().nullable().optional(),
|
|
3009
|
+
recentVulnerabilities: z2.array(vulnerabilityOverviewSchema).nullable().optional()
|
|
3010
|
+
}).nullable().optional();
|
|
3011
|
+
var quickstartInfoSchema = z2.object({
|
|
3012
|
+
installCommand: z2.string().nullable().optional(),
|
|
3013
|
+
usageExample: z2.string().nullable().optional()
|
|
3014
|
+
}).nullable().optional();
|
|
3015
|
+
var changelogEntrySchema = z2.object({
|
|
3016
|
+
version: z2.string().nullable().optional(),
|
|
3017
|
+
publishedAt: z2.string().nullable().optional(),
|
|
3018
|
+
body: z2.string().nullable().optional()
|
|
3019
|
+
});
|
|
3020
|
+
var packageSummaryResponseSchema = z2.object({
|
|
3021
|
+
package: packageIdentitySchema.nullable().optional(),
|
|
3022
|
+
security: packageSecurityOverviewSchema,
|
|
3023
|
+
quickstart: quickstartInfoSchema,
|
|
3024
|
+
latestChangelogs: z2.array(changelogEntrySchema).nullable().optional()
|
|
3025
|
+
});
|
|
3026
|
+
var graphQLErrorSchema2 = z2.object({
|
|
3027
|
+
message: z2.string(),
|
|
3028
|
+
extensions: z2.record(z2.string(), z2.unknown()).optional()
|
|
3029
|
+
});
|
|
3030
|
+
var graphQLResponseSchema = z2.object({
|
|
3031
|
+
data: z2.object({
|
|
3032
|
+
packageSummary: packageSummaryResponseSchema.nullable().optional()
|
|
3033
|
+
}).nullable().optional(),
|
|
3034
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3035
|
+
});
|
|
3036
|
+
var PACKAGE_SUMMARY_QUERY = `
|
|
3037
|
+
query PackageSummary($registry: Registry!, $name: String!) {
|
|
3038
|
+
packageSummary(registry: $registry, name: $name) {
|
|
3039
|
+
package {
|
|
3040
|
+
name
|
|
3041
|
+
registry
|
|
3042
|
+
description
|
|
3043
|
+
latestVersion
|
|
3044
|
+
latestVersionPublishedAt
|
|
3045
|
+
homepage
|
|
3046
|
+
repositoryUrl
|
|
3047
|
+
license
|
|
3048
|
+
downloadsLastMonth
|
|
3049
|
+
downloadsTotal
|
|
3050
|
+
githubRepository {
|
|
3051
|
+
stargazersCount
|
|
3052
|
+
forksCount
|
|
3053
|
+
openIssuesCount
|
|
3054
|
+
archived
|
|
3055
|
+
language
|
|
3056
|
+
topics
|
|
3057
|
+
pushedAt
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
security {
|
|
3061
|
+
vulnerabilityCount
|
|
3062
|
+
hasCurrentVulnerabilities
|
|
3063
|
+
recentVulnerabilities {
|
|
3064
|
+
osvId
|
|
3065
|
+
summary
|
|
3066
|
+
severityScore
|
|
3067
|
+
publishedAt
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
quickstart {
|
|
3071
|
+
installCommand
|
|
3072
|
+
usageExample
|
|
3073
|
+
}
|
|
3074
|
+
latestChangelogs(limit: 3) {
|
|
3075
|
+
version
|
|
3076
|
+
publishedAt
|
|
3077
|
+
body
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}`;
|
|
3081
|
+
var packageVersionIdentitySchema = z2.object({
|
|
3082
|
+
name: z2.string().nullable().optional(),
|
|
3083
|
+
registry: z2.string().nullable().optional(),
|
|
3084
|
+
version: z2.string().nullable().optional()
|
|
3085
|
+
});
|
|
3086
|
+
var vulnerabilityDetailSchema = z2.object({
|
|
3087
|
+
osvId: z2.string().nullable().optional(),
|
|
3088
|
+
summary: z2.string().nullable().optional(),
|
|
3089
|
+
severityScore: z2.number().nullable().optional(),
|
|
3090
|
+
severityType: z2.string().nullable().optional(),
|
|
3091
|
+
affectedVersionRanges: z2.array(z2.string()).nullable().optional(),
|
|
3092
|
+
fixedInVersions: z2.array(z2.string()).nullable().optional(),
|
|
3093
|
+
publishedAt: z2.string().nullable().optional(),
|
|
3094
|
+
modifiedAt: z2.string().nullable().optional(),
|
|
3095
|
+
withdrawnAt: z2.string().nullable().optional(),
|
|
3096
|
+
aliases: z2.array(z2.string()).nullable().optional(),
|
|
3097
|
+
isMalicious: z2.boolean().nullable().optional()
|
|
3098
|
+
});
|
|
3099
|
+
var vulnerabilitySecurityDetailsSchema = z2.object({
|
|
3100
|
+
vulnerabilityCount: z2.number().int().nullable().optional(),
|
|
3101
|
+
currentVersionAffected: z2.boolean().nullable().optional(),
|
|
3102
|
+
vulnerabilities: z2.array(vulnerabilityDetailSchema).nullable().optional(),
|
|
3103
|
+
upgradePaths: z2.array(z2.string()).nullable().optional()
|
|
3104
|
+
}).nullable().optional();
|
|
3105
|
+
var vulnerabilityReportResponseSchema = z2.object({
|
|
3106
|
+
package: packageVersionIdentitySchema.nullable().optional(),
|
|
3107
|
+
security: vulnerabilitySecurityDetailsSchema
|
|
3108
|
+
});
|
|
3109
|
+
var vulnerabilitiesGraphQLResponseSchema = z2.object({
|
|
3110
|
+
data: z2.object({
|
|
3111
|
+
packageVulnerabilities: vulnerabilityReportResponseSchema.nullable().optional()
|
|
3112
|
+
}).nullable().optional(),
|
|
3113
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3114
|
+
});
|
|
3115
|
+
var PACKAGE_VULNERABILITIES_QUERY = `
|
|
3116
|
+
query PackageVulnerabilities(
|
|
3117
|
+
$registry: Registry!
|
|
3118
|
+
$name: String!
|
|
3119
|
+
$version: String
|
|
3120
|
+
$minSeverity: Float
|
|
3121
|
+
$includeWithdrawn: Boolean
|
|
3122
|
+
) {
|
|
3123
|
+
packageVulnerabilities(
|
|
3124
|
+
registry: $registry
|
|
3125
|
+
name: $name
|
|
3126
|
+
version: $version
|
|
3127
|
+
minSeverity: $minSeverity
|
|
3128
|
+
includeWithdrawn: $includeWithdrawn
|
|
3129
|
+
) {
|
|
3130
|
+
package {
|
|
3131
|
+
name
|
|
3132
|
+
registry
|
|
3133
|
+
version
|
|
3134
|
+
}
|
|
3135
|
+
security {
|
|
3136
|
+
vulnerabilityCount
|
|
3137
|
+
currentVersionAffected
|
|
3138
|
+
upgradePaths
|
|
3139
|
+
vulnerabilities {
|
|
3140
|
+
osvId
|
|
3141
|
+
summary
|
|
3142
|
+
severityScore
|
|
3143
|
+
severityType
|
|
3144
|
+
affectedVersionRanges
|
|
3145
|
+
fixedInVersions
|
|
3146
|
+
publishedAt
|
|
3147
|
+
modifiedAt
|
|
3148
|
+
withdrawnAt
|
|
3149
|
+
aliases
|
|
3150
|
+
isMalicious
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
}`;
|
|
3155
|
+
var directDependencySchema = z2.object({
|
|
3156
|
+
name: z2.string().nullable().optional(),
|
|
3157
|
+
versionConstraint: z2.string().nullable().optional(),
|
|
3158
|
+
type: z2.string().nullable().optional()
|
|
3159
|
+
});
|
|
3160
|
+
var dependencyGraphNodeSchema = z2.object({
|
|
3161
|
+
registry: z2.string(),
|
|
3162
|
+
name: z2.string(),
|
|
3163
|
+
version: z2.string().nullable().optional()
|
|
3164
|
+
});
|
|
3165
|
+
var dependencyGraphEdgeSchema = z2.object({
|
|
3166
|
+
fromIndex: z2.number().int().nullable().optional(),
|
|
3167
|
+
toIndex: z2.number().int(),
|
|
3168
|
+
constraint: z2.string().nullable().optional(),
|
|
3169
|
+
dependencyType: z2.string().nullable().optional()
|
|
3170
|
+
});
|
|
3171
|
+
var dependencyGraphSchema = z2.object({
|
|
3172
|
+
formatVersion: z2.number().int(),
|
|
3173
|
+
nodes: z2.array(dependencyGraphNodeSchema),
|
|
3174
|
+
edges: z2.array(dependencyGraphEdgeSchema)
|
|
3175
|
+
});
|
|
3176
|
+
var dependencyConflictEdgeSchema = z2.object({
|
|
3177
|
+
fromIndex: z2.number().int().nullable().optional(),
|
|
3178
|
+
toIndex: z2.number().int(),
|
|
3179
|
+
versionConstraint: z2.string(),
|
|
3180
|
+
dependencyType: z2.string()
|
|
3181
|
+
});
|
|
3182
|
+
var dependencyConflictSchema = z2.object({
|
|
3183
|
+
packageName: z2.string(),
|
|
3184
|
+
requiredVersions: z2.array(z2.string()),
|
|
3185
|
+
conflictingEdges: z2.array(dependencyConflictEdgeSchema)
|
|
3186
|
+
});
|
|
3187
|
+
var circularDependencyCycleSchema = z2.object({
|
|
3188
|
+
cycleStart: z2.string(),
|
|
3189
|
+
circularPath: z2.array(z2.string()),
|
|
3190
|
+
displayChain: z2.string()
|
|
3191
|
+
});
|
|
3192
|
+
var environmentMarkerSchema = z2.object({
|
|
3193
|
+
type: z2.string().nullable().optional(),
|
|
3194
|
+
value: z2.string().nullable().optional(),
|
|
3195
|
+
raw: z2.string().nullable().optional()
|
|
3196
|
+
});
|
|
3197
|
+
var transitiveDependencySchema = z2.object({
|
|
3198
|
+
totalEdges: z2.number().int().nullable().optional(),
|
|
3199
|
+
uniquePackagesCount: z2.number().int().nullable().optional(),
|
|
3200
|
+
uniqueDependencies: z2.array(z2.string()).nullable().optional(),
|
|
3201
|
+
dependencyConflicts: z2.array(dependencyConflictSchema).nullable().optional(),
|
|
3202
|
+
circularDependencyCycles: z2.array(circularDependencyCycleSchema).nullable().optional(),
|
|
3203
|
+
dependencyGraph: dependencyGraphSchema.nullable().optional()
|
|
3204
|
+
}).nullable().optional();
|
|
3205
|
+
var dependencyBundleSchema = z2.object({
|
|
3206
|
+
direct: z2.array(directDependencySchema).nullable().optional(),
|
|
3207
|
+
transitive: transitiveDependencySchema
|
|
3208
|
+
}).nullable().optional();
|
|
3209
|
+
var groupDependencySchema = z2.object({
|
|
3210
|
+
name: z2.string(),
|
|
3211
|
+
constraint: z2.string().nullable().optional()
|
|
3212
|
+
});
|
|
3213
|
+
var dependencyGroupSchema = z2.object({
|
|
3214
|
+
name: z2.string(),
|
|
3215
|
+
lifecycle: z2.string(),
|
|
3216
|
+
conditionType: z2.string(),
|
|
3217
|
+
conditionValue: z2.string().nullable().optional(),
|
|
3218
|
+
selectionMode: z2.string(),
|
|
3219
|
+
exclusiveGroup: z2.string().nullable().optional(),
|
|
3220
|
+
fallbackPriority: z2.number().int().nullable().optional(),
|
|
3221
|
+
compatibleWith: z2.array(z2.string()).nullable().optional(),
|
|
3222
|
+
defaultEnabled: z2.boolean().nullable().optional(),
|
|
3223
|
+
dependencies: z2.array(groupDependencySchema)
|
|
3224
|
+
});
|
|
3225
|
+
var dependencyGroupsInfoSchema = z2.object({
|
|
3226
|
+
primaryGroup: z2.string().nullable().optional(),
|
|
3227
|
+
environmentMarkers: z2.array(environmentMarkerSchema).nullable().optional(),
|
|
3228
|
+
groups: z2.array(dependencyGroupSchema)
|
|
3229
|
+
}).nullable().optional();
|
|
3230
|
+
var dependencyReportResponseSchema = z2.object({
|
|
3231
|
+
package: packageVersionIdentitySchema.nullable().optional(),
|
|
3232
|
+
dependencies: dependencyBundleSchema,
|
|
3233
|
+
dependencyGroups: dependencyGroupsInfoSchema
|
|
3234
|
+
});
|
|
3235
|
+
var dependenciesGraphQLResponseSchema = z2.object({
|
|
3236
|
+
data: z2.object({
|
|
3237
|
+
packageDependencies: dependencyReportResponseSchema.nullable().optional()
|
|
3238
|
+
}).nullable().optional(),
|
|
3239
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3240
|
+
});
|
|
3241
|
+
var PACKAGE_DEPENDENCIES_QUERY = `
|
|
3242
|
+
query PackageDependencies(
|
|
3243
|
+
$registry: Registry!
|
|
3244
|
+
$name: String!
|
|
3245
|
+
$version: String
|
|
3246
|
+
$includeTransitive: Boolean
|
|
3247
|
+
$maxDepth: Int
|
|
3248
|
+
$lifecycle: [String!]
|
|
3249
|
+
) {
|
|
3250
|
+
packageDependencies(
|
|
3251
|
+
registry: $registry
|
|
3252
|
+
name: $name
|
|
3253
|
+
version: $version
|
|
3254
|
+
includeTransitive: $includeTransitive
|
|
3255
|
+
maxDepth: $maxDepth
|
|
3256
|
+
lifecycle: $lifecycle
|
|
3257
|
+
) {
|
|
3258
|
+
package {
|
|
3259
|
+
name
|
|
3260
|
+
registry
|
|
3261
|
+
version
|
|
3262
|
+
}
|
|
3263
|
+
dependencies {
|
|
3264
|
+
# Backend-side summary block intentionally not selected — our
|
|
3265
|
+
# envelope computes runtime.count client-side from direct[].length
|
|
3266
|
+
# so the invariant runtime.count === runtime.items.length always
|
|
3267
|
+
# holds regardless of backend-side drift.
|
|
3268
|
+
direct {
|
|
3269
|
+
name
|
|
3270
|
+
versionConstraint
|
|
3271
|
+
type
|
|
3272
|
+
}
|
|
3273
|
+
transitive {
|
|
3274
|
+
totalEdges
|
|
3275
|
+
uniquePackagesCount
|
|
3276
|
+
uniqueDependencies
|
|
3277
|
+
dependencyConflicts {
|
|
3278
|
+
packageName
|
|
3279
|
+
requiredVersions
|
|
3280
|
+
conflictingEdges {
|
|
3281
|
+
fromIndex
|
|
3282
|
+
toIndex
|
|
3283
|
+
versionConstraint
|
|
3284
|
+
dependencyType
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
circularDependencyCycles {
|
|
3288
|
+
cycleStart
|
|
3289
|
+
circularPath
|
|
3290
|
+
displayChain
|
|
3291
|
+
}
|
|
3292
|
+
dependencyGraph {
|
|
3293
|
+
formatVersion
|
|
3294
|
+
nodes {
|
|
3295
|
+
registry
|
|
3296
|
+
name
|
|
3297
|
+
version
|
|
3298
|
+
}
|
|
3299
|
+
edges {
|
|
3300
|
+
fromIndex
|
|
3301
|
+
toIndex
|
|
3302
|
+
constraint
|
|
3303
|
+
dependencyType
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
dependencyGroups {
|
|
3309
|
+
primaryGroup
|
|
3310
|
+
environmentMarkers {
|
|
3311
|
+
type
|
|
3312
|
+
value
|
|
3313
|
+
raw
|
|
3314
|
+
}
|
|
3315
|
+
groups {
|
|
3316
|
+
name
|
|
3317
|
+
lifecycle
|
|
3318
|
+
conditionType
|
|
3319
|
+
conditionValue
|
|
3320
|
+
selectionMode
|
|
3321
|
+
exclusiveGroup
|
|
3322
|
+
fallbackPriority
|
|
3323
|
+
compatibleWith
|
|
3324
|
+
defaultEnabled
|
|
3325
|
+
dependencies {
|
|
3326
|
+
name
|
|
3327
|
+
constraint
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}`;
|
|
3333
|
+
var changelogPackageInfoSchema = z2.object({
|
|
3334
|
+
name: z2.string().nullable().optional(),
|
|
3335
|
+
registry: z2.string().nullable().optional(),
|
|
3336
|
+
repoUrl: z2.string().nullable().optional(),
|
|
3337
|
+
fromVersion: z2.string().nullable().optional(),
|
|
3338
|
+
toVersion: z2.string().nullable().optional(),
|
|
3339
|
+
limit: z2.number().int().nullable().optional()
|
|
3340
|
+
}).nullable().optional();
|
|
3341
|
+
var changelogEntryDetailSchema = z2.object({
|
|
3342
|
+
version: z2.string().nullable().optional(),
|
|
3343
|
+
normalizedVersion: z2.string().nullable().optional(),
|
|
3344
|
+
body: z2.string().nullable().optional(),
|
|
3345
|
+
htmlUrl: z2.string().nullable().optional(),
|
|
3346
|
+
publishedAt: z2.string().nullable().optional(),
|
|
3347
|
+
metadata: z2.unknown().nullable().optional()
|
|
3348
|
+
});
|
|
3349
|
+
var changelogReportResponseSchema = z2.object({
|
|
3350
|
+
package: changelogPackageInfoSchema,
|
|
3351
|
+
source: z2.string().nullable().optional(),
|
|
3352
|
+
entries: z2.array(changelogEntryDetailSchema).nullable().optional()
|
|
3353
|
+
});
|
|
3354
|
+
var changelogGraphQLResponseSchema = z2.object({
|
|
3355
|
+
data: z2.object({
|
|
3356
|
+
packageChangelog: changelogReportResponseSchema.nullable().optional()
|
|
3357
|
+
}).nullable().optional(),
|
|
3358
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3359
|
+
});
|
|
3360
|
+
var PACKAGE_CHANGELOG_QUERY = `
|
|
3361
|
+
query PackageChangelog(
|
|
3362
|
+
$registry: Registry
|
|
3363
|
+
$name: String
|
|
3364
|
+
$repoUrl: String
|
|
3365
|
+
$gitRef: String
|
|
3366
|
+
$fromVersion: String
|
|
3367
|
+
$toVersion: String
|
|
3368
|
+
$limit: Int
|
|
3369
|
+
) {
|
|
3370
|
+
packageChangelog(
|
|
3371
|
+
registry: $registry
|
|
3372
|
+
name: $name
|
|
3373
|
+
repoUrl: $repoUrl
|
|
3374
|
+
gitRef: $gitRef
|
|
3375
|
+
fromVersion: $fromVersion
|
|
3376
|
+
toVersion: $toVersion
|
|
3377
|
+
limit: $limit
|
|
3378
|
+
) {
|
|
3379
|
+
package {
|
|
3380
|
+
name
|
|
3381
|
+
registry
|
|
3382
|
+
repoUrl
|
|
3383
|
+
fromVersion
|
|
3384
|
+
toVersion
|
|
3385
|
+
limit
|
|
3386
|
+
}
|
|
3387
|
+
source
|
|
3388
|
+
entries {
|
|
3389
|
+
version
|
|
3390
|
+
normalizedVersion
|
|
3391
|
+
body
|
|
3392
|
+
htmlUrl
|
|
3393
|
+
publishedAt
|
|
3394
|
+
metadata
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
}`;
|
|
3398
|
+
var packageDocSourceKindSchema = z2.enum(["CRAWLED", "REPOSITORY"]);
|
|
3399
|
+
var packageDocPageSummarySchema = z2.object({
|
|
3400
|
+
id: z2.string().nullable().optional(),
|
|
3401
|
+
title: z2.string().nullable().optional(),
|
|
3402
|
+
slug: z2.string().nullable().optional(),
|
|
3403
|
+
order: z2.number().int().nullable().optional(),
|
|
3404
|
+
linkName: z2.string().nullable().optional(),
|
|
3405
|
+
lastUpdatedAt: z2.string().nullable().optional(),
|
|
3406
|
+
sourceKind: packageDocSourceKindSchema.nullable().optional(),
|
|
3407
|
+
sourceUrl: z2.string().nullable().optional(),
|
|
3408
|
+
repoUrl: z2.string().nullable().optional(),
|
|
3409
|
+
gitRef: z2.string().nullable().optional(),
|
|
3410
|
+
requestedRef: z2.string().nullable().optional(),
|
|
3411
|
+
filePath: z2.string().nullable().optional()
|
|
3412
|
+
});
|
|
3413
|
+
var packageDocsPageInfoSchema = z2.object({
|
|
3414
|
+
hasNextPage: z2.boolean(),
|
|
3415
|
+
endCursor: z2.string().nullable().optional(),
|
|
3416
|
+
totalCount: z2.number().int().nullable().optional()
|
|
3417
|
+
}).nullable().optional();
|
|
3418
|
+
var packageDocsListResponseSchema = z2.object({
|
|
3419
|
+
registry: z2.string().nullable().optional(),
|
|
3420
|
+
packageName: z2.string().nullable().optional(),
|
|
3421
|
+
version: z2.string().nullable().optional(),
|
|
3422
|
+
stale: z2.boolean().nullable().optional(),
|
|
3423
|
+
pages: z2.array(packageDocPageSummarySchema).nullable().optional(),
|
|
3424
|
+
pageInfo: packageDocsPageInfoSchema
|
|
3425
|
+
});
|
|
3426
|
+
var packageDocSourceSchema = z2.object({
|
|
3427
|
+
url: z2.string().nullable().optional(),
|
|
3428
|
+
label: z2.string().nullable().optional()
|
|
3429
|
+
}).nullable().optional();
|
|
3430
|
+
var packageDocPageSchema = z2.object({
|
|
3431
|
+
id: z2.string().nullable().optional(),
|
|
3432
|
+
title: z2.string().nullable().optional(),
|
|
3433
|
+
content: z2.string().nullable().optional(),
|
|
3434
|
+
contentFormat: z2.string().nullable().optional(),
|
|
3435
|
+
breadcrumbs: z2.array(z2.string()).nullable().optional(),
|
|
3436
|
+
linkName: z2.string().nullable().optional(),
|
|
3437
|
+
lastUpdatedAt: z2.string().nullable().optional(),
|
|
3438
|
+
sourceKind: packageDocSourceKindSchema.nullable().optional(),
|
|
3439
|
+
source: packageDocSourceSchema,
|
|
3440
|
+
repoUrl: z2.string().nullable().optional(),
|
|
3441
|
+
gitRef: z2.string().nullable().optional(),
|
|
3442
|
+
requestedRef: z2.string().nullable().optional(),
|
|
3443
|
+
filePath: z2.string().nullable().optional(),
|
|
3444
|
+
baseUrl: z2.string().nullable().optional()
|
|
3445
|
+
}).nullable().optional();
|
|
3446
|
+
var packageDocResultResponseSchema = z2.object({
|
|
3447
|
+
registry: z2.string().nullable().optional(),
|
|
3448
|
+
packageName: z2.string().nullable().optional(),
|
|
3449
|
+
version: z2.string().nullable().optional(),
|
|
3450
|
+
sourceKind: packageDocSourceKindSchema.nullable().optional(),
|
|
3451
|
+
page: packageDocPageSchema
|
|
3452
|
+
});
|
|
3453
|
+
var packageDocsListGraphQLResponseSchema = z2.object({
|
|
3454
|
+
data: z2.object({
|
|
3455
|
+
listPackageDocs: packageDocsListResponseSchema.nullable().optional()
|
|
3456
|
+
}).nullable().optional(),
|
|
3457
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3458
|
+
});
|
|
3459
|
+
var packageDocReadGraphQLResponseSchema = z2.object({
|
|
3460
|
+
data: z2.object({
|
|
3461
|
+
getDocPage: packageDocResultResponseSchema.nullable().optional()
|
|
3462
|
+
}).nullable().optional(),
|
|
3463
|
+
errors: z2.array(graphQLErrorSchema2).optional()
|
|
3464
|
+
});
|
|
3465
|
+
var LIST_PACKAGE_DOCS_QUERY = `
|
|
3466
|
+
query ListPackageDocs(
|
|
3467
|
+
$registry: Registry!
|
|
3468
|
+
$packageName: String!
|
|
3469
|
+
$version: String
|
|
3470
|
+
$limit: Int
|
|
3471
|
+
$after: String
|
|
3472
|
+
) {
|
|
3473
|
+
listPackageDocs(
|
|
3474
|
+
registry: $registry
|
|
3475
|
+
packageName: $packageName
|
|
3476
|
+
version: $version
|
|
3477
|
+
limit: $limit
|
|
3478
|
+
after: $after
|
|
3479
|
+
) {
|
|
3480
|
+
registry
|
|
3481
|
+
packageName
|
|
3482
|
+
version
|
|
3483
|
+
stale
|
|
3484
|
+
pages {
|
|
3485
|
+
id
|
|
3486
|
+
title
|
|
3487
|
+
slug
|
|
3488
|
+
order
|
|
3489
|
+
linkName
|
|
3490
|
+
lastUpdatedAt
|
|
3491
|
+
sourceKind
|
|
3492
|
+
sourceUrl
|
|
3493
|
+
repoUrl
|
|
3494
|
+
gitRef
|
|
3495
|
+
requestedRef
|
|
3496
|
+
filePath
|
|
3497
|
+
}
|
|
3498
|
+
pageInfo {
|
|
3499
|
+
hasNextPage
|
|
3500
|
+
endCursor
|
|
3501
|
+
totalCount
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
}`;
|
|
3505
|
+
var READ_PACKAGE_DOC_QUERY = `
|
|
3506
|
+
query ReadPackageDoc($pageId: String!) {
|
|
3507
|
+
getDocPage(pageId: $pageId) {
|
|
3508
|
+
registry
|
|
3509
|
+
packageName
|
|
3510
|
+
version
|
|
3511
|
+
sourceKind
|
|
3512
|
+
page {
|
|
3513
|
+
id
|
|
3514
|
+
title
|
|
3515
|
+
content
|
|
3516
|
+
contentFormat
|
|
3517
|
+
breadcrumbs
|
|
3518
|
+
linkName
|
|
3519
|
+
lastUpdatedAt
|
|
3520
|
+
sourceKind
|
|
3521
|
+
source {
|
|
3522
|
+
url
|
|
3523
|
+
label
|
|
3524
|
+
}
|
|
3525
|
+
repoUrl
|
|
3526
|
+
gitRef
|
|
3527
|
+
requestedRef
|
|
3528
|
+
filePath
|
|
3529
|
+
baseUrl
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}`;
|
|
3533
|
+
|
|
3534
|
+
class PackageIntelligenceServiceImpl {
|
|
3535
|
+
endpointUrl;
|
|
3536
|
+
tokenProvider;
|
|
3537
|
+
fetchFn;
|
|
3538
|
+
constructor(endpointUrl, tokenProvider, fetchFn = globalThis.fetch) {
|
|
3539
|
+
this.endpointUrl = endpointUrl;
|
|
3540
|
+
this.tokenProvider = tokenProvider;
|
|
3541
|
+
this.fetchFn = fetchFn;
|
|
3542
|
+
}
|
|
3543
|
+
async packageSummary(params) {
|
|
3544
|
+
return withTelemetrySpan("pkg-intel.summary.request", () => executeWithTokenRefresh({
|
|
3545
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
3546
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
3547
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
3548
|
+
executeWithToken: (token) => this.executePackageSummary(token, params)
|
|
3549
|
+
}));
|
|
3550
|
+
}
|
|
3551
|
+
async executePackageSummary(token, params) {
|
|
3552
|
+
let response;
|
|
3553
|
+
try {
|
|
3554
|
+
response = await postPkgseerGraphql({
|
|
3555
|
+
endpointUrl: this.endpointUrl,
|
|
3556
|
+
token,
|
|
3557
|
+
query: PACKAGE_SUMMARY_QUERY,
|
|
3558
|
+
variables: {
|
|
3559
|
+
registry: params.registry,
|
|
3560
|
+
name: params.packageName
|
|
3561
|
+
},
|
|
3562
|
+
fetchFn: this.fetchFn
|
|
3563
|
+
});
|
|
3564
|
+
} catch (cause) {
|
|
3565
|
+
if (cause instanceof PkgseerTransportError) {
|
|
3566
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
3567
|
+
}
|
|
3568
|
+
throw cause;
|
|
3569
|
+
}
|
|
3570
|
+
if (response.status < 200 || response.status >= 300) {
|
|
3571
|
+
throw this.createHttpError(response);
|
|
3572
|
+
}
|
|
3573
|
+
const parsed = graphQLResponseSchema.safeParse(response.parsedBody);
|
|
3574
|
+
if (!parsed.success) {
|
|
3575
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
3576
|
+
}
|
|
3577
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
3578
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
3579
|
+
}
|
|
3580
|
+
const data = parsed.data.data?.packageSummary;
|
|
3581
|
+
if (!data) {
|
|
3582
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
3583
|
+
}
|
|
3584
|
+
return this.normalise(data);
|
|
3585
|
+
}
|
|
3586
|
+
createHttpError(response) {
|
|
3587
|
+
const status = response.status;
|
|
3588
|
+
const detail = parseDetail3(response.responseBody);
|
|
3589
|
+
if (status === 401) {
|
|
3590
|
+
return new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
3591
|
+
}
|
|
3592
|
+
if (status === 403) {
|
|
3593
|
+
return new PackageIntelligenceAccessError(detail ?? "Access denied.");
|
|
3594
|
+
}
|
|
3595
|
+
if (status >= 500) {
|
|
3596
|
+
return new PackageIntelligenceBackendError(detail ? `Server error (${status}): ${detail}` : `Server error (${status})`, status);
|
|
3597
|
+
}
|
|
3598
|
+
return new PackageIntelligenceBackendError(detail ?? `Request failed with status ${status}`, status);
|
|
3599
|
+
}
|
|
3600
|
+
createGraphQLError(errors) {
|
|
3601
|
+
const message = errors.map((error) => error.message).join(", ");
|
|
3602
|
+
const extensions = getPrimaryExtensions2(errors);
|
|
3603
|
+
const code = typeof extensions?.code === "string" ? extensions.code : undefined;
|
|
3604
|
+
const retryable = typeof extensions?.retryable === "boolean" ? extensions.retryable : undefined;
|
|
3605
|
+
switch (code) {
|
|
3606
|
+
case "NOT_FOUND":
|
|
3607
|
+
case "PACKAGE_NOT_FOUND":
|
|
3608
|
+
return new PackageIntelligenceTargetNotFoundError(message);
|
|
3609
|
+
case "VERSION_NOT_FOUND":
|
|
3610
|
+
return new PackageIntelligenceVersionNotFoundError(message, typeof extensions?.package === "string" ? extensions.package : undefined, typeof extensions?.requested_version === "string" ? extensions.requested_version : undefined, parseVersionList(extensions?.available_versions ?? extensions?.availableVersions));
|
|
3611
|
+
case "UNSUPPORTED_REGISTRY":
|
|
3612
|
+
case "VALIDATION_ERROR":
|
|
3613
|
+
return new PackageIntelligenceValidationError(message);
|
|
3614
|
+
case "FEATURE_FLAG_REQUIRED":
|
|
3615
|
+
return new PackageIntelligenceFeatureFlagRequiredError(message);
|
|
3616
|
+
case "UNAUTHORIZED":
|
|
3617
|
+
return new AuthenticationError("Authentication required. Run `githits login` to authenticate.");
|
|
3618
|
+
case "FORBIDDEN":
|
|
3619
|
+
return new PackageIntelligenceAccessError("Access denied. This feature may not be enabled for your account.");
|
|
3620
|
+
case "UPSTREAM_ERROR":
|
|
3621
|
+
case "TIMEOUT":
|
|
3622
|
+
case "RATE_LIMITED":
|
|
3623
|
+
case "INTERNAL_ERROR":
|
|
3624
|
+
case "UNKNOWN_ERROR":
|
|
3625
|
+
return new PackageIntelligenceBackendError(message, undefined, code, retryable);
|
|
3626
|
+
default:
|
|
3627
|
+
break;
|
|
3628
|
+
}
|
|
3629
|
+
return new PackageIntelligenceBackendError(message, undefined, code, retryable);
|
|
3630
|
+
}
|
|
3631
|
+
normalise(data) {
|
|
3632
|
+
const name = data.package?.name ?? undefined;
|
|
3633
|
+
const latestVersion = data.package?.latestVersion ?? undefined;
|
|
3634
|
+
if (!name || !latestVersion) {
|
|
3635
|
+
throw new MalformedPackageIntelligenceResponseError("Package summary response missing required name/latestVersion.");
|
|
3636
|
+
}
|
|
3637
|
+
const pkg = data.package;
|
|
3638
|
+
const github = pkg?.githubRepository;
|
|
3639
|
+
const identity = {
|
|
3640
|
+
name,
|
|
3641
|
+
latestVersion,
|
|
3642
|
+
registry: pkg?.registry ?? undefined,
|
|
3643
|
+
description: pkg?.description ?? undefined,
|
|
3644
|
+
latestVersionPublishedAt: pkg?.latestVersionPublishedAt ?? undefined,
|
|
3645
|
+
homepage: pkg?.homepage ?? undefined,
|
|
3646
|
+
repositoryUrl: pkg?.repositoryUrl ?? undefined,
|
|
3647
|
+
license: pkg?.license ?? undefined,
|
|
3648
|
+
downloadsLastMonth: pkg?.downloadsLastMonth ?? undefined,
|
|
3649
|
+
downloadsTotal: pkg?.downloadsTotal ?? undefined,
|
|
3650
|
+
githubRepository: github ? {
|
|
3651
|
+
stargazersCount: github.stargazersCount ?? undefined,
|
|
3652
|
+
forksCount: github.forksCount ?? undefined,
|
|
3653
|
+
openIssuesCount: github.openIssuesCount ?? undefined,
|
|
3654
|
+
archived: github.archived ?? undefined,
|
|
3655
|
+
language: github.language ?? undefined,
|
|
3656
|
+
topics: github.topics ?? undefined,
|
|
3657
|
+
pushedAt: github.pushedAt ?? undefined
|
|
3658
|
+
} : undefined
|
|
3659
|
+
};
|
|
3660
|
+
const security = data.security ? {
|
|
3661
|
+
vulnerabilityCount: data.security.vulnerabilityCount ?? undefined,
|
|
3662
|
+
hasCurrentVulnerabilities: data.security.hasCurrentVulnerabilities ?? undefined,
|
|
3663
|
+
recentVulnerabilities: data.security.recentVulnerabilities?.map((vuln) => ({
|
|
3664
|
+
osvId: vuln.osvId ?? undefined,
|
|
3665
|
+
summary: vuln.summary ?? undefined,
|
|
3666
|
+
severityScore: vuln.severityScore ?? undefined,
|
|
3667
|
+
publishedAt: vuln.publishedAt ?? undefined
|
|
3668
|
+
})) ?? undefined
|
|
3669
|
+
} : undefined;
|
|
3670
|
+
const quickstart = data.quickstart ? {
|
|
3671
|
+
installCommand: data.quickstart.installCommand ?? undefined,
|
|
3672
|
+
usageExample: data.quickstart.usageExample ?? undefined
|
|
3673
|
+
} : undefined;
|
|
3674
|
+
const latestChangelogs = data.latestChangelogs?.map((entry) => ({
|
|
3675
|
+
version: entry.version ?? undefined,
|
|
3676
|
+
publishedAt: entry.publishedAt ?? undefined,
|
|
3677
|
+
body: entry.body ?? undefined
|
|
3678
|
+
})) ?? undefined;
|
|
3679
|
+
return {
|
|
3680
|
+
package: identity,
|
|
3681
|
+
security,
|
|
3682
|
+
quickstart,
|
|
3683
|
+
latestChangelogs
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
async packageVulnerabilities(params) {
|
|
3687
|
+
return withTelemetrySpan("pkg-intel.vulnerabilities.request", () => executeWithTokenRefresh({
|
|
3688
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
3689
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
3690
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
3691
|
+
executeWithToken: (token) => this.executePackageVulnerabilities(token, params)
|
|
3692
|
+
}));
|
|
3693
|
+
}
|
|
3694
|
+
async executePackageVulnerabilities(token, params) {
|
|
3695
|
+
let response;
|
|
3696
|
+
try {
|
|
3697
|
+
response = await postPkgseerGraphql({
|
|
3698
|
+
endpointUrl: this.endpointUrl,
|
|
3699
|
+
token,
|
|
3700
|
+
query: PACKAGE_VULNERABILITIES_QUERY,
|
|
3701
|
+
variables: {
|
|
3702
|
+
registry: params.registry,
|
|
3703
|
+
name: params.packageName,
|
|
3704
|
+
version: params.version,
|
|
3705
|
+
minSeverity: params.minSeverity,
|
|
3706
|
+
includeWithdrawn: params.includeWithdrawn
|
|
3707
|
+
},
|
|
3708
|
+
fetchFn: this.fetchFn
|
|
3709
|
+
});
|
|
3710
|
+
} catch (cause) {
|
|
3711
|
+
if (cause instanceof PkgseerTransportError) {
|
|
3712
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
3713
|
+
}
|
|
3714
|
+
throw cause;
|
|
3715
|
+
}
|
|
3716
|
+
if (response.status < 200 || response.status >= 300) {
|
|
3717
|
+
throw this.createHttpError(response);
|
|
3718
|
+
}
|
|
3719
|
+
const parsed = vulnerabilitiesGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
3720
|
+
if (!parsed.success) {
|
|
3721
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
3722
|
+
}
|
|
3723
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
3724
|
+
throw promoteGenericVersionNotFound(this.createGraphQLError(parsed.data.errors), params);
|
|
3725
|
+
}
|
|
3726
|
+
const data = parsed.data.data?.packageVulnerabilities;
|
|
3727
|
+
if (!data) {
|
|
3728
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
3729
|
+
}
|
|
3730
|
+
return this.normaliseVulnerabilityReport(data);
|
|
3731
|
+
}
|
|
3732
|
+
normaliseVulnerabilityReport(data) {
|
|
3733
|
+
const name = data.package?.name ?? undefined;
|
|
3734
|
+
const version2 = data.package?.version ?? undefined;
|
|
3735
|
+
if (!name || !version2) {
|
|
3736
|
+
throw new MalformedPackageIntelligenceResponseError("Vulnerability report response missing required name/version.");
|
|
3737
|
+
}
|
|
3738
|
+
const identity = {
|
|
3739
|
+
name,
|
|
3740
|
+
version: version2,
|
|
3741
|
+
registry: data.package?.registry ?? undefined
|
|
3742
|
+
};
|
|
3743
|
+
const security = data.security ? {
|
|
3744
|
+
vulnerabilityCount: data.security.vulnerabilityCount ?? undefined,
|
|
3745
|
+
currentVersionAffected: data.security.currentVersionAffected ?? undefined,
|
|
3746
|
+
vulnerabilities: data.security.vulnerabilities?.map((vuln) => ({
|
|
3747
|
+
osvId: vuln.osvId ?? undefined,
|
|
3748
|
+
summary: vuln.summary ?? undefined,
|
|
3749
|
+
severityScore: vuln.severityScore ?? undefined,
|
|
3750
|
+
severityType: vuln.severityType ?? undefined,
|
|
3751
|
+
affectedVersionRanges: vuln.affectedVersionRanges ?? undefined,
|
|
3752
|
+
fixedInVersions: vuln.fixedInVersions ?? undefined,
|
|
3753
|
+
publishedAt: vuln.publishedAt ?? undefined,
|
|
3754
|
+
modifiedAt: vuln.modifiedAt ?? undefined,
|
|
3755
|
+
withdrawnAt: vuln.withdrawnAt ?? undefined,
|
|
3756
|
+
aliases: vuln.aliases ?? undefined,
|
|
3757
|
+
isMalicious: vuln.isMalicious ?? undefined
|
|
3758
|
+
})) ?? undefined,
|
|
3759
|
+
upgradePaths: data.security.upgradePaths ?? undefined
|
|
3760
|
+
} : undefined;
|
|
3761
|
+
return {
|
|
3762
|
+
package: identity,
|
|
3763
|
+
security
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
async packageDependencies(params) {
|
|
3767
|
+
return withTelemetrySpan("pkg-intel.dependencies.request", () => executeWithTokenRefresh({
|
|
3768
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
3769
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
3770
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
3771
|
+
executeWithToken: (token) => this.executePackageDependencies(token, params)
|
|
3772
|
+
}));
|
|
3773
|
+
}
|
|
3774
|
+
async executePackageDependencies(token, params) {
|
|
3775
|
+
let response;
|
|
3776
|
+
try {
|
|
3777
|
+
response = await postPkgseerGraphql({
|
|
3778
|
+
endpointUrl: this.endpointUrl,
|
|
3779
|
+
token,
|
|
3780
|
+
query: PACKAGE_DEPENDENCIES_QUERY,
|
|
3781
|
+
variables: {
|
|
3782
|
+
registry: params.registry,
|
|
3783
|
+
name: params.packageName,
|
|
3784
|
+
version: params.version,
|
|
3785
|
+
includeTransitive: params.includeTransitive,
|
|
3786
|
+
maxDepth: params.maxDepth,
|
|
3787
|
+
lifecycle: params.lifecycle && params.lifecycle.length > 0 ? params.lifecycle : undefined
|
|
3788
|
+
},
|
|
3789
|
+
fetchFn: this.fetchFn
|
|
3790
|
+
});
|
|
3791
|
+
} catch (cause) {
|
|
3792
|
+
if (cause instanceof PkgseerTransportError) {
|
|
3793
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
3794
|
+
}
|
|
3795
|
+
throw cause;
|
|
3796
|
+
}
|
|
3797
|
+
if (response.status < 200 || response.status >= 300) {
|
|
3798
|
+
throw this.createHttpError(response);
|
|
3799
|
+
}
|
|
3800
|
+
const parsed = dependenciesGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
3801
|
+
if (!parsed.success) {
|
|
3802
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
3803
|
+
}
|
|
3804
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
3805
|
+
throw promoteGenericVersionNotFound(this.createGraphQLError(parsed.data.errors), params);
|
|
3806
|
+
}
|
|
3807
|
+
const data = parsed.data.data?.packageDependencies;
|
|
3808
|
+
if (!data) {
|
|
3809
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
3810
|
+
}
|
|
3811
|
+
return this.normaliseDependencyReport(data);
|
|
3812
|
+
}
|
|
3813
|
+
normaliseDependencyReport(data) {
|
|
3814
|
+
const name = data.package?.name ?? undefined;
|
|
3815
|
+
const version2 = data.package?.version ?? undefined;
|
|
3816
|
+
if (!name || !version2) {
|
|
3817
|
+
throw new MalformedPackageIntelligenceResponseError("Package dependencies response missing required name/version.");
|
|
3818
|
+
}
|
|
3819
|
+
const identity = {
|
|
3820
|
+
name,
|
|
3821
|
+
version: version2,
|
|
3822
|
+
registry: data.package?.registry ?? undefined
|
|
3823
|
+
};
|
|
3824
|
+
const bundle = data.dependencies;
|
|
3825
|
+
const dependencies = bundle ? {
|
|
3826
|
+
direct: bundle.direct?.map((entry) => {
|
|
3827
|
+
if (!entry.name) {
|
|
3828
|
+
throw new MalformedPackageIntelligenceResponseError("Dependency entry missing required name.");
|
|
3829
|
+
}
|
|
3830
|
+
return {
|
|
3831
|
+
name: entry.name,
|
|
3832
|
+
versionConstraint: entry.versionConstraint ?? undefined,
|
|
3833
|
+
type: entry.type ?? undefined
|
|
3834
|
+
};
|
|
3835
|
+
}) ?? undefined,
|
|
3836
|
+
transitive: bundle.transitive ? {
|
|
3837
|
+
totalEdges: bundle.transitive.totalEdges ?? undefined,
|
|
3838
|
+
uniquePackagesCount: bundle.transitive.uniquePackagesCount ?? undefined,
|
|
3839
|
+
uniqueDependencies: bundle.transitive.uniqueDependencies ?? undefined,
|
|
3840
|
+
dependencyConflicts: bundle.transitive.dependencyConflicts?.map((c) => ({
|
|
3841
|
+
packageName: c.packageName,
|
|
3842
|
+
requiredVersions: c.requiredVersions,
|
|
3843
|
+
conflictingEdges: c.conflictingEdges.map((edge) => ({
|
|
3844
|
+
fromIndex: edge.fromIndex ?? undefined,
|
|
3845
|
+
toIndex: edge.toIndex,
|
|
3846
|
+
versionConstraint: edge.versionConstraint,
|
|
3847
|
+
dependencyType: edge.dependencyType
|
|
3848
|
+
}))
|
|
3849
|
+
})) ?? undefined,
|
|
3850
|
+
circularDependencyCycles: bundle.transitive.circularDependencyCycles?.map((cycle) => ({
|
|
3851
|
+
cycleStart: cycle.cycleStart,
|
|
3852
|
+
circularPath: cycle.circularPath,
|
|
3853
|
+
displayChain: cycle.displayChain
|
|
3854
|
+
})) ?? undefined,
|
|
3855
|
+
dependencyGraph: bundle.transitive.dependencyGraph ? {
|
|
3856
|
+
formatVersion: bundle.transitive.dependencyGraph.formatVersion,
|
|
3857
|
+
nodes: bundle.transitive.dependencyGraph.nodes.map((n) => ({
|
|
3858
|
+
registry: n.registry,
|
|
3859
|
+
name: n.name,
|
|
3860
|
+
version: n.version ?? undefined
|
|
3861
|
+
})),
|
|
3862
|
+
edges: bundle.transitive.dependencyGraph.edges.map((e) => ({
|
|
3863
|
+
fromIndex: e.fromIndex ?? undefined,
|
|
3864
|
+
toIndex: e.toIndex,
|
|
3865
|
+
constraint: e.constraint ?? undefined,
|
|
3866
|
+
dependencyType: e.dependencyType ?? undefined
|
|
3867
|
+
}))
|
|
3868
|
+
} : undefined
|
|
3869
|
+
} : undefined
|
|
3870
|
+
} : undefined;
|
|
3871
|
+
const dependencyGroups = data.dependencyGroups ? {
|
|
3872
|
+
primaryGroup: data.dependencyGroups.primaryGroup ?? undefined,
|
|
3873
|
+
environmentMarkers: data.dependencyGroups.environmentMarkers?.map((m) => ({
|
|
3874
|
+
type: m.type ?? undefined,
|
|
3875
|
+
value: m.value ?? undefined,
|
|
3876
|
+
raw: m.raw ?? undefined
|
|
3877
|
+
})) ?? undefined,
|
|
3878
|
+
groups: data.dependencyGroups.groups.map((group) => ({
|
|
3879
|
+
name: group.name,
|
|
3880
|
+
lifecycle: group.lifecycle,
|
|
3881
|
+
conditionType: group.conditionType,
|
|
3882
|
+
conditionValue: group.conditionValue ?? undefined,
|
|
3883
|
+
selectionMode: group.selectionMode,
|
|
3884
|
+
exclusiveGroup: group.exclusiveGroup ?? undefined,
|
|
3885
|
+
fallbackPriority: group.fallbackPriority ?? undefined,
|
|
3886
|
+
compatibleWith: group.compatibleWith ?? undefined,
|
|
3887
|
+
defaultEnabled: group.defaultEnabled ?? undefined,
|
|
3888
|
+
dependencies: group.dependencies.map((entry) => ({
|
|
3889
|
+
name: entry.name,
|
|
3890
|
+
constraint: entry.constraint ?? undefined
|
|
3891
|
+
}))
|
|
3892
|
+
}))
|
|
3893
|
+
} : undefined;
|
|
3894
|
+
return {
|
|
3895
|
+
package: identity,
|
|
3896
|
+
dependencies,
|
|
3897
|
+
dependencyGroups
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
async packageChangelog(params) {
|
|
3901
|
+
return withTelemetrySpan("pkg-intel.changelog.request", () => executeWithTokenRefresh({
|
|
3902
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
3903
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
3904
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
3905
|
+
executeWithToken: (token) => this.executePackageChangelog(token, params)
|
|
3906
|
+
}));
|
|
3907
|
+
}
|
|
3908
|
+
async executePackageChangelog(token, params) {
|
|
3909
|
+
let response;
|
|
3910
|
+
try {
|
|
3911
|
+
response = await postPkgseerGraphql({
|
|
3912
|
+
endpointUrl: this.endpointUrl,
|
|
3913
|
+
token,
|
|
3914
|
+
query: PACKAGE_CHANGELOG_QUERY,
|
|
3915
|
+
variables: {
|
|
3916
|
+
registry: params.registry,
|
|
3917
|
+
name: params.packageName,
|
|
3918
|
+
repoUrl: params.repoUrl,
|
|
3919
|
+
gitRef: params.gitRef,
|
|
3920
|
+
fromVersion: params.fromVersion,
|
|
3921
|
+
toVersion: params.toVersion,
|
|
3922
|
+
limit: params.limit
|
|
3923
|
+
},
|
|
3924
|
+
fetchFn: this.fetchFn
|
|
3925
|
+
});
|
|
3926
|
+
} catch (cause) {
|
|
3927
|
+
if (cause instanceof PkgseerTransportError) {
|
|
3928
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
3929
|
+
}
|
|
3930
|
+
throw cause;
|
|
3931
|
+
}
|
|
3932
|
+
if (response.status < 200 || response.status >= 300) {
|
|
3933
|
+
throw this.createHttpError(response);
|
|
3934
|
+
}
|
|
3935
|
+
const parsed = changelogGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
3936
|
+
if (!parsed.success) {
|
|
3937
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
3938
|
+
}
|
|
3939
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
3940
|
+
throw promoteGenericVersionNotFound(this.createGraphQLError(parsed.data.errors), params);
|
|
3941
|
+
}
|
|
3942
|
+
const data = parsed.data.data?.packageChangelog;
|
|
3943
|
+
if (!data) {
|
|
3944
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
3945
|
+
}
|
|
3946
|
+
return this.normaliseChangelogReport(data, params);
|
|
3947
|
+
}
|
|
3948
|
+
normaliseChangelogReport(data, params) {
|
|
3949
|
+
const source = data.source ?? undefined;
|
|
3950
|
+
if (!source) {
|
|
3951
|
+
const target = params.repoUrl ?? (params.registry && params.packageName ? `${params.registry.toLowerCase()}:${params.packageName}` : "package");
|
|
3952
|
+
throw new PackageIntelligenceChangelogSourceNotFoundError(`No changelog source available for ${target} (tried GitHub Releases, CHANGELOG.md, and HexDocs).`);
|
|
3953
|
+
}
|
|
3954
|
+
const rawEntries = data.entries ?? [];
|
|
3955
|
+
const entries = rawEntries.map((entry) => ({
|
|
3956
|
+
version: entry.version ?? undefined,
|
|
3957
|
+
normalizedVersion: entry.normalizedVersion ?? undefined,
|
|
3958
|
+
body: entry.body ?? undefined,
|
|
3959
|
+
htmlUrl: entry.htmlUrl ?? undefined,
|
|
3960
|
+
publishedAt: entry.publishedAt ?? undefined,
|
|
3961
|
+
metadata: entry.metadata ?? undefined
|
|
3962
|
+
}));
|
|
3963
|
+
const packageInfo = data.package ? {
|
|
3964
|
+
name: data.package.name ?? undefined,
|
|
3965
|
+
registry: data.package.registry ?? undefined,
|
|
3966
|
+
repoUrl: data.package.repoUrl ?? undefined,
|
|
3967
|
+
fromVersion: data.package.fromVersion ?? undefined,
|
|
3968
|
+
toVersion: data.package.toVersion ?? undefined,
|
|
3969
|
+
limit: data.package.limit ?? undefined
|
|
3970
|
+
} : undefined;
|
|
3971
|
+
return {
|
|
3972
|
+
package: packageInfo,
|
|
3973
|
+
source,
|
|
3974
|
+
entries
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
async listPackageDocs(params) {
|
|
3978
|
+
return withTelemetrySpan("pkg-intel.docs.list", () => executeWithTokenRefresh({
|
|
3979
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
3980
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
3981
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
3982
|
+
executeWithToken: (token) => this.executeListPackageDocs(token, params)
|
|
3983
|
+
}));
|
|
3984
|
+
}
|
|
3985
|
+
async executeListPackageDocs(token, params) {
|
|
3986
|
+
let response;
|
|
3987
|
+
try {
|
|
3988
|
+
response = await postPkgseerGraphql({
|
|
3989
|
+
endpointUrl: this.endpointUrl,
|
|
3990
|
+
token,
|
|
3991
|
+
query: LIST_PACKAGE_DOCS_QUERY,
|
|
3992
|
+
variables: {
|
|
3993
|
+
registry: params.registry,
|
|
3994
|
+
packageName: params.packageName,
|
|
3995
|
+
version: params.version,
|
|
3996
|
+
limit: params.limit,
|
|
3997
|
+
after: params.after
|
|
3998
|
+
},
|
|
3999
|
+
fetchFn: this.fetchFn
|
|
4000
|
+
});
|
|
4001
|
+
} catch (cause) {
|
|
4002
|
+
if (cause instanceof PkgseerTransportError) {
|
|
4003
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
4004
|
+
}
|
|
4005
|
+
throw cause;
|
|
4006
|
+
}
|
|
4007
|
+
if (response.status < 200 || response.status >= 300) {
|
|
4008
|
+
throw this.createHttpError(response);
|
|
4009
|
+
}
|
|
4010
|
+
const parsed = packageDocsListGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
4011
|
+
if (!parsed.success) {
|
|
4012
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
4013
|
+
}
|
|
4014
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
4015
|
+
throw promoteGenericVersionNotFound(this.createGraphQLError(parsed.data.errors), params);
|
|
4016
|
+
}
|
|
4017
|
+
const data = parsed.data.data?.listPackageDocs;
|
|
4018
|
+
if (!data) {
|
|
4019
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
4020
|
+
}
|
|
4021
|
+
return this.normalisePackageDocsList(data);
|
|
4022
|
+
}
|
|
4023
|
+
normalisePackageDocsList(data) {
|
|
4024
|
+
return {
|
|
4025
|
+
registry: data.registry ?? undefined,
|
|
4026
|
+
packageName: data.packageName ?? undefined,
|
|
4027
|
+
version: data.version ?? undefined,
|
|
4028
|
+
stale: data.stale ?? undefined,
|
|
4029
|
+
pages: data.pages?.map((page) => ({
|
|
4030
|
+
id: page.id ?? undefined,
|
|
4031
|
+
title: page.title ?? undefined,
|
|
4032
|
+
slug: page.slug ?? undefined,
|
|
4033
|
+
order: page.order ?? undefined,
|
|
4034
|
+
linkName: page.linkName ?? undefined,
|
|
4035
|
+
lastUpdatedAt: page.lastUpdatedAt ?? undefined,
|
|
4036
|
+
sourceKind: page.sourceKind ?? undefined,
|
|
4037
|
+
sourceUrl: page.sourceUrl ?? undefined,
|
|
4038
|
+
repoUrl: page.repoUrl ?? undefined,
|
|
4039
|
+
gitRef: page.gitRef ?? undefined,
|
|
4040
|
+
requestedRef: page.requestedRef ?? undefined,
|
|
4041
|
+
filePath: page.filePath ?? undefined
|
|
4042
|
+
})) ?? [],
|
|
4043
|
+
pageInfo: data.pageInfo ? {
|
|
4044
|
+
hasNextPage: data.pageInfo.hasNextPage,
|
|
4045
|
+
endCursor: data.pageInfo.endCursor ?? undefined,
|
|
4046
|
+
totalCount: data.pageInfo.totalCount ?? undefined
|
|
4047
|
+
} : undefined
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4050
|
+
async readPackageDoc(params) {
|
|
4051
|
+
return withTelemetrySpan("pkg-intel.docs.read", () => executeWithTokenRefresh({
|
|
4052
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
4053
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
4054
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
4055
|
+
executeWithToken: (token) => this.executeReadPackageDoc(token, params)
|
|
4056
|
+
}));
|
|
4057
|
+
}
|
|
4058
|
+
async executeReadPackageDoc(token, params) {
|
|
4059
|
+
let response;
|
|
4060
|
+
try {
|
|
4061
|
+
response = await postPkgseerGraphql({
|
|
4062
|
+
endpointUrl: this.endpointUrl,
|
|
4063
|
+
token,
|
|
4064
|
+
query: READ_PACKAGE_DOC_QUERY,
|
|
4065
|
+
variables: {
|
|
4066
|
+
pageId: params.pageId
|
|
4067
|
+
},
|
|
4068
|
+
fetchFn: this.fetchFn
|
|
4069
|
+
});
|
|
4070
|
+
} catch (cause) {
|
|
4071
|
+
if (cause instanceof PkgseerTransportError) {
|
|
4072
|
+
throw new PackageIntelligenceNetworkError("Could not reach the package intelligence service. Check your connection or set GITHITS_CODE_NAV_URL.", { cause });
|
|
4073
|
+
}
|
|
4074
|
+
throw cause;
|
|
4075
|
+
}
|
|
4076
|
+
if (response.status < 200 || response.status >= 300) {
|
|
4077
|
+
throw this.createHttpError(response);
|
|
4078
|
+
}
|
|
4079
|
+
const parsed = packageDocReadGraphQLResponseSchema.safeParse(response.parsedBody);
|
|
4080
|
+
if (!parsed.success) {
|
|
4081
|
+
throw new MalformedPackageIntelligenceResponseError("Malformed response from the package-intelligence service.");
|
|
4082
|
+
}
|
|
4083
|
+
if (parsed.data.errors && parsed.data.errors.length > 0) {
|
|
4084
|
+
throw this.createGraphQLError(parsed.data.errors);
|
|
4085
|
+
}
|
|
4086
|
+
const data = parsed.data.data?.getDocPage;
|
|
4087
|
+
if (!data) {
|
|
4088
|
+
throw new MalformedPackageIntelligenceResponseError("Empty response from the package-intelligence service.");
|
|
4089
|
+
}
|
|
4090
|
+
return this.normalisePackageDocResult(data);
|
|
4091
|
+
}
|
|
4092
|
+
normalisePackageDocResult(data) {
|
|
4093
|
+
return {
|
|
4094
|
+
registry: data.registry ?? undefined,
|
|
4095
|
+
packageName: data.packageName ?? undefined,
|
|
4096
|
+
version: data.version ?? undefined,
|
|
4097
|
+
sourceKind: data.sourceKind ?? undefined,
|
|
4098
|
+
page: data.page ? {
|
|
4099
|
+
id: data.page.id ?? undefined,
|
|
4100
|
+
title: data.page.title ?? undefined,
|
|
4101
|
+
content: data.page.content ?? undefined,
|
|
4102
|
+
contentFormat: data.page.contentFormat ?? undefined,
|
|
4103
|
+
breadcrumbs: data.page.breadcrumbs ?? undefined,
|
|
4104
|
+
linkName: data.page.linkName ?? undefined,
|
|
4105
|
+
lastUpdatedAt: data.page.lastUpdatedAt ?? undefined,
|
|
4106
|
+
sourceKind: data.page.sourceKind ?? undefined,
|
|
4107
|
+
source: data.page.source ? {
|
|
4108
|
+
url: data.page.source.url ?? undefined,
|
|
4109
|
+
label: data.page.source.label ?? undefined
|
|
4110
|
+
} : undefined,
|
|
4111
|
+
repoUrl: data.page.repoUrl ?? undefined,
|
|
4112
|
+
gitRef: data.page.gitRef ?? undefined,
|
|
4113
|
+
requestedRef: data.page.requestedRef ?? undefined,
|
|
4114
|
+
filePath: data.page.filePath ?? undefined,
|
|
4115
|
+
baseUrl: data.page.baseUrl ?? undefined
|
|
4116
|
+
} : undefined
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
function parseDetail3(body) {
|
|
4121
|
+
if (!body)
|
|
4122
|
+
return;
|
|
4123
|
+
try {
|
|
4124
|
+
const parsed = JSON.parse(body);
|
|
4125
|
+
if (typeof parsed.detail === "string")
|
|
4126
|
+
return parsed.detail;
|
|
4127
|
+
if (typeof parsed.error === "string")
|
|
4128
|
+
return parsed.error;
|
|
4129
|
+
} catch {
|
|
4130
|
+
return body;
|
|
4131
|
+
}
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
function getPrimaryExtensions2(errors) {
|
|
4135
|
+
for (const error of errors) {
|
|
4136
|
+
if (error.extensions && Object.keys(error.extensions).length > 0) {
|
|
4137
|
+
return error.extensions;
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
function parseVersionList(raw) {
|
|
4143
|
+
if (!Array.isArray(raw))
|
|
4144
|
+
return;
|
|
4145
|
+
const versions = [];
|
|
4146
|
+
for (const item of raw) {
|
|
4147
|
+
if (typeof item === "string" && item.length > 0) {
|
|
4148
|
+
versions.push(item);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
return versions.length > 0 ? versions : undefined;
|
|
4152
|
+
}
|
|
4153
|
+
// src/services/prompt-service.ts
|
|
4154
|
+
import { checkbox, select } from "@inquirer/prompts";
|
|
4155
|
+
|
|
4156
|
+
class PromptServiceImpl {
|
|
4157
|
+
async checkbox(message, choices) {
|
|
4158
|
+
return checkbox({ message, choices });
|
|
4159
|
+
}
|
|
4160
|
+
async confirm3(message) {
|
|
4161
|
+
return select({
|
|
4162
|
+
message,
|
|
4163
|
+
choices: [
|
|
4164
|
+
{ value: "yes", name: "Yes" },
|
|
4165
|
+
{ value: "no", name: "No" },
|
|
4166
|
+
{
|
|
4167
|
+
value: "always",
|
|
4168
|
+
name: "Yes to all",
|
|
4169
|
+
description: "Skip confirmation for remaining agents"
|
|
4170
|
+
}
|
|
4171
|
+
]
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
// src/services/refreshing-githits-service.ts
|
|
4176
|
+
class RefreshingGitHitsService {
|
|
4177
|
+
apiUrl;
|
|
4178
|
+
tokenProvider;
|
|
4179
|
+
serviceFactory;
|
|
4180
|
+
constructor(apiUrl, tokenProvider, serviceFactory = (url, token) => new GitHitsServiceImpl(url, token)) {
|
|
4181
|
+
this.apiUrl = apiUrl;
|
|
4182
|
+
this.tokenProvider = tokenProvider;
|
|
4183
|
+
this.serviceFactory = serviceFactory;
|
|
4184
|
+
}
|
|
4185
|
+
async search(params) {
|
|
4186
|
+
return this.withTokenRefresh((service) => service.search(params));
|
|
4187
|
+
}
|
|
4188
|
+
async getLanguages() {
|
|
4189
|
+
return this.withTokenRefresh((service) => service.getLanguages());
|
|
4190
|
+
}
|
|
4191
|
+
async submitFeedback(params) {
|
|
4192
|
+
return this.withTokenRefresh((service) => service.submitFeedback(params));
|
|
4193
|
+
}
|
|
4194
|
+
async withTokenRefresh(operation) {
|
|
4195
|
+
return executeWithTokenRefresh({
|
|
4196
|
+
getToken: () => this.tokenProvider.getToken(),
|
|
4197
|
+
forceRefresh: () => this.tokenProvider.forceRefresh(),
|
|
4198
|
+
shouldRefresh: (error) => error instanceof AuthenticationError,
|
|
4199
|
+
executeWithToken: async (token) => {
|
|
4200
|
+
const service = this.serviceFactory(this.apiUrl, token);
|
|
4201
|
+
return operation(service);
|
|
4202
|
+
}
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
// src/services/token-manager.ts
|
|
4207
|
+
var PROACTIVE_REFRESH_RATIO = 0.9;
|
|
4208
|
+
function shouldRefreshToken(token, ratio, now) {
|
|
4209
|
+
if (!token.expiresAt) {
|
|
4210
|
+
return { expired: false, shouldRefresh: false };
|
|
4211
|
+
}
|
|
4212
|
+
const expiresAt = new Date(token.expiresAt).getTime();
|
|
4213
|
+
const nowMs = now.getTime();
|
|
4214
|
+
if (nowMs >= expiresAt) {
|
|
4215
|
+
return { expired: true, shouldRefresh: true };
|
|
4216
|
+
}
|
|
4217
|
+
const createdAt = new Date(token.createdAt).getTime();
|
|
4218
|
+
const lifetime = expiresAt - createdAt;
|
|
4219
|
+
if (lifetime <= 0) {
|
|
4220
|
+
return { expired: false, shouldRefresh: false };
|
|
4221
|
+
}
|
|
4222
|
+
const threshold = createdAt + lifetime * ratio;
|
|
4223
|
+
return { expired: false, shouldRefresh: nowMs >= threshold };
|
|
4224
|
+
}
|
|
4225
|
+
async function refreshExpiredToken(authService, authStorage, mcpUrl) {
|
|
4226
|
+
const manager = new TokenManager({ authService, authStorage, mcpUrl });
|
|
4227
|
+
return manager.forceRefresh();
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
class TokenManager {
|
|
4231
|
+
authService;
|
|
4232
|
+
authStorage;
|
|
4233
|
+
mcpUrl;
|
|
4234
|
+
cachedToken = null;
|
|
4235
|
+
refreshPromise = null;
|
|
4236
|
+
constructor(deps) {
|
|
4237
|
+
this.authService = deps.authService;
|
|
4238
|
+
this.authStorage = deps.authStorage;
|
|
4239
|
+
this.mcpUrl = deps.mcpUrl;
|
|
4240
|
+
}
|
|
4241
|
+
async getToken() {
|
|
4242
|
+
return withTelemetrySpan("token-manager.get-token", async () => {
|
|
4243
|
+
if (!this.cachedToken) {
|
|
4244
|
+
this.cachedToken = await withTelemetrySpan("token-manager.load-tokens", () => this.authStorage.loadTokens(this.mcpUrl));
|
|
4245
|
+
if (!this.cachedToken)
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
const currentToken = this.cachedToken.accessToken;
|
|
4249
|
+
const { expired, shouldRefresh } = shouldRefreshToken(this.cachedToken, PROACTIVE_REFRESH_RATIO, new Date);
|
|
4250
|
+
if (!shouldRefresh) {
|
|
4251
|
+
return currentToken;
|
|
4252
|
+
}
|
|
4253
|
+
const refreshedToken = await this.doRefresh();
|
|
4254
|
+
if (refreshedToken) {
|
|
4255
|
+
return refreshedToken;
|
|
4256
|
+
}
|
|
4257
|
+
if (!expired) {
|
|
4258
|
+
return currentToken;
|
|
4259
|
+
}
|
|
4260
|
+
return;
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
async forceRefresh() {
|
|
4264
|
+
return withTelemetrySpan("token-manager.force-refresh", () => this.doRefresh());
|
|
4265
|
+
}
|
|
4266
|
+
async doRefresh() {
|
|
4267
|
+
if (this.refreshPromise) {
|
|
4268
|
+
return this.refreshPromise;
|
|
4269
|
+
}
|
|
4270
|
+
this.refreshPromise = this.executeRefresh();
|
|
4271
|
+
try {
|
|
4272
|
+
return await this.refreshPromise;
|
|
4273
|
+
} finally {
|
|
4274
|
+
this.refreshPromise = null;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
async executeRefresh() {
|
|
4278
|
+
return withTelemetrySpan("token-manager.refresh", async () => {
|
|
4279
|
+
const tokens = this.cachedToken ?? await withTelemetrySpan("token-manager.load-tokens", () => this.authStorage.loadTokens(this.mcpUrl));
|
|
4280
|
+
if (!tokens)
|
|
4281
|
+
return;
|
|
4282
|
+
const client = await withTelemetrySpan("token-manager.load-client", () => this.authStorage.loadClient(this.mcpUrl));
|
|
4283
|
+
if (!client)
|
|
4284
|
+
return;
|
|
4285
|
+
let response;
|
|
4286
|
+
try {
|
|
4287
|
+
const metadata = await withTelemetrySpan("token-manager.discover-endpoints", () => this.authService.discoverEndpoints(this.mcpUrl));
|
|
4288
|
+
response = await withTelemetrySpan("token-manager.refresh-access-token", () => this.authService.refreshAccessToken({
|
|
4289
|
+
tokenEndpoint: metadata.tokenEndpoint,
|
|
4290
|
+
clientId: client.clientId,
|
|
4291
|
+
clientSecret: client.clientSecret,
|
|
4292
|
+
refreshToken: tokens.refreshToken
|
|
4293
|
+
}));
|
|
4294
|
+
} catch {
|
|
4295
|
+
const isExpired = tokens.expiresAt ? new Date >= new Date(tokens.expiresAt) : false;
|
|
4296
|
+
if (isExpired) {
|
|
4297
|
+
this.cachedToken = null;
|
|
4298
|
+
await withTelemetrySpan("token-manager.clear-tokens", () => this.authStorage.clearTokens(this.mcpUrl));
|
|
4299
|
+
}
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
const newTokenData = {
|
|
4303
|
+
accessToken: response.accessToken,
|
|
4304
|
+
refreshToken: response.refreshToken,
|
|
4305
|
+
expiresAt: new Date(Date.now() + response.expiresIn * 1000).toISOString(),
|
|
4306
|
+
createdAt: new Date().toISOString()
|
|
4307
|
+
};
|
|
4308
|
+
await withTelemetrySpan("token-manager.save-tokens", () => this.authStorage.saveTokens(this.mcpUrl, newTokenData));
|
|
4309
|
+
this.cachedToken = newTokenData;
|
|
4310
|
+
return response.accessToken;
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
// src/container.ts
|
|
4315
|
+
function createAuthStorage(fileSystemService) {
|
|
4316
|
+
return withTelemetrySpanSync("container.create-auth-storage", () => {
|
|
4317
|
+
const fileStorage = new AuthStorageImpl(fileSystemService);
|
|
4318
|
+
const rawKeyring = new KeyringServiceImpl;
|
|
4319
|
+
const keyring = process.platform === "win32" ? new ChunkingKeyringService(rawKeyring, WINDOWS_MAX_ENTRY_SIZE) : rawKeyring;
|
|
4320
|
+
const keychainStorage = new KeychainAuthStorage(keyring);
|
|
4321
|
+
return new MigratingAuthStorage(keychainStorage, fileStorage, (error) => {
|
|
4322
|
+
if (!(error instanceof KeychainUnavailableError))
|
|
4323
|
+
return;
|
|
4324
|
+
console.error("Warning: System keychain unavailable. Falling back to file-based credential storage.");
|
|
4325
|
+
});
|
|
4326
|
+
});
|
|
4327
|
+
}
|
|
4328
|
+
function createStaticTokenProvider(token) {
|
|
4329
|
+
return {
|
|
4330
|
+
getToken: async () => token,
|
|
4331
|
+
forceRefresh: async () => {
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4336
|
+
async function createContainer() {
|
|
4337
|
+
return withTelemetrySpan("container.create", async () => {
|
|
4338
|
+
const mcpUrl = getMcpUrl();
|
|
4339
|
+
const apiUrl = getApiUrl();
|
|
4340
|
+
const codeNavigationUrl = getCodeNavigationUrl();
|
|
4341
|
+
const codeNavigationCliOverrideEnabled = isCodeNavigationCliOverrideEnabled();
|
|
4342
|
+
const fileSystemService = new FileSystemServiceImpl;
|
|
4343
|
+
const authStorage = createAuthStorage(fileSystemService);
|
|
4344
|
+
const authService = new AuthServiceImpl;
|
|
4345
|
+
const browserService = new BrowserServiceImpl;
|
|
4346
|
+
const envToken = getEnvApiToken();
|
|
4347
|
+
if (envToken) {
|
|
4348
|
+
const tokenProvider = createStaticTokenProvider(envToken);
|
|
4349
|
+
const codeNavigationService2 = codeNavigationUrl ? new CodeNavigationServiceImpl(codeNavigationUrl, tokenProvider) : undefined;
|
|
4350
|
+
const packageIntelligenceService2 = codeNavigationUrl ? new PackageIntelligenceServiceImpl(codeNavigationUrl, tokenProvider) : undefined;
|
|
4351
|
+
return {
|
|
4352
|
+
authStorage,
|
|
4353
|
+
authService,
|
|
4354
|
+
browserService,
|
|
4355
|
+
fileSystemService,
|
|
4356
|
+
mcpUrl,
|
|
4357
|
+
apiUrl,
|
|
4358
|
+
apiToken: envToken,
|
|
4359
|
+
hasValidToken: true,
|
|
4360
|
+
envApiToken: envToken,
|
|
4361
|
+
codeNavigationCapability: getCodeNavigationCapability(envToken),
|
|
4362
|
+
codeNavigationCliOverrideEnabled,
|
|
4363
|
+
codeNavigationUrl,
|
|
4364
|
+
codeNavigationService: codeNavigationService2,
|
|
4365
|
+
packageIntelligenceService: packageIntelligenceService2,
|
|
4366
|
+
githitsService: new GitHitsServiceImpl(apiUrl, envToken)
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
const tokenManager = new TokenManager({ authService, authStorage, mcpUrl });
|
|
4370
|
+
const apiToken = await withTelemetrySpan("container.token.get", () => tokenManager.getToken());
|
|
4371
|
+
const codeNavigationService = codeNavigationUrl ? new CodeNavigationServiceImpl(codeNavigationUrl, tokenManager) : undefined;
|
|
4372
|
+
const packageIntelligenceService = codeNavigationUrl ? new PackageIntelligenceServiceImpl(codeNavigationUrl, tokenManager) : undefined;
|
|
4373
|
+
return {
|
|
4374
|
+
authStorage,
|
|
4375
|
+
authService,
|
|
4376
|
+
browserService,
|
|
4377
|
+
fileSystemService,
|
|
4378
|
+
mcpUrl,
|
|
4379
|
+
apiUrl,
|
|
4380
|
+
apiToken,
|
|
4381
|
+
hasValidToken: apiToken !== undefined,
|
|
4382
|
+
envApiToken: undefined,
|
|
4383
|
+
codeNavigationCapability: getCodeNavigationCapability(apiToken),
|
|
4384
|
+
codeNavigationCliOverrideEnabled,
|
|
4385
|
+
codeNavigationUrl,
|
|
4386
|
+
codeNavigationService,
|
|
4387
|
+
packageIntelligenceService,
|
|
4388
|
+
githitsService: new RefreshingGitHitsService(apiUrl, tokenManager)
|
|
4389
|
+
};
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
async function resolveStartupCodeNavigationCapability() {
|
|
4393
|
+
const state = await resolveStartupCodeNavigationRegistrationState();
|
|
4394
|
+
return state.capability;
|
|
4395
|
+
}
|
|
4396
|
+
async function resolveStartupCodeNavigationRegistrationState() {
|
|
4397
|
+
return withTelemetrySpan("startup.resolve-code-nav-registration-state", async () => {
|
|
4398
|
+
const envToken = getEnvApiToken();
|
|
4399
|
+
if (envToken) {
|
|
4400
|
+
return {
|
|
4401
|
+
capability: getCodeNavigationCapability(envToken),
|
|
4402
|
+
expiredStoredAuth: false
|
|
4403
|
+
};
|
|
4404
|
+
}
|
|
4405
|
+
const tokens = await loadStartupTokens(getMcpUrl());
|
|
4406
|
+
if (tokens?.expiresAt && new Date(tokens.expiresAt) < new Date) {
|
|
4407
|
+
return { capability: "unknown", expiredStoredAuth: true };
|
|
4408
|
+
}
|
|
4409
|
+
return {
|
|
4410
|
+
capability: getCodeNavigationCapability(tokens?.accessToken),
|
|
4411
|
+
expiredStoredAuth: false
|
|
4412
|
+
};
|
|
4413
|
+
});
|
|
4414
|
+
}
|
|
4415
|
+
async function loadStartupTokens(mcpUrl) {
|
|
4416
|
+
return withTelemetrySpan("startup.load-tokens", async () => {
|
|
4417
|
+
const fileSystemService = new FileSystemServiceImpl;
|
|
4418
|
+
const fileStorage = new AuthStorageImpl(fileSystemService);
|
|
4419
|
+
try {
|
|
4420
|
+
const rawKeyring = new KeyringServiceImpl;
|
|
4421
|
+
const keyring = process.platform === "win32" ? new ChunkingKeyringService(rawKeyring, WINDOWS_MAX_ENTRY_SIZE) : rawKeyring;
|
|
4422
|
+
const keychainStorage = new KeychainAuthStorage(keyring);
|
|
4423
|
+
const keychainTokens = await keychainStorage.loadTokens(mcpUrl);
|
|
4424
|
+
if (keychainTokens) {
|
|
4425
|
+
return keychainTokens;
|
|
4426
|
+
}
|
|
4427
|
+
} catch (error) {
|
|
4428
|
+
if (!(error instanceof KeychainUnavailableError)) {
|
|
4429
|
+
throw error;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
return fileStorage.loadTokens(mcpUrl);
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
export { getCodeNavigationCapability, debugLog, setMcpClientVersionProvider, setClientMode, isTelemetryEnabled, withTelemetrySpan, startTelemetrySpan, endTelemetrySpan, flushTelemetry, AuthenticationError, CodeNavigationAccessError, CodeNavigationGraphQLError, CodeNavigationIndexingError, CodeNavigationUnresolvableError, MalformedCodeNavigationResponseError, CodeNavigationTargetNotFoundError, CodeNavigationFileNotFoundError, CodeNavigationVersionNotFoundError, CodeNavigationValidationError, CodeNavigationFeatureFlagRequiredError, CodeNavigationNetworkError, CodeNavigationBackendError, getCodeNavigationUrl, getEnvApiToken, isCodeNavigationCliOverrideEnabled, ExecServiceImpl, FileSystemServiceImpl, PackageIntelligenceAccessError, PackageIntelligenceFeatureFlagRequiredError, PackageIntelligenceNetworkError, PackageIntelligenceBackendError, PackageIntelligenceGraphQLError, PackageIntelligenceTargetNotFoundError, PackageIntelligenceValidationError, PackageIntelligenceVersionNotFoundError, MalformedPackageIntelligenceResponseError, PackageIntelligenceChangelogSourceNotFoundError, PromptServiceImpl, refreshExpiredToken, createContainer, resolveStartupCodeNavigationCapability, resolveStartupCodeNavigationRegistrationState };
|