githits 0.3.0 → 0.3.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/dist/cli.js +70 -72
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-5mk9k2gv.js → chunk-0s7mxdt3.js} +391 -10
- package/dist/shared/{chunk-gxya097b.js → chunk-m1xx8hzw.js} +1 -1
- package/dist/shared/{chunk-1gqfte6e.js → chunk-zg5dnven.js} +2 -2
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
package/.plugin/plugin.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AuthConfigError,
|
|
4
|
+
AuthStorageLockTimeoutError,
|
|
4
5
|
AuthStoragePolicyError,
|
|
5
6
|
AuthenticationError,
|
|
6
7
|
CLIENT_UPDATE_REQUIRED_REASON,
|
|
@@ -48,15 +49,59 @@ import {
|
|
|
48
49
|
shouldRunUpdateCheck,
|
|
49
50
|
startTelemetrySpan,
|
|
50
51
|
withTelemetrySpan
|
|
51
|
-
} from "./shared/chunk-
|
|
52
|
+
} from "./shared/chunk-0s7mxdt3.js";
|
|
52
53
|
import {
|
|
53
54
|
__require,
|
|
54
55
|
version
|
|
55
|
-
} from "./shared/chunk-
|
|
56
|
+
} from "./shared/chunk-m1xx8hzw.js";
|
|
56
57
|
|
|
57
58
|
// src/cli.ts
|
|
58
59
|
import { Command } from "commander";
|
|
59
60
|
|
|
61
|
+
// src/shared/require-auth.ts
|
|
62
|
+
class AuthRequiredError extends Error {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "AuthRequiredError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function requireAuth(deps, context) {
|
|
69
|
+
if (deps.hasValidToken)
|
|
70
|
+
return;
|
|
71
|
+
const suffix = context ? ` ${context}` : "";
|
|
72
|
+
console.log(`Authentication required${suffix}.
|
|
73
|
+
`);
|
|
74
|
+
if (deps.mcpUrl !== "https://mcp.githits.com") {
|
|
75
|
+
console.log(` Environment: ${deps.mcpUrl}`);
|
|
76
|
+
console.log(` You're using a custom environment.
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
console.log("To authenticate:");
|
|
80
|
+
console.log(` githits login
|
|
81
|
+
`);
|
|
82
|
+
console.log("Or set GITHITS_API_TOKEN environment variable.");
|
|
83
|
+
console.log(`
|
|
84
|
+
Need help? support@githits.com`);
|
|
85
|
+
throw new AuthRequiredError(`Authentication required${suffix}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/cli/errors.ts
|
|
89
|
+
function handleCliError(error, deps) {
|
|
90
|
+
if (error instanceof AuthRequiredError) {
|
|
91
|
+
deps.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (isUserFacingError(error)) {
|
|
94
|
+
deps.stderr.write(`${error.message}
|
|
95
|
+
|
|
96
|
+
`);
|
|
97
|
+
deps.exit(1);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
function isUserFacingError(error) {
|
|
102
|
+
return error instanceof AuthConfigError || error instanceof AuthStorageLockTimeoutError || error instanceof AuthStoragePolicyError;
|
|
103
|
+
}
|
|
104
|
+
|
|
60
105
|
// src/cli/update-check.ts
|
|
61
106
|
function startUpdateCheckTask(service) {
|
|
62
107
|
const controller = new AbortController;
|
|
@@ -522,7 +567,8 @@ function classify(error2) {
|
|
|
522
567
|
return {
|
|
523
568
|
code: "AUTH_REQUIRED",
|
|
524
569
|
message: error2.message,
|
|
525
|
-
retryable: false
|
|
570
|
+
retryable: false,
|
|
571
|
+
details: { action: "Run `githits login`, then retry this tool call." }
|
|
526
572
|
};
|
|
527
573
|
}
|
|
528
574
|
if (error2 instanceof CodeNavigationNetworkError) {
|
|
@@ -2483,7 +2529,8 @@ function classify2(error2) {
|
|
|
2483
2529
|
return {
|
|
2484
2530
|
code: "AUTH_REQUIRED",
|
|
2485
2531
|
message: error2.message,
|
|
2486
|
-
retryable: false
|
|
2532
|
+
retryable: false,
|
|
2533
|
+
details: { action: "Run `githits login`, then retry this tool call." }
|
|
2487
2534
|
};
|
|
2488
2535
|
}
|
|
2489
2536
|
if (error2 instanceof PackageIntelligenceNetworkError) {
|
|
@@ -3807,32 +3854,6 @@ function buildHeader3(envelope, useColors) {
|
|
|
3807
3854
|
const prefix = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "documentation";
|
|
3808
3855
|
return `${colorize(`${prefix} ${badge}`, "bold", useColors)}${title ? ` - ${title}` : ""}`;
|
|
3809
3856
|
}
|
|
3810
|
-
// src/shared/require-auth.ts
|
|
3811
|
-
class AuthRequiredError extends Error {
|
|
3812
|
-
constructor(message) {
|
|
3813
|
-
super(message);
|
|
3814
|
-
this.name = "AuthRequiredError";
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
function requireAuth(deps, context) {
|
|
3818
|
-
if (deps.hasValidToken)
|
|
3819
|
-
return;
|
|
3820
|
-
const suffix = context ? ` ${context}` : "";
|
|
3821
|
-
console.log(`Authentication required${suffix}.
|
|
3822
|
-
`);
|
|
3823
|
-
if (deps.mcpUrl !== "https://mcp.githits.com") {
|
|
3824
|
-
console.log(` Environment: ${deps.mcpUrl}`);
|
|
3825
|
-
console.log(` You're using a custom environment.
|
|
3826
|
-
`);
|
|
3827
|
-
}
|
|
3828
|
-
console.log("To authenticate:");
|
|
3829
|
-
console.log(` githits login
|
|
3830
|
-
`);
|
|
3831
|
-
console.log("Or set GITHITS_API_TOKEN environment variable.");
|
|
3832
|
-
console.log(`
|
|
3833
|
-
Need help? support@githits.com`);
|
|
3834
|
-
throw new AuthRequiredError(`Authentication required${suffix}`);
|
|
3835
|
-
}
|
|
3836
3857
|
// src/shared/unified-search-request.ts
|
|
3837
3858
|
var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
|
|
3838
3859
|
function buildUnifiedSearchParams(input) {
|
|
@@ -5056,7 +5077,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
5056
5077
|
match in --verbose output; full payload in --json).`;
|
|
5057
5078
|
function registerCodeGrepCommand(pkgCommand) {
|
|
5058
5079
|
return pkgCommand.command("grep").summary("Deterministic text grep over indexed dependency source").description(PKG_GREP_DESCRIPTION).argument("[spec-or-pattern]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the pattern.").argument("[pattern-or-prefix]", "Spec mode: the pattern. Repo mode: optional path-prefix.").argument("[path-prefix]", "Spec mode only: optional path-prefix. Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--path <path>", "Exact file path to grep").option("--glob <glob>", "Glob scope (repeatable)", collectRepeatable, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable, []).option("--regex", "Interpret the pattern as RE2 regex").option("--case-sensitive", "Enable ASCII case-sensitive matching").option("-C, --context <n>", "Context lines before and after each match (0-10)").option("-B, --before-context <n>", "Context lines before each match (0-10)").option("-A, --after-context <n>", "Context lines after each match (0-10)").option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--limit <n>", "Max matches to return on this page (1-1000, default 50)").option("--per-file-limit <n>", "Cap matches per file within this page (0-1000, 0 = unlimited)").option("--cursor <cursor>", "Opaque nextCursor from a previous grep result").option("--symbol-field <field>", `Repeatable; surfaces in --json and under each --verbose match. ${GREP_REPO_SYMBOL_FIELDS_NOTE}`, collectRepeatable, []).option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render grouped output with file headers").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, arg3, options) => {
|
|
5059
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
5080
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-zg5dnven.js");
|
|
5060
5081
|
const deps = await createContainer2();
|
|
5061
5082
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
5062
5083
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -5575,7 +5596,7 @@ function registerExampleCommand(program) {
|
|
|
5575
5596
|
});
|
|
5576
5597
|
}
|
|
5577
5598
|
async function loadContainer() {
|
|
5578
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
5599
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-zg5dnven.js");
|
|
5579
5600
|
return createContainer2();
|
|
5580
5601
|
}
|
|
5581
5602
|
// src/commands/feedback.ts
|
|
@@ -6364,14 +6385,11 @@ async function preflightAuthPersistence(authStorage, mcpUrl) {
|
|
|
6364
6385
|
createdAt: new Date(0).toISOString()
|
|
6365
6386
|
};
|
|
6366
6387
|
try {
|
|
6367
|
-
await authStorage.
|
|
6368
|
-
await authStorage.
|
|
6369
|
-
await authStorage.clearTokens(probeUrl);
|
|
6370
|
-
await authStorage.clearClient(probeUrl);
|
|
6388
|
+
await authStorage.saveAuthSession(probeUrl, probeClient, probeTokens);
|
|
6389
|
+
await authStorage.clearAuthSession(probeUrl);
|
|
6371
6390
|
return null;
|
|
6372
6391
|
} catch (error2) {
|
|
6373
|
-
await authStorage.
|
|
6374
|
-
await authStorage.clearClient(probeUrl).catch(() => {});
|
|
6392
|
+
await authStorage.clearAuthSession(probeUrl).catch(() => {});
|
|
6375
6393
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
6376
6394
|
return {
|
|
6377
6395
|
status: "failed",
|
|
@@ -6425,7 +6443,6 @@ async function loginFlow(options, deps) {
|
|
|
6425
6443
|
redirectUri,
|
|
6426
6444
|
registeredAt: new Date().toISOString()
|
|
6427
6445
|
};
|
|
6428
|
-
await authStorage.saveClient(mcpUrl, client);
|
|
6429
6446
|
}
|
|
6430
6447
|
port = options.port;
|
|
6431
6448
|
} else {
|
|
@@ -6447,7 +6464,6 @@ async function loginFlow(options, deps) {
|
|
|
6447
6464
|
redirectUri,
|
|
6448
6465
|
registeredAt: new Date().toISOString()
|
|
6449
6466
|
};
|
|
6450
|
-
await authStorage.saveClient(mcpUrl, client);
|
|
6451
6467
|
}
|
|
6452
6468
|
const { verifier, challenge, state } = authService.generatePkceParams();
|
|
6453
6469
|
const authUrl = authService.buildAuthUrl({
|
|
@@ -6512,7 +6528,7 @@ async function loginFlow(options, deps) {
|
|
|
6512
6528
|
};
|
|
6513
6529
|
}
|
|
6514
6530
|
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
|
|
6515
|
-
await authStorage.
|
|
6531
|
+
await authStorage.saveAuthSession(mcpUrl, client, {
|
|
6516
6532
|
accessToken: tokenResponse.accessToken,
|
|
6517
6533
|
refreshToken: tokenResponse.refreshToken,
|
|
6518
6534
|
expiresAt,
|
|
@@ -6803,17 +6819,7 @@ function registerLanguagesCommand(program) {
|
|
|
6803
6819
|
async function logoutAction(deps) {
|
|
6804
6820
|
const { authStorage, mcpUrl } = deps;
|
|
6805
6821
|
const auth = await authStorage.loadTokens(mcpUrl);
|
|
6806
|
-
|
|
6807
|
-
try {
|
|
6808
|
-
await authStorage.clearTokens(mcpUrl);
|
|
6809
|
-
} catch (error2) {
|
|
6810
|
-
firstError = error2;
|
|
6811
|
-
}
|
|
6812
|
-
try {
|
|
6813
|
-
await authStorage.clearClient(mcpUrl);
|
|
6814
|
-
} catch (error2) {
|
|
6815
|
-
firstError ??= error2;
|
|
6816
|
-
}
|
|
6822
|
+
await authStorage.clearAuthSession(mcpUrl);
|
|
6817
6823
|
if (!auth) {
|
|
6818
6824
|
console.log(`Not currently logged in.
|
|
6819
6825
|
`);
|
|
@@ -6823,8 +6829,6 @@ async function logoutAction(deps) {
|
|
|
6823
6829
|
`);
|
|
6824
6830
|
console.log(` Environment: ${mcpUrl}`);
|
|
6825
6831
|
}
|
|
6826
|
-
if (firstError)
|
|
6827
|
-
throw firstError;
|
|
6828
6832
|
}
|
|
6829
6833
|
var LOGOUT_DESCRIPTION = `Remove stored credentials.
|
|
6830
6834
|
|
|
@@ -6870,8 +6874,9 @@ function classify3(operation, error2) {
|
|
|
6870
6874
|
if (error2 instanceof AuthenticationError) {
|
|
6871
6875
|
return {
|
|
6872
6876
|
error: error2.message,
|
|
6873
|
-
code: "
|
|
6874
|
-
retryable: false
|
|
6877
|
+
code: "AUTH_REQUIRED",
|
|
6878
|
+
retryable: false,
|
|
6879
|
+
details: { action: "Run `githits login`, then retry this tool call." }
|
|
6875
6880
|
};
|
|
6876
6881
|
}
|
|
6877
6882
|
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
@@ -7318,17 +7323,15 @@ function buildPackageChangelogSuccessPayload(report, options) {
|
|
|
7318
7323
|
}
|
|
7319
7324
|
return lean;
|
|
7320
7325
|
});
|
|
7321
|
-
if (report.source == null) {
|
|
7322
|
-
throw new Error("Changelog envelope builder received a null source — should have been promoted to NOT_FOUND at the service boundary.");
|
|
7323
|
-
}
|
|
7324
7326
|
const envelope = {
|
|
7325
|
-
source: report.source,
|
|
7326
7327
|
mode: options.mode,
|
|
7327
7328
|
entries: {
|
|
7328
7329
|
count: items.length,
|
|
7329
7330
|
items
|
|
7330
7331
|
}
|
|
7331
7332
|
};
|
|
7333
|
+
if (report.source)
|
|
7334
|
+
envelope.source = report.source;
|
|
7332
7335
|
if (options.registry)
|
|
7333
7336
|
envelope.registry = options.registry;
|
|
7334
7337
|
if (options.name)
|
|
@@ -7407,7 +7410,7 @@ function appendBodyLines(lines, body, options) {
|
|
|
7407
7410
|
}
|
|
7408
7411
|
function buildSummaryLine(envelope, options) {
|
|
7409
7412
|
const identity = envelope.registry && envelope.name ? `${envelope.name} · ${envelope.registry}` : envelope.repoUrl ?? "(unknown)";
|
|
7410
|
-
const sourceLabel = humanizeSource(envelope.source);
|
|
7413
|
+
const sourceLabel = envelope.source ? humanizeSource(envelope.source) : "package versions";
|
|
7411
7414
|
const modeLabel = envelope.mode === "range" ? rangeLabel(envelope) : latestLabel(envelope);
|
|
7412
7415
|
const countLabel = `${envelope.entries.count} ${plural2("entry", "entries", envelope.entries.count)}`;
|
|
7413
7416
|
const parts = [identity, `source: ${sourceLabel}`, modeLabel, countLabel];
|
|
@@ -7476,7 +7479,7 @@ var schema6 = {
|
|
|
7476
7479
|
git_ref: z7.string().optional().describe("Git branch or tag for CHANGELOG.md source (no effect on GitHub Releases or HexDocs). Defaults to the repository's default branch."),
|
|
7477
7480
|
include_bodies: z7.boolean().optional().describe("When false, each entry in `entries.items[]` omits its `body` field. Default true. Set false when you only need the version / date / URL timeline — drops 10 KB+ per entry on large release notes.")
|
|
7478
7481
|
};
|
|
7479
|
-
var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response
|
|
7482
|
+
var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), ' + "`entries: { count, items }` with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Package-version entries without changelog text succeed with `source` " + "omitted; no-source plus no entries returns `NOT_FOUND`. Supports npm, " + "PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, Packagist.";
|
|
7480
7483
|
function createPackageChangelogTool(service) {
|
|
7481
7484
|
return {
|
|
7482
7485
|
name: "pkg_changelog",
|
|
@@ -8663,7 +8666,7 @@ function requireSearchService(deps) {
|
|
|
8663
8666
|
return deps.codeNavigationService;
|
|
8664
8667
|
}
|
|
8665
8668
|
async function loadContainer2() {
|
|
8666
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
8669
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-zg5dnven.js");
|
|
8667
8670
|
return createContainer2();
|
|
8668
8671
|
}
|
|
8669
8672
|
function parseTargetSpecs(specs) {
|
|
@@ -9072,15 +9075,10 @@ registerAuthStatusCommand(authCommand);
|
|
|
9072
9075
|
try {
|
|
9073
9076
|
await runWithUpdateCheckFlush(() => withTelemetrySpan("cli.parse", () => program.parseAsync()), updateCheckTask, { stderr: process.stderr, requiredUpdateRefreshTask });
|
|
9074
9077
|
} catch (error2) {
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
}
|
|
9080
|
-
throw error2;
|
|
9081
|
-
}
|
|
9082
|
-
function isUserFacingError(error2) {
|
|
9083
|
-
return error2 instanceof AuthConfigError || error2 instanceof AuthStoragePolicyError;
|
|
9078
|
+
handleCliError(error2, {
|
|
9079
|
+
stderr: process.stderr,
|
|
9080
|
+
exit: process.exit
|
|
9081
|
+
});
|
|
9084
9082
|
}
|
|
9085
9083
|
function stripRootRegistrationOptions(args) {
|
|
9086
9084
|
return args.filter((arg) => arg !== "--no-color");
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
__require,
|
|
2
3
|
version
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-m1xx8hzw.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
|
|
|
@@ -2698,10 +2751,24 @@ class KeychainAuthStorage {
|
|
|
2698
2751
|
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2699
2752
|
this.keyring.setPassword(SERVICE_NAME, key, JSON.stringify(data));
|
|
2700
2753
|
}
|
|
2754
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
2755
|
+
const current = await this.loadTokens(baseUrl2);
|
|
2756
|
+
if (!sameTokenData(current, expected))
|
|
2757
|
+
return false;
|
|
2758
|
+
await this.saveTokens(baseUrl2, data);
|
|
2759
|
+
return true;
|
|
2760
|
+
}
|
|
2701
2761
|
async clearTokens(baseUrl2) {
|
|
2702
2762
|
const key = `${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2703
2763
|
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2704
2764
|
}
|
|
2765
|
+
async clearTokensIfUnchanged(baseUrl2, expected) {
|
|
2766
|
+
const current = await this.loadTokens(baseUrl2);
|
|
2767
|
+
if (!sameTokenData(current, expected))
|
|
2768
|
+
return false;
|
|
2769
|
+
await this.clearTokens(baseUrl2);
|
|
2770
|
+
return true;
|
|
2771
|
+
}
|
|
2705
2772
|
async loadClient(baseUrl2) {
|
|
2706
2773
|
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2707
2774
|
const json = this.keyring.getPassword(SERVICE_NAME, key);
|
|
@@ -2718,6 +2785,13 @@ class KeychainAuthStorage {
|
|
|
2718
2785
|
const key = `${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;
|
|
2719
2786
|
this.keyring.deletePassword(SERVICE_NAME, key);
|
|
2720
2787
|
}
|
|
2788
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
2789
|
+
await this.saveClient(baseUrl2, client);
|
|
2790
|
+
await this.saveTokens(baseUrl2, tokens);
|
|
2791
|
+
}
|
|
2792
|
+
async clearAuthSession(baseUrl2) {
|
|
2793
|
+
await clearAuthSessionBestEffort(() => this.clearTokens(baseUrl2), () => this.clearClient(baseUrl2));
|
|
2794
|
+
}
|
|
2721
2795
|
getStorageLocation() {
|
|
2722
2796
|
switch (process.platform) {
|
|
2723
2797
|
case "darwin":
|
|
@@ -2767,6 +2841,223 @@ class KeyringServiceImpl {
|
|
|
2767
2841
|
}
|
|
2768
2842
|
}
|
|
2769
2843
|
}
|
|
2844
|
+
// src/services/locked-auth-storage.ts
|
|
2845
|
+
import { execFile } from "node:child_process";
|
|
2846
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2847
|
+
import { mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
2848
|
+
import { dirname as dirname2 } from "node:path";
|
|
2849
|
+
import { promisify } from "node:util";
|
|
2850
|
+
var LOCK_DIR = "auth.lock";
|
|
2851
|
+
var LOCK_TIMEOUT_MS = 1e4;
|
|
2852
|
+
var LOCK_RETRY_MS = 25;
|
|
2853
|
+
var ORPHANED_LOCK_MS = 5000;
|
|
2854
|
+
var OWNER_FILE = "owner.json";
|
|
2855
|
+
var execFileAsync = promisify(execFile);
|
|
2856
|
+
|
|
2857
|
+
class AuthStorageLockTimeoutError extends Error {
|
|
2858
|
+
constructor(message) {
|
|
2859
|
+
super(message);
|
|
2860
|
+
this.name = "AuthStorageLockTimeoutError";
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
class LockedAuthStorage {
|
|
2865
|
+
storage;
|
|
2866
|
+
lockPath;
|
|
2867
|
+
lockTimeoutMs;
|
|
2868
|
+
isOwnerAlive;
|
|
2869
|
+
currentOwner = null;
|
|
2870
|
+
constructor(storage, fileSystemService, options = {}) {
|
|
2871
|
+
this.storage = storage;
|
|
2872
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? LOCK_TIMEOUT_MS;
|
|
2873
|
+
this.isOwnerAlive = options.isOwnerAlive ?? isOriginalProcessAlive;
|
|
2874
|
+
this.lockPath = fileSystemService.joinPath(getAppConfigDir(fileSystemService), LOCK_DIR);
|
|
2875
|
+
}
|
|
2876
|
+
loadTokens(baseUrl2) {
|
|
2877
|
+
return this.storage.loadTokens(baseUrl2);
|
|
2878
|
+
}
|
|
2879
|
+
saveTokens(baseUrl2, data) {
|
|
2880
|
+
return this.withLock(() => this.storage.saveTokens(baseUrl2, data));
|
|
2881
|
+
}
|
|
2882
|
+
saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
2883
|
+
return this.withLock(() => this.storage.saveTokensIfUnchanged(baseUrl2, expected, data));
|
|
2884
|
+
}
|
|
2885
|
+
clearTokens(baseUrl2) {
|
|
2886
|
+
return this.withLock(() => this.storage.clearTokens(baseUrl2));
|
|
2887
|
+
}
|
|
2888
|
+
clearTokensIfUnchanged(baseUrl2, expected) {
|
|
2889
|
+
return this.withLock(() => this.storage.clearTokensIfUnchanged(baseUrl2, expected));
|
|
2890
|
+
}
|
|
2891
|
+
loadClient(baseUrl2) {
|
|
2892
|
+
return this.storage.loadClient(baseUrl2);
|
|
2893
|
+
}
|
|
2894
|
+
saveClient(baseUrl2, data) {
|
|
2895
|
+
return this.withLock(() => this.storage.saveClient(baseUrl2, data));
|
|
2896
|
+
}
|
|
2897
|
+
clearClient(baseUrl2) {
|
|
2898
|
+
return this.withLock(() => this.storage.clearClient(baseUrl2));
|
|
2899
|
+
}
|
|
2900
|
+
saveAuthSession(baseUrl2, client, tokens) {
|
|
2901
|
+
return this.withLock(() => this.storage.saveAuthSession(baseUrl2, client, tokens));
|
|
2902
|
+
}
|
|
2903
|
+
clearAuthSession(baseUrl2) {
|
|
2904
|
+
return this.withLock(() => this.storage.clearAuthSession(baseUrl2));
|
|
2905
|
+
}
|
|
2906
|
+
getStorageLocation() {
|
|
2907
|
+
return this.storage.getStorageLocation();
|
|
2908
|
+
}
|
|
2909
|
+
async withLock(fn) {
|
|
2910
|
+
await this.acquireLock();
|
|
2911
|
+
try {
|
|
2912
|
+
return await fn();
|
|
2913
|
+
} finally {
|
|
2914
|
+
await this.releaseLock();
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
async acquireLock() {
|
|
2918
|
+
const startedAt = Date.now();
|
|
2919
|
+
await mkdir2(dirname2(this.lockPath), { recursive: true, mode: 448 });
|
|
2920
|
+
while (true) {
|
|
2921
|
+
try {
|
|
2922
|
+
await mkdir2(this.lockPath, { recursive: false, mode: 448 });
|
|
2923
|
+
try {
|
|
2924
|
+
await this.writeOwner();
|
|
2925
|
+
} catch (error) {
|
|
2926
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
2927
|
+
return;
|
|
2928
|
+
});
|
|
2929
|
+
throw error;
|
|
2930
|
+
}
|
|
2931
|
+
return;
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
if (error.code !== "EEXIST")
|
|
2934
|
+
throw error;
|
|
2935
|
+
await this.reclaimStaleLock();
|
|
2936
|
+
if (Date.now() - startedAt >= this.lockTimeoutMs) {
|
|
2937
|
+
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.`);
|
|
2938
|
+
}
|
|
2939
|
+
await sleep(LOCK_RETRY_MS);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async writeOwner() {
|
|
2944
|
+
const owner = {
|
|
2945
|
+
id: randomUUID3(),
|
|
2946
|
+
pid: process.pid,
|
|
2947
|
+
createdAt: new Date().toISOString(),
|
|
2948
|
+
processStartedAt: await getProcessStartedAt(process.pid)
|
|
2949
|
+
};
|
|
2950
|
+
this.currentOwner = owner;
|
|
2951
|
+
await writeFile2(this.ownerPath(), JSON.stringify(owner), { mode: 384 });
|
|
2952
|
+
}
|
|
2953
|
+
async reclaimStaleLock() {
|
|
2954
|
+
const owner = await this.readOwner();
|
|
2955
|
+
if (!owner) {
|
|
2956
|
+
await this.reclaimOldOwnerlessLock();
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const ownerDead = !await this.isOwnerAlive(owner.pid, owner.processStartedAt);
|
|
2960
|
+
if (!ownerDead)
|
|
2961
|
+
return;
|
|
2962
|
+
const currentOwner = await this.readOwner();
|
|
2963
|
+
if (!currentOwner || currentOwner.id !== owner.id)
|
|
2964
|
+
return;
|
|
2965
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
2966
|
+
return;
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
async reclaimOldOwnerlessLock() {
|
|
2970
|
+
const createdAtMs = await lockCreatedAtMs(this.lockPath);
|
|
2971
|
+
if (Date.now() - createdAtMs < ORPHANED_LOCK_MS)
|
|
2972
|
+
return;
|
|
2973
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
2974
|
+
return;
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
async readOwner() {
|
|
2978
|
+
try {
|
|
2979
|
+
const raw = await readFile2(this.ownerPath(), "utf8");
|
|
2980
|
+
const parsed = JSON.parse(raw);
|
|
2981
|
+
if (typeof parsed.id !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || !(typeof parsed.processStartedAt === "string" || parsed.processStartedAt === null)) {
|
|
2982
|
+
return null;
|
|
2983
|
+
}
|
|
2984
|
+
return {
|
|
2985
|
+
id: parsed.id,
|
|
2986
|
+
pid: parsed.pid,
|
|
2987
|
+
createdAt: parsed.createdAt,
|
|
2988
|
+
processStartedAt: parsed.processStartedAt
|
|
2989
|
+
};
|
|
2990
|
+
} catch {
|
|
2991
|
+
return null;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
async releaseLock() {
|
|
2995
|
+
const owner = this.currentOwner;
|
|
2996
|
+
this.currentOwner = null;
|
|
2997
|
+
if (!owner)
|
|
2998
|
+
return;
|
|
2999
|
+
const currentOwner = await this.readOwner();
|
|
3000
|
+
if (!currentOwner || currentOwner.id !== owner.id)
|
|
3001
|
+
return;
|
|
3002
|
+
await rm(this.lockPath, { recursive: true, force: true }).catch(() => {
|
|
3003
|
+
return;
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
ownerPath() {
|
|
3007
|
+
return `${this.lockPath}/${OWNER_FILE}`;
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
function sleep(ms) {
|
|
3011
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3012
|
+
}
|
|
3013
|
+
async function isOriginalProcessAlive(pid, processStartedAt) {
|
|
3014
|
+
if (!isProcessAlive(pid))
|
|
3015
|
+
return false;
|
|
3016
|
+
if (!processStartedAt)
|
|
3017
|
+
return true;
|
|
3018
|
+
return await getProcessStartedAt(pid) === processStartedAt;
|
|
3019
|
+
}
|
|
3020
|
+
function isProcessAlive(pid) {
|
|
3021
|
+
if (pid <= 0)
|
|
3022
|
+
return false;
|
|
3023
|
+
try {
|
|
3024
|
+
process.kill(pid, 0);
|
|
3025
|
+
return true;
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
const code = error.code;
|
|
3028
|
+
return code === "EPERM";
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
async function getProcessStartedAt(pid) {
|
|
3032
|
+
try {
|
|
3033
|
+
if (process.platform === "win32") {
|
|
3034
|
+
const { stdout: stdout2 } = await execFileAsync("powershell.exe", [
|
|
3035
|
+
"-NoProfile",
|
|
3036
|
+
"-Command",
|
|
3037
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString('o')`
|
|
3038
|
+
]);
|
|
3039
|
+
return stdout2.trim() || null;
|
|
3040
|
+
}
|
|
3041
|
+
const { stdout } = await execFileAsync("ps", [
|
|
3042
|
+
"-p",
|
|
3043
|
+
String(pid),
|
|
3044
|
+
"-o",
|
|
3045
|
+
"lstart="
|
|
3046
|
+
]);
|
|
3047
|
+
const parsed = Date.parse(stdout.trim());
|
|
3048
|
+
return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
|
|
3049
|
+
} catch {
|
|
3050
|
+
return null;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
async function lockCreatedAtMs(path) {
|
|
3054
|
+
try {
|
|
3055
|
+
const { stat: stat2 } = await import("node:fs/promises");
|
|
3056
|
+
return (await stat2(path)).mtimeMs;
|
|
3057
|
+
} catch {
|
|
3058
|
+
return 0;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
2770
3061
|
// src/services/mode-aware-file-auth-storage.ts
|
|
2771
3062
|
class AuthStoragePolicyError extends Error {
|
|
2772
3063
|
constructor(message) {
|
|
@@ -2806,9 +3097,16 @@ class ModeAwareFileAuthStorage {
|
|
|
2806
3097
|
this.assertFileMode();
|
|
2807
3098
|
await this.storage.saveTokens(baseUrl2, data);
|
|
2808
3099
|
}
|
|
3100
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
3101
|
+
this.assertFileMode();
|
|
3102
|
+
return this.storage.saveTokensIfUnchanged(baseUrl2, expected, data);
|
|
3103
|
+
}
|
|
2809
3104
|
clearTokens(baseUrl2) {
|
|
2810
3105
|
return this.storage.clearTokens(baseUrl2);
|
|
2811
3106
|
}
|
|
3107
|
+
clearTokensIfUnchanged(baseUrl2, expected) {
|
|
3108
|
+
return this.storage.clearTokensIfUnchanged(baseUrl2, expected);
|
|
3109
|
+
}
|
|
2812
3110
|
loadClient(baseUrl2) {
|
|
2813
3111
|
return this.storage.loadClient(baseUrl2);
|
|
2814
3112
|
}
|
|
@@ -2819,6 +3117,13 @@ class ModeAwareFileAuthStorage {
|
|
|
2819
3117
|
clearClient(baseUrl2) {
|
|
2820
3118
|
return this.storage.clearClient(baseUrl2);
|
|
2821
3119
|
}
|
|
3120
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
3121
|
+
this.assertFileMode();
|
|
3122
|
+
await this.storage.saveAuthSession(baseUrl2, client, tokens);
|
|
3123
|
+
}
|
|
3124
|
+
clearAuthSession(baseUrl2) {
|
|
3125
|
+
return this.storage.clearAuthSession(baseUrl2);
|
|
3126
|
+
}
|
|
2822
3127
|
getStorageLocation() {
|
|
2823
3128
|
return this.storage.getStorageLocation();
|
|
2824
3129
|
}
|
|
@@ -2864,6 +3169,13 @@ class MigratingAuthStorage {
|
|
|
2864
3169
|
throw this.toPolicyError(error);
|
|
2865
3170
|
}
|
|
2866
3171
|
}
|
|
3172
|
+
async saveTokensIfUnchanged(baseUrl2, expected, data) {
|
|
3173
|
+
const current = await this.loadTokens(baseUrl2);
|
|
3174
|
+
if (!this.sameTokenData(current, expected))
|
|
3175
|
+
return false;
|
|
3176
|
+
await this.saveTokens(baseUrl2, data);
|
|
3177
|
+
return true;
|
|
3178
|
+
}
|
|
2867
3179
|
async clearTokens(baseUrl2) {
|
|
2868
3180
|
const primaryError = await this.clearBestEffort(() => this.primary.clearTokens(baseUrl2));
|
|
2869
3181
|
await this.clearBestEffort(() => this.file.clearTokens(baseUrl2));
|
|
@@ -2872,6 +3184,13 @@ class MigratingAuthStorage {
|
|
|
2872
3184
|
throw primaryError;
|
|
2873
3185
|
}
|
|
2874
3186
|
}
|
|
3187
|
+
async clearTokensIfUnchanged(baseUrl2, expected) {
|
|
3188
|
+
const current = await this.loadTokens(baseUrl2);
|
|
3189
|
+
if (!this.sameTokenData(current, expected))
|
|
3190
|
+
return false;
|
|
3191
|
+
await this.clearTokens(baseUrl2);
|
|
3192
|
+
return true;
|
|
3193
|
+
}
|
|
2875
3194
|
async loadClient(baseUrl2) {
|
|
2876
3195
|
if (this.mode === "file") {
|
|
2877
3196
|
return this.loadClientFileMode(baseUrl2);
|
|
@@ -2897,6 +3216,25 @@ class MigratingAuthStorage {
|
|
|
2897
3216
|
throw primaryError;
|
|
2898
3217
|
}
|
|
2899
3218
|
}
|
|
3219
|
+
async saveAuthSession(baseUrl2, client, tokens) {
|
|
3220
|
+
if (this.mode === "file") {
|
|
3221
|
+
await this.file.saveAuthSession(baseUrl2, client, tokens);
|
|
3222
|
+
return;
|
|
3223
|
+
}
|
|
3224
|
+
try {
|
|
3225
|
+
await this.primary.saveAuthSession(baseUrl2, client, tokens);
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
throw this.toPolicyError(error);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
async clearAuthSession(baseUrl2) {
|
|
3231
|
+
const primaryError = await this.clearBestEffort(() => this.primary.clearAuthSession(baseUrl2));
|
|
3232
|
+
await this.clearBestEffort(() => this.file.clearAuthSession(baseUrl2));
|
|
3233
|
+
await this.clearBestEffort(() => this.legacy.clearAuthSession(baseUrl2));
|
|
3234
|
+
if (primaryError && !(primaryError instanceof KeychainUnavailableError)) {
|
|
3235
|
+
throw primaryError;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
2900
3238
|
getStorageLocation() {
|
|
2901
3239
|
return this.mode === "file" ? this.file.getStorageLocation() : this.primary.getStorageLocation();
|
|
2902
3240
|
}
|
|
@@ -3103,6 +3441,11 @@ class MigratingAuthStorage {
|
|
|
3103
3441
|
this.warnedAmbiguousPlaintext = true;
|
|
3104
3442
|
this.onWarning("Warning: multiple plaintext auth entries exist with ambiguous timestamps; using the new config auth path and leaving the other entry intact.");
|
|
3105
3443
|
}
|
|
3444
|
+
sameTokenData(a, b) {
|
|
3445
|
+
if (a === null || b === null)
|
|
3446
|
+
return a === b;
|
|
3447
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.createdAt === b.createdAt;
|
|
3448
|
+
}
|
|
3106
3449
|
}
|
|
3107
3450
|
// src/services/package-intelligence-service.ts
|
|
3108
3451
|
import { z as z3 } from "zod";
|
|
@@ -4283,12 +4626,12 @@ class PackageIntelligenceServiceImpl {
|
|
|
4283
4626
|
return this.normaliseChangelogReport(data, params);
|
|
4284
4627
|
}
|
|
4285
4628
|
normaliseChangelogReport(data, params) {
|
|
4286
|
-
const source = data.source
|
|
4287
|
-
|
|
4629
|
+
const source = data.source && data.source.trim() ? data.source : undefined;
|
|
4630
|
+
const rawEntries = data.entries ?? [];
|
|
4631
|
+
if (!source && rawEntries.length === 0) {
|
|
4288
4632
|
const target = params.repoUrl ?? (params.registry && params.packageName ? `${params.registry.toLowerCase()}:${params.packageName}` : "package");
|
|
4289
4633
|
throw new PackageIntelligenceChangelogSourceNotFoundError(`No changelog source available for ${target} (tried GitHub Releases, CHANGELOG.md, and HexDocs).`);
|
|
4290
4634
|
}
|
|
4291
|
-
const rawEntries = data.entries ?? [];
|
|
4292
4635
|
const entries = rawEntries.map((entry) => ({
|
|
4293
4636
|
version: entry.version ?? undefined,
|
|
4294
4637
|
normalizedVersion: entry.normalizedVersion ?? undefined,
|
|
@@ -4629,24 +4972,62 @@ class TokenManager {
|
|
|
4629
4972
|
refreshToken: tokens.refreshToken
|
|
4630
4973
|
}));
|
|
4631
4974
|
} catch {
|
|
4975
|
+
const reloadedToken = await this.loadExternallyUpdatedToken(tokens);
|
|
4976
|
+
if (reloadedToken)
|
|
4977
|
+
return reloadedToken.accessToken;
|
|
4632
4978
|
const isExpired = tokens.expiresAt ? new Date >= new Date(tokens.expiresAt) : false;
|
|
4633
4979
|
if (isExpired) {
|
|
4980
|
+
const currentStoredTokens = await this.loadExternallyUpdatedToken(tokens);
|
|
4981
|
+
if (currentStoredTokens)
|
|
4982
|
+
return currentStoredTokens.accessToken;
|
|
4983
|
+
const cleared = await withTelemetrySpan("token-manager.clear-tokens-if-unchanged", () => this.authStorage.clearTokensIfUnchanged(this.mcpUrl, tokens));
|
|
4984
|
+
if (!cleared) {
|
|
4985
|
+
const currentToken = await this.authStorage.loadTokens(this.mcpUrl);
|
|
4986
|
+
this.cachedToken = currentToken;
|
|
4987
|
+
return currentToken?.accessToken;
|
|
4988
|
+
}
|
|
4634
4989
|
this.cachedToken = null;
|
|
4635
|
-
await withTelemetrySpan("token-manager.clear-tokens", () => this.authStorage.clearTokens(this.mcpUrl));
|
|
4636
4990
|
}
|
|
4637
4991
|
return;
|
|
4638
4992
|
}
|
|
4639
4993
|
const newTokenData = {
|
|
4640
4994
|
accessToken: response.accessToken,
|
|
4641
|
-
refreshToken: response.refreshToken,
|
|
4995
|
+
refreshToken: response.refreshToken ?? tokens.refreshToken,
|
|
4642
4996
|
expiresAt: new Date(Date.now() + response.expiresIn * 1000).toISOString(),
|
|
4643
4997
|
createdAt: new Date().toISOString()
|
|
4644
4998
|
};
|
|
4645
|
-
|
|
4999
|
+
const externallyUpdatedToken = await this.loadExternallyUpdatedToken(tokens, {
|
|
5000
|
+
treatMissingAsExternalUpdate: true
|
|
5001
|
+
});
|
|
5002
|
+
if (externallyUpdatedToken === null)
|
|
5003
|
+
return;
|
|
5004
|
+
if (externallyUpdatedToken)
|
|
5005
|
+
return externallyUpdatedToken.accessToken;
|
|
5006
|
+
const saved = await withTelemetrySpan("token-manager.save-tokens", () => this.authStorage.saveTokensIfUnchanged(this.mcpUrl, tokens, newTokenData));
|
|
5007
|
+
if (!saved) {
|
|
5008
|
+
const currentToken = await this.authStorage.loadTokens(this.mcpUrl);
|
|
5009
|
+
this.cachedToken = currentToken;
|
|
5010
|
+
return currentToken?.accessToken;
|
|
5011
|
+
}
|
|
4646
5012
|
this.cachedToken = newTokenData;
|
|
4647
5013
|
return response.accessToken;
|
|
4648
5014
|
});
|
|
4649
5015
|
}
|
|
5016
|
+
async loadExternallyUpdatedToken(failedTokens, options = {}) {
|
|
5017
|
+
const storedTokens = await withTelemetrySpan("token-manager.reload-tokens", () => this.authStorage.loadTokens(this.mcpUrl));
|
|
5018
|
+
if (!storedTokens) {
|
|
5019
|
+
if (options.treatMissingAsExternalUpdate)
|
|
5020
|
+
this.cachedToken = null;
|
|
5021
|
+
return options.treatMissingAsExternalUpdate ? null : undefined;
|
|
5022
|
+
}
|
|
5023
|
+
if (areSameTokenData(storedTokens, failedTokens))
|
|
5024
|
+
return;
|
|
5025
|
+
this.cachedToken = storedTokens;
|
|
5026
|
+
return storedTokens;
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
function areSameTokenData(a, b) {
|
|
5030
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.createdAt === b.createdAt;
|
|
4650
5031
|
}
|
|
4651
5032
|
// src/services/update-check-service.ts
|
|
4652
5033
|
import semver from "semver";
|
|
@@ -4984,7 +5365,7 @@ function createAuthStorageForMode(fileSystemService, mode, configPath = "your Gi
|
|
|
4984
5365
|
const rawKeyring = new KeyringServiceImpl;
|
|
4985
5366
|
const keyring = process.platform === "win32" ? new ChunkingKeyringService(rawKeyring, WINDOWS_MAX_ENTRY_SIZE) : rawKeyring;
|
|
4986
5367
|
const keychainStorage = new KeychainAuthStorage(keyring);
|
|
4987
|
-
return new MigratingAuthStorage(keychainStorage, fileStorage, legacyStorage, mode, configPath, (message) => console.error(message));
|
|
5368
|
+
return new LockedAuthStorage(new MigratingAuthStorage(keychainStorage, fileStorage, legacyStorage, mode, configPath, (message) => console.error(message)), fileSystemService);
|
|
4988
5369
|
}
|
|
4989
5370
|
async function createAuthCommandDependencies() {
|
|
4990
5371
|
return withTelemetrySpan("container.create-auth-command", async () => {
|
|
@@ -5076,4 +5457,4 @@ async function createContainer() {
|
|
|
5076
5457
|
});
|
|
5077
5458
|
}
|
|
5078
5459
|
|
|
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 };
|
|
5460
|
+
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 };
|
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
createAuthCommandDependencies,
|
|
3
3
|
createAuthStatusDependencies,
|
|
4
4
|
createContainer
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import"./chunk-
|
|
5
|
+
} from "./chunk-0s7mxdt3.js";
|
|
6
|
+
import"./chunk-m1xx8hzw.js";
|
|
7
7
|
export {
|
|
8
8
|
createContainer,
|
|
9
9
|
createAuthStatusDependencies,
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED