githits 0.3.0 → 0.3.2
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/dist/cli.js +938 -231
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-1gqfte6e.js → chunk-rwcs5gyy.js} +2 -2
- package/dist/shared/{chunk-gxya097b.js → chunk-w5kw8sef.js} +1 -1
- package/dist/shared/{chunk-5mk9k2gv.js → chunk-zarrkzvq.js} +424 -10
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
createAuthCommandDependencies,
|
|
3
3
|
createAuthStatusDependencies,
|
|
4
4
|
createContainer
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import"./chunk-
|
|
5
|
+
} from "./chunk-zarrkzvq.js";
|
|
6
|
+
import"./chunk-w5kw8sef.js";
|
|
7
7
|
export {
|
|
8
8
|
createContainer,
|
|
9
9
|
createAuthStatusDependencies,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
__require,
|
|
2
3
|
version
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-w5kw8sef.js";
|
|
4
5
|
|
|
5
6
|
// src/services/app-config-paths.ts
|
|
6
7
|
var APP_DIR = "githits";
|
|
@@ -250,7 +251,7 @@ class AuthServiceImpl {
|
|
|
250
251
|
const error = await response.text();
|
|
251
252
|
throw new Error(`Token refresh failed: ${error}`);
|
|
252
253
|
}
|
|
253
|
-
return
|
|
254
|
+
return parseRefreshTokenResponse(await response.json());
|
|
254
255
|
}
|
|
255
256
|
}
|
|
256
257
|
function parseTokenResponse(data) {
|
|
@@ -264,6 +265,17 @@ function parseTokenResponse(data) {
|
|
|
264
265
|
expiresIn: d.expires_in || 3600
|
|
265
266
|
};
|
|
266
267
|
}
|
|
268
|
+
function parseRefreshTokenResponse(data) {
|
|
269
|
+
const d = data;
|
|
270
|
+
if (!d.access_token) {
|
|
271
|
+
throw new Error("Token response missing required fields");
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
accessToken: d.access_token,
|
|
275
|
+
refreshToken: typeof d.refresh_token === "string" ? d.refresh_token : undefined,
|
|
276
|
+
expiresIn: d.expires_in || 3600
|
|
277
|
+
};
|
|
278
|
+
}
|
|
267
279
|
function successHtml(title = "Authentication successful") {
|
|
268
280
|
return `<!DOCTYPE html>
|
|
269
281
|
<html><head><title>GitHits CLI</title>
|
|
@@ -429,6 +441,13 @@ class AuthStorageImpl {
|
|
|
429
441
|
await this.fs.ensureDir(this.configDir, DIR_MODE);
|
|
430
442
|
await this.fs.atomicWriteFile(this.authPath, JSON.stringify(stored, null, 2));
|
|
431
443
|
}
|
|
444
|
+
async saveTokensIfUnchanged(baseUrl, expected, data) {
|
|
445
|
+
const current = await this.loadTokens(baseUrl);
|
|
446
|
+
if (!sameTokenData(current, expected))
|
|
447
|
+
return false;
|
|
448
|
+
await this.saveTokens(baseUrl, data);
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
432
451
|
async clearTokens(baseUrl) {
|
|
433
452
|
const stored = await this.loadAuthFile();
|
|
434
453
|
if (!stored)
|
|
@@ -440,6 +459,13 @@ class AuthStorageImpl {
|
|
|
440
459
|
await this.fs.atomicWriteFile(this.authPath, JSON.stringify(stored, null, 2));
|
|
441
460
|
}
|
|
442
461
|
}
|
|
462
|
+
async clearTokensIfUnchanged(baseUrl, expected) {
|
|
463
|
+
const current = await this.loadTokens(baseUrl);
|
|
464
|
+
if (!sameTokenData(current, expected))
|
|
465
|
+
return false;
|
|
466
|
+
await this.clearTokens(baseUrl);
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
443
469
|
async loadClient(baseUrl) {
|
|
444
470
|
const stored = await this.loadClientFile();
|
|
445
471
|
if (!stored)
|
|
@@ -466,6 +492,13 @@ class AuthStorageImpl {
|
|
|
466
492
|
await this.fs.ensureDir(this.configDir, DIR_MODE);
|
|
467
493
|
await this.fs.atomicWriteFile(this.clientPath, JSON.stringify(stored, null, 2));
|
|
468
494
|
}
|
|
495
|
+
async saveAuthSession(baseUrl, client, tokens) {
|
|
496
|
+
await this.saveClient(baseUrl, client);
|
|
497
|
+
await this.saveTokens(baseUrl, tokens);
|
|
498
|
+
}
|
|
499
|
+
async clearAuthSession(baseUrl) {
|
|
500
|
+
await clearAuthSessionBestEffort(() => this.clearTokens(baseUrl), () => this.clearClient(baseUrl));
|
|
501
|
+
}
|
|
469
502
|
async loadAuthFile() {
|
|
470
503
|
if (!await this.fs.exists(this.authPath))
|
|
471
504
|
return null;
|
|
@@ -496,6 +529,26 @@ class AuthStorageImpl {
|
|
|
496
529
|
function normalizeBaseUrl(url) {
|
|
497
530
|
return url.replace(/\/+$/, "");
|
|
498
531
|
}
|
|
532
|
+
function sameTokenData(a, b) {
|
|
533
|
+
if (a === null || b === null)
|
|
534
|
+
return a === b;
|
|
535
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.createdAt === b.createdAt;
|
|
536
|
+
}
|
|
537
|
+
async function clearAuthSessionBestEffort(clearTokens, clearClient) {
|
|
538
|
+
let firstError;
|
|
539
|
+
try {
|
|
540
|
+
await clearTokens();
|
|
541
|
+
} catch (error) {
|
|
542
|
+
firstError = error;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
await clearClient();
|
|
546
|
+
} catch (error) {
|
|
547
|
+
firstError ??= error;
|
|
548
|
+
}
|
|
549
|
+
if (firstError)
|
|
550
|
+
throw firstError;
|
|
551
|
+
}
|
|
499
552
|
// src/services/browser-service.ts
|
|
500
553
|
import open from "open";
|
|
501
554
|
|
|
@@ -1617,6 +1670,16 @@ query ListRepoFiles(
|
|
|
1617
1670
|
$gitRef: String
|
|
1618
1671
|
$version: String
|
|
1619
1672
|
$pathPrefix: String
|
|
1673
|
+
$pathSelectors: [FilePathSelectorInput!]
|
|
1674
|
+
$extensions: [String!]
|
|
1675
|
+
$fileTypes: [String!]
|
|
1676
|
+
$languages: [String!]
|
|
1677
|
+
$fileIntent: FileIntent
|
|
1678
|
+
$fileIntents: [FileIntent!]
|
|
1679
|
+
$excludeFileIntents: [FileIntent!]
|
|
1680
|
+
$excludeDocFiles: Boolean
|
|
1681
|
+
$excludeTestFiles: Boolean
|
|
1682
|
+
$includeHidden: Boolean
|
|
1620
1683
|
$limit: Int
|
|
1621
1684
|
$waitTimeoutMs: Int
|
|
1622
1685
|
) {
|
|
@@ -1627,6 +1690,16 @@ query ListRepoFiles(
|
|
|
1627
1690
|
gitRef: $gitRef
|
|
1628
1691
|
version: $version
|
|
1629
1692
|
pathPrefix: $pathPrefix
|
|
1693
|
+
pathSelectors: $pathSelectors
|
|
1694
|
+
extensions: $extensions
|
|
1695
|
+
fileTypes: $fileTypes
|
|
1696
|
+
languages: $languages
|
|
1697
|
+
fileIntent: $fileIntent
|
|
1698
|
+
fileIntents: $fileIntents
|
|
1699
|
+
excludeFileIntents: $excludeFileIntents
|
|
1700
|
+
excludeDocFiles: $excludeDocFiles
|
|
1701
|
+
excludeTestFiles: $excludeTestFiles
|
|
1702
|
+
includeHidden: $includeHidden
|
|
1630
1703
|
limit: $limit
|
|
1631
1704
|
waitTimeoutMs: $waitTimeoutMs
|
|
1632
1705
|
) {
|
|
@@ -2241,6 +2314,19 @@ class CodeNavigationServiceImpl {
|
|
|
2241
2314
|
gitRef: params.target.gitRef,
|
|
2242
2315
|
version: params.target.version,
|
|
2243
2316
|
pathPrefix: params.pathPrefix,
|
|
2317
|
+
pathSelectors: params.pathSelectors?.map((entry) => ({
|
|
2318
|
+
kind: entry.kind,
|
|
2319
|
+
value: entry.value
|
|
2320
|
+
})),
|
|
2321
|
+
extensions: params.extensions,
|
|
2322
|
+
fileTypes: params.fileTypes,
|
|
2323
|
+
languages: params.languages,
|
|
2324
|
+
fileIntent: params.fileIntent,
|
|
2325
|
+
fileIntents: params.fileIntents,
|
|
2326
|
+
excludeFileIntents: params.excludeFileIntents,
|
|
2327
|
+
excludeDocFiles: params.excludeDocFiles,
|
|
2328
|
+
excludeTestFiles: params.excludeTestFiles,
|
|
2329
|
+
includeHidden: params.includeHidden,
|
|
2244
2330
|
limit: params.limit,
|
|
2245
2331
|
waitTimeoutMs: params.waitTimeoutMs
|
|
2246
2332
|
},
|
|
@@ -2698,10 +2784,24 @@ class KeychainAuthStorage {
|
|
|
2698
2784
|
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2699
2785
|
this.keyring.setPassword(SERVICE_NAME, key, JSON.stringify(data));
|
|
2700
2786
|
}
|
|
2787
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
2788
|
+
const current = await this.loadTokens(baseUrl2);
|
|
2789
|
+
if (!sameTokenData(current, expected))
|
|
2790
|
+
return false;
|
|
2791
|
+
await this.saveTokens(baseUrl2, data);
|
|
2792
|
+
return true;
|
|
2793
|
+
}
|
|
2701
2794
|
async clearTokens(baseUrl2) {
|
|
2702
2795
|
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2703
2796
|
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2704
2797
|
}
|
|
2798
|
+
async clearTokensIfUnchanged(baseUrl2, expected) {
|
|
2799
|
+
const current = await this.loadTokens(baseUrl2);
|
|
2800
|
+
if (!sameTokenData(current, expected))
|
|
2801
|
+
return false;
|
|
2802
|
+
await this.clearTokens(baseUrl2);
|
|
2803
|
+
return true;
|
|
2804
|
+
}
|
|
2705
2805
|
async loadClient(baseUrl2) {
|
|
2706
2806
|
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2707
2807
|
const json = this.keyring.getPassword(SERVICE_NAME, key);
|
|
@@ -2718,6 +2818,13 @@ class KeychainAuthStorage {
|
|
|
2718
2818
|
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2719
2819
|
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2720
2820
|
}
|
|
2821
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
2822
|
+
await this.saveClient(baseUrl2, client);
|
|
2823
|
+
await this.saveTokens(baseUrl2, tokens);
|
|
2824
|
+
}
|
|
2825
|
+
async clearAuthSession(baseUrl2) {
|
|
2826
|
+
await clearAuthSessionBestEffort(() => this.clearTokens(baseUrl2), () => this.clearClient(baseUrl2));
|
|
2827
|
+
}
|
|
2721
2828
|
getStorageLocation() {
|
|
2722
2829
|
switch (process.platform) {
|
|
2723
2830
|
case "darwin":
|
|
@@ -2767,6 +2874,223 @@ class KeyringServiceImpl {
|
|
|
2767
2874
|
}
|
|
2768
2875
|
}
|
|
2769
2876
|
}
|
|
2877
|
+
// src/services/locked-auth-storage.ts
|
|
2878
|
+
import { execFile } from "node:child_process";
|
|
2879
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2880
|
+
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
2881
|
+
import { dirname as dirname2 } from "node:path";
|
|
2882
|
+
import { promisify } from "node:util";
|
|
2883
|
+
var LOCK_DIR = "auth.lock";
|
|
2884
|
+
var LOCK_TIMEOUT_MS = 1e4;
|
|
2885
|
+
var LOCK_RETRY_MS = 25;
|
|
2886
|
+
var ORPHANED_LOCK_MS = 5000;
|
|
2887
|
+
var OWNER_FILE = "owner.json";
|
|
2888
|
+
var execFileAsync = promisify(execFile);
|
|
2889
|
+
|
|
2890
|
+
class AuthStorageLockTimeoutError extends Error {
|
|
2891
|
+
constructor(message) {
|
|
2892
|
+
super(message);
|
|
2893
|
+
this.name = "AuthStorageLockTimeoutError";
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
class LockedAuthStorage {
|
|
2898
|
+
storage;
|
|
2899
|
+
lockPath;
|
|
2900
|
+
lockTimeoutMs;
|
|
2901
|
+
isOwnerAlive;
|
|
2902
|
+
currentOwner = null;
|
|
2903
|
+
constructor(storage, fileSystemService, options = {}) {
|
|
2904
|
+
this.storage = storage;
|
|
2905
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? LOCK_TIMEOUT_MS;
|
|
2906
|
+
this.isOwnerAlive = options.isOwnerAlive ?? isOriginalProcessAlive;
|
|
2907
|
+
this.lockPath = fileSystemService.joinPath(getAppConfigDir(fileSystemService), LOCK_DIR);
|
|
2908
|
+
}
|
|
2909
|
+
loadTokens(baseUrl2) {
|
|
2910
|
+
return this.storage.loadTokens(baseUrl2);
|
|
2911
|
+
}
|
|
2912
|
+
saveTokens(baseUrl2, data) {
|
|
2913
|
+
return this.withLock(() => this.storage.saveTokens(baseUrl2, data));
|
|
2914
|
+
}
|
|
2915
|
+
saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
2916
|
+
return this.withLock(() => this.storage.saveTokensIfUnchanged(baseUrl2, expected, data));
|
|
2917
|
+
}
|
|
2918
|
+
clearTokens(baseUrl2) {
|
|
2919
|
+
return this.withLock(() => this.storage.clearTokens(baseUrl2));
|
|
2920
|
+
}
|
|
2921
|
+
clearTokensIfUnchanged(baseUrl2, expected) {
|
|
2922
|
+
return this.withLock(() => this.storage.clearTokensIfUnchanged(baseUrl2, expected));
|
|
2923
|
+
}
|
|
2924
|
+
loadClient(baseUrl2) {
|
|
2925
|
+
return this.storage.loadClient(baseUrl2);
|
|
2926
|
+
}
|
|
2927
|
+
saveClient(baseUrl2, data) {
|
|
2928
|
+
return this.withLock(() => this.storage.saveClient(baseUrl2, data));
|
|
2929
|
+
}
|
|
2930
|
+
clearClient(baseUrl2) {
|
|
2931
|
+
return this.withLock(() => this.storage.clearClient(baseUrl2));
|
|
2932
|
+
}
|
|
2933
|
+
saveAuthSession(baseUrl2, client, tokens) {
|
|
2934
|
+
return this.withLock(() => this.storage.saveAuthSession(baseUrl2, client, tokens));
|
|
2935
|
+
}
|
|
2936
|
+
clearAuthSession(baseUrl2) {
|
|
2937
|
+
return this.withLock(() => this.storage.clearAuthSession(baseUrl2));
|
|
2938
|
+
}
|
|
2939
|
+
getStorageLocation() {
|
|
2940
|
+
return this.storage.getStorageLocation();
|
|
2941
|
+
}
|
|
2942
|
+
async withLock(fn) {
|
|
2943
|
+
await this.acquireLock();
|
|
2944
|
+
try {
|
|
2945
|
+
return await fn();
|
|
2946
|
+
} finally {
|
|
2947
|
+
await this.releaseLock();
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
async acquireLock() {
|
|
2951
|
+
const startedAt = Date.now();
|
|
2952
|
+
await mkdir2(dirname2(this.lockPath), { recursive: true, mode: 448 });
|
|
2953
|
+
while (true) {
|
|
2954
|
+
try {
|
|
2955
|
+
await mkdir2(this.lockPath, { recursive: false, mode: 448 });
|
|
2956
|
+
try {
|
|
2957
|
+
await this.writeOwner();
|
|
2958
|
+
} catch (error) {
|
|
2959
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
2960
|
+
return;
|
|
2961
|
+
});
|
|
2962
|
+
throw error;
|
|
2963
|
+
}
|
|
2964
|
+
return;
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
if (error.code !== "EEXIST")
|
|
2967
|
+
throw error;
|
|
2968
|
+
await this.reclaimStaleLock();
|
|
2969
|
+
if (Date.now() - startedAt >= this.lockTimeoutMs) {
|
|
2970
|
+
throw new AuthStorageLockTimeoutError(`Timed out waiting for GitHits auth storage lock at ${this.lockPath}. If no githits process is running, remove this directory and retry.`);
|
|
2971
|
+
}
|
|
2972
|
+
await sleep(LOCK_RETRY_MS);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
async writeOwner() {
|
|
2977
|
+
const owner = {
|
|
2978
|
+
id: randomUUID3(),
|
|
2979
|
+
pid: process.pid,
|
|
2980
|
+
createdAt: new Date().toISOString(),
|
|
2981
|
+
processStartedAt: await getProcessStartedAt(process.pid)
|
|
2982
|
+
};
|
|
2983
|
+
this.currentOwner = owner;
|
|
2984
|
+
await writeFile2(this.ownerPath(), JSON.stringify(owner), { mode: 384 });
|
|
2985
|
+
}
|
|
2986
|
+
async reclaimStaleLock() {
|
|
2987
|
+
const owner = await this.readOwner();
|
|
2988
|
+
if (!owner) {
|
|
2989
|
+
await this.reclaimOldOwnerlessLock();
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
const ownerDead = !await this.isOwnerAlive(owner.pid, owner.processStartedAt);
|
|
2993
|
+
if (!ownerDead)
|
|
2994
|
+
return;
|
|
2995
|
+
const currentOwner = await this.readOwner();
|
|
2996
|
+
if (!currentOwner || currentOwner.id !== owner.id)
|
|
2997
|
+
return;
|
|
2998
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
2999
|
+
return;
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
async reclaimOldOwnerlessLock() {
|
|
3003
|
+
const createdAtMs = await lockCreatedAtMs(this.lockPath);
|
|
3004
|
+
if (Date.now() - createdAtMs < ORPHANED_LOCK_MS)
|
|
3005
|
+
return;
|
|
3006
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
3007
|
+
return;
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
async readOwner() {
|
|
3011
|
+
try {
|
|
3012
|
+
const raw = await readFile2(this.ownerPath(), "utf8");
|
|
3013
|
+
const parsed = JSON.parse(raw);
|
|
3014
|
+
if (typeof parsed.id !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || !(typeof parsed.processStartedAt === "string" || parsed.processStartedAt === null)) {
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
return {
|
|
3018
|
+
id: parsed.id,
|
|
3019
|
+
pid: parsed.pid,
|
|
3020
|
+
createdAt: parsed.createdAt,
|
|
3021
|
+
processStartedAt: parsed.processStartedAt
|
|
3022
|
+
};
|
|
3023
|
+
} catch {
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
async releaseLock() {
|
|
3028
|
+
const owner = this.currentOwner;
|
|
3029
|
+
this.currentOwner = null;
|
|
3030
|
+
if (!owner)
|
|
3031
|
+
return;
|
|
3032
|
+
const currentOwner = await this.readOwner();
|
|
3033
|
+
if (!currentOwner || currentOwner.id !== owner.id)
|
|
3034
|
+
return;
|
|
3035
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
3036
|
+
return;
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
ownerPath() {
|
|
3040
|
+
return `${this.lockPath}/${OWNER_FILE}`;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
function sleep(ms) {
|
|
3044
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3045
|
+
}
|
|
3046
|
+
async function isOriginalProcessAlive(pid, processStartedAt) {
|
|
3047
|
+
if (!isProcessAlive(pid))
|
|
3048
|
+
return false;
|
|
3049
|
+
if (!processStartedAt)
|
|
3050
|
+
return true;
|
|
3051
|
+
return await getProcessStartedAt(pid) === processStartedAt;
|
|
3052
|
+
}
|
|
3053
|
+
function isProcessAlive(pid) {
|
|
3054
|
+
if (pid <= 0)
|
|
3055
|
+
return false;
|
|
3056
|
+
try {
|
|
3057
|
+
process.kill(pid, 0);
|
|
3058
|
+
return true;
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
const code = error.code;
|
|
3061
|
+
return code === "EPERM";
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async function getProcessStartedAt(pid) {
|
|
3065
|
+
try {
|
|
3066
|
+
if (process.platform === "win32") {
|
|
3067
|
+
const { stdout: stdout2 } = await execFileAsync("powershell.exe", [
|
|
3068
|
+
"-NoProfile",
|
|
3069
|
+
"-Command",
|
|
3070
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString('o')`
|
|
3071
|
+
]);
|
|
3072
|
+
return stdout2.trim() || null;
|
|
3073
|
+
}
|
|
3074
|
+
const { stdout } = await execFileAsync("ps", [
|
|
3075
|
+
"-p",
|
|
3076
|
+
String(pid),
|
|
3077
|
+
"-o",
|
|
3078
|
+
"lstart="
|
|
3079
|
+
]);
|
|
3080
|
+
const parsed = Date.parse(stdout.trim());
|
|
3081
|
+
return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
|
|
3082
|
+
} catch {
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
async function lockCreatedAtMs(path) {
|
|
3087
|
+
try {
|
|
3088
|
+
const { stat: stat2 } = await import("node:fs/promises");
|
|
3089
|
+
return (await stat2(path)).mtimeMs;
|
|
3090
|
+
} catch {
|
|
3091
|
+
return 0;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
2770
3094
|
// src/services/mode-aware-file-auth-storage.ts
|
|
2771
3095
|
class AuthStoragePolicyError extends Error {
|
|
2772
3096
|
constructor(message) {
|
|
@@ -2806,9 +3130,16 @@ class ModeAwareFileAuthStorage {
|
|
|
2806
3130
|
this.assertFileMode();
|
|
2807
3131
|
await this.storage.saveTokens(baseUrl2, data);
|
|
2808
3132
|
}
|
|
3133
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
3134
|
+
this.assertFileMode();
|
|
3135
|
+
return this.storage.saveTokensIfUnchanged(baseUrl2, expected, data);
|
|
3136
|
+
}
|
|
2809
3137
|
clearTokens(baseUrl2) {
|
|
2810
3138
|
return this.storage.clearTokens(baseUrl2);
|
|
2811
3139
|
}
|
|
3140
|
+
clearTokensIfUnchanged(baseUrl2, expected) {
|
|
3141
|
+
return this.storage.clearTokensIfUnchanged(baseUrl2, expected);
|
|
3142
|
+
}
|
|
2812
3143
|
loadClient(baseUrl2) {
|
|
2813
3144
|
return this.storage.loadClient(baseUrl2);
|
|
2814
3145
|
}
|
|
@@ -2819,6 +3150,13 @@ class ModeAwareFileAuthStorage {
|
|
|
2819
3150
|
clearClient(baseUrl2) {
|
|
2820
3151
|
return this.storage.clearClient(baseUrl2);
|
|
2821
3152
|
}
|
|
3153
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
3154
|
+
this.assertFileMode();
|
|
3155
|
+
await this.storage.saveAuthSession(baseUrl2, client, tokens);
|
|
3156
|
+
}
|
|
3157
|
+
clearAuthSession(baseUrl2) {
|
|
3158
|
+
return this.storage.clearAuthSession(baseUrl2);
|
|
3159
|
+
}
|
|
2822
3160
|
getStorageLocation() {
|
|
2823
3161
|
return this.storage.getStorageLocation();
|
|
2824
3162
|
}
|
|
@@ -2864,6 +3202,13 @@ class MigratingAuthStorage {
|
|
|
2864
3202
|
throw this.toPolicyError(error);
|
|
2865
3203
|
}
|
|
2866
3204
|
}
|
|
3205
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
3206
|
+
const current = await this.loadTokens(baseUrl2);
|
|
3207
|
+
if (!this.sameTokenData(current, expected))
|
|
3208
|
+
return false;
|
|
3209
|
+
await this.saveTokens(baseUrl2, data);
|
|
3210
|
+
return true;
|
|
3211
|
+
}
|
|
2867
3212
|
async clearTokens(baseUrl2) {
|
|
2868
3213
|
const primaryError = await this.clearBestEffort(() => this.primary.clearTokens(baseUrl2));
|
|
2869
3214
|
await this.clearBestEffort(() => this.file.clearTokens(baseUrl2));
|
|
@@ -2872,6 +3217,13 @@ class MigratingAuthStorage {
|
|
|
2872
3217
|
throw primaryError;
|
|
2873
3218
|
}
|
|
2874
3219
|
}
|
|
3220
|
+
async clearTokensIfUnchanged(baseUrl2, expected) {
|
|
3221
|
+
const current = await this.loadTokens(baseUrl2);
|
|
3222
|
+
if (!this.sameTokenData(current, expected))
|
|
3223
|
+
return false;
|
|
3224
|
+
await this.clearTokens(baseUrl2);
|
|
3225
|
+
return true;
|
|
3226
|
+
}
|
|
2875
3227
|
async loadClient(baseUrl2) {
|
|
2876
3228
|
if (this.mode === "file") {
|
|
2877
3229
|
return this.loadClientFileMode(baseUrl2);
|
|
@@ -2897,6 +3249,25 @@ class MigratingAuthStorage {
|
|
|
2897
3249
|
throw primaryError;
|
|
2898
3250
|
}
|
|
2899
3251
|
}
|
|
3252
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
3253
|
+
if (this.mode === "file") {
|
|
3254
|
+
await this.file.saveAuthSession(baseUrl2, client, tokens);
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
try {
|
|
3258
|
+
await this.primary.saveAuthSession(baseUrl2, client, tokens);
|
|
3259
|
+
} catch (error) {
|
|
3260
|
+
throw this.toPolicyError(error);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
async clearAuthSession(baseUrl2) {
|
|
3264
|
+
const primaryError = await this.clearBestEffort(() => this.primary.clearAuthSession(baseUrl2));
|
|
3265
|
+
await this.clearBestEffort(() => this.file.clearAuthSession(baseUrl2));
|
|
3266
|
+
await this.clearBestEffort(() => this.legacy.clearAuthSession(baseUrl2));
|
|
3267
|
+
if (primaryError && !(primaryError instanceof KeychainUnavailableError)) {
|
|
3268
|
+
throw primaryError;
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
2900
3271
|
getStorageLocation() {
|
|
2901
3272
|
return this.mode === "file" ? this.file.getStorageLocation() : this.primary.getStorageLocation();
|
|
2902
3273
|
}
|
|
@@ -3103,6 +3474,11 @@ class MigratingAuthStorage {
|
|
|
3103
3474
|
this.warnedAmbiguousPlaintext = true;
|
|
3104
3475
|
this.onWarning("Warning: multiple plaintext auth entries exist with ambiguous timestamps; using the new config auth path and leaving the other entry intact.");
|
|
3105
3476
|
}
|
|
3477
|
+
sameTokenData(a, b) {
|
|
3478
|
+
if (a === null || b === null)
|
|
3479
|
+
return a === b;
|
|
3480
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.createdAt === b.createdAt;
|
|
3481
|
+
}
|
|
3106
3482
|
}
|
|
3107
3483
|
// src/services/package-intelligence-service.ts
|
|
3108
3484
|
import { z as z3 } from "zod";
|
|
@@ -4283,12 +4659,12 @@ class PackageIntelligenceServiceImpl {
|
|
|
4283
4659
|
return this.normaliseChangelogReport(data, params);
|
|
4284
4660
|
}
|
|
4285
4661
|
normaliseChangelogReport(data, params) {
|
|
4286
|
-
const source = data.source
|
|
4287
|
-
|
|
4662
|
+
const source = data.source && data.source.trim() ? data.source : undefined;
|
|
4663
|
+
const rawEntries = data.entries ?? [];
|
|
4664
|
+
if (!source && rawEntries.length === 0) {
|
|
4288
4665
|
const target = params.repoUrl ?? (params.registry && params.packageName ? `${params.registry.toLowerCase()}:${params.packageName}` : "package");
|
|
4289
4666
|
throw new PackageIntelligenceChangelogSourceNotFoundError(`No changelog source available for ${target} (tried GitHub Releases, CHANGELOG.md, and HexDocs).`);
|
|
4290
4667
|
}
|
|
4291
|
-
const rawEntries = data.entries ?? [];
|
|
4292
4668
|
const entries = rawEntries.map((entry) => ({
|
|
4293
4669
|
version: entry.version ?? undefined,
|
|
4294
4670
|
normalizedVersion: entry.normalizedVersion ?? undefined,
|
|
@@ -4629,24 +5005,62 @@ class TokenManager {
|
|
|
4629
5005
|
refreshToken: tokens.refreshToken
|
|
4630
5006
|
}));
|
|
4631
5007
|
} catch {
|
|
5008
|
+
const reloadedToken = await this.loadExternallyUpdatedToken(tokens);
|
|
5009
|
+
if (reloadedToken)
|
|
5010
|
+
return reloadedToken.accessToken;
|
|
4632
5011
|
const isExpired = tokens.expiresAt ? new Date >= new Date(tokens.expiresAt) : false;
|
|
4633
5012
|
if (isExpired) {
|
|
5013
|
+
const currentStoredTokens = await this.loadExternallyUpdatedToken(tokens);
|
|
5014
|
+
if (currentStoredTokens)
|
|
5015
|
+
return currentStoredTokens.accessToken;
|
|
5016
|
+
const cleared = await withTelemetrySpan("token-manager.clear-tokens-if-unchanged", () => this.authStorage.clearTokensIfUnchanged(this.mcpUrl, tokens));
|
|
5017
|
+
if (!cleared) {
|
|
5018
|
+
const currentToken = await this.authStorage.loadTokens(this.mcpUrl);
|
|
5019
|
+
this.cachedToken = currentToken;
|
|
5020
|
+
return currentToken?.accessToken;
|
|
5021
|
+
}
|
|
4634
5022
|
this.cachedToken = null;
|
|
4635
|
-
await withTelemetrySpan("token-manager.clear-tokens", () => this.authStorage.clearTokens(this.mcpUrl));
|
|
4636
5023
|
}
|
|
4637
5024
|
return;
|
|
4638
5025
|
}
|
|
4639
5026
|
const newTokenData = {
|
|
4640
5027
|
accessToken: response.accessToken,
|
|
4641
|
-
refreshToken: response.refreshToken,
|
|
5028
|
+
refreshToken: response.refreshToken ?? tokens.refreshToken,
|
|
4642
5029
|
expiresAt: new Date(Date.now() + response.expiresIn * 1000).toISOString(),
|
|
4643
5030
|
createdAt: new Date().toISOString()
|
|
4644
5031
|
};
|
|
4645
|
-
|
|
5032
|
+
const externallyUpdatedToken = await this.loadExternallyUpdatedToken(tokens, {
|
|
5033
|
+
treatMissingAsExternalUpdate: true
|
|
5034
|
+
});
|
|
5035
|
+
if (externallyUpdatedToken === null)
|
|
5036
|
+
return;
|
|
5037
|
+
if (externallyUpdatedToken)
|
|
5038
|
+
return externallyUpdatedToken.accessToken;
|
|
5039
|
+
const saved = await withTelemetrySpan("token-manager.save-tokens", () => this.authStorage.saveTokensIfUnchanged(this.mcpUrl, tokens, newTokenData));
|
|
5040
|
+
if (!saved) {
|
|
5041
|
+
const currentToken = await this.authStorage.loadTokens(this.mcpUrl);
|
|
5042
|
+
this.cachedToken = currentToken;
|
|
5043
|
+
return currentToken?.accessToken;
|
|
5044
|
+
}
|
|
4646
5045
|
this.cachedToken = newTokenData;
|
|
4647
5046
|
return response.accessToken;
|
|
4648
5047
|
});
|
|
4649
5048
|
}
|
|
5049
|
+
async loadExternallyUpdatedToken(failedTokens, options = {}) {
|
|
5050
|
+
const storedTokens = await withTelemetrySpan("token-manager.reload-tokens", () => this.authStorage.loadTokens(this.mcpUrl));
|
|
5051
|
+
if (!storedTokens) {
|
|
5052
|
+
if (options.treatMissingAsExternalUpdate)
|
|
5053
|
+
this.cachedToken = null;
|
|
5054
|
+
return options.treatMissingAsExternalUpdate ? null : undefined;
|
|
5055
|
+
}
|
|
5056
|
+
if (areSameTokenData(storedTokens, failedTokens))
|
|
5057
|
+
return;
|
|
5058
|
+
this.cachedToken = storedTokens;
|
|
5059
|
+
return storedTokens;
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
function areSameTokenData(a, b) {
|
|
5063
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.createdAt === b.createdAt;
|
|
4650
5064
|
}
|
|
4651
5065
|
// src/services/update-check-service.ts
|
|
4652
5066
|
import semver from "semver";
|
|
@@ -4984,7 +5398,7 @@ function createAuthStorageForMode(fileSystemService, mode, configPath = "your Gi
|
|
|
4984
5398
|
const rawKeyring = new KeyringServiceImpl;
|
|
4985
5399
|
const keyring = process.platform === "win32" ? new ChunkingKeyringService(rawKeyring, WINDOWS_MAX_ENTRY_SIZE) : rawKeyring;
|
|
4986
5400
|
const keychainStorage = new KeychainAuthStorage(keyring);
|
|
4987
|
-
return new MigratingAuthStorage(keychainStorage, fileStorage, legacyStorage, mode, configPath, (message) => console.error(message));
|
|
5401
|
+
return new LockedAuthStorage(new MigratingAuthStorage(keychainStorage, fileStorage, legacyStorage, mode, configPath, (message) => console.error(message)), fileSystemService);
|
|
4988
5402
|
}
|
|
4989
5403
|
async function createAuthCommandDependencies() {
|
|
4990
5404
|
return withTelemetrySpan("container.create-auth-command", async () => {
|
|
@@ -5076,4 +5490,4 @@ async function createContainer() {
|
|
|
5076
5490
|
});
|
|
5077
5491
|
}
|
|
5078
5492
|
|
|
5079
|
-
export { AuthConfigError, CLIENT_UPDATE_REQUIRED_REASON, ClientUpdateRequiredError, debugLog, setMcpClientVersionProvider, setClientMode, isTelemetryEnabled, withTelemetrySpan, startTelemetrySpan, endTelemetrySpan, flushTelemetry, AuthenticationError, CodeNavigationAccessError, CodeNavigationGraphQLError, CodeNavigationIndexingError, CodeNavigationUnresolvableError, MalformedCodeNavigationResponseError, CodeNavigationTargetNotFoundError, CodeNavigationFileNotFoundError, CodeNavigationVersionNotFoundError, CodeNavigationValidationError, CodeNavigationFeatureFlagRequiredError, CodeNavigationNetworkError, CodeNavigationBackendError, getCodeNavigationUrl, ExecServiceImpl, FileSystemServiceImpl, AuthStoragePolicyError, PackageIntelligenceAccessError, PackageIntelligenceFeatureFlagRequiredError, PackageIntelligenceNetworkError, PackageIntelligenceBackendError, PackageIntelligenceGraphQLError, PackageIntelligenceTargetNotFoundError, PackageIntelligenceValidationError, PackageIntelligenceVersionNotFoundError, MalformedPackageIntelligenceResponseError, PackageIntelligenceChangelogSourceNotFoundError, PromptServiceImpl, refreshExpiredToken, NpmRegistryUpdateCheckService, shouldRunUpdateCheck, shouldRunRequiredUpdateEnforcement, formatUpdateNotice, formatRequiredUpdateNotice, createAuthCommandDependencies, createAuthStatusDependencies, createContainer };
|
|
5493
|
+
export { AuthConfigError, CLIENT_UPDATE_REQUIRED_REASON, ClientUpdateRequiredError, debugLog, setMcpClientVersionProvider, setClientMode, isTelemetryEnabled, withTelemetrySpan, startTelemetrySpan, endTelemetrySpan, flushTelemetry, AuthenticationError, CodeNavigationAccessError, CodeNavigationGraphQLError, CodeNavigationIndexingError, CodeNavigationUnresolvableError, MalformedCodeNavigationResponseError, CodeNavigationTargetNotFoundError, CodeNavigationFileNotFoundError, CodeNavigationVersionNotFoundError, CodeNavigationValidationError, CodeNavigationFeatureFlagRequiredError, CodeNavigationNetworkError, CodeNavigationBackendError, getCodeNavigationUrl, ExecServiceImpl, FileSystemServiceImpl, AuthStorageLockTimeoutError, AuthStoragePolicyError, PackageIntelligenceAccessError, PackageIntelligenceFeatureFlagRequiredError, PackageIntelligenceNetworkError, PackageIntelligenceBackendError, PackageIntelligenceGraphQLError, PackageIntelligenceTargetNotFoundError, PackageIntelligenceValidationError, PackageIntelligenceVersionNotFoundError, MalformedPackageIntelligenceResponseError, PackageIntelligenceChangelogSourceNotFoundError, PromptServiceImpl, refreshExpiredToken, NpmRegistryUpdateCheckService, shouldRunUpdateCheck, shouldRunRequiredUpdateEnforcement, formatUpdateNotice, formatRequiredUpdateNotice, createAuthCommandDependencies, createAuthStatusDependencies, createContainer };
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED