github-search-cli 0.8.2 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -57,6 +57,8 @@ export interface SearchOptions {
57
57
  token?: string;
58
58
  /** Page number for pagination (1-based, default 1). */
59
59
  page?: number;
60
+ /** Trending period: daily|weekly|monthly. Used by TrendingAdapter. */
61
+ trendingSince?: "daily" | "weekly" | "monthly";
60
62
  /** Verbose logging (outgoing query, errors, rate limits). */
61
63
  verbose: boolean;
62
64
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Update checker — checks npm registry for a newer ghfind version.
3
+ *
4
+ * Uses the npm registry HTTP API (no subprocess needed — Bun has native fetch).
5
+ * Persists check state in the XDG state dir via storage.ts.
6
+ */
7
+ import { readJSON, writeJSON, debugLog } from "./storage";
8
+ import { stateDir } from "./config";
9
+
10
+ const REGISTRY_URL = "https://registry.npmjs.org/github-search-cli/latest";
11
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
12
+ const REQUEST_TIMEOUT_MS = 5000;
13
+
14
+ export interface UpdateState {
15
+ /** Timestamp of last successful check. */
16
+ lastCheck: number;
17
+ /** Timestamp until which we should suppress the panel (snooze). */
18
+ snoozeUntil: number;
19
+ /** If true, never show the update panel again. */
20
+ suppressed: boolean;
21
+ /** Latest version found on last check (for display). */
22
+ latestVersion?: string;
23
+ }
24
+
25
+ const STATE_FILE = "update-state.json";
26
+ export const DEFAULTS: UpdateState = {
27
+ lastCheck: 0,
28
+ snoozeUntil: 0,
29
+ suppressed: false,
30
+ };
31
+
32
+ /** Read update-check state from disk. */
33
+ export function readUpdateState(): UpdateState {
34
+ return { ...DEFAULTS, ...readJSON<Partial<UpdateState>>(STATE_FILE, {}) };
35
+ }
36
+
37
+ /** Write update-check state to disk. */
38
+ export function writeUpdateState(state: UpdateState): void {
39
+ writeJSON(STATE_FILE, state);
40
+ }
41
+
42
+ /**
43
+ * Whether we should run an update check now, based on cached state.
44
+ * Returns false if: suppressed, snoozed, or checked within the last 24h.
45
+ */
46
+ export function shouldCheckUpdate(): boolean {
47
+ const state = readUpdateState();
48
+ if (state.suppressed) return false;
49
+ if (Date.now() < state.snoozeUntil) return false;
50
+ if (Date.now() - state.lastCheck < CHECK_INTERVAL_MS) return false;
51
+ return true;
52
+ }
53
+
54
+ /**
55
+ * Compare two semver strings. Returns true if `latest` is newer than `current`.
56
+ * Uses Bun's built-in semver if available, falls back to simple comparison.
57
+ */
58
+ export function isNewerVersion(current: string, latest: string): boolean {
59
+ // ponytail: Bun.semver.order is the right tool — native, correct on edge cases
60
+ if (typeof Bun !== "undefined" && Bun.semver) {
61
+ return Bun.semver.order(current, latest) < 0;
62
+ }
63
+ // Fallback: naive string comparison (works for simple semver; does not handle pre-release tags)
64
+ const curParts = current.split(".").map((s) => Number(s.split("-")[0]));
65
+ const newParts = latest.split(".").map((s) => Number(s.split("-")[0]));
66
+ for (let i = 0; i < Math.max(curParts.length, newParts.length); i++) {
67
+ const c = curParts[i] ?? 0;
68
+ const n = newParts[i] ?? 0;
69
+ if (n > c) return true;
70
+ if (n < c) return false;
71
+ }
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * Check the npm registry for a newer version of ghfind.
77
+ * Returns the latest version string if an update is available, or null.
78
+ * Never throws — failures (offline, timeout, DNS) return null silently.
79
+ */
80
+ export async function checkForUpdate(
81
+ currentVersion: string,
82
+ ): Promise<string | null> {
83
+ if (process.env.DEBUG) debugLog(`Checking for update (current: ${currentVersion})`);
84
+ try {
85
+ const res = await fetch(REGISTRY_URL, {
86
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
87
+ });
88
+ if (!res.ok) {
89
+ if (process.env.DEBUG) debugLog(`Registry returned ${res.status}`);
90
+ return null;
91
+ }
92
+ const data = await res.json() as { version?: string };
93
+ const latest = data.version;
94
+ if (!latest) {
95
+ if (process.env.DEBUG) debugLog("No version in registry response");
96
+ return null;
97
+ }
98
+ if (process.env.DEBUG) debugLog(`Registry latest: ${latest}`);
99
+ if (!isNewerVersion(currentVersion, latest)) return null;
100
+ return latest;
101
+ } catch (e) {
102
+ if (process.env.DEBUG) debugLog(`Update check failed: ${e instanceof Error ? e.message : String(e)}`);
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Mark the update check as done. Stores the latest version if one was found.
109
+ */
110
+ export function markUpdateChecked(latestVersion?: string): void {
111
+ writeUpdateState({
112
+ ...readUpdateState(),
113
+ lastCheck: Date.now(),
114
+ latestVersion,
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Snooze update notifications for the given number of days.
120
+ */
121
+ export function snoozeUpdateNotices(days: number): void {
122
+ writeUpdateState({
123
+ ...readUpdateState(),
124
+ snoozeUntil: Date.now() + days * 24 * 60 * 60 * 1000,
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Permanently suppress the update panel.
130
+ */
131
+ export function suppressUpdateNotices(): void {
132
+ writeUpdateState({
133
+ ...readUpdateState(),
134
+ suppressed: true,
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Attempt to update ghfind in-place.
140
+ * Uses `process.versions.bun` for reliable runtime detection (works on all platforms).
141
+ */
142
+ export async function performUpdate(): Promise<boolean> {
143
+ const isBun = typeof process.versions !== "undefined" && "bun" in process.versions;
144
+ const cmd = isBun
145
+ ? ["bun", "install", "-g", "ghfind"]
146
+ : ["npm", "install", "-g", "ghfind"];
147
+
148
+ try {
149
+ const proc = Bun.spawn(cmd, {
150
+ stdout: "pipe",
151
+ stderr: "pipe",
152
+ });
153
+ await proc.exited;
154
+ if (proc.exitCode !== 0) {
155
+ const err = await proc.stderr.text();
156
+ debugLog(`Install failed (code ${proc.exitCode}): ${err}`);
157
+ }
158
+ return proc.exitCode === 0;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
package/src/watch.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { ParsedQuery, SearchOptions, Repo } from "./types";
6
6
  import { parseQuery, applyFlagFilters, rankRepos, createGitHubSearch } from "./search";
7
+ import { createTrendingSearch } from "./search";
7
8
 
8
9
  export interface WatchOptions {
9
10
  query: string;
@@ -28,17 +29,28 @@ export async function runWatch(
28
29
 
29
30
  const tick = async () => {
30
31
  try {
31
- const parsed = applyFlagFilters(parseQuery(opts.query), {});
32
- const provider = createGitHubSearch(undefined, opts.token ? [opts.token] : []);
33
- const options: SearchOptions = {
34
- limit: opts.limit,
35
- sort: opts.sort,
36
- json: false,
37
- verbose: false,
38
- token: opts.token,
39
- };
40
- const response = await provider.search(parsed, options);
41
- const repos = rankRepos(response.repos, options.sort);
32
+ let repos: Repo[];
33
+ if (opts.trending) {
34
+ const since = opts.query === "weekly" ? "weekly" : opts.query === "monthly" ? "monthly" : "daily";
35
+ const trendingSearch = createTrendingSearch();
36
+ const response = await trendingSearch.search(
37
+ { keywords: [], qualifiers: [], raw: "trending" },
38
+ { limit: opts.limit, sort: opts.sort, json: false, verbose: false, trendingSince: since },
39
+ );
40
+ repos = response.repos;
41
+ } else {
42
+ const parsed = applyFlagFilters(parseQuery(opts.query), {});
43
+ const provider = createGitHubSearch(undefined, opts.token ? [opts.token] : []);
44
+ const options: SearchOptions = {
45
+ limit: opts.limit,
46
+ sort: opts.sort,
47
+ json: false,
48
+ verbose: false,
49
+ token: opts.token,
50
+ };
51
+ const response = await provider.search(parsed, options);
52
+ repos = rankRepos(response.repos, options.sort);
53
+ }
42
54
 
43
55
  failCount = 0; // reset on success
44
56
  const delta = previous.length > 0 ? repos.length - previous.length : 0;
package/vendor/bun.exe ADDED
Binary file