github-search-cli 9.4.2 → 9.4.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 +26 -4
- package/README.md +8 -3
- package/bin/ghfind.js +98 -89
- package/completions/ghfind.bash +1 -1
- package/completions/ghfind.fish +3 -1
- package/completions/ghfind.zsh +6 -0
- package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
- package/dist/cli.js +761 -342
- package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
- package/package.json +6 -3
- package/release-notes/9.3.0.md +32 -29
- package/scripts/build.ts +34 -19
- package/scripts/generate-svg-demo.js +218 -168
- package/scripts/postinstall.js +176 -176
- package/src/bookmarks.ts +69 -69
- package/src/cli.ts +17 -1
- package/src/compare.ts +69 -47
- package/src/config.ts +70 -65
- package/src/deepdive.ts +149 -118
- package/src/doctor.ts +287 -287
- package/src/errors.ts +62 -59
- package/src/explore.ts +34 -29
- package/src/export.ts +73 -63
- package/src/help.ts +157 -157
- package/src/history.ts +69 -55
- package/src/init.ts +60 -55
- package/src/landing.ts +106 -104
- package/src/login.ts +186 -0
- package/src/markdown-render.ts +1 -6
- package/src/notifications.ts +75 -59
- package/src/open-url.ts +16 -16
- package/src/org.ts +43 -15
- package/src/output.ts +153 -137
- package/src/package-browser.ts +317 -305
- package/src/package.ts +149 -137
- package/src/pipe.ts +60 -54
- package/src/releases.ts +182 -144
- package/src/saved-searches.ts +72 -50
- package/src/search-state.ts +283 -229
- package/src/search.ts +678 -653
- package/src/session.ts +26 -24
- package/src/share.ts +61 -54
- package/src/status.ts +68 -61
- package/src/storage.ts +136 -106
- package/src/themes.ts +259 -116
- package/src/tips.ts +42 -42
- package/src/toast.ts +70 -69
- package/src/trending-parser.ts +104 -0
- package/src/trending.ts +481 -462
- package/src/tui.ts +149 -153
- package/src/types.ts +160 -158
- package/src/watch.ts +95 -74
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Publish
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## [9.4.2] — 2026-09-12 (released)
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
@@ -25,6 +25,21 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Publish
|
|
|
25
25
|
- **README trust & comparison** — npm version / weekly downloads / CI /
|
|
26
26
|
TypeScript / license badges; a "Why ghfind?" table vs `gh search` and
|
|
27
27
|
`hub`; standalone-binary install section.
|
|
28
|
+
- **`ghfind login`** — stores a GitHub token for 5,000 req/hr rate limits.
|
|
29
|
+
Detects a token managed by the GitHub CLI (`~/.config/gh/hosts.yml`) and
|
|
30
|
+
offers to import it, or accepts a pasted token; warns if it doesn't look
|
|
31
|
+
like a GitHub credential (`ghp_` / `gho_` / `github_pat_`). Writes the same
|
|
32
|
+
config file as `ghfind init`.
|
|
33
|
+
- **Better failure hints** — the `bin/ghfind.js` entry now points broken
|
|
34
|
+
installs at `ghfind --doctor` instead of failing silently (previously a
|
|
35
|
+
missing `src/` + `dist/` exited 0 without a word).
|
|
36
|
+
- **Hardened trending parser** — `parseTrendingHtml` moved to its own module
|
|
37
|
+
(`src/trending-parser.ts`, no TUI/@opentui dependency). Layout changes that
|
|
38
|
+
previously produced an **empty trending list with no explanation** now throw
|
|
39
|
+
a friendly `ParseError` ("GitHub may have changed the layout. [r]etry").
|
|
40
|
+
New golden-test harness: `tests/fixtures/trending.html` is a live capture
|
|
41
|
+
of the page, and 11 tests pin the parser to it (including the login
|
|
42
|
+
`/login?return_to=…` href and `/owner/name/stargazers`-link false positives).
|
|
28
43
|
|
|
29
44
|
### Changed
|
|
30
45
|
|
|
@@ -43,12 +58,19 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Publish
|
|
|
43
58
|
`package.json` via filesystem paths in `cli.ts`, `tui.ts`,
|
|
44
59
|
`update-check.ts`, and `error-report.ts`, which do not exist inside a
|
|
45
60
|
single-file executable. Version is now embedded at build time.
|
|
61
|
+
- **TUI crashed on boot with upgrade state (`bun start` → exit 1)** — the
|
|
62
|
+
update modal's `showOverlay("update")` called `updateSelect.focus()`, but
|
|
63
|
+
`updateSelect` was never declared (vestigial reference from the 9.4.0
|
|
64
|
+
refactor). Every launch where `update-state.json` recorded a version
|
|
65
|
+
upgrade threw `ReferenceError: updateSelect is not defined` before the
|
|
66
|
+
render loop started. The vestigial call is removed; the modal is driven by
|
|
67
|
+
`updateSelectedOption` + the global key handler, which now renders
|
|
68
|
+
correctly ("Updated ghfind X → Y" panel). Guarded by a source-grep test in
|
|
69
|
+
`tests/overlay-registry.test.ts`.
|
|
46
70
|
|
|
47
71
|
---
|
|
48
72
|
|
|
49
|
-
## [Unreleased]
|
|
50
|
-
|
|
51
|
-
## [Unreleased] — 9.4.1
|
|
73
|
+
## [Unreleased]
|
|
52
74
|
|
|
53
75
|
### Changed
|
|
54
76
|
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ ghfind
|
|
|
62
62
|
ghfind "language:Rust stars:>1000" --json | jq '.[].fullName'
|
|
63
63
|
ghfind "language:Zig" --count
|
|
64
64
|
ghfind --trending --json --since weekly
|
|
65
|
+
ghfind login # import the gh CLI token or paste one
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
### Non-interactive
|
|
@@ -190,7 +191,7 @@ Prefix with `-` to exclude. Supported: `language`, `stars`, `fork`, `archived`,
|
|
|
190
191
|
|
|
191
192
|
## Config
|
|
192
193
|
|
|
193
|
-
`~/.config/ghfind/config.json` (auto-created, or run `ghfind init`):
|
|
194
|
+
`~/.config/ghfind/config.json` (auto-created, or run `ghfind init`; `ghfind login` will set your GitHub token):
|
|
194
195
|
|
|
195
196
|
```json
|
|
196
197
|
{
|
|
@@ -214,14 +215,18 @@ cd ghfind
|
|
|
214
215
|
# With Bun (recommended for TUI)
|
|
215
216
|
bun install
|
|
216
217
|
bun start # TUI
|
|
217
|
-
bun test #
|
|
218
|
+
bun test # 215 tests
|
|
219
|
+
bun run check # format check + tests — the green gate
|
|
218
220
|
bun run build # build dist/
|
|
219
221
|
|
|
220
222
|
# Or with npm
|
|
221
223
|
npm install
|
|
222
224
|
npm start # TUI (requires Node 20+)
|
|
223
|
-
npm test #
|
|
225
|
+
npm test # 215 tests
|
|
224
226
|
npm run build # build dist/
|
|
227
|
+
|
|
228
|
+
> Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md) for commands,
|
|
229
|
+
> conventions, and the two tracked quality gates (`typecheck`, `lint`).
|
|
225
230
|
```
|
|
226
231
|
|
|
227
232
|
---
|
package/bin/ghfind.js
CHANGED
|
@@ -1,89 +1,98 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* ghfind — npm bin entry point.
|
|
4
|
-
* Requires Node.js >= 20 or Bun.
|
|
5
|
-
*
|
|
6
|
-
* Strategy 1: Use a Bun binary to run TypeScript source directly. Prefers the
|
|
7
|
-
* bundled vendor/bun (downloaded by postinstall), then falls back to a
|
|
8
|
-
* system-installed `bun` on PATH. Only runnable binaries are considered.
|
|
9
|
-
* Strategy 2: Fall back to the bundled dist/cli.js with Node.js (works for
|
|
10
|
-
* non-interactive modes).
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync } from "fs";
|
|
13
|
-
import { spawnSync } from "child_process";
|
|
14
|
-
import { join, dirname } from "path";
|
|
15
|
-
import { fileURLToPath, pathToFileURL } from "url";
|
|
16
|
-
import { platform } from "os";
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const PKG_DIR = join(__dirname, "..");
|
|
19
|
-
|
|
20
|
-
// Path to bundled Bun binary
|
|
21
|
-
const BUN_BIN = join(
|
|
22
|
-
PKG_DIR,
|
|
23
|
-
"vendor",
|
|
24
|
-
platform() === "win32" ? "bun.exe" : "bun",
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
// Path to bundled dist
|
|
28
|
-
const distCli = join(PKG_DIR, "dist", "cli.js");
|
|
29
|
-
|
|
30
|
-
// Path to source
|
|
31
|
-
const srcCli = join(PKG_DIR, "src", "cli.ts");
|
|
32
|
-
|
|
33
|
-
/** Returns "bun" if a system Bun is available on PATH, else undefined. */
|
|
34
|
-
function systemBun() {
|
|
35
|
-
try {
|
|
36
|
-
const res = spawnSync("bun", ["--version"], {
|
|
37
|
-
encoding: "utf8",
|
|
38
|
-
timeout: 15000,
|
|
39
|
-
});
|
|
40
|
-
return res.status === 0 ? "bun" : undefined;
|
|
41
|
-
} catch {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Returns true if the given Bun binary actually runs. */
|
|
47
|
-
function bunRuns(bin) {
|
|
48
|
-
try {
|
|
49
|
-
const res = spawnSync(bin, ["--version"], {
|
|
50
|
-
encoding: "utf8",
|
|
51
|
-
timeout: 15000,
|
|
52
|
-
});
|
|
53
|
-
return res.status === 0;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Strategy 1: Use Bun to run source directly (bundled binary first, then system bun)
|
|
60
|
-
if (existsSync(srcCli)) {
|
|
61
|
-
const candidates = [];
|
|
62
|
-
if (existsSync(BUN_BIN)) candidates.push(BUN_BIN);
|
|
63
|
-
const sysBun = systemBun();
|
|
64
|
-
if (sysBun) candidates.push(sysBun);
|
|
65
|
-
|
|
66
|
-
for (const bunBin of candidates) {
|
|
67
|
-
if (!bunRuns(bunBin)) continue;
|
|
68
|
-
const result = spawnSync(
|
|
69
|
-
bunBin,
|
|
70
|
-
["run", srcCli, ...process.argv.slice(2)],
|
|
71
|
-
{
|
|
72
|
-
stdio: "inherit",
|
|
73
|
-
env: { ...process.env, GHFIND_BUNDLED: "1" },
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
process.exit(result.status ?? 0);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Strategy 2: Use bundled dist/cli.js with Node (non-interactive only on Node 23+)
|
|
81
|
-
if (existsSync(distCli)) {
|
|
82
|
-
try {
|
|
83
|
-
await import(pathToFileURL(distCli).href);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error("Failed to start ghfind. Node.js >= 20 or Bun required.");
|
|
86
|
-
console.error(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ghfind — npm bin entry point.
|
|
4
|
+
* Requires Node.js >= 20 or Bun.
|
|
5
|
+
*
|
|
6
|
+
* Strategy 1: Use a Bun binary to run TypeScript source directly. Prefers the
|
|
7
|
+
* bundled vendor/bun (downloaded by postinstall), then falls back to a
|
|
8
|
+
* system-installed `bun` on PATH. Only runnable binaries are considered.
|
|
9
|
+
* Strategy 2: Fall back to the bundled dist/cli.js with Node.js (works for
|
|
10
|
+
* non-interactive modes).
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { spawnSync } from "child_process";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
16
|
+
import { platform } from "os";
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const PKG_DIR = join(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
// Path to bundled Bun binary
|
|
21
|
+
const BUN_BIN = join(
|
|
22
|
+
PKG_DIR,
|
|
23
|
+
"vendor",
|
|
24
|
+
platform() === "win32" ? "bun.exe" : "bun",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Path to bundled dist
|
|
28
|
+
const distCli = join(PKG_DIR, "dist", "cli.js");
|
|
29
|
+
|
|
30
|
+
// Path to source
|
|
31
|
+
const srcCli = join(PKG_DIR, "src", "cli.ts");
|
|
32
|
+
|
|
33
|
+
/** Returns "bun" if a system Bun is available on PATH, else undefined. */
|
|
34
|
+
function systemBun() {
|
|
35
|
+
try {
|
|
36
|
+
const res = spawnSync("bun", ["--version"], {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
timeout: 15000,
|
|
39
|
+
});
|
|
40
|
+
return res.status === 0 ? "bun" : undefined;
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Returns true if the given Bun binary actually runs. */
|
|
47
|
+
function bunRuns(bin) {
|
|
48
|
+
try {
|
|
49
|
+
const res = spawnSync(bin, ["--version"], {
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
timeout: 15000,
|
|
52
|
+
});
|
|
53
|
+
return res.status === 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Strategy 1: Use Bun to run source directly (bundled binary first, then system bun)
|
|
60
|
+
if (existsSync(srcCli)) {
|
|
61
|
+
const candidates = [];
|
|
62
|
+
if (existsSync(BUN_BIN)) candidates.push(BUN_BIN);
|
|
63
|
+
const sysBun = systemBun();
|
|
64
|
+
if (sysBun) candidates.push(sysBun);
|
|
65
|
+
|
|
66
|
+
for (const bunBin of candidates) {
|
|
67
|
+
if (!bunRuns(bunBin)) continue;
|
|
68
|
+
const result = spawnSync(
|
|
69
|
+
bunBin,
|
|
70
|
+
["run", srcCli, ...process.argv.slice(2)],
|
|
71
|
+
{
|
|
72
|
+
stdio: "inherit",
|
|
73
|
+
env: { ...process.env, GHFIND_BUNDLED: "1" },
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
process.exit(result.status ?? 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Strategy 2: Use bundled dist/cli.js with Node (non-interactive only on Node 23+)
|
|
81
|
+
if (existsSync(distCli)) {
|
|
82
|
+
try {
|
|
83
|
+
await import(pathToFileURL(distCli).href);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error("Failed to start ghfind. Node.js >= 20 or Bun required.");
|
|
86
|
+
console.error(
|
|
87
|
+
" Tip: run 'ghfind --doctor' for a full environment report.",
|
|
88
|
+
);
|
|
89
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Neither strategy produced a runnable CLI: report clearly instead of exiting 0.
|
|
95
|
+
console.error("ghfind: could not start — neither src/ nor dist/ is available.");
|
|
96
|
+
console.error(" Try reinstalling: npm install -g github-search-cli");
|
|
97
|
+
console.error(" Tip: run 'ghfind --doctor' for a full environment report.");
|
|
98
|
+
process.exit(1);
|
package/completions/ghfind.bash
CHANGED
|
@@ -7,7 +7,7 @@ _ghfind() {
|
|
|
7
7
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
8
8
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
9
9
|
|
|
10
|
-
opts="--json --csv --markdown --count --limit --sort --token --trending --since --pipe --format --registry --watch --interval --version --help --completion pkg"
|
|
10
|
+
opts="--json --csv --markdown --count --limit --sort --token --trending --since --pipe --format --registry --watch --interval --version --help --completion pkg login init doctor org"
|
|
11
11
|
|
|
12
12
|
case "${prev}" in
|
|
13
13
|
--sort)
|
package/completions/ghfind.fish
CHANGED
|
@@ -18,4 +18,6 @@ complete -c ghfind -l watch -d "Watch mode"
|
|
|
18
18
|
complete -c ghfind -l interval -d "Watch interval seconds" -r
|
|
19
19
|
complete -c ghfind -l version -d "Print version"
|
|
20
20
|
complete -c ghfind -l help -d "Print help"
|
|
21
|
-
complete -c ghfind -l completion -d "Shell" -r -f -a "bash zsh fish"
|
|
21
|
+
complete -c ghfind -l completion -d "Shell" -r -f -a "bash zsh fish"
|
|
22
|
+
|
|
23
|
+
complete -c ghfind -f -a "login init doctor org pkg"
|
package/completions/ghfind.zsh
CHANGED
|
@@ -22,6 +22,12 @@ _ghfind() {
|
|
|
22
22
|
'--version[Print version]'
|
|
23
23
|
'--help[Print help]'
|
|
24
24
|
'--completion[Shell]:(bash zsh fish)'
|
|
25
|
+
|
|
26
|
+
'login[Import a GitHub token from the gh CLI or paste one]'
|
|
27
|
+
'init[Run the setup wizard]'
|
|
28
|
+
'doctor[Environment diagnostics]'
|
|
29
|
+
'org[Org profile]:org name:'
|
|
30
|
+
'pkg[Search packages]:query:'
|
|
25
31
|
)
|
|
26
32
|
_arguments "${opts[@]}" '*:query:'
|
|
27
33
|
}
|
|
@@ -38,7 +38,10 @@ function loadConfig() {
|
|
|
38
38
|
}
|
|
39
39
|
function saveConfig(config) {
|
|
40
40
|
const path = configPath();
|
|
41
|
-
const dir = path.slice(
|
|
41
|
+
const dir = path.slice(
|
|
42
|
+
0,
|
|
43
|
+
Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
44
|
+
);
|
|
42
45
|
mkdirSync(dir, { recursive: true });
|
|
43
46
|
writeFileSync(path, JSON.stringify(config, null, 2));
|
|
44
47
|
}
|