@kitlangton/ghui 0.3.2 → 0.4.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/README.md CHANGED
@@ -8,13 +8,18 @@ Terminal UI for keeping up with your open GitHub pull requests across repositori
8
8
 
9
9
  ## Install
10
10
 
11
+ ```bash
12
+ brew install kitlangton/tap/ghui
13
+ ```
14
+
15
+ Or with npm:
16
+
11
17
  ```bash
12
18
  npm install -g @kitlangton/ghui
13
19
  ```
14
20
 
15
21
  Requirements:
16
22
 
17
- - Bun runtime installed
18
23
  - GitHub CLI installed and authenticated with `gh auth login`
19
24
 
20
25
  Run it from anywhere:
@@ -67,10 +72,12 @@ You can also copy `.env.example` to `.env` and edit the values locally.
67
72
  - `esc`: return from expanded details, leave diff/comment mode, or close modal
68
73
  - `r`: refresh
69
74
  - `d`: view stacked diff for all changed files
70
- - `c`: enter or exit diff comment mode while viewing a diff
71
- - `up` / `down` / `pageup` / `pagedown`: move comment target while in diff comment mode
75
+ - `shift-r`: review or approve the selected pull request
76
+ - `up` / `down` / `pageup` / `pagedown`: move comment target while viewing a diff
72
77
  - `enter`: open a commented diff line, or start a comment on an uncommented line
73
- - `a`: add a comment while in diff comment mode
78
+ - `v`: start or clear a multi-line diff comment range
79
+ - `n` / `p`: jump between diff comment threads
80
+ - `f`: open the changed-files navigator while viewing a diff
74
81
  - `left` / `right`: choose the deleted or added side while in split diff comment mode
75
82
  - `[` / `]`: switch files while viewing or commenting on a diff
76
83
  - `s`: toggle draft or ready-for-review state
@@ -81,3 +88,11 @@ You can also copy `.env.example` to `.env` and edit the values locally.
81
88
  - `o`: open PR in browser
82
89
  - `y`: copy PR metadata
83
90
  - `q`: quit
91
+
92
+ Review submission:
93
+
94
+ - Press `shift-r` to open the review modal.
95
+ - Use `j` / `k` or `up` / `down` to choose Comment, Approve, or Request changes.
96
+ - Press `enter` to move to the optional summary area.
97
+ - Press `enter` again to submit, or `shift-enter` to insert a newline.
98
+ - Press `esc` from the summary to return to action selection; press `esc` from action selection to cancel.
package/bin/ghui.js CHANGED
@@ -1,6 +1,26 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
 
3
- const packageJson = await Bun.file(new URL("../package.json", import.meta.url)).json()
3
+ import childProcess from "node:child_process"
4
+ import fs from "node:fs"
5
+ import os from "node:os"
6
+ import path from "node:path"
7
+ import { createRequire } from "node:module"
8
+ import { fileURLToPath } from "node:url"
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const requireFromHere = createRequire(import.meta.url)
12
+
13
+ const packageJson = requireFromHere("../package.json")
14
+
15
+ const platformMap = {
16
+ darwin: "darwin",
17
+ linux: "linux",
18
+ }
19
+
20
+ const archMap = {
21
+ arm64: "arm64",
22
+ x64: "x64",
23
+ }
4
24
 
5
25
  const help = `ghui ${packageJson.version}
6
26
 
@@ -14,58 +34,90 @@ Usage:
14
34
  ghui -h, --help Show this help message
15
35
  `
16
36
 
17
- const args = Bun.argv.slice(2)
18
- const command = args[0]
19
- const commands = ["upgrade", "help", "version"]
20
-
21
- const editDistance = (a, b) => {
22
- const distances = Array.from({ length: a.length + 1 }, (_, i) => [i])
23
- for (let j = 1; j <= b.length; j++) distances[0][j] = j
24
-
25
- for (let i = 1; i <= a.length; i++) {
26
- for (let j = 1; j <= b.length; j++) {
27
- distances[i][j] = Math.min(
28
- distances[i - 1][j] + 1,
29
- distances[i][j - 1] + 1,
30
- distances[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
31
- )
32
- }
37
+ const run = (target, args = process.argv.slice(2)) => {
38
+ const result = childProcess.spawnSync(target, args, { stdio: "inherit" })
39
+ if (result.error) {
40
+ console.error(result.error.message)
41
+ process.exit(1)
33
42
  }
43
+ process.exit(typeof result.status === "number" ? result.status : 0)
44
+ }
34
45
 
35
- return distances[a.length][b.length]
46
+ if (process.env.GHUI_BIN_PATH) {
47
+ run(process.env.GHUI_BIN_PATH)
36
48
  }
37
49
 
38
- if (command === "-h" || command === "--help" || command === "help") {
50
+ if (process.argv[2] === "-h" || process.argv[2] === "--help" || process.argv[2] === "help") {
39
51
  console.log(help)
40
52
  process.exit(0)
41
53
  }
42
54
 
43
- if (command === "-v" || command === "--version" || command === "version") {
55
+ if (process.argv[2] === "-v" || process.argv[2] === "--version" || process.argv[2] === "version") {
44
56
  console.log(packageJson.version)
45
57
  process.exit(0)
46
58
  }
47
59
 
48
- if (command === "upgrade") {
49
- const proc = Bun.spawn({
50
- cmd: ["npm", "install", "-g", `${packageJson.name}@latest`],
51
- stdin: "inherit",
52
- stdout: "inherit",
53
- stderr: "inherit",
54
- })
55
- process.exit(await proc.exited)
60
+ if (process.argv[2] === "upgrade") {
61
+ const result = childProcess.spawnSync("npm", ["install", "-g", `${packageJson.name}@latest`], { stdio: "inherit" })
62
+ if (result.error) {
63
+ console.error(result.error.message)
64
+ process.exit(1)
65
+ }
66
+ process.exit(typeof result.status === "number" ? result.status : 0)
67
+ }
68
+
69
+ const scriptPath = fs.realpathSync(__filename)
70
+ const scriptDir = path.dirname(scriptPath)
71
+
72
+ const platform = platformMap[os.platform()]
73
+ const arch = archMap[os.arch()]
74
+
75
+ const isMusl = () => {
76
+ if (os.platform() !== "linux") return false
77
+ try {
78
+ if (fs.existsSync("/etc/alpine-release")) return true
79
+ } catch {}
80
+ try {
81
+ const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
82
+ return `${result.stdout ?? ""}${result.stderr ?? ""}`.toLowerCase().includes("musl")
83
+ } catch {
84
+ return false
85
+ }
56
86
  }
57
87
 
58
- if (command !== undefined) {
59
- const suggestion = commands.find((name) => editDistance(command, name) <= 2)
60
- console.error(`Unknown command: ${command}`)
61
- if (suggestion) console.error(`Did you mean: ghui ${suggestion}?`)
62
- console.error("Run `ghui --help` for usage.")
88
+ if (!platform || !arch) {
89
+ console.error(`Unsupported platform for ${packageJson.name}: ${os.platform()}-${os.arch()}`)
63
90
  process.exit(1)
64
91
  }
65
92
 
66
- const sourceEntry = new URL("../src/index.tsx", import.meta.url)
67
- if (await Bun.file(sourceEntry).exists()) {
68
- await import(sourceEntry.href)
69
- } else {
70
- await import("../dist/index.js")
93
+ if (platform === "linux" && isMusl()) {
94
+ console.error(`${packageJson.name} does not publish musl Linux binaries yet.`)
95
+ console.error("Use a glibc-based Linux distribution, Homebrew on Linux, or the source checkout with Bun.")
96
+ process.exit(1)
97
+ }
98
+
99
+ const packageName = `${packageJson.name}-${platform}-${arch}`
100
+
101
+ const resolveBinary = () => {
102
+ try {
103
+ const packageJsonPath = requireFromHere.resolve(`${packageName}/package.json`)
104
+ return path.join(path.dirname(packageJsonPath), "bin", "ghui")
105
+ } catch {
106
+ return null
107
+ }
108
+ }
109
+
110
+ const binaryPath = resolveBinary()
111
+
112
+ if (!binaryPath || !fs.existsSync(binaryPath)) {
113
+ const sourceEntry = path.join(scriptDir, "..", "src", "standalone.ts")
114
+ if (fs.existsSync(sourceEntry)) {
115
+ run("bun", [sourceEntry, ...process.argv.slice(2)])
116
+ }
117
+
118
+ console.error(`Could not find the ${packageName} binary package for this platform.`)
119
+ console.error(`Try reinstalling ${packageJson.name}, or install ${packageName} manually.`)
120
+ process.exit(1)
71
121
  }
122
+
123
+ run(binaryPath)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitlangton/ghui",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Terminal UI for GitHub pull requests",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,60 +16,25 @@
16
16
  "github",
17
17
  "pull-requests",
18
18
  "terminal",
19
- "tui",
20
- "bun"
19
+ "tui"
21
20
  ],
21
+ "bin": {
22
+ "ghui": "bin/ghui.js"
23
+ },
22
24
  "files": [
23
25
  "bin",
24
- "dist",
25
26
  "README.md",
26
- ".env.example",
27
27
  "LICENSE"
28
28
  ],
29
+ "optionalDependencies": {
30
+ "@kitlangton/ghui-darwin-arm64": "0.4.0",
31
+ "@kitlangton/ghui-darwin-x64": "0.4.0",
32
+ "@kitlangton/ghui-linux-arm64": "0.4.0",
33
+ "@kitlangton/ghui-linux-x64": "0.4.0"
34
+ },
29
35
  "publishConfig": {
30
36
  "access": "public",
31
37
  "provenance": true,
32
38
  "registry": "https://registry.npmjs.org/"
33
- },
34
- "bin": {
35
- "ghui": "bin/ghui.js"
36
- },
37
- "scripts": {
38
- "build:cli": "bun run dev/build-cli.ts",
39
- "changeset": "npm exec --package @changesets/cli@2.31.0 -- changeset",
40
- "changeset:status": "npm exec --package @changesets/cli@2.31.0 -- changeset status",
41
- "changeset:version": "npm exec --package @changesets/cli@2.31.0 -- changeset version",
42
- "dev": "GHUI_MOTEL_PORT=${GHUI_MOTEL_PORT:-27686} bun --watch bin/ghui.js",
43
- "dev:logo": "bun --watch dev/loadingLogo.tsx",
44
- "start": "bun run src/index.tsx",
45
- "start:mock": "GHUI_MOCK_PR_COUNT=400 GHUI_MOCK_REPO_COUNT=4 bun run src/index.tsx",
46
- "test": "bun test test",
47
- "typecheck": "tsc --noEmit",
48
- "lint": "oxlint --tsconfig tsconfig.json src/ test/",
49
- "format": "oxfmt src/ test/ dev/",
50
- "format:check": "oxfmt --check src/ test/ dev/",
51
- "package:smoke": "bun run dev/package-smoke.ts",
52
- "prepack": "bun run build:cli"
53
- },
54
- "workspaces": [
55
- ".",
56
- "packages/*"
57
- ],
58
- "dependencies": {
59
- "@effect/atom-react": "4.0.0-beta.59",
60
- "@opentui/core": "0.2.1",
61
- "@opentui/react": "0.2.1",
62
- "effect": "4.0.0-beta.59",
63
- "react": "19.2.5",
64
- "scheduler": "0.27.0"
65
- },
66
- "devDependencies": {
67
- "@ghui/keymap": "workspace:*",
68
- "@effect/language-service": "0.85.1",
69
- "@types/bun": "1.3.12",
70
- "@types/react": "19.2.14",
71
- "oxfmt": "0.47.0",
72
- "oxlint": "1.62.0",
73
- "typescript": "6.0.2"
74
39
  }
75
40
  }
package/.env.example DELETED
@@ -1,6 +0,0 @@
1
- GHUI_AUTHOR=@me
2
- GHUI_PR_FETCH_LIMIT=200
3
-
4
- # Optional local observability. Motel's default local port is 27686.
5
- # GHUI_MOTEL_PORT=27686
6
- # GHUI_OTLP_ENDPOINT=http://127.0.0.1:27686