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.
Files changed (52) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/README.md +8 -3
  3. package/bin/ghfind.js +98 -89
  4. package/completions/ghfind.bash +1 -1
  5. package/completions/ghfind.fish +3 -1
  6. package/completions/ghfind.zsh +6 -0
  7. package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
  8. package/dist/cli.js +761 -342
  9. package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
  10. package/package.json +6 -3
  11. package/release-notes/9.3.0.md +32 -29
  12. package/scripts/build.ts +34 -19
  13. package/scripts/generate-svg-demo.js +218 -168
  14. package/scripts/postinstall.js +176 -176
  15. package/src/bookmarks.ts +69 -69
  16. package/src/cli.ts +17 -1
  17. package/src/compare.ts +69 -47
  18. package/src/config.ts +70 -65
  19. package/src/deepdive.ts +149 -118
  20. package/src/doctor.ts +287 -287
  21. package/src/errors.ts +62 -59
  22. package/src/explore.ts +34 -29
  23. package/src/export.ts +73 -63
  24. package/src/help.ts +157 -157
  25. package/src/history.ts +69 -55
  26. package/src/init.ts +60 -55
  27. package/src/landing.ts +106 -104
  28. package/src/login.ts +186 -0
  29. package/src/markdown-render.ts +1 -6
  30. package/src/notifications.ts +75 -59
  31. package/src/open-url.ts +16 -16
  32. package/src/org.ts +43 -15
  33. package/src/output.ts +153 -137
  34. package/src/package-browser.ts +317 -305
  35. package/src/package.ts +149 -137
  36. package/src/pipe.ts +60 -54
  37. package/src/releases.ts +182 -144
  38. package/src/saved-searches.ts +72 -50
  39. package/src/search-state.ts +283 -229
  40. package/src/search.ts +678 -653
  41. package/src/session.ts +26 -24
  42. package/src/share.ts +61 -54
  43. package/src/status.ts +68 -61
  44. package/src/storage.ts +136 -106
  45. package/src/themes.ts +259 -116
  46. package/src/tips.ts +42 -42
  47. package/src/toast.ts +70 -69
  48. package/src/trending-parser.ts +104 -0
  49. package/src/trending.ts +481 -462
  50. package/src/tui.ts +149 -153
  51. package/src/types.ts +160 -158
  52. 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.4.2
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] — 9.4.1
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 # 179 tests
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 # 179 tests
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(err instanceof Error ? err.message : String(err));
87
- process.exit(1);
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);
@@ -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)
@@ -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"
@@ -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(0, Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")));
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
  }