github-search-cli 9.8.1 → 9.8.3
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/CHANGELOG.md +120 -0
- package/LICENSE +21 -21
- package/README.md +45 -25
- package/completions/ghfind.bash +55 -55
- package/completions/ghfind.fish +28 -28
- package/completions/ghfind.zsh +52 -52
- package/demo.svg +71 -71
- package/dist/cli.js +1097 -735
- package/package.json +1 -1
- package/release-notes/9.8.2.md +65 -0
- package/release-notes/9.8.3.md +98 -0
- package/scripts/brew.rb +53 -53
- package/scripts/postinstall.js +38 -0
- package/src/cli.ts +40 -22
- package/src/config.ts +12 -2
- package/src/deepdive.ts +70 -66
- package/src/doctor.ts +3 -2
- package/src/error-report.ts +81 -11
- package/src/errors.ts +27 -1
- package/src/help.ts +11 -0
- package/src/history.ts +2 -5
- package/src/image-loader.ts +57 -2
- package/src/init.ts +1 -2
- package/src/markdown-images.ts +93 -4
- package/src/markdown-view.ts +49 -20
- package/src/mcp-server.ts +22 -11
- package/src/notifications.ts +9 -3
- package/src/org.ts +15 -14
- package/src/output.ts +1 -13
- package/src/readme.ts +19 -3
- package/src/releases.ts +29 -27
- package/src/search.ts +23 -2
- package/src/session.ts +58 -2
- package/src/skill-finder.ts +33 -2
- package/src/storage.ts +22 -47
- package/src/tui.ts +190 -44
- package/src/user.ts +15 -14
- package/dist/chunk-GYIL4S5X.js +0 -59
- package/dist/config-OCIPF2IF.js +0 -14
package/src/tui.ts
CHANGED
|
@@ -56,7 +56,14 @@ import {
|
|
|
56
56
|
import { tabSince, TAB_NAMES, fmtStars } from "./trending";
|
|
57
57
|
import { loadConfig, saveConfig } from "./config";
|
|
58
58
|
import { buildHelpSections, HELP_KEYS_COLUMN } from "./help";
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
SearchCliError,
|
|
61
|
+
NoResultsError,
|
|
62
|
+
NetworkError,
|
|
63
|
+
RateLimitError,
|
|
64
|
+
AuthError,
|
|
65
|
+
ForbiddenError,
|
|
66
|
+
} from "./errors";
|
|
60
67
|
import {
|
|
61
68
|
appendHistory,
|
|
62
69
|
readHistory,
|
|
@@ -204,7 +211,10 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
204
211
|
|
|
205
212
|
// ── Config ──
|
|
206
213
|
const config = loadConfig();
|
|
207
|
-
|
|
214
|
+
// Mutable: the AuthError flow lets the user fix or clear the token without
|
|
215
|
+
// restarting (the next search re-reads this variable).
|
|
216
|
+
let githubToken: string | undefined =
|
|
217
|
+
config.githubToken || process.env.GITHUB_TOKEN;
|
|
208
218
|
|
|
209
219
|
// Apply theme
|
|
210
220
|
const theme = loadTheme(theme_override || config.theme);
|
|
@@ -438,6 +448,11 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
438
448
|
currentMode = "search";
|
|
439
449
|
showLanding(false);
|
|
440
450
|
showSearchMode();
|
|
451
|
+
// Restore the saved query (if any) as a prefill, never auto-run it.
|
|
452
|
+
if (currentQueryInput) {
|
|
453
|
+
searchInput.value = currentQueryInput;
|
|
454
|
+
renderer.requestRender();
|
|
455
|
+
}
|
|
441
456
|
},
|
|
442
457
|
},
|
|
443
458
|
{
|
|
@@ -1891,6 +1906,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
1891
1906
|
try {
|
|
1892
1907
|
const readme = await fetchReadme(repo.owner, repo.name, {
|
|
1893
1908
|
token: githubToken,
|
|
1909
|
+
private: repo.private,
|
|
1894
1910
|
});
|
|
1895
1911
|
if (readme) {
|
|
1896
1912
|
readmeView.setContent(readme.text, readme.sourceUrl);
|
|
@@ -1922,12 +1938,125 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
1922
1938
|
}
|
|
1923
1939
|
}, 2000);
|
|
1924
1940
|
|
|
1941
|
+
// ── Token fix flow (from 401/403 error hints) ───────────────────────
|
|
1942
|
+
// Inline prompt box that slides over the search input row. Enter saves the
|
|
1943
|
+
// token to config and re-runs the failed query; Esc cancels.
|
|
1944
|
+
const tokenPromptBox = new BoxRenderable(renderer, {
|
|
1945
|
+
visible: false,
|
|
1946
|
+
position: "absolute",
|
|
1947
|
+
top: 0,
|
|
1948
|
+
left: 0,
|
|
1949
|
+
width: "100%",
|
|
1950
|
+
flexDirection: "row",
|
|
1951
|
+
paddingY: 1,
|
|
1952
|
+
paddingLeft: 1,
|
|
1953
|
+
backgroundColor: colors.surface,
|
|
1954
|
+
});
|
|
1955
|
+
const tokenPromptLabel = new TextRenderable(renderer, {
|
|
1956
|
+
content: " GitHub token:",
|
|
1957
|
+
fg: colors.accent,
|
|
1958
|
+
height: 1,
|
|
1959
|
+
});
|
|
1960
|
+
const tokenPromptInput = new InputRenderable(renderer, {
|
|
1961
|
+
placeholder: "paste token, Enter to save — Esc cancels",
|
|
1962
|
+
value: "",
|
|
1963
|
+
backgroundColor: colors.surface,
|
|
1964
|
+
textColor: colors.text,
|
|
1965
|
+
flexGrow: 1,
|
|
1966
|
+
});
|
|
1967
|
+
tokenPromptBox.add(tokenPromptLabel);
|
|
1968
|
+
tokenPromptBox.add(tokenPromptInput);
|
|
1969
|
+
root.add(tokenPromptBox);
|
|
1970
|
+
|
|
1971
|
+
let tokenPromptQuery: string | null = null;
|
|
1972
|
+
|
|
1973
|
+
function showTokenPrompt(query: string) {
|
|
1974
|
+
tokenPromptQuery = query;
|
|
1975
|
+
tokenPromptInput.value = "";
|
|
1976
|
+
tokenPromptBox.visible = true;
|
|
1977
|
+
searchBox.visible = false;
|
|
1978
|
+
tokenPromptInput.focus();
|
|
1979
|
+
renderer.requestRender();
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
function hideTokenPrompt() {
|
|
1983
|
+
tokenPromptBox.visible = false;
|
|
1984
|
+
tokenPromptQuery = null;
|
|
1985
|
+
if (currentMode === "search" || currentMode === "packages") {
|
|
1986
|
+
searchBox.visible = true;
|
|
1987
|
+
searchInput.focus();
|
|
1988
|
+
}
|
|
1989
|
+
renderer.requestRender();
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
function persistToken(token: string): void {
|
|
1993
|
+
githubToken = token;
|
|
1994
|
+
config.githubToken = token;
|
|
1995
|
+
saveConfig(config);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
tokenPromptInput.on("enter", () => {
|
|
1999
|
+
const token = tokenPromptInput.value.trim();
|
|
2000
|
+
const query = tokenPromptQuery;
|
|
2001
|
+
hideTokenPrompt();
|
|
2002
|
+
if (!token || !query) return;
|
|
2003
|
+
persistToken(token);
|
|
2004
|
+
setStatus("Token saved");
|
|
2005
|
+
doSearch(query);
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
// ── Error-hint key actions ([t] fix token, [u] unset) ──────────────
|
|
2009
|
+
|
|
2010
|
+
function fixTokenAction() {
|
|
2011
|
+
showTokenPrompt(currentQueryInput || "stars:>10000");
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function unsetTokenAction() {
|
|
2015
|
+
githubToken = undefined;
|
|
2016
|
+
if (config.githubToken) {
|
|
2017
|
+
delete config.githubToken;
|
|
2018
|
+
saveConfig(config);
|
|
2019
|
+
}
|
|
2020
|
+
setStatus("Token cleared — searching without one");
|
|
2021
|
+
if (currentQueryInput) doSearch(currentQueryInput);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1925
2024
|
// ── Helper functions ────────────────────────────────────────────────
|
|
1926
2025
|
|
|
1927
2026
|
function setStatus(msg: string) {
|
|
1928
2027
|
statusMgr.set("idle", msg);
|
|
1929
2028
|
}
|
|
1930
2029
|
|
|
2030
|
+
/**
|
|
2031
|
+
* Which error family is showing in the status bar, for the bracketed key
|
|
2032
|
+
* hints the messages advertise ([r]etry, [t]/[c] token, [u]nset).
|
|
2033
|
+
* Null once the next search starts, so the hint keys can't hijack
|
|
2034
|
+
* theme/compare forever after a transient failure.
|
|
2035
|
+
*/
|
|
2036
|
+
type ErrorHintKind = "auth" | "forbidden" | "ratelimit" | "network" | null;
|
|
2037
|
+
let errorHint: ErrorHintKind = null;
|
|
2038
|
+
|
|
2039
|
+
/** Render an error in the status bar and remember which hints apply. */
|
|
2040
|
+
function reportError(err: unknown) {
|
|
2041
|
+
errorHint =
|
|
2042
|
+
err instanceof AuthError
|
|
2043
|
+
? "auth"
|
|
2044
|
+
: err instanceof ForbiddenError
|
|
2045
|
+
? "forbidden"
|
|
2046
|
+
: err instanceof RateLimitError
|
|
2047
|
+
? "ratelimit"
|
|
2048
|
+
: err instanceof NetworkError
|
|
2049
|
+
? "network"
|
|
2050
|
+
: null;
|
|
2051
|
+
const msg =
|
|
2052
|
+
err instanceof SearchCliError
|
|
2053
|
+
? err.userMessage
|
|
2054
|
+
: err instanceof Error
|
|
2055
|
+
? err.message
|
|
2056
|
+
: String(err);
|
|
2057
|
+
statusMgr.set("error", msg.slice(0, 60));
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1931
2060
|
/** Session snapshot; only restorable modes are persisted (see SessionState). */
|
|
1932
2061
|
function buildSessionState(): SessionState {
|
|
1933
2062
|
const mode: SessionState["mode"] =
|
|
@@ -2221,6 +2350,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
2221
2350
|
async function loadTrending() {
|
|
2222
2351
|
currentMode = "trending";
|
|
2223
2352
|
isLoading = true;
|
|
2353
|
+
errorHint = null;
|
|
2224
2354
|
searchBox.visible = false;
|
|
2225
2355
|
toolbarText.visible = false;
|
|
2226
2356
|
trendingTabBox.visible = true;
|
|
@@ -2276,16 +2406,10 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
2276
2406
|
resultCount: fetched.length,
|
|
2277
2407
|
});
|
|
2278
2408
|
} catch (err) {
|
|
2279
|
-
const msg =
|
|
2280
|
-
err instanceof SearchCliError
|
|
2281
|
-
? err.userMessage
|
|
2282
|
-
: err instanceof Error
|
|
2283
|
-
? err.message
|
|
2284
|
-
: String(err);
|
|
2285
2409
|
resultsSelect.options = [
|
|
2286
2410
|
{ name: " (error)", description: "", value: null },
|
|
2287
2411
|
];
|
|
2288
|
-
|
|
2412
|
+
reportError(err);
|
|
2289
2413
|
}
|
|
2290
2414
|
isLoading = false;
|
|
2291
2415
|
renderer.requestRender();
|
|
@@ -2328,6 +2452,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
2328
2452
|
if (q === "" || isLoading) return;
|
|
2329
2453
|
currentQueryInput = q;
|
|
2330
2454
|
isLoading = true;
|
|
2455
|
+
errorHint = null; // npm errors never carry GitHub auth hints
|
|
2331
2456
|
resultsSelect.options = [
|
|
2332
2457
|
{ name: " Searching packages...", description: "", value: null },
|
|
2333
2458
|
];
|
|
@@ -2366,6 +2491,7 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
2366
2491
|
const q = queryText.trim();
|
|
2367
2492
|
if (q === "" || isLoading) return;
|
|
2368
2493
|
|
|
2494
|
+
errorHint = null; // a fresh search retires the previous error's hint keys
|
|
2369
2495
|
if (!append) currentPage = 1;
|
|
2370
2496
|
deepDiveActive = false;
|
|
2371
2497
|
|
|
@@ -2426,17 +2552,14 @@ export async function launchBrowser(theme_override?: string): Promise<void> {
|
|
|
2426
2552
|
}
|
|
2427
2553
|
}
|
|
2428
2554
|
} catch (err) {
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
? err.message
|
|
2438
|
-
: String(err);
|
|
2439
|
-
statusMgr.set("error", msg.slice(0, 60));
|
|
2555
|
+
// A failed "load more" page must not wipe the pages already on screen.
|
|
2556
|
+
if (!append) {
|
|
2557
|
+
currentRepos = [];
|
|
2558
|
+
resultsSelect.options = [
|
|
2559
|
+
{ name: " (error)", description: "", value: null },
|
|
2560
|
+
];
|
|
2561
|
+
}
|
|
2562
|
+
reportError(err);
|
|
2440
2563
|
}
|
|
2441
2564
|
|
|
2442
2565
|
isLoading = false;
|
|
@@ -2554,6 +2677,18 @@ ${pack.description ?? ""}`;
|
|
|
2554
2677
|
|
|
2555
2678
|
// ── Global keyboard shortcuts ──────────────────────────────────────
|
|
2556
2679
|
renderer.keyInput.on("keypress", (key) => {
|
|
2680
|
+
// ── Token prompt (own keyboard scope) ──────────────────────────
|
|
2681
|
+
if (tokenPromptBox.visible) {
|
|
2682
|
+
if (key.name === "escape") {
|
|
2683
|
+
hideTokenPrompt();
|
|
2684
|
+
setStatus("Token entry cancelled");
|
|
2685
|
+
renderer.requestRender();
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (key.name === "enter" || key.name === "return") return; // InputRenderable fires "enter"
|
|
2689
|
+
return; // let the input consume everything else (letters, paste, etc.)
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2557
2692
|
// ── Overlay-mode handling ────────────────────────────────────────
|
|
2558
2693
|
if (currentOverlay !== "none") {
|
|
2559
2694
|
// Escape or q closes any overlay (except: leader uses Esc for back-nav, q to close)
|
|
@@ -2926,6 +3061,34 @@ ${pack.description ?? ""}`;
|
|
|
2926
3061
|
return;
|
|
2927
3062
|
}
|
|
2928
3063
|
|
|
3064
|
+
// Error-hint keys — active only while a status-bar error that advertises
|
|
3065
|
+
// them ([r]etry / [t]oken / [u]nset / [c]ycle) is on screen; the next
|
|
3066
|
+
// search clears the state, returning t/c to their normal roles.
|
|
3067
|
+
if (!searchInput.focused && errorHint) {
|
|
3068
|
+
if (key.name === "r") {
|
|
3069
|
+
errorHint = null;
|
|
3070
|
+
if (currentMode === "trending") loadTrending();
|
|
3071
|
+
else if (currentQueryInput) doSearch(currentQueryInput);
|
|
3072
|
+
renderer.requestRender();
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (
|
|
3076
|
+
key.name === "t" &&
|
|
3077
|
+
(errorHint === "auth" || errorHint === "forbidden")
|
|
3078
|
+
) {
|
|
3079
|
+
fixTokenAction();
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
if (key.name === "u" && errorHint === "auth") {
|
|
3083
|
+
unsetTokenAction();
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if (key.name === "c" && errorHint === "ratelimit") {
|
|
3087
|
+
fixTokenAction(); // TUI manages a single token — prompt for a different one
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
|
|
2929
3092
|
// 't' toggles theme and persists to config
|
|
2930
3093
|
if (key.name === "t" && !searchInput.focused) {
|
|
2931
3094
|
const current = config.theme;
|
|
@@ -3045,44 +3208,27 @@ ${pack.description ?? ""}`;
|
|
|
3045
3208
|
// ── Update check (before start) ──────────────────────────────────────
|
|
3046
3209
|
const _currentVersion = getVersion();
|
|
3047
3210
|
// ── Start ──────────────────────────────────────────────────────────
|
|
3048
|
-
//
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3211
|
+
// Always start on the landing menu. Saved sessions restore preferences
|
|
3212
|
+
// (sort/limit/trending tab) and pre-fill the query once a mode is picked,
|
|
3213
|
+
// but never auto-run a search — a stale or corrupted session (e.g. an MCP
|
|
3214
|
+
// handshake blob saved as a query) used to hijack startup and skip the
|
|
3215
|
+
// menu on every launch.
|
|
3216
|
+
showLanding(true);
|
|
3052
3217
|
checkForUpdateAndShow();
|
|
3053
3218
|
checkPostUpgradePanel();
|
|
3054
3219
|
renderer.start();
|
|
3055
|
-
if (session?.mode) {
|
|
3056
|
-
if (currentMode === "trending") {
|
|
3057
|
-
loadTrending();
|
|
3058
|
-
} else {
|
|
3059
|
-
searchInput.value = currentQueryInput;
|
|
3060
|
-
if (currentQueryInput) {
|
|
3061
|
-
doSearch(currentQueryInput);
|
|
3062
|
-
} else {
|
|
3063
|
-
showSearchMode();
|
|
3064
|
-
}
|
|
3065
|
-
searchInput.focus();
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
3220
|
renderer.requestRender();
|
|
3069
3221
|
}
|
|
3070
3222
|
|
|
3071
3223
|
// ── Utilities ─────────────────────────────────────────────────────────
|
|
3072
3224
|
|
|
3073
3225
|
function formatResultLine(repo: Repo): string {
|
|
3074
|
-
const stars =
|
|
3226
|
+
const stars = `★ ${fmtStars(repo.stars)}`;
|
|
3075
3227
|
const lang = (repo.language ?? "?").padEnd(12).slice(0, 12);
|
|
3076
3228
|
const name = repo.fullName.padEnd(40).slice(0, 40);
|
|
3077
3229
|
return `${name} ${lang} ${stars}`;
|
|
3078
3230
|
}
|
|
3079
3231
|
|
|
3080
|
-
function formatStars(n: number): string {
|
|
3081
|
-
if (n >= 1000000) return `★ ${(n / 1000000).toFixed(1).replace(/\.0$/, "")}M`;
|
|
3082
|
-
if (n >= 1000) return `★ ${(n / 1000).toFixed(1).replace(/\.0$/, "")}k`;
|
|
3083
|
-
return `★ ${n}`;
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
3232
|
function formatToolbar(
|
|
3087
3233
|
sort: SortStrategy | PackageSortMode,
|
|
3088
3234
|
limit: number,
|
package/src/user.ts
CHANGED
|
@@ -212,20 +212,21 @@ export async function fetchUserProfile(
|
|
|
212
212
|
if (userPayload.public_repos > 0) {
|
|
213
213
|
const perPage = Math.min(limit, 100);
|
|
214
214
|
const maxPages = Math.ceil(limit / perPage);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
215
|
+
const pages = await Promise.all(
|
|
216
|
+
Array.from({ length: maxPages }, (_, i) => i + 1).map(async (page) => {
|
|
217
|
+
const url =
|
|
218
|
+
`https://api.github.com/users/${encodeURIComponent(name)}/repos` +
|
|
219
|
+
`?per_page=${perPage}&sort=pushed&page=${page}`;
|
|
220
|
+
const res = await fetch(url, { headers: apiHeaders(token) });
|
|
221
|
+
if (!res.ok) {
|
|
222
|
+
// User metadata succeeded; degraded summary beats a hard failure.
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const items = (await res.json()) as GitHubRepoPayload[];
|
|
226
|
+
return Array.isArray(items) ? items : []; // unexpected shape — skip
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
repos.push(...pages.flat());
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
return normalizeUser(userPayload, repos.slice(0, limit));
|
package/dist/chunk-GYIL4S5X.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// src/config.ts
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
-
function stateDir() {
|
|
6
|
-
const xdg = process.env.XDG_STATE_HOME;
|
|
7
|
-
const base = xdg ? join(xdg, "ghfind") : join(homedir(), ".local", "share", "ghfind");
|
|
8
|
-
return base;
|
|
9
|
-
}
|
|
10
|
-
function cacheDir() {
|
|
11
|
-
const xdg = process.env.XDG_CACHE_HOME;
|
|
12
|
-
const base = xdg ? join(xdg, "ghfind") : join(homedir(), ".cache", "ghfind");
|
|
13
|
-
return base;
|
|
14
|
-
}
|
|
15
|
-
var DEFAULTS = {
|
|
16
|
-
defaultSort: "best-match",
|
|
17
|
-
defaultLimit: 50,
|
|
18
|
-
theme: "tokyo-night",
|
|
19
|
-
cacheTtlSeconds: 300,
|
|
20
|
-
defaultTab: "search"
|
|
21
|
-
};
|
|
22
|
-
function configPath() {
|
|
23
|
-
const envPath = process.env.GHFIND_CONFIG;
|
|
24
|
-
if (envPath) return envPath;
|
|
25
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
26
|
-
const base = xdg ? join(xdg, "ghfind") : join(homedir(), ".config", "ghfind");
|
|
27
|
-
return join(base, "config.json");
|
|
28
|
-
}
|
|
29
|
-
function loadConfig() {
|
|
30
|
-
const path = configPath();
|
|
31
|
-
try {
|
|
32
|
-
const raw = readFileSync(path, "utf-8");
|
|
33
|
-
const parsed = JSON.parse(raw);
|
|
34
|
-
return mergeWithEnv({ ...DEFAULTS, ...parsed });
|
|
35
|
-
} catch {
|
|
36
|
-
return mergeWithEnv({ ...DEFAULTS });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function saveConfig(config) {
|
|
40
|
-
const path = configPath();
|
|
41
|
-
const dir = path.slice(
|
|
42
|
-
0,
|
|
43
|
-
Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
44
|
-
);
|
|
45
|
-
mkdirSync(dir, { recursive: true });
|
|
46
|
-
writeFileSync(path, JSON.stringify(config, null, 2));
|
|
47
|
-
}
|
|
48
|
-
function mergeWithEnv(config) {
|
|
49
|
-
if (process.env.GITHUB_TOKEN) config.githubToken = process.env.GITHUB_TOKEN;
|
|
50
|
-
return config;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
stateDir,
|
|
55
|
-
cacheDir,
|
|
56
|
-
configPath,
|
|
57
|
-
loadConfig,
|
|
58
|
-
saveConfig
|
|
59
|
-
};
|