@xuhaojun/githunk 0.1.2 → 0.2.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/bin/githunk.js +86 -14
- package/dist/githunk.js +97 -46
- package/package.json +16 -29
package/bin/githunk.js
CHANGED
|
@@ -1,23 +1,95 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// githunk launcher: prefer the prebuilt standalone binary installed as an
|
|
4
|
+
// optional platform package, and fall back to the Node bundle otherwise.
|
|
5
|
+
//
|
|
6
|
+
// npm installs only the optional dependency whose `os`/`cpu` matches this
|
|
7
|
+
// machine, so the binary usually lives at
|
|
8
|
+
// `<install-root>/node_modules/@xuhaojun/githunk-<os>-<arch>/bin/githunk`.
|
|
9
|
+
// The lookup walks up from this file (the hunk `findInstalledBinary` shape in
|
|
10
|
+
// `learn-projects/hunk/bin/hunk.cjs`), which keeps working no matter which
|
|
11
|
+
// Node version owns the invoking shell: the prebuilt binary embeds its own
|
|
12
|
+
// Bun runtime and takes no `--experimental-ffi` flag. `$GITHUNK_BIN_PATH`
|
|
13
|
+
// overrides everything for debugging and custom installs.
|
|
14
|
+
|
|
3
15
|
import { spawn } from "node:child_process"
|
|
16
|
+
import { existsSync, realpathSync } from "node:fs"
|
|
4
17
|
import { constants } from "node:os"
|
|
18
|
+
import { dirname, join } from "node:path"
|
|
5
19
|
import { fileURLToPath } from "node:url"
|
|
6
20
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
21
|
+
const PLATFORM_PACKAGES = [
|
|
22
|
+
{ packageName: "@xuhaojun/githunk-darwin-arm64", binary: "bin/githunk" },
|
|
23
|
+
{ packageName: "@xuhaojun/githunk-darwin-x64", binary: "bin/githunk" },
|
|
24
|
+
{ packageName: "@xuhaojun/githunk-linux-arm64", binary: "bin/githunk" },
|
|
25
|
+
{ packageName: "@xuhaojun/githunk-linux-x64", binary: "bin/githunk" },
|
|
26
|
+
{ packageName: "@xuhaojun/githunk-windows-x64", binary: "bin/githunk.exe" },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function hostCandidates() {
|
|
30
|
+
const platformMap = {
|
|
31
|
+
darwin: "darwin",
|
|
32
|
+
linux: "linux",
|
|
33
|
+
win32: "windows",
|
|
34
|
+
}
|
|
35
|
+
const archMap = {
|
|
36
|
+
x64: "x64",
|
|
37
|
+
arm64: "arm64",
|
|
38
|
+
}
|
|
39
|
+
const platform = platformMap[process.platform]
|
|
40
|
+
const arch = archMap[process.arch]
|
|
41
|
+
if (platform === undefined || arch === undefined) return []
|
|
42
|
+
return PLATFORM_PACKAGES.filter((candidate) => candidate.packageName === `@xuhaojun/githunk-${platform}-${arch}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Find the prebuilt binary installed next to this package, walking up past nested installs. */
|
|
46
|
+
function findInstalledBinary(startDir) {
|
|
47
|
+
let current = startDir
|
|
48
|
+
for (;;) {
|
|
49
|
+
const modulesDir = join(current, "node_modules")
|
|
50
|
+
for (const candidate of hostCandidates()) {
|
|
51
|
+
const resolved = join(modulesDir, ...candidate.packageName.split("/"), candidate.binary)
|
|
52
|
+
if (existsSync(resolved)) return resolved
|
|
53
|
+
}
|
|
54
|
+
const parent = dirname(current)
|
|
55
|
+
if (parent === current) return null
|
|
56
|
+
current = parent
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function launch(target, args) {
|
|
61
|
+
const child = spawn(target, args, { stdio: "inherit" })
|
|
11
62
|
|
|
12
|
-
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]) {
|
|
13
|
-
|
|
63
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]) {
|
|
64
|
+
process.once(signal, () => { child.kill(signal) })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
child.once("error", (error) => {
|
|
68
|
+
process.stderr.write(`githunk: failed to start: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
69
|
+
process.exitCode = 1
|
|
70
|
+
})
|
|
71
|
+
child.once("close", (exitCode, signal) => {
|
|
72
|
+
const signalNumber = signal === null ? undefined : constants.signals[signal]
|
|
73
|
+
process.exitCode = exitCode ?? (signalNumber === undefined ? 1 : 128 + signalNumber)
|
|
74
|
+
})
|
|
14
75
|
}
|
|
15
76
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
77
|
+
const forwardedArgs = process.argv.slice(2)
|
|
78
|
+
const overrideBinary = process.env.GITHUNK_BIN_PATH
|
|
79
|
+
if (typeof overrideBinary === "string" && overrideBinary !== "") {
|
|
80
|
+
launch(overrideBinary, forwardedArgs)
|
|
81
|
+
} else {
|
|
82
|
+
let scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
83
|
+
try {
|
|
84
|
+
scriptDir = realpathSync(scriptDir)
|
|
85
|
+
} catch {
|
|
86
|
+
// Unresolved symlinks just start the walk from the literal directory.
|
|
87
|
+
}
|
|
88
|
+
const prebuiltBinary = findInstalledBinary(scriptDir)
|
|
89
|
+
if (prebuiltBinary !== null) {
|
|
90
|
+
launch(prebuiltBinary, forwardedArgs)
|
|
91
|
+
} else {
|
|
92
|
+
const cliPath = fileURLToPath(new URL("../dist/githunk.js", import.meta.url))
|
|
93
|
+
launch(process.execPath, ["--experimental-ffi", cliPath, ...forwardedArgs])
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/githunk.js
CHANGED
|
@@ -2,35 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/args.ts
|
|
4
4
|
import { Command, CommanderError } from "commander";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
5
|
+
// package.json
|
|
6
|
+
var package_default = {
|
|
7
|
+
name: "@xuhaojun/githunk",
|
|
8
|
+
version: "0.2.0",
|
|
9
|
+
description: "A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.",
|
|
10
|
+
type: "module",
|
|
11
|
+
bin: {
|
|
12
|
+
githunk: "bin/githunk.js"
|
|
13
|
+
},
|
|
14
|
+
files: [
|
|
15
|
+
"bin",
|
|
16
|
+
"dist/githunk.js",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
scripts: {
|
|
21
|
+
start: "bun run src/main.ts",
|
|
22
|
+
dev: "bun --watch src/main.ts",
|
|
23
|
+
build: "bun build src/cli.ts --target=node --format=esm --packages=external --outfile=dist/githunk.js --banner '#!/usr/bin/env node'",
|
|
24
|
+
"build:bin": "bun run ./scripts/build-bin.ts",
|
|
25
|
+
"build:prebuilt:artifact": "bun run ./scripts/build-prebuilt-artifact.ts",
|
|
26
|
+
"stage:prebuilt:release": "bun run ./scripts/stage-prebuilt-npm.ts --artifact-root ./dist/release/artifacts",
|
|
27
|
+
"check:release-version": "bun run ./scripts/check-release-version.ts",
|
|
28
|
+
"check:prebuilt-pack": "bun run ./scripts/check-prebuilt-pack.ts",
|
|
29
|
+
"publish:prebuilt:npm": "bun run ./scripts/publish-prebuilt-npm.ts",
|
|
30
|
+
"smoke:prebuilt-install": "bun run ./scripts/smoke-prebuilt-install.ts",
|
|
31
|
+
prepack: "bun run build",
|
|
32
|
+
"pack:check": "npm pack --dry-run",
|
|
33
|
+
"spike:selection": "bun run spikes/selection/src/main.ts",
|
|
34
|
+
test: "bun test --path-ignore-patterns='learn-projects/**'",
|
|
35
|
+
typecheck: "tsc --noEmit",
|
|
36
|
+
check: "bun run typecheck && bun run test",
|
|
37
|
+
"bench:review-load": "bun run benchmarks/review-document-load.ts",
|
|
38
|
+
"bench:review-rows": "bun run benchmarks/review-row-plan.ts",
|
|
39
|
+
"bench:review-reconcile": "bun run benchmarks/review-reconcile.ts"
|
|
40
|
+
},
|
|
41
|
+
keywords: [
|
|
42
|
+
"git",
|
|
43
|
+
"tui",
|
|
44
|
+
"code-review",
|
|
45
|
+
"cli"
|
|
46
|
+
],
|
|
47
|
+
homepage: "https://github.com/XuHaoJun/githunk#readme",
|
|
48
|
+
bugs: {
|
|
49
|
+
url: "https://github.com/XuHaoJun/githunk/issues"
|
|
50
|
+
},
|
|
51
|
+
repository: {
|
|
52
|
+
type: "git",
|
|
53
|
+
url: "git+https://github.com/XuHaoJun/githunk.git"
|
|
54
|
+
},
|
|
55
|
+
license: "MIT",
|
|
56
|
+
engines: {
|
|
57
|
+
node: ">=26.1.0"
|
|
58
|
+
},
|
|
59
|
+
packageManager: "bun@1.4.0",
|
|
60
|
+
publishConfig: {
|
|
61
|
+
access: "public"
|
|
62
|
+
},
|
|
63
|
+
dependencies: {
|
|
64
|
+
"@opentui/core": "0.5.10",
|
|
65
|
+
"@opentui/react": "0.5.10",
|
|
66
|
+
"@pierre/diffs": "1.3.5",
|
|
67
|
+
commander: "^15.0.0",
|
|
68
|
+
react: "19.2.8",
|
|
69
|
+
zod: "^4.4.3"
|
|
70
|
+
},
|
|
71
|
+
devDependencies: {
|
|
72
|
+
"@types/bun": "1.4.0",
|
|
73
|
+
"@types/react": "19.2.8",
|
|
74
|
+
typescript: "5.9.2"
|
|
25
75
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/cli/args.ts
|
|
79
|
+
var cliVersion = typeof package_default.version === "string" && package_default.version !== "" ? package_default.version : "0.0.0-dev";
|
|
29
80
|
function parseCliArgs(argv) {
|
|
30
81
|
let stdout = "";
|
|
31
82
|
let stderr = "";
|
|
32
83
|
const program = new Command;
|
|
33
|
-
program.name("githunk").description("A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.").version(
|
|
84
|
+
program.name("githunk").description("A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.").version(cliVersion, "-V, --version", "output the version number").option("-p, --path <dir>", "path to the Git repository to open").argument("[path]", "path to the Git repository to open").exitOverride().configureOutput({
|
|
34
85
|
writeOut: (text) => {
|
|
35
86
|
stdout += text;
|
|
36
87
|
},
|
|
@@ -978,7 +1029,7 @@ function fingerprintWorkingTreeFile(target, filePatch) {
|
|
|
978
1029
|
|
|
979
1030
|
// src/storage/local-state-file.ts
|
|
980
1031
|
import { mkdir, open, rename, stat, unlink, lstat, link, readFile } from "node:fs/promises";
|
|
981
|
-
import { dirname
|
|
1032
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
982
1033
|
import { randomUUID } from "node:crypto";
|
|
983
1034
|
async function assertNoSymlinkInPath(path, pathKind) {
|
|
984
1035
|
const absolute = resolve(path);
|
|
@@ -1008,7 +1059,7 @@ class LocalStateFile {
|
|
|
1008
1059
|
this.pathKind = options.pathKind ?? "state";
|
|
1009
1060
|
}
|
|
1010
1061
|
get path() {
|
|
1011
|
-
return this.resolvedPath ??
|
|
1062
|
+
return this.resolvedPath ?? join(this.runner.cwd, ".git", this.relativePath);
|
|
1012
1063
|
}
|
|
1013
1064
|
async resolvePath() {
|
|
1014
1065
|
if (this.resolvedPath !== undefined)
|
|
@@ -1016,7 +1067,7 @@ class LocalStateFile {
|
|
|
1016
1067
|
const output = (await this.runner.run(["rev-parse", "--git-path", this.relativePath], { readOnly: true })).stdout.trim();
|
|
1017
1068
|
if (output.length === 0)
|
|
1018
1069
|
throw new Error(`git returned an empty path for ${this.relativePath}`);
|
|
1019
|
-
this.resolvedPath = isAbsolute(output) ? output :
|
|
1070
|
+
this.resolvedPath = isAbsolute(output) ? output : join(this.runner.cwd, output);
|
|
1020
1071
|
return this.resolvedPath;
|
|
1021
1072
|
}
|
|
1022
1073
|
async readText() {
|
|
@@ -1033,7 +1084,7 @@ class LocalStateFile {
|
|
|
1033
1084
|
async writeText(text) {
|
|
1034
1085
|
const path = await this.resolvePath();
|
|
1035
1086
|
await assertNoSymlinkInPath(path, this.pathKind);
|
|
1036
|
-
await mkdir(
|
|
1087
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
1037
1088
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
1038
1089
|
await assertNoSymlinkInPath(temporary, this.pathKind);
|
|
1039
1090
|
const handle = await open(temporary, "wx", 384);
|
|
@@ -1046,7 +1097,7 @@ class LocalStateFile {
|
|
|
1046
1097
|
}
|
|
1047
1098
|
await rename(temporary, path);
|
|
1048
1099
|
try {
|
|
1049
|
-
const directory = await open(
|
|
1100
|
+
const directory = await open(dirname(path), "r");
|
|
1050
1101
|
try {
|
|
1051
1102
|
await directory.sync();
|
|
1052
1103
|
} finally {
|
|
@@ -1070,7 +1121,7 @@ class LocalStateFile {
|
|
|
1070
1121
|
async createTextExclusive(text) {
|
|
1071
1122
|
const path = await this.resolvePath();
|
|
1072
1123
|
await assertNoSymlinkInPath(path, this.pathKind);
|
|
1073
|
-
await mkdir(
|
|
1124
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
1074
1125
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
1075
1126
|
await assertNoSymlinkInPath(temporary, this.pathKind);
|
|
1076
1127
|
const handle = await open(temporary, "wx", 384);
|
|
@@ -1099,7 +1150,7 @@ class LocalStateFile {
|
|
|
1099
1150
|
return;
|
|
1100
1151
|
});
|
|
1101
1152
|
try {
|
|
1102
|
-
const directory = await open(
|
|
1153
|
+
const directory = await open(dirname(path), "r");
|
|
1103
1154
|
try {
|
|
1104
1155
|
await directory.sync();
|
|
1105
1156
|
} finally {
|
|
@@ -1439,7 +1490,7 @@ class GitMutations {
|
|
|
1439
1490
|
await this.runner.run(["restore", "--staged", "--", path], { acceptedExitCodes: [0, 1] });
|
|
1440
1491
|
}
|
|
1441
1492
|
await this.runner.run(["restore", "--", path], { acceptedExitCodes: [0, 1] });
|
|
1442
|
-
await this.runner.run(["clean", "-
|
|
1493
|
+
await this.runner.run(["clean", "-ff", "-d", "--", path]);
|
|
1443
1494
|
await this.refresh();
|
|
1444
1495
|
});
|
|
1445
1496
|
}
|
|
@@ -1449,7 +1500,7 @@ class GitMutations {
|
|
|
1449
1500
|
await this.runner.run(["restore", "--staged", "--", path], { acceptedExitCodes: [0, 1] });
|
|
1450
1501
|
}
|
|
1451
1502
|
await this.runner.run(["restore", "--", path], { acceptedExitCodes: [0, 1] });
|
|
1452
|
-
await this.runner.run(["clean", "-
|
|
1503
|
+
await this.runner.run(["clean", "-ff", "-d", "--", path]);
|
|
1453
1504
|
});
|
|
1454
1505
|
}
|
|
1455
1506
|
async applySelection(document, includedLineIndexes, options = { reverse: false, wholeFile: false }) {
|
|
@@ -2134,7 +2185,7 @@ async function listReflog(runner, options = {}) {
|
|
|
2134
2185
|
|
|
2135
2186
|
// src/git/worktrees.ts
|
|
2136
2187
|
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
2137
|
-
import { dirname as
|
|
2188
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
2138
2189
|
function finalizeEntry(entry) {
|
|
2139
2190
|
return {
|
|
2140
2191
|
path: entry.path,
|
|
@@ -2236,7 +2287,7 @@ async function resolveRepositoryPaths(runner) {
|
|
|
2236
2287
|
const worktreeGitDirPath = lines[1] ?? "";
|
|
2237
2288
|
const repoGitDirPath = lines[2] ?? "";
|
|
2238
2289
|
const isSubmodule = (lines[3] ?? "").length > 0;
|
|
2239
|
-
const repoPath = worktreeGitDirPath === repoGitDirPath || isSubmodule ? worktreePath :
|
|
2290
|
+
const repoPath = worktreeGitDirPath === repoGitDirPath || isSubmodule ? worktreePath : dirname2(repoGitDirPath);
|
|
2240
2291
|
return { worktreePath, worktreeGitDirPath, repoPath, repoGitDirPath };
|
|
2241
2292
|
}
|
|
2242
2293
|
async function isPathMissing(path) {
|
|
@@ -2269,11 +2320,11 @@ async function readTrimmedFile(path) {
|
|
|
2269
2320
|
}
|
|
2270
2321
|
async function inProgressBranch(gitDir) {
|
|
2271
2322
|
for (const directory of ["rebase-merge", "rebase-apply"]) {
|
|
2272
|
-
const headName = await readTrimmedFile(
|
|
2323
|
+
const headName = await readTrimmedFile(join2(gitDir, directory, "head-name"));
|
|
2273
2324
|
if (headName !== undefined)
|
|
2274
2325
|
return headName.replace(/^refs\/heads\//, "");
|
|
2275
2326
|
}
|
|
2276
|
-
return await readTrimmedFile(
|
|
2327
|
+
return await readTrimmedFile(join2(gitDir, "BISECT_START"));
|
|
2277
2328
|
}
|
|
2278
2329
|
async function listWorktrees(runner) {
|
|
2279
2330
|
const repositoryPaths = await resolveRepositoryPaths(runner);
|
|
@@ -2340,7 +2391,7 @@ function worktreeRemovalRequiresForce(error) {
|
|
|
2340
2391
|
|
|
2341
2392
|
// src/git/submodules.ts
|
|
2342
2393
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2343
|
-
import { join as
|
|
2394
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
2344
2395
|
|
|
2345
2396
|
// src/domain/submodule.ts
|
|
2346
2397
|
function submoduleFullName(submodule) {
|
|
@@ -2395,7 +2446,7 @@ function parseGitModules(raw) {
|
|
|
2395
2446
|
}
|
|
2396
2447
|
async function readGitModules(directory) {
|
|
2397
2448
|
try {
|
|
2398
|
-
return await readFile3(
|
|
2449
|
+
return await readFile3(join3(directory, ".gitmodules"), "utf8");
|
|
2399
2450
|
} catch (error) {
|
|
2400
2451
|
if (error instanceof Error && "code" in error) {
|
|
2401
2452
|
const code = error.code;
|
|
@@ -2406,7 +2457,7 @@ async function readGitModules(directory) {
|
|
|
2406
2457
|
}
|
|
2407
2458
|
}
|
|
2408
2459
|
async function collectSubmodules(worktreePath, parentModule, visited) {
|
|
2409
|
-
const directory = parentModule === undefined ? worktreePath :
|
|
2460
|
+
const directory = parentModule === undefined ? worktreePath : join3(worktreePath, submoduleFullPath(parentModule));
|
|
2410
2461
|
const resolved = resolve2(directory);
|
|
2411
2462
|
if (visited.has(resolved))
|
|
2412
2463
|
return [];
|
|
@@ -15418,7 +15469,7 @@ async function loadRefsSnapshot(runner) {
|
|
|
15418
15469
|
}
|
|
15419
15470
|
|
|
15420
15471
|
// src/git/editor.ts
|
|
15421
|
-
import { join as
|
|
15472
|
+
import { join as join4, basename } from "node:path";
|
|
15422
15473
|
function standardTerminalPreset(editor) {
|
|
15423
15474
|
return {
|
|
15424
15475
|
edit: `${editor} -- {{filename}}`,
|
|
@@ -15556,7 +15607,7 @@ async function resolveEditCommand(files, options = {}) {
|
|
|
15556
15607
|
function absolutePath(repoRoot, relativePath) {
|
|
15557
15608
|
if (relativePath.startsWith("/"))
|
|
15558
15609
|
return relativePath;
|
|
15559
|
-
return
|
|
15610
|
+
return join4(repoRoot, relativePath);
|
|
15560
15611
|
}
|
|
15561
15612
|
|
|
15562
15613
|
// src/app/create-app.ts
|
|
@@ -15564,7 +15615,7 @@ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
|
15564
15615
|
|
|
15565
15616
|
// src/app/index-watcher.ts
|
|
15566
15617
|
import { watch, statSync } from "node:fs";
|
|
15567
|
-
import { basename as basename2, dirname as
|
|
15618
|
+
import { basename as basename2, dirname as dirname3 } from "node:path";
|
|
15568
15619
|
var DEFAULT_INDEX_EVENT_DEBOUNCE_MS = 50;
|
|
15569
15620
|
var BUSY_RETRY_MS = 50;
|
|
15570
15621
|
function fingerprint(path) {
|
|
@@ -15605,7 +15656,7 @@ class IndexWatcher {
|
|
|
15605
15656
|
this.stopped = false;
|
|
15606
15657
|
this.baseline = fingerprint(this.options.indexPath);
|
|
15607
15658
|
try {
|
|
15608
|
-
const watcher = watch(
|
|
15659
|
+
const watcher = watch(dirname3(this.options.indexPath), { persistent: false }, (_eventType, filename) => {
|
|
15609
15660
|
if (this.stopped)
|
|
15610
15661
|
return;
|
|
15611
15662
|
const name = eventFileName(filename);
|
|
@@ -16271,7 +16322,7 @@ function reviewHelp(focus, state) {
|
|
|
16271
16322
|
}
|
|
16272
16323
|
|
|
16273
16324
|
// src/ui/review-workspace/review-sidebar.ts
|
|
16274
|
-
import { basename as basename3, dirname as
|
|
16325
|
+
import { basename as basename3, dirname as dirname4 } from "node:path/posix";
|
|
16275
16326
|
function normalizeDiffPath(p) {
|
|
16276
16327
|
return p?.replace(/[\r\n]+$/u, "");
|
|
16277
16328
|
}
|
|
@@ -16357,7 +16408,7 @@ function buildReviewSidebarEntries(state) {
|
|
|
16357
16408
|
let activeGroup;
|
|
16358
16409
|
visible.forEach((file, index) => {
|
|
16359
16410
|
const path = formatTerminalPath(normalizeDiffPath(file.path) ?? file.path);
|
|
16360
|
-
const group =
|
|
16411
|
+
const group = dirname4(path);
|
|
16361
16412
|
if (group !== activeGroup) {
|
|
16362
16413
|
activeGroup = group;
|
|
16363
16414
|
entries.push({
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xuhaojun/githunk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A review-first Git TUI combining lazygit's everyday Git workflow with focused hunk review.",
|
|
5
|
-
"type": "module",
|
|
6
5
|
"bin": {
|
|
7
6
|
"githunk": "bin/githunk.js"
|
|
8
7
|
},
|
|
@@ -12,42 +11,24 @@
|
|
|
12
11
|
"README.md",
|
|
13
12
|
"LICENSE"
|
|
14
13
|
],
|
|
15
|
-
"
|
|
16
|
-
"start": "bun run src/main.ts",
|
|
17
|
-
"dev": "bun --watch src/main.ts",
|
|
18
|
-
"build": "bun build src/cli.ts --target=node --format=esm --packages=external --outfile=dist/githunk.js --banner '#!/usr/bin/env node'",
|
|
19
|
-
"prepack": "bun run build",
|
|
20
|
-
"pack:check": "npm pack --dry-run",
|
|
21
|
-
"spike:selection": "bun run spikes/selection/src/main.ts",
|
|
22
|
-
"test": "bun test --path-ignore-patterns='learn-projects/**'",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"check": "bun run typecheck && bun run test",
|
|
25
|
-
"bench:review-load": "bun run benchmarks/review-document-load.ts",
|
|
26
|
-
"bench:review-rows": "bun run benchmarks/review-row-plan.ts",
|
|
27
|
-
"bench:review-reconcile": "bun run benchmarks/review-reconcile.ts"
|
|
28
|
-
},
|
|
14
|
+
"type": "module",
|
|
29
15
|
"keywords": [
|
|
30
16
|
"git",
|
|
31
17
|
"tui",
|
|
32
18
|
"code-review",
|
|
33
19
|
"cli"
|
|
34
20
|
],
|
|
35
|
-
"homepage": "https://github.com/XuHaoJun/githunk#readme",
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/XuHaoJun/githunk/issues"
|
|
38
|
-
},
|
|
39
21
|
"repository": {
|
|
40
22
|
"type": "git",
|
|
41
23
|
"url": "git+https://github.com/XuHaoJun/githunk.git"
|
|
42
24
|
},
|
|
43
|
-
"
|
|
25
|
+
"homepage": "https://github.com/XuHaoJun/githunk#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/XuHaoJun/githunk/issues"
|
|
28
|
+
},
|
|
44
29
|
"engines": {
|
|
45
30
|
"node": ">=26.1.0"
|
|
46
31
|
},
|
|
47
|
-
"packageManager": "bun@1.4.0",
|
|
48
|
-
"publishConfig": {
|
|
49
|
-
"access": "public"
|
|
50
|
-
},
|
|
51
32
|
"dependencies": {
|
|
52
33
|
"@opentui/core": "0.5.10",
|
|
53
34
|
"@opentui/react": "0.5.10",
|
|
@@ -56,9 +37,15 @@
|
|
|
56
37
|
"react": "19.2.8",
|
|
57
38
|
"zod": "^4.4.3"
|
|
58
39
|
},
|
|
59
|
-
"
|
|
60
|
-
"@
|
|
61
|
-
"@
|
|
62
|
-
"
|
|
40
|
+
"optionalDependencies": {
|
|
41
|
+
"@xuhaojun/githunk-darwin-arm64": "0.2.0",
|
|
42
|
+
"@xuhaojun/githunk-darwin-x64": "0.2.0",
|
|
43
|
+
"@xuhaojun/githunk-linux-arm64": "0.2.0",
|
|
44
|
+
"@xuhaojun/githunk-linux-x64": "0.2.0",
|
|
45
|
+
"@xuhaojun/githunk-windows-x64": "0.2.0"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
63
50
|
}
|
|
64
51
|
}
|