@youtyan/code-viewer 0.6.2 → 0.6.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/README.md +17 -0
- package/dist/code-viewer.js +980 -307
- package/package.json +1 -1
- package/web/app.js +13 -5
- package/web/style.css +1 -0
package/dist/code-viewer.js
CHANGED
|
@@ -659,6 +659,175 @@ var init_annotations = __esm(() => {
|
|
|
659
659
|
});
|
|
660
660
|
});
|
|
661
661
|
|
|
662
|
+
// web-src/server/command-resolver.ts
|
|
663
|
+
import { accessSync, constants, realpathSync, statSync } from "node:fs";
|
|
664
|
+
import { dirname as dirname2, isAbsolute, join as join2, relative } from "node:path";
|
|
665
|
+
function isExternalCommandName(value) {
|
|
666
|
+
return commandNameSet.has(value);
|
|
667
|
+
}
|
|
668
|
+
function parseExternalCommandOverride(raw, flag = "--bin", allowedNames = EXTERNAL_COMMAND_NAMES) {
|
|
669
|
+
const eq = raw.indexOf("=");
|
|
670
|
+
if (eq <= 0 || eq === raw.length - 1) {
|
|
671
|
+
return { ok: false, error: `${flag} requires <name>=<absolute-path>` };
|
|
672
|
+
}
|
|
673
|
+
const name = raw.slice(0, eq).trim();
|
|
674
|
+
const path = raw.slice(eq + 1);
|
|
675
|
+
if (!isExternalCommandName(name)) {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
error: `${flag} unsupported command: ${name}`
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (!allowedNames.includes(name)) {
|
|
682
|
+
return {
|
|
683
|
+
ok: false,
|
|
684
|
+
error: `${flag} unsupported command: ${name}`
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
return { ok: true, override: { name, path } };
|
|
688
|
+
}
|
|
689
|
+
function configureExternalCommands(opts) {
|
|
690
|
+
const env = opts.env ?? process.env;
|
|
691
|
+
const allowedNames = opts.allowedNames ?? EXTERNAL_COMMAND_NAMES;
|
|
692
|
+
const selected = new Map;
|
|
693
|
+
for (const name of allowedNames) {
|
|
694
|
+
const value = env[envNameForCommand(name)];
|
|
695
|
+
if (value)
|
|
696
|
+
selected.set(name, { path: value, source: "env" });
|
|
697
|
+
}
|
|
698
|
+
for (const override of opts.cliOverrides ?? []) {
|
|
699
|
+
if (!allowedNames.includes(override.name)) {
|
|
700
|
+
return {
|
|
701
|
+
ok: false,
|
|
702
|
+
error: `--bin unsupported command: ${override.name}`
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
selected.set(override.name, { path: override.path, source: "cli" });
|
|
706
|
+
}
|
|
707
|
+
if (selected.size === 0) {
|
|
708
|
+
activeOverrides.clear();
|
|
709
|
+
return { ok: true };
|
|
710
|
+
}
|
|
711
|
+
const roots = forbiddenExecutableRoots(opts.cwd);
|
|
712
|
+
if ("error" in roots)
|
|
713
|
+
return { ok: false, error: roots.error };
|
|
714
|
+
const resolved = new Map;
|
|
715
|
+
for (const [name, value] of selected) {
|
|
716
|
+
const validated = validateExecutablePath(value.path, roots.roots);
|
|
717
|
+
if ("error" in validated) {
|
|
718
|
+
return {
|
|
719
|
+
ok: false,
|
|
720
|
+
error: `${sourceLabel(value.source)} ${name}: ${validated.error}`
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
resolved.set(name, {
|
|
724
|
+
path: value.path,
|
|
725
|
+
realPath: validated.realPath,
|
|
726
|
+
source: value.source
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
activeOverrides.clear();
|
|
730
|
+
for (const [name, value] of resolved)
|
|
731
|
+
activeOverrides.set(name, value);
|
|
732
|
+
return { ok: true };
|
|
733
|
+
}
|
|
734
|
+
function commandForExternal(name) {
|
|
735
|
+
return activeOverrides.get(name)?.realPath ?? name;
|
|
736
|
+
}
|
|
737
|
+
function isCommandNotFoundResult(command, result) {
|
|
738
|
+
if (result.code === 0)
|
|
739
|
+
return false;
|
|
740
|
+
return isCommandNotFoundMessage(command, result.stderr || "");
|
|
741
|
+
}
|
|
742
|
+
function commandNotFoundDetail(command) {
|
|
743
|
+
const resolved = commandForExternal(command);
|
|
744
|
+
if (resolved === command)
|
|
745
|
+
return `${command} not found in PATH`;
|
|
746
|
+
return `${command} binary not found or not executable: ${resolved}`;
|
|
747
|
+
}
|
|
748
|
+
function envNameForCommand(name) {
|
|
749
|
+
return `CODE_VIEWER_BIN_${name.toUpperCase().replace(/-/g, "_")}`;
|
|
750
|
+
}
|
|
751
|
+
function sourceLabel(source) {
|
|
752
|
+
return source === "cli" ? "--bin" : "environment override";
|
|
753
|
+
}
|
|
754
|
+
function validateExecutablePath(raw, forbiddenRoots) {
|
|
755
|
+
if (!raw)
|
|
756
|
+
return { error: "path must not be empty" };
|
|
757
|
+
if (raw.includes("\x00") || /[\r\n]/.test(raw)) {
|
|
758
|
+
return { error: "path must be single-line and must not contain NUL" };
|
|
759
|
+
}
|
|
760
|
+
if (!isAbsolute(raw))
|
|
761
|
+
return { error: "path must be absolute" };
|
|
762
|
+
let realPath;
|
|
763
|
+
try {
|
|
764
|
+
realPath = realpathSync(raw);
|
|
765
|
+
const st = statSync(realPath);
|
|
766
|
+
if (!st.isFile())
|
|
767
|
+
return { error: "path must point to a file" };
|
|
768
|
+
accessSync(realPath, constants.X_OK);
|
|
769
|
+
} catch {
|
|
770
|
+
return { error: "path must point to an executable file" };
|
|
771
|
+
}
|
|
772
|
+
for (const root of forbiddenRoots) {
|
|
773
|
+
if (sameOrInside(realPath, root)) {
|
|
774
|
+
return {
|
|
775
|
+
error: "path must not point inside the current repository or working directory"
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return { realPath };
|
|
780
|
+
}
|
|
781
|
+
function forbiddenExecutableRoots(cwd) {
|
|
782
|
+
let cwdReal;
|
|
783
|
+
try {
|
|
784
|
+
cwdReal = realpathSync(cwd);
|
|
785
|
+
} catch {
|
|
786
|
+
return { error: `--cwd must point to an existing directory: ${cwd}` };
|
|
787
|
+
}
|
|
788
|
+
const roots = [cwdReal];
|
|
789
|
+
const gitRoot = findGitRootByWalking(cwdReal);
|
|
790
|
+
if (gitRoot && !roots.some((root) => sameOrInside(gitRoot, root))) {
|
|
791
|
+
roots.push(gitRoot);
|
|
792
|
+
}
|
|
793
|
+
return { roots };
|
|
794
|
+
}
|
|
795
|
+
function findGitRootByWalking(start) {
|
|
796
|
+
let current = start;
|
|
797
|
+
for (;; ) {
|
|
798
|
+
try {
|
|
799
|
+
statSync(join2(current, ".git"));
|
|
800
|
+
return realpathSync(current);
|
|
801
|
+
} catch {}
|
|
802
|
+
const parent = dirname2(current);
|
|
803
|
+
if (parent === current)
|
|
804
|
+
return null;
|
|
805
|
+
current = parent;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function sameOrInside(path, root) {
|
|
809
|
+
const rel = relative(root, path);
|
|
810
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !isAbsolute(rel);
|
|
811
|
+
}
|
|
812
|
+
function isCommandNotFoundMessage(command, message) {
|
|
813
|
+
const lower = message.toLowerCase();
|
|
814
|
+
if (lower.includes("enoent"))
|
|
815
|
+
return true;
|
|
816
|
+
if (lower.includes(`spawn ${command.toLowerCase()}`))
|
|
817
|
+
return true;
|
|
818
|
+
if (lower.includes(`${command.toLowerCase()}: command not found`))
|
|
819
|
+
return true;
|
|
820
|
+
if (lower.includes(`${command.toLowerCase()}: not found`))
|
|
821
|
+
return true;
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
var EXTERNAL_COMMAND_NAMES, commandNameSet, activeOverrides;
|
|
825
|
+
var init_command_resolver = __esm(() => {
|
|
826
|
+
EXTERNAL_COMMAND_NAMES = ["git", "rg", "docker"];
|
|
827
|
+
commandNameSet = new Set(EXTERNAL_COMMAND_NAMES);
|
|
828
|
+
activeOverrides = new Map;
|
|
829
|
+
});
|
|
830
|
+
|
|
662
831
|
// web-src/server/runtime.ts
|
|
663
832
|
import { spawn, spawnSync } from "node:child_process";
|
|
664
833
|
import { createReadStream, promises as fs } from "node:fs";
|
|
@@ -677,7 +846,7 @@ function runSync(args, cwd, options = {}) {
|
|
|
677
846
|
return {
|
|
678
847
|
code: proc.status ?? (proc.error ? 1 : 0),
|
|
679
848
|
stdout: new TextDecoder().decode(proc.stdout || new Uint8Array),
|
|
680
|
-
stderr: new TextDecoder().decode(proc.stderr || new Uint8Array)
|
|
849
|
+
stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
|
|
681
850
|
};
|
|
682
851
|
}
|
|
683
852
|
function runBytesSync(args, cwd, options = {}) {
|
|
@@ -691,7 +860,7 @@ function runBytesSync(args, cwd, options = {}) {
|
|
|
691
860
|
return {
|
|
692
861
|
code: proc.status ?? (proc.error ? 1 : 0),
|
|
693
862
|
stdout: new Uint8Array(proc.stdout || new Uint8Array),
|
|
694
|
-
stderr: new TextDecoder().decode(proc.stderr || new Uint8Array)
|
|
863
|
+
stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
|
|
695
864
|
};
|
|
696
865
|
}
|
|
697
866
|
function spawnDetached(args) {
|
|
@@ -699,6 +868,9 @@ function spawnDetached(args) {
|
|
|
699
868
|
detached: true,
|
|
700
869
|
stdio: "ignore"
|
|
701
870
|
});
|
|
871
|
+
child.on("error", (err) => {
|
|
872
|
+
console.warn("[code-viewer] failed to start detached command:", err.message);
|
|
873
|
+
});
|
|
702
874
|
child.unref();
|
|
703
875
|
}
|
|
704
876
|
function spawnStream(args, cwd) {
|
|
@@ -706,12 +878,32 @@ function spawnStream(args, cwd) {
|
|
|
706
878
|
cwd,
|
|
707
879
|
stdio: ["ignore", "pipe", "ignore"]
|
|
708
880
|
});
|
|
881
|
+
let errorCode = 0;
|
|
882
|
+
proc.on("error", () => {
|
|
883
|
+
errorCode = 1;
|
|
884
|
+
});
|
|
709
885
|
return {
|
|
710
886
|
stream: Readable.toWeb(proc.stdout),
|
|
711
|
-
exited: new Promise((resolve) =>
|
|
887
|
+
exited: new Promise((resolve) => {
|
|
888
|
+
let settled = false;
|
|
889
|
+
const done = (code) => {
|
|
890
|
+
if (settled)
|
|
891
|
+
return;
|
|
892
|
+
settled = true;
|
|
893
|
+
resolve(code);
|
|
894
|
+
};
|
|
895
|
+
proc.on("error", () => done(1));
|
|
896
|
+
proc.on("close", (code) => done(errorCode || (code ?? 1)));
|
|
897
|
+
}),
|
|
712
898
|
kill: (signal) => proc.kill(signal)
|
|
713
899
|
};
|
|
714
900
|
}
|
|
901
|
+
function appendProcessError(stderr, err) {
|
|
902
|
+
if (!err)
|
|
903
|
+
return stderr;
|
|
904
|
+
return `${stderr}${stderr ? `
|
|
905
|
+
` : ""}${err.message}`;
|
|
906
|
+
}
|
|
715
907
|
function fileReadableStream(path) {
|
|
716
908
|
return Readable.toWeb(createReadStream(path));
|
|
717
909
|
}
|
|
@@ -847,9 +1039,9 @@ import {
|
|
|
847
1039
|
lstatSync,
|
|
848
1040
|
readdirSync,
|
|
849
1041
|
readFileSync,
|
|
850
|
-
statSync
|
|
1042
|
+
statSync as statSync2
|
|
851
1043
|
} from "node:fs";
|
|
852
|
-
import { join as
|
|
1044
|
+
import { join as join3 } from "node:path";
|
|
853
1045
|
function normalizeBlameRef(ref, base) {
|
|
854
1046
|
const rawRef = ref || "worktree";
|
|
855
1047
|
if (base === "worktree" && rawRef !== "worktree") {
|
|
@@ -861,19 +1053,75 @@ function normalizeBlameRef(ref, base) {
|
|
|
861
1053
|
return { base, ref: rawRef };
|
|
862
1054
|
}
|
|
863
1055
|
function run(args, cwd) {
|
|
864
|
-
return runSync(args, cwd);
|
|
1056
|
+
return runSync(resolveGitArgs(args), cwd);
|
|
865
1057
|
}
|
|
866
1058
|
function runBytes(args, cwd) {
|
|
867
|
-
return runBytesSync(args, cwd);
|
|
1059
|
+
return runBytesSync(resolveGitArgs(args), cwd);
|
|
1060
|
+
}
|
|
1061
|
+
function resolveGitArgs(args) {
|
|
1062
|
+
if (args[0] !== "git")
|
|
1063
|
+
return args;
|
|
1064
|
+
return [commandForExternal("git"), ...args.slice(1)];
|
|
1065
|
+
}
|
|
1066
|
+
function gitFailureMessage(res, fallback) {
|
|
1067
|
+
if (isCommandNotFoundResult("git", res))
|
|
1068
|
+
return commandNotFoundDetail("git");
|
|
1069
|
+
return res.stderr?.trim() || fallback;
|
|
1070
|
+
}
|
|
1071
|
+
function gitFailureResult(res, fallback) {
|
|
1072
|
+
if (!isCommandNotFoundResult("git", res)) {
|
|
1073
|
+
return { error: fallback };
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
error: commandNotFoundDetail("git"),
|
|
1077
|
+
status: 503
|
|
1078
|
+
};
|
|
868
1079
|
}
|
|
869
1080
|
function repoRoot(cwd) {
|
|
870
1081
|
const res = run(["git", "rev-parse", "--show-toplevel"], cwd);
|
|
871
1082
|
return res.code === 0 ? res.stdout.trimEnd() : null;
|
|
872
1083
|
}
|
|
1084
|
+
function repoRootResult(cwd) {
|
|
1085
|
+
const res = run(["git", "rev-parse", "--show-toplevel"], cwd);
|
|
1086
|
+
if (res.code === 0)
|
|
1087
|
+
return { kind: "root", root: res.stdout.trimEnd() };
|
|
1088
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
1089
|
+
return { kind: "error", error: commandNotFoundDetail("git") };
|
|
1090
|
+
}
|
|
1091
|
+
const stderr = res.stderr.trim();
|
|
1092
|
+
if (/not a git repository/i.test(stderr))
|
|
1093
|
+
return { kind: "outside" };
|
|
1094
|
+
return { kind: "error", error: stderr || "git rev-parse failed" };
|
|
1095
|
+
}
|
|
873
1096
|
function currentBranch(cwd) {
|
|
874
1097
|
const res = run(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
875
1098
|
return res.code === 0 ? res.stdout.trimEnd() : null;
|
|
876
1099
|
}
|
|
1100
|
+
function verifyCommit(ref, cwd) {
|
|
1101
|
+
const res = run(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
|
|
1102
|
+
if (res.code === 0)
|
|
1103
|
+
return { ok: true, sha: res.stdout.trim() };
|
|
1104
|
+
return { ok: false, error: gitFailureMessage(res, "unknown ref") };
|
|
1105
|
+
}
|
|
1106
|
+
function statusPorcelainForPath(path, cwd) {
|
|
1107
|
+
const res = run([
|
|
1108
|
+
"git",
|
|
1109
|
+
"-c",
|
|
1110
|
+
"core.quotepath=false",
|
|
1111
|
+
"status",
|
|
1112
|
+
"--porcelain=v1",
|
|
1113
|
+
"-z",
|
|
1114
|
+
"--untracked-files=normal",
|
|
1115
|
+
"--",
|
|
1116
|
+
path
|
|
1117
|
+
], cwd);
|
|
1118
|
+
if (res.code === 0)
|
|
1119
|
+
return { ok: true, stdout: res.stdout };
|
|
1120
|
+
return {
|
|
1121
|
+
ok: false,
|
|
1122
|
+
error: gitFailureMessage(res, "git status failed")
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
877
1125
|
function show(ref, path, cwd) {
|
|
878
1126
|
return run(["git", "show", `${ref}:${path}`], cwd);
|
|
879
1127
|
}
|
|
@@ -881,7 +1129,7 @@ function showBytes(ref, path, cwd) {
|
|
|
881
1129
|
return runBytes(["git", "show", `${ref}:${path}`], cwd);
|
|
882
1130
|
}
|
|
883
1131
|
function catFileBlobStream(oid, cwd) {
|
|
884
|
-
return spawnStream(["git", "cat-file", "blob", oid], cwd);
|
|
1132
|
+
return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
|
|
885
1133
|
}
|
|
886
1134
|
function objectSize(ref, path, cwd) {
|
|
887
1135
|
const res = run(["git", "cat-file", "-s", `${ref}:${path}`], cwd);
|
|
@@ -917,14 +1165,19 @@ function objectId(ref, path, cwd) {
|
|
|
917
1165
|
return { code: 0, oid, stderr: "" };
|
|
918
1166
|
}
|
|
919
1167
|
function verifyTreeRef(ref, cwd) {
|
|
1168
|
+
return verifyTreeRefResult(ref, cwd).ok;
|
|
1169
|
+
}
|
|
1170
|
+
function verifyTreeRefResult(ref, cwd) {
|
|
920
1171
|
if (!ref || ref === "worktree")
|
|
921
|
-
return false;
|
|
1172
|
+
return { ok: false, error: "invalid target", status: 400 };
|
|
922
1173
|
if (ref.startsWith("-"))
|
|
923
|
-
return false;
|
|
1174
|
+
return { ok: false, error: "invalid target", status: 400 };
|
|
924
1175
|
const res = run(["git", "rev-parse", "--verify", `${ref}^{tree}`], cwd);
|
|
925
|
-
|
|
1176
|
+
if (res.code === 0)
|
|
1177
|
+
return { ok: true };
|
|
1178
|
+
return { ok: false, ...gitFailureResult(res, "invalid target") };
|
|
926
1179
|
}
|
|
927
|
-
function
|
|
1180
|
+
function refsResult(cwd) {
|
|
928
1181
|
const out = {
|
|
929
1182
|
branches: [],
|
|
930
1183
|
tags: [],
|
|
@@ -939,6 +1192,9 @@ function refs(cwd) {
|
|
|
939
1192
|
"refs/heads",
|
|
940
1193
|
"refs/remotes"
|
|
941
1194
|
], cwd);
|
|
1195
|
+
if (branches.code !== 0 && isCommandNotFoundResult("git", branches)) {
|
|
1196
|
+
return { refs: out, ...gitFailureResult(branches, "git refs failed") };
|
|
1197
|
+
}
|
|
942
1198
|
if (branches.code === 0) {
|
|
943
1199
|
for (const line of branches.stdout.split(`
|
|
944
1200
|
`)) {
|
|
@@ -955,6 +1211,9 @@ function refs(cwd) {
|
|
|
955
1211
|
"--format=%(refname:short)%09%(creatordate:iso-strict)",
|
|
956
1212
|
"refs/tags"
|
|
957
1213
|
], cwd);
|
|
1214
|
+
if (tags.code !== 0 && isCommandNotFoundResult("git", tags)) {
|
|
1215
|
+
return { refs: out, ...gitFailureResult(tags, "git refs failed") };
|
|
1216
|
+
}
|
|
958
1217
|
if (tags.code === 0) {
|
|
959
1218
|
for (const line of tags.stdout.split(`
|
|
960
1219
|
`)) {
|
|
@@ -964,9 +1223,16 @@ function refs(cwd) {
|
|
|
964
1223
|
out.tags.push({ name, when });
|
|
965
1224
|
}
|
|
966
1225
|
}
|
|
967
|
-
|
|
1226
|
+
const commits = refCommitPageResult(cwd, {
|
|
1227
|
+
query: "",
|
|
1228
|
+
max: DEFAULT_REF_COMMIT_LIMIT
|
|
1229
|
+
});
|
|
1230
|
+
if (commits.error) {
|
|
1231
|
+
return { refs: out, error: commits.error, status: commits.status };
|
|
1232
|
+
}
|
|
1233
|
+
out.commits = commits.commits;
|
|
968
1234
|
out.current = currentBranch(cwd) || "";
|
|
969
|
-
return out;
|
|
1235
|
+
return { refs: out };
|
|
970
1236
|
}
|
|
971
1237
|
function clampCommitLimit(max) {
|
|
972
1238
|
return Math.max(1, Math.min(max, MAX_REF_COMMIT_LIMIT));
|
|
@@ -1019,14 +1285,15 @@ function mergeCommitResults(limit, ...groups) {
|
|
|
1019
1285
|
}
|
|
1020
1286
|
return merged;
|
|
1021
1287
|
}
|
|
1022
|
-
function
|
|
1288
|
+
function runCommitLogResult(cwd, args) {
|
|
1023
1289
|
const commits = run(args, cwd);
|
|
1024
|
-
|
|
1290
|
+
if (commits.code === 0)
|
|
1291
|
+
return { commits: parseCommitLog(commits.stdout) };
|
|
1292
|
+
if (isCommandNotFoundResult("git", commits))
|
|
1293
|
+
return { commits: [], ...gitFailureResult(commits, "git log failed") };
|
|
1294
|
+
return { commits: [] };
|
|
1025
1295
|
}
|
|
1026
|
-
function
|
|
1027
|
-
return refCommitPage(cwd, { query, max }).commits;
|
|
1028
|
-
}
|
|
1029
|
-
function refCommitPage(cwd, options = {}) {
|
|
1296
|
+
function refCommitPageResult(cwd, options = {}) {
|
|
1030
1297
|
const limit = clampCommitLimit(options.max ?? DEFAULT_REF_COMMIT_LIMIT);
|
|
1031
1298
|
const skip = clampCommitSkip(options.skip ?? 0);
|
|
1032
1299
|
const fetchLimit = limit + 1;
|
|
@@ -1034,6 +1301,13 @@ function refCommitPage(cwd, options = {}) {
|
|
|
1034
1301
|
const trimmed = (options.query || "").trim().slice(0, 200).replace(/\0/g, "");
|
|
1035
1302
|
if (skip === 0 && /^[0-9a-f]{4,40}$/i.test(trimmed)) {
|
|
1036
1303
|
const verified = run(["git", "rev-parse", "--verify", `${trimmed}^{commit}`], cwd);
|
|
1304
|
+
if (verified.code !== 0 && isCommandNotFoundResult("git", verified)) {
|
|
1305
|
+
return {
|
|
1306
|
+
commits: [],
|
|
1307
|
+
hasMore: false,
|
|
1308
|
+
...gitFailureResult(verified, "unknown ref")
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1037
1311
|
const single = run([
|
|
1038
1312
|
"git",
|
|
1039
1313
|
"log",
|
|
@@ -1042,30 +1316,62 @@ function refCommitPage(cwd, options = {}) {
|
|
|
1042
1316
|
`--format=${COMMIT_FORMAT}`,
|
|
1043
1317
|
verified.code === 0 && verified.stdout.trim() ? verified.stdout.trim() : trimmed
|
|
1044
1318
|
], cwd);
|
|
1319
|
+
if (single.code !== 0 && isCommandNotFoundResult("git", single)) {
|
|
1320
|
+
return {
|
|
1321
|
+
commits: [],
|
|
1322
|
+
hasMore: false,
|
|
1323
|
+
...gitFailureResult(single, "git log failed")
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1045
1326
|
if (single.code === 0 && single.stdout.trim()) {
|
|
1046
1327
|
hashMatches.push(...parseCommitLog(single.stdout));
|
|
1047
1328
|
}
|
|
1048
1329
|
}
|
|
1049
1330
|
if (!trimmed) {
|
|
1050
|
-
const
|
|
1331
|
+
const result = runCommitLogResult(cwd, commitLogArgs(fetchLimit, skip));
|
|
1332
|
+
if (result.error) {
|
|
1333
|
+
return {
|
|
1334
|
+
commits: [],
|
|
1335
|
+
hasMore: false,
|
|
1336
|
+
error: result.error,
|
|
1337
|
+
status: result.status
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
const commits = result.commits;
|
|
1051
1341
|
return {
|
|
1052
1342
|
commits: commits.slice(0, limit),
|
|
1053
1343
|
hasMore: commits.length > limit
|
|
1054
1344
|
};
|
|
1055
1345
|
}
|
|
1056
|
-
const subjectMatches =
|
|
1346
|
+
const subjectMatches = runCommitLogResult(cwd, [
|
|
1057
1347
|
...commitLogArgs(fetchLimit, skip),
|
|
1058
1348
|
"--regexp-ignore-case",
|
|
1059
1349
|
"--fixed-strings",
|
|
1060
1350
|
`--grep=${trimmed}`
|
|
1061
1351
|
]);
|
|
1062
|
-
|
|
1352
|
+
if (subjectMatches.error) {
|
|
1353
|
+
return {
|
|
1354
|
+
commits: [],
|
|
1355
|
+
hasMore: false,
|
|
1356
|
+
error: subjectMatches.error,
|
|
1357
|
+
status: subjectMatches.status
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
const authorMatches = runCommitLogResult(cwd, [
|
|
1063
1361
|
...commitLogArgs(fetchLimit, skip),
|
|
1064
1362
|
"--regexp-ignore-case",
|
|
1065
1363
|
"--fixed-strings",
|
|
1066
1364
|
`--author=${trimmed}`
|
|
1067
1365
|
]);
|
|
1068
|
-
|
|
1366
|
+
if (authorMatches.error) {
|
|
1367
|
+
return {
|
|
1368
|
+
commits: [],
|
|
1369
|
+
hasMore: false,
|
|
1370
|
+
error: authorMatches.error,
|
|
1371
|
+
status: authorMatches.status
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
const merged = mergeCommitResults(fetchLimit, hashMatches, subjectMatches.commits, authorMatches.commits);
|
|
1069
1375
|
return {
|
|
1070
1376
|
commits: merged.slice(0, limit),
|
|
1071
1377
|
hasMore: merged.length > limit
|
|
@@ -1160,7 +1466,11 @@ function commitHistory(cwd, options) {
|
|
|
1160
1466
|
return { commits: [], hasMore: false, error: "invalid ref" };
|
|
1161
1467
|
const verified = run(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
|
|
1162
1468
|
if (verified.code !== 0)
|
|
1163
|
-
return {
|
|
1469
|
+
return {
|
|
1470
|
+
commits: [],
|
|
1471
|
+
hasMore: false,
|
|
1472
|
+
...gitFailureResult(verified, "unknown ref")
|
|
1473
|
+
};
|
|
1164
1474
|
const skip = Math.max(0, Math.floor(options.skip) || 0);
|
|
1165
1475
|
const limit = Math.max(1, Math.min(Math.floor(options.limit) || 1, MAX_HISTORY_LIMIT));
|
|
1166
1476
|
const { filterArgs, pathspec, shaTerm } = historyQueryArgs(options.query || "");
|
|
@@ -1184,7 +1494,11 @@ function commitHistory(cwd, options) {
|
|
|
1184
1494
|
...pathArgs
|
|
1185
1495
|
], cwd);
|
|
1186
1496
|
if (res.code !== 0)
|
|
1187
|
-
return {
|
|
1497
|
+
return {
|
|
1498
|
+
commits: [],
|
|
1499
|
+
hasMore: false,
|
|
1500
|
+
...gitFailureResult(res, "git log failed")
|
|
1501
|
+
};
|
|
1188
1502
|
let parsed = parseHistoryLog(res.stdout);
|
|
1189
1503
|
if (shaTerm && skip === 0) {
|
|
1190
1504
|
const bySha = run(["git", "rev-parse", "--verify", `${shaTerm}^{commit}`], cwd);
|
|
@@ -1200,7 +1514,7 @@ function commitHistory(cwd, options) {
|
|
|
1200
1514
|
const hasMore = parsed.length > limit;
|
|
1201
1515
|
return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
|
|
1202
1516
|
}
|
|
1203
|
-
function
|
|
1517
|
+
function nameStatusResult(args, cwd) {
|
|
1204
1518
|
const res = run([
|
|
1205
1519
|
"git",
|
|
1206
1520
|
"-c",
|
|
@@ -1213,8 +1527,12 @@ function nameStatus(args, cwd) {
|
|
|
1213
1527
|
"-z",
|
|
1214
1528
|
...args
|
|
1215
1529
|
], cwd);
|
|
1216
|
-
if (res.code !== 0)
|
|
1217
|
-
return
|
|
1530
|
+
if (res.code !== 0) {
|
|
1531
|
+
return {
|
|
1532
|
+
files: [],
|
|
1533
|
+
error: gitFailureMessage(res, "git diff --name-status failed")
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1218
1536
|
const parts = res.stdout.split("\x00");
|
|
1219
1537
|
const files = [];
|
|
1220
1538
|
for (let i = 0;i < parts.length; ) {
|
|
@@ -1238,9 +1556,9 @@ function nameStatus(args, cwd) {
|
|
|
1238
1556
|
files.push({ status: kind, path });
|
|
1239
1557
|
}
|
|
1240
1558
|
}
|
|
1241
|
-
return files;
|
|
1559
|
+
return { files };
|
|
1242
1560
|
}
|
|
1243
|
-
function
|
|
1561
|
+
function numstatZResult(args, cwd) {
|
|
1244
1562
|
const res = run([
|
|
1245
1563
|
"git",
|
|
1246
1564
|
"-c",
|
|
@@ -1253,8 +1571,12 @@ function numstatZ(args, cwd) {
|
|
|
1253
1571
|
"-z",
|
|
1254
1572
|
...args
|
|
1255
1573
|
], cwd);
|
|
1256
|
-
if (res.code !== 0)
|
|
1257
|
-
return
|
|
1574
|
+
if (res.code !== 0) {
|
|
1575
|
+
return {
|
|
1576
|
+
files: [],
|
|
1577
|
+
error: gitFailureMessage(res, "git diff --numstat failed")
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1258
1580
|
const parts = res.stdout.split("\x00");
|
|
1259
1581
|
const files = [];
|
|
1260
1582
|
for (let i = 0;i < parts.length; ) {
|
|
@@ -1277,7 +1599,7 @@ function numstatZ(args, cwd) {
|
|
|
1277
1599
|
files.push({ path: rest, additions, deletions, binary });
|
|
1278
1600
|
}
|
|
1279
1601
|
}
|
|
1280
|
-
return files;
|
|
1602
|
+
return { files };
|
|
1281
1603
|
}
|
|
1282
1604
|
function pathHasSegment(path, segment) {
|
|
1283
1605
|
const target = segment.toLowerCase();
|
|
@@ -1290,9 +1612,9 @@ function isGitInternalPath(path) {
|
|
|
1290
1612
|
return pathHasSegment(path, ".git");
|
|
1291
1613
|
}
|
|
1292
1614
|
function syntheticUncommittedBlameFromWorktree(cwd, path) {
|
|
1293
|
-
const filePath =
|
|
1615
|
+
const filePath = join3(cwd, path);
|
|
1294
1616
|
try {
|
|
1295
|
-
const stat =
|
|
1617
|
+
const stat = statSync2(filePath);
|
|
1296
1618
|
if (!stat.isFile())
|
|
1297
1619
|
return { lines: [], commits: {}, error: "not a file" };
|
|
1298
1620
|
const text = readFileSync(filePath, "utf8");
|
|
@@ -1344,6 +1666,14 @@ function blame(cwd, options) {
|
|
|
1344
1666
|
args.push("--", path);
|
|
1345
1667
|
const res = run(args, cwd);
|
|
1346
1668
|
if (res.code !== 0) {
|
|
1669
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
1670
|
+
return {
|
|
1671
|
+
lines: [],
|
|
1672
|
+
commits: {},
|
|
1673
|
+
error: commandNotFoundDetail("git"),
|
|
1674
|
+
status: 503
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1347
1677
|
if (normalized.base === "worktree") {
|
|
1348
1678
|
return syntheticUncommittedBlameFromWorktree(cwd, path);
|
|
1349
1679
|
}
|
|
@@ -1439,7 +1769,7 @@ function omittedWorktreeDirectoryReason(name, omitDirNames) {
|
|
|
1439
1769
|
return omitDirNames.has(name) ? "heavy" : undefined;
|
|
1440
1770
|
}
|
|
1441
1771
|
function worktreeSubmodulePaths(cwd) {
|
|
1442
|
-
if (!existsSync(
|
|
1772
|
+
if (!existsSync(join3(cwd, ".gitmodules")))
|
|
1443
1773
|
return new Set;
|
|
1444
1774
|
const res = run(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
|
|
1445
1775
|
if (res.code !== 0)
|
|
@@ -1458,7 +1788,7 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
|
|
|
1458
1788
|
type: isDirectory ? "tree" : "blob"
|
|
1459
1789
|
};
|
|
1460
1790
|
const entryPath = base ? `${base}/${name}` : name;
|
|
1461
|
-
const type = isDirectory ? hasDotGitEntry(
|
|
1791
|
+
const type = isDirectory ? hasDotGitEntry(join3(dir, name)) ? "commit" : "tree" : "blob";
|
|
1462
1792
|
const omittedReason = type === "tree" ? omittedWorktreeDirectoryReason(name, omitDirNames) : undefined;
|
|
1463
1793
|
const submodule = type === "commit" && submodulePaths.has(entryPath) ? true : undefined;
|
|
1464
1794
|
const baseEntry = submodule ? { name, path: entryPath, type, submodule } : { name, path: entryPath, type };
|
|
@@ -1470,7 +1800,7 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
|
|
|
1470
1800
|
}
|
|
1471
1801
|
function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
1472
1802
|
const base = normalizeTreePath(path);
|
|
1473
|
-
const root =
|
|
1803
|
+
const root = join3(cwd, base);
|
|
1474
1804
|
const omitDirNameSet = new Set(omitDirNames);
|
|
1475
1805
|
const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
|
|
1476
1806
|
const submodulePaths = worktreeSubmodulePaths(cwd);
|
|
@@ -1517,7 +1847,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
1517
1847
|
if (excludeNameSet.has(entry.name.toLowerCase()))
|
|
1518
1848
|
continue;
|
|
1519
1849
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1520
|
-
const full =
|
|
1850
|
+
const full = join3(dir, entry.name);
|
|
1521
1851
|
if (entry.isDirectory()) {
|
|
1522
1852
|
const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
|
|
1523
1853
|
if (omittedReason) {
|
|
@@ -1549,7 +1879,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
1549
1879
|
}
|
|
1550
1880
|
function hasDotGitEntry(dir) {
|
|
1551
1881
|
try {
|
|
1552
|
-
lstatSync(
|
|
1882
|
+
lstatSync(join3(dir, ".git"));
|
|
1553
1883
|
return true;
|
|
1554
1884
|
} catch (err) {
|
|
1555
1885
|
return !!err && typeof err === "object" && "code" in err && err.code !== "ENOENT";
|
|
@@ -1612,14 +1942,20 @@ function listTree(ref, path, cwd, options = {}) {
|
|
|
1612
1942
|
stderr: ""
|
|
1613
1943
|
};
|
|
1614
1944
|
}
|
|
1945
|
+
function listTreeResult(ref, path, cwd, options = {}) {
|
|
1946
|
+
const result = listTree(ref, path, cwd, options);
|
|
1947
|
+
if (result.code === 0)
|
|
1948
|
+
return { entries: result.entries };
|
|
1949
|
+
return { entries: [], ...gitFailureResult(result, "git ls-tree failed") };
|
|
1950
|
+
}
|
|
1615
1951
|
function untrackedMeta(cwd) {
|
|
1616
1952
|
return untracked(cwd).flatMap((path) => {
|
|
1617
|
-
const full =
|
|
1953
|
+
const full = join3(cwd, path);
|
|
1618
1954
|
let binary = false;
|
|
1619
1955
|
let lines = 0;
|
|
1620
1956
|
let fileExists = false;
|
|
1621
1957
|
try {
|
|
1622
|
-
fileExists = existsSync(full) &&
|
|
1958
|
+
fileExists = existsSync(full) && statSync2(full).isFile();
|
|
1623
1959
|
} catch {
|
|
1624
1960
|
fileExists = false;
|
|
1625
1961
|
}
|
|
@@ -1645,11 +1981,15 @@ function untrackedMeta(cwd) {
|
|
|
1645
1981
|
];
|
|
1646
1982
|
});
|
|
1647
1983
|
}
|
|
1648
|
-
function
|
|
1649
|
-
const ns =
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1984
|
+
function fileMetaResult(args, cwd, includeUntracked = false) {
|
|
1985
|
+
const ns = nameStatusResult(args, cwd);
|
|
1986
|
+
if (ns.error)
|
|
1987
|
+
return { files: [], error: ns.error };
|
|
1988
|
+
const nm = numstatZResult(args, cwd);
|
|
1989
|
+
if (nm.error)
|
|
1990
|
+
return { files: [], error: nm.error };
|
|
1991
|
+
const byPath = new Map(nm.files.map((file) => [file.path, file]));
|
|
1992
|
+
const files = ns.files.map((file) => {
|
|
1653
1993
|
const stats = byPath.get(file.path);
|
|
1654
1994
|
return {
|
|
1655
1995
|
...file,
|
|
@@ -1658,11 +1998,13 @@ function fileMeta(args, cwd, includeUntracked = false) {
|
|
|
1658
1998
|
binary: stats?.binary || false
|
|
1659
1999
|
};
|
|
1660
2000
|
});
|
|
1661
|
-
return
|
|
2001
|
+
return {
|
|
2002
|
+
files: includeUntracked ? files.concat(untrackedMeta(cwd)) : files
|
|
2003
|
+
};
|
|
1662
2004
|
}
|
|
1663
2005
|
function fileDiffText(args, path, cwd) {
|
|
1664
2006
|
const paths = Array.isArray(path) ? path : [path];
|
|
1665
|
-
|
|
2007
|
+
const res = run([
|
|
1666
2008
|
"git",
|
|
1667
2009
|
"-c",
|
|
1668
2010
|
"core.quotepath=false",
|
|
@@ -1674,9 +2016,13 @@ function fileDiffText(args, path, cwd) {
|
|
|
1674
2016
|
"--",
|
|
1675
2017
|
...paths
|
|
1676
2018
|
], cwd);
|
|
2019
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2020
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2021
|
+
}
|
|
2022
|
+
return res;
|
|
1677
2023
|
}
|
|
1678
2024
|
function untrackedFileDiff(extras, path, cwd) {
|
|
1679
|
-
|
|
2025
|
+
const res = run([
|
|
1680
2026
|
"git",
|
|
1681
2027
|
"-c",
|
|
1682
2028
|
"core.quotepath=false",
|
|
@@ -1688,6 +2034,10 @@ function untrackedFileDiff(extras, path, cwd) {
|
|
|
1688
2034
|
"/dev/null",
|
|
1689
2035
|
path
|
|
1690
2036
|
], cwd);
|
|
2037
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2038
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2039
|
+
}
|
|
2040
|
+
return res;
|
|
1691
2041
|
}
|
|
1692
2042
|
function splitHunks(diffText) {
|
|
1693
2043
|
if (!diffText)
|
|
@@ -1764,6 +2114,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
1764
2114
|
}
|
|
1765
2115
|
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
1766
2116
|
var init_git = __esm(() => {
|
|
2117
|
+
init_command_resolver();
|
|
1767
2118
|
init_runtime();
|
|
1768
2119
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
1769
2120
|
"node_modules",
|
|
@@ -1820,16 +2171,16 @@ import {
|
|
|
1820
2171
|
writeFileSync
|
|
1821
2172
|
} from "node:fs";
|
|
1822
2173
|
import { homedir } from "node:os";
|
|
1823
|
-
import { join as
|
|
2174
|
+
import { join as join4 } from "node:path";
|
|
1824
2175
|
function registryDir() {
|
|
1825
2176
|
const override = process.env.CODE_VIEWER_TEST_SERVER_REGISTRY_DIR;
|
|
1826
2177
|
if (override)
|
|
1827
2178
|
return override;
|
|
1828
|
-
return
|
|
2179
|
+
return join4(homedir(), ".cache", "code-viewer", "servers");
|
|
1829
2180
|
}
|
|
1830
2181
|
function serverRegistryFilePath(root) {
|
|
1831
2182
|
const hash = createHash("sha256").update(root).digest("hex").slice(0, 16);
|
|
1832
|
-
return
|
|
2183
|
+
return join4(registryDir(), `${hash}.json`);
|
|
1833
2184
|
}
|
|
1834
2185
|
function writeServerRegistry(entry) {
|
|
1835
2186
|
try {
|
|
@@ -1870,7 +2221,7 @@ function removeServerRegistry(root, pid) {
|
|
|
1870
2221
|
var init_server_registry = () => {};
|
|
1871
2222
|
|
|
1872
2223
|
// web-src/server/cli-helpers.ts
|
|
1873
|
-
import { realpathSync } from "node:fs";
|
|
2224
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1874
2225
|
function takeValue(argv, index, flag) {
|
|
1875
2226
|
const value = argv[index + 1];
|
|
1876
2227
|
if (value === undefined)
|
|
@@ -1918,14 +2269,21 @@ function validateRepoRelativePathValue(value, flag) {
|
|
|
1918
2269
|
}
|
|
1919
2270
|
function resolveRepoRootSafe(cwdOption) {
|
|
1920
2271
|
const base = cwdOption || process.cwd();
|
|
2272
|
+
let baseReal;
|
|
1921
2273
|
try {
|
|
1922
|
-
|
|
2274
|
+
baseReal = realpathSync2(base);
|
|
1923
2275
|
} catch {
|
|
1924
2276
|
return {
|
|
1925
2277
|
ok: false,
|
|
1926
2278
|
error: `--cwd must point to an existing directory: ${base}`
|
|
1927
2279
|
};
|
|
1928
2280
|
}
|
|
2281
|
+
const root = repoRootResult(baseReal);
|
|
2282
|
+
if (root.kind === "root")
|
|
2283
|
+
return { ok: true, root: root.root };
|
|
2284
|
+
if (root.kind === "error")
|
|
2285
|
+
return { ok: false, error: root.error };
|
|
2286
|
+
return { ok: true, root: baseReal };
|
|
1929
2287
|
}
|
|
1930
2288
|
function resolveRepoRoot(cwdOption) {
|
|
1931
2289
|
const result = resolveRepoRootSafe(cwdOption);
|
|
@@ -3024,8 +3382,8 @@ __export(exports_file_cli, {
|
|
|
3024
3382
|
FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
|
|
3025
3383
|
FILE_AGENT_HELP: () => FILE_AGENT_HELP
|
|
3026
3384
|
});
|
|
3027
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as
|
|
3028
|
-
import { join as
|
|
3385
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
|
|
3386
|
+
import { join as join5, relative as relative2 } from "node:path";
|
|
3029
3387
|
function validatePath(value) {
|
|
3030
3388
|
return validateRepoRelativePathValue(value, "--path");
|
|
3031
3389
|
}
|
|
@@ -3043,6 +3401,7 @@ function parseFileArgs(argv) {
|
|
|
3043
3401
|
let cwd;
|
|
3044
3402
|
const options = new Map;
|
|
3045
3403
|
const flags = new Set;
|
|
3404
|
+
const commandOverrides = [];
|
|
3046
3405
|
for (let i = 0;i < argv.length; i++) {
|
|
3047
3406
|
const arg = argv[i];
|
|
3048
3407
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -3054,6 +3413,17 @@ function parseFileArgs(argv) {
|
|
|
3054
3413
|
return { ok: false, error: taken.error };
|
|
3055
3414
|
cwd = taken.value;
|
|
3056
3415
|
i = taken.next;
|
|
3416
|
+
} else if (arg === "--bin") {
|
|
3417
|
+
const taken = takeValue(argv, i, arg);
|
|
3418
|
+
if ("error" in taken)
|
|
3419
|
+
return { ok: false, error: taken.error };
|
|
3420
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
3421
|
+
"git"
|
|
3422
|
+
]);
|
|
3423
|
+
if (parsed.ok === false)
|
|
3424
|
+
return { ok: false, error: parsed.error };
|
|
3425
|
+
commandOverrides.push(parsed.override);
|
|
3426
|
+
i = taken.next;
|
|
3057
3427
|
} else if (VALUE_FLAGS.has(arg)) {
|
|
3058
3428
|
const taken = takeValue(argv, i, arg);
|
|
3059
3429
|
if ("error" in taken)
|
|
@@ -3115,7 +3485,8 @@ function parseFileArgs(argv) {
|
|
|
3115
3485
|
base,
|
|
3116
3486
|
json
|
|
3117
3487
|
},
|
|
3118
|
-
cwd
|
|
3488
|
+
cwd,
|
|
3489
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3119
3490
|
}
|
|
3120
3491
|
};
|
|
3121
3492
|
}
|
|
@@ -3153,7 +3524,8 @@ function parseFileArgs(argv) {
|
|
|
3153
3524
|
query,
|
|
3154
3525
|
json
|
|
3155
3526
|
},
|
|
3156
|
-
cwd
|
|
3527
|
+
cwd,
|
|
3528
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3157
3529
|
}
|
|
3158
3530
|
};
|
|
3159
3531
|
}
|
|
@@ -3192,7 +3564,8 @@ function parseFileArgs(argv) {
|
|
|
3192
3564
|
end,
|
|
3193
3565
|
json
|
|
3194
3566
|
},
|
|
3195
|
-
cwd
|
|
3567
|
+
cwd,
|
|
3568
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3196
3569
|
}
|
|
3197
3570
|
};
|
|
3198
3571
|
}
|
|
@@ -3281,7 +3654,8 @@ function parseFileArgs(argv) {
|
|
|
3281
3654
|
maxLines,
|
|
3282
3655
|
json
|
|
3283
3656
|
},
|
|
3284
|
-
cwd
|
|
3657
|
+
cwd,
|
|
3658
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3285
3659
|
}
|
|
3286
3660
|
};
|
|
3287
3661
|
}
|
|
@@ -3383,13 +3757,13 @@ function sliceLines(text, start, end) {
|
|
|
3383
3757
|
function safeWorktreePathFromRoot(root, path) {
|
|
3384
3758
|
if (validatePath(path))
|
|
3385
3759
|
return null;
|
|
3386
|
-
const full =
|
|
3760
|
+
const full = join5(root, path);
|
|
3387
3761
|
if (!existsSync3(full))
|
|
3388
3762
|
return null;
|
|
3389
3763
|
try {
|
|
3390
|
-
const realRoot =
|
|
3391
|
-
const realFull =
|
|
3392
|
-
const rel =
|
|
3764
|
+
const realRoot = realpathSync3(root);
|
|
3765
|
+
const realFull = realpathSync3(full);
|
|
3766
|
+
const rel = relative2(realRoot, realFull);
|
|
3393
3767
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\")) {
|
|
3394
3768
|
return null;
|
|
3395
3769
|
}
|
|
@@ -3413,7 +3787,7 @@ function readShowText(root, command) {
|
|
|
3413
3787
|
};
|
|
3414
3788
|
}
|
|
3415
3789
|
try {
|
|
3416
|
-
const stat =
|
|
3790
|
+
const stat = statSync3(full);
|
|
3417
3791
|
if (!stat.isFile()) {
|
|
3418
3792
|
return { code: 1, stdout: "", stderr: "not a file" };
|
|
3419
3793
|
}
|
|
@@ -3466,12 +3840,12 @@ function runShow(root, command) {
|
|
|
3466
3840
|
}
|
|
3467
3841
|
}
|
|
3468
3842
|
function buildFileDiffRangeArgs(from, to) {
|
|
3469
|
-
const
|
|
3843
|
+
const refs = [];
|
|
3470
3844
|
if (from && from !== "worktree")
|
|
3471
|
-
|
|
3845
|
+
refs.push(from);
|
|
3472
3846
|
if (to && to !== "worktree")
|
|
3473
|
-
|
|
3474
|
-
return
|
|
3847
|
+
refs.push(to);
|
|
3848
|
+
return refs;
|
|
3475
3849
|
}
|
|
3476
3850
|
function buildFileDiffReport(root, command) {
|
|
3477
3851
|
const base = {
|
|
@@ -3550,7 +3924,7 @@ async function runFileCli(argv) {
|
|
|
3550
3924
|
console.error('Run "code-viewer file --help" for usage.');
|
|
3551
3925
|
process.exit(1);
|
|
3552
3926
|
}
|
|
3553
|
-
const { command, cwd } = parsed.args;
|
|
3927
|
+
const { command, cwd, commandOverrides = [] } = parsed.args;
|
|
3554
3928
|
if (command.kind === "help") {
|
|
3555
3929
|
console.log(FILE_HELP);
|
|
3556
3930
|
return;
|
|
@@ -3559,6 +3933,15 @@ async function runFileCli(argv) {
|
|
|
3559
3933
|
console.log(FILE_AGENT_HELP);
|
|
3560
3934
|
return;
|
|
3561
3935
|
}
|
|
3936
|
+
const commandConfig = configureExternalCommands({
|
|
3937
|
+
cwd: cwd || process.cwd(),
|
|
3938
|
+
cliOverrides: commandOverrides,
|
|
3939
|
+
allowedNames: ["git"]
|
|
3940
|
+
});
|
|
3941
|
+
if (commandConfig.ok === false) {
|
|
3942
|
+
console.error(commandConfig.error);
|
|
3943
|
+
process.exit(1);
|
|
3944
|
+
}
|
|
3562
3945
|
const root = resolveRepoRoot(cwd);
|
|
3563
3946
|
if (command.kind === "blame")
|
|
3564
3947
|
return runBlame(root, command);
|
|
@@ -3572,13 +3955,14 @@ async function runFileCli(argv) {
|
|
|
3572
3955
|
var FILE_DEFAULT_HISTORY_LIMIT = 20, FILE_HISTORY_HARD_CAP = 200, FILE_DIFF_DEFAULT_MAX_HUNKS = 3, FILE_DIFF_DEFAULT_MAX_LINES = 1200, FILE_DIFF_HUNK_HARD_CAP = 100, FILE_DIFF_LINE_HARD_CAP = 1e5, FILE_HELP, FILE_AGENT_HELP, VALUE_FLAGS, BOOL_FLAGS, DEFAULT_BLAME_REF = "worktree", DEFAULT_BLAME_BASE = "worktree", DEFAULT_HISTORY_REF = "HEAD", DEFAULT_SHOW_REF = "worktree", DEFAULT_DIFF_FROM = "HEAD", DEFAULT_DIFF_TO = "worktree";
|
|
3573
3956
|
var init_file_cli = __esm(() => {
|
|
3574
3957
|
init_cli_helpers();
|
|
3958
|
+
init_command_resolver();
|
|
3575
3959
|
init_git();
|
|
3576
3960
|
FILE_HELP = `code-viewer file — inspect a path's blame, history, contents, or diff
|
|
3577
3961
|
|
|
3578
3962
|
Usage:
|
|
3579
|
-
code-viewer file blame --path <path> [--ref <ref>] [--base <worktree|HEAD>] [--json] [--cwd <dir>]
|
|
3580
|
-
code-viewer file history --path <path> [--ref <ref>] [--limit <n>] [--skip <n>] [--query <text>] [--json] [--cwd <dir>]
|
|
3581
|
-
code-viewer file show --path <path> [--ref <ref>] [--start <line>] [--end <line>] [--json] [--cwd <dir>]
|
|
3963
|
+
code-viewer file blame --path <path> [--ref <ref>] [--base <worktree|HEAD>] [--json] [--cwd <dir>] [--bin git=<path>]
|
|
3964
|
+
code-viewer file history --path <path> [--ref <ref>] [--limit <n>] [--skip <n>] [--query <text>] [--json] [--cwd <dir>] [--bin git=<path>]
|
|
3965
|
+
code-viewer file show --path <path> [--ref <ref>] [--start <line>] [--end <line>] [--json] [--cwd <dir>] [--bin git=<path>]
|
|
3582
3966
|
code-viewer file diff --path <path> [--from <ref>] [--to <ref>] [--old-path <path>] [--untracked]
|
|
3583
3967
|
[--ignore-ws] [--ignore-blank] [--max-hunks <n>] [--max-lines <n>] [--full] [--json] [--cwd <dir>]
|
|
3584
3968
|
code-viewer file --help
|
|
@@ -3591,6 +3975,7 @@ Common options:
|
|
|
3591
3975
|
HEAD. Single-line, no NUL, no leading "-". blame/show accept
|
|
3592
3976
|
the literal "worktree" to mean the working tree.
|
|
3593
3977
|
--cwd <dir> Repository to target (default: process.cwd()).
|
|
3978
|
+
--bin git=<p> Override git executable path.
|
|
3594
3979
|
--json Emit a structured JSON payload instead of plain text.
|
|
3595
3980
|
--help, -h Show this help.
|
|
3596
3981
|
|
|
@@ -3657,7 +4042,8 @@ Diff Viewer views.
|
|
|
3657
4042
|
- Run from inside the repository, or pass --cwd <repo>.
|
|
3658
4043
|
- No code-viewer server is required (this command reads git refs or the
|
|
3659
4044
|
worktree directly).
|
|
3660
|
-
- Git must be available on PATH
|
|
4045
|
+
- Git must be available on PATH, or supplied with --bin git=/absolute/path
|
|
4046
|
+
/ CODE_VIEWER_BIN_GIT.
|
|
3661
4047
|
|
|
3662
4048
|
## How to call
|
|
3663
4049
|
|
|
@@ -3902,6 +4288,7 @@ function parseQueryArgs(argv) {
|
|
|
3902
4288
|
let server;
|
|
3903
4289
|
const options = new Map;
|
|
3904
4290
|
const flags = new Set;
|
|
4291
|
+
const commandOverrides = [];
|
|
3905
4292
|
for (let i = 0;i < argv.length; i++) {
|
|
3906
4293
|
const arg = argv[i];
|
|
3907
4294
|
if (arg === "--help" || arg === "-h")
|
|
@@ -3915,6 +4302,17 @@ function parseQueryArgs(argv) {
|
|
|
3915
4302
|
else
|
|
3916
4303
|
server = taken.value;
|
|
3917
4304
|
i = taken.next;
|
|
4305
|
+
} else if (arg === "--bin") {
|
|
4306
|
+
const taken = takeValue(argv, i, arg);
|
|
4307
|
+
if ("error" in taken)
|
|
4308
|
+
return { ok: false, error: taken.error };
|
|
4309
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
4310
|
+
"git"
|
|
4311
|
+
]);
|
|
4312
|
+
if (parsed.ok === false)
|
|
4313
|
+
return { ok: false, error: parsed.error };
|
|
4314
|
+
commandOverrides.push(parsed.override);
|
|
4315
|
+
i = taken.next;
|
|
3918
4316
|
} else if (VALUE_FLAGS2.has(arg)) {
|
|
3919
4317
|
const taken = takeValue(argv, i, arg);
|
|
3920
4318
|
if ("error" in taken)
|
|
@@ -3932,7 +4330,11 @@ function parseQueryArgs(argv) {
|
|
|
3932
4330
|
const subcommand = rest[0];
|
|
3933
4331
|
if (!subcommand)
|
|
3934
4332
|
return { ok: true, args: { command: { kind: "help" } } };
|
|
3935
|
-
const globalArgs = {
|
|
4333
|
+
const globalArgs = {
|
|
4334
|
+
cwd,
|
|
4335
|
+
server,
|
|
4336
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
4337
|
+
};
|
|
3936
4338
|
if (subcommand === "agent-help") {
|
|
3937
4339
|
return { ok: true, args: { command: { kind: "agent-help" } } };
|
|
3938
4340
|
}
|
|
@@ -4707,7 +5109,7 @@ async function runQueryCli(argv) {
|
|
|
4707
5109
|
console.error('Run "code-viewer query --help" for usage.');
|
|
4708
5110
|
process.exit(1);
|
|
4709
5111
|
}
|
|
4710
|
-
const { command, cwd, server } = parsed.args;
|
|
5112
|
+
const { command, cwd, server, commandOverrides = [] } = parsed.args;
|
|
4711
5113
|
if (command.kind === "help") {
|
|
4712
5114
|
console.log(QUERY_HELP);
|
|
4713
5115
|
return;
|
|
@@ -4716,7 +5118,16 @@ async function runQueryCli(argv) {
|
|
|
4716
5118
|
console.log(QUERY_AGENT_HELP);
|
|
4717
5119
|
return;
|
|
4718
5120
|
}
|
|
4719
|
-
const
|
|
5121
|
+
const commandConfig = configureExternalCommands({
|
|
5122
|
+
cwd: cwd || process.cwd(),
|
|
5123
|
+
cliOverrides: commandOverrides,
|
|
5124
|
+
allowedNames: ["git"]
|
|
5125
|
+
});
|
|
5126
|
+
if (commandConfig.ok === false) {
|
|
5127
|
+
console.error(commandConfig.error);
|
|
5128
|
+
process.exit(1);
|
|
5129
|
+
}
|
|
5130
|
+
const root = server ? cwd || process.cwd() : resolveRepoRoot(cwd);
|
|
4720
5131
|
const serverUrl = await ensureServerUrl(root, server, "/");
|
|
4721
5132
|
if (command.kind === "sources")
|
|
4722
5133
|
return runSources(serverUrl, command);
|
|
@@ -5561,6 +5972,8 @@ Usage:
|
|
|
5561
5972
|
Global options:
|
|
5562
5973
|
--cwd <dir> repository directory (default: current directory)
|
|
5563
5974
|
--server <url> code-viewer server URL (default: auto-discovered)
|
|
5975
|
+
--bin git=<p> override the CLI-side git path used for server discovery.
|
|
5976
|
+
Server-side rg/docker paths are set when starting code-viewer.
|
|
5564
5977
|
|
|
5565
5978
|
Examples:
|
|
5566
5979
|
code-viewer query sources --json
|
|
@@ -5966,6 +6379,7 @@ object bytes (text-shaped objects are previewable via \`s3 text\`).
|
|
|
5966
6379
|
var init_query_cli = __esm(() => {
|
|
5967
6380
|
init_routes();
|
|
5968
6381
|
init_cli_helpers();
|
|
6382
|
+
init_command_resolver();
|
|
5969
6383
|
init_cli_helpers();
|
|
5970
6384
|
VALUE_FLAGS2 = new Set([
|
|
5971
6385
|
"--db",
|
|
@@ -6458,6 +6872,7 @@ function parseSearchArgs(argv) {
|
|
|
6458
6872
|
const options = new Map;
|
|
6459
6873
|
const paths = [];
|
|
6460
6874
|
const flags = new Set;
|
|
6875
|
+
const commandOverrides = [];
|
|
6461
6876
|
for (let i = 0;i < argv.length; i++) {
|
|
6462
6877
|
const arg = argv[i];
|
|
6463
6878
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -6472,6 +6887,17 @@ function parseSearchArgs(argv) {
|
|
|
6472
6887
|
else
|
|
6473
6888
|
server = taken.value;
|
|
6474
6889
|
i = taken.next;
|
|
6890
|
+
} else if (arg === "--bin") {
|
|
6891
|
+
const taken = takeValue(argv, i, arg);
|
|
6892
|
+
if ("error" in taken)
|
|
6893
|
+
return { ok: false, error: taken.error };
|
|
6894
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
6895
|
+
"git"
|
|
6896
|
+
]);
|
|
6897
|
+
if (parsed.ok === false)
|
|
6898
|
+
return { ok: false, error: parsed.error };
|
|
6899
|
+
commandOverrides.push(parsed.override);
|
|
6900
|
+
i = taken.next;
|
|
6475
6901
|
} else if (REPEATABLE_VALUE_FLAGS.has(arg)) {
|
|
6476
6902
|
const taken = takeValue(argv, i, arg);
|
|
6477
6903
|
if ("error" in taken)
|
|
@@ -6559,7 +6985,8 @@ function parseSearchArgs(argv) {
|
|
|
6559
6985
|
json: flags.has("--json")
|
|
6560
6986
|
},
|
|
6561
6987
|
cwd,
|
|
6562
|
-
server
|
|
6988
|
+
server,
|
|
6989
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
6563
6990
|
}
|
|
6564
6991
|
};
|
|
6565
6992
|
}
|
|
@@ -6594,7 +7021,8 @@ function parseSearchArgs(argv) {
|
|
|
6594
7021
|
json: flags.has("--json")
|
|
6595
7022
|
},
|
|
6596
7023
|
cwd,
|
|
6597
|
-
server
|
|
7024
|
+
server,
|
|
7025
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
6598
7026
|
}
|
|
6599
7027
|
};
|
|
6600
7028
|
}
|
|
@@ -6686,7 +7114,7 @@ async function runSearchCli(argv) {
|
|
|
6686
7114
|
console.error('Run "code-viewer search --help" for usage.');
|
|
6687
7115
|
process.exit(1);
|
|
6688
7116
|
}
|
|
6689
|
-
const { command, cwd, server } = parsed.args;
|
|
7117
|
+
const { command, cwd, server, commandOverrides = [] } = parsed.args;
|
|
6690
7118
|
if (command.kind === "help") {
|
|
6691
7119
|
console.log(SEARCH_HELP);
|
|
6692
7120
|
return;
|
|
@@ -6695,7 +7123,16 @@ async function runSearchCli(argv) {
|
|
|
6695
7123
|
console.log(SEARCH_AGENT_HELP);
|
|
6696
7124
|
return;
|
|
6697
7125
|
}
|
|
6698
|
-
const
|
|
7126
|
+
const commandConfig = configureExternalCommands({
|
|
7127
|
+
cwd: cwd || process.cwd(),
|
|
7128
|
+
cliOverrides: commandOverrides,
|
|
7129
|
+
allowedNames: ["git"]
|
|
7130
|
+
});
|
|
7131
|
+
if (commandConfig.ok === false) {
|
|
7132
|
+
console.error(commandConfig.error);
|
|
7133
|
+
process.exit(1);
|
|
7134
|
+
}
|
|
7135
|
+
const root = server ? cwd || process.cwd() : resolveRepoRoot(cwd);
|
|
6699
7136
|
const serverUrl = await ensureServerUrl(root, server, "/");
|
|
6700
7137
|
if (command.kind === "code")
|
|
6701
7138
|
return runCode(serverUrl, command);
|
|
@@ -6705,15 +7142,16 @@ async function runSearchCli(argv) {
|
|
|
6705
7142
|
var FILE_NAME_SEARCH_DEFAULT_MAX = 50, SEARCH_HELP, SEARCH_AGENT_HELP, VALUE_FLAGS3, REPEATABLE_VALUE_FLAGS, BOOL_FLAGS3;
|
|
6706
7143
|
var init_search_cli = __esm(() => {
|
|
6707
7144
|
init_cli_helpers();
|
|
7145
|
+
init_command_resolver();
|
|
6708
7146
|
init_search();
|
|
6709
7147
|
SEARCH_HELP = `code-viewer search — text and filename search across the worktree or a git ref
|
|
6710
7148
|
|
|
6711
7149
|
Usage:
|
|
6712
7150
|
code-viewer search code --term <text> [--ref <ref>] [--path <path>...]
|
|
6713
7151
|
[--regex] [--max <n>] [--json]
|
|
6714
|
-
[--cwd <dir>] [--server <url>]
|
|
7152
|
+
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
6715
7153
|
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json]
|
|
6716
|
-
[--cwd <dir>] [--server <url>]
|
|
7154
|
+
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
6717
7155
|
code-viewer search --help
|
|
6718
7156
|
code-viewer search agent-help
|
|
6719
7157
|
|
|
@@ -6726,6 +7164,8 @@ Common options:
|
|
|
6726
7164
|
--json Emit a structured JSON payload instead of plain lines.
|
|
6727
7165
|
--cwd <dir> Repository to target (default: process.cwd()).
|
|
6728
7166
|
--server <url> code-viewer server URL (default: auto-discover).
|
|
7167
|
+
--bin git=<p> Override the CLI-side git path used for server discovery.
|
|
7168
|
+
Server-side rg/docker paths are set when starting code-viewer.
|
|
6729
7169
|
--help, -h Show this help.
|
|
6730
7170
|
|
|
6731
7171
|
search code only:
|
|
@@ -6852,24 +7292,24 @@ Parse failures and unreachable servers exit 1.
|
|
|
6852
7292
|
|
|
6853
7293
|
// web-src/server/root.ts
|
|
6854
7294
|
import { existsSync as existsSync4 } from "node:fs";
|
|
6855
|
-
import { dirname as
|
|
7295
|
+
import { dirname as dirname3, join as join6, normalize } from "node:path";
|
|
6856
7296
|
import { fileURLToPath } from "node:url";
|
|
6857
7297
|
function findRoot(start) {
|
|
6858
7298
|
let current = start;
|
|
6859
7299
|
for (let i = 0;i < 5; i++) {
|
|
6860
|
-
if (existsSync4(
|
|
7300
|
+
if (existsSync4(join6(current, "package.json")) && existsSync4(join6(current, "web"))) {
|
|
6861
7301
|
return normalize(current);
|
|
6862
7302
|
}
|
|
6863
|
-
const parent =
|
|
7303
|
+
const parent = dirname3(current);
|
|
6864
7304
|
if (parent === current)
|
|
6865
7305
|
break;
|
|
6866
7306
|
current = parent;
|
|
6867
7307
|
}
|
|
6868
|
-
return normalize(
|
|
7308
|
+
return normalize(join6(start, "..", ".."));
|
|
6869
7309
|
}
|
|
6870
7310
|
var ROOT;
|
|
6871
7311
|
var init_root = __esm(() => {
|
|
6872
|
-
ROOT = findRoot(
|
|
7312
|
+
ROOT = findRoot(dirname3(fileURLToPath(import.meta.url)));
|
|
6873
7313
|
});
|
|
6874
7314
|
|
|
6875
7315
|
// web-src/server/skill-cli.ts
|
|
@@ -6884,7 +7324,7 @@ __export(exports_skill_cli, {
|
|
|
6884
7324
|
});
|
|
6885
7325
|
import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
6886
7326
|
import { homedir as homedir2 } from "node:os";
|
|
6887
|
-
import { join as
|
|
7327
|
+
import { join as join7, resolve } from "node:path";
|
|
6888
7328
|
function parseAgentList(value) {
|
|
6889
7329
|
if (value === "all")
|
|
6890
7330
|
return [...AGENT_NAMES];
|
|
@@ -6944,7 +7384,7 @@ function parseSkillArgs(argv) {
|
|
|
6944
7384
|
function discoverBundledSkills(skillsRoot) {
|
|
6945
7385
|
if (!existsSync5(skillsRoot))
|
|
6946
7386
|
return [];
|
|
6947
|
-
return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(
|
|
7387
|
+
return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync5(join7(skillsRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
|
|
6948
7388
|
}
|
|
6949
7389
|
function installSkill(args, deps) {
|
|
6950
7390
|
const skills = discoverBundledSkills(deps.skillsRoot);
|
|
@@ -6958,8 +7398,8 @@ function installSkill(args, deps) {
|
|
|
6958
7398
|
const results = [];
|
|
6959
7399
|
for (const agent of args.agents) {
|
|
6960
7400
|
for (const skill of skills) {
|
|
6961
|
-
const sourceDir =
|
|
6962
|
-
const target =
|
|
7401
|
+
const sourceDir = join7(deps.skillsRoot, skill);
|
|
7402
|
+
const target = join7(base, AGENT_SKILL_DIRS[agent], "skills", skill);
|
|
6963
7403
|
const action = existsSync5(target) ? "updated" : "installed";
|
|
6964
7404
|
try {
|
|
6965
7405
|
mkdirSync2(target, { recursive: true });
|
|
@@ -6988,7 +7428,7 @@ function runSkillCli(argv) {
|
|
|
6988
7428
|
return;
|
|
6989
7429
|
}
|
|
6990
7430
|
const result = installSkill(parsed.args, {
|
|
6991
|
-
skillsRoot:
|
|
7431
|
+
skillsRoot: join7(ROOT, "skills"),
|
|
6992
7432
|
homeDir: homedir2(),
|
|
6993
7433
|
projectDir: process.cwd()
|
|
6994
7434
|
});
|
|
@@ -7122,6 +7562,7 @@ function parseStatusArgs(argv) {
|
|
|
7122
7562
|
const options = new Map;
|
|
7123
7563
|
const flags = new Set;
|
|
7124
7564
|
const positional = [];
|
|
7565
|
+
const commandOverrides = [];
|
|
7125
7566
|
for (let i = 0;i < argv.length; i++) {
|
|
7126
7567
|
const arg = argv[i];
|
|
7127
7568
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -7133,6 +7574,17 @@ function parseStatusArgs(argv) {
|
|
|
7133
7574
|
return { ok: false, error: taken.error };
|
|
7134
7575
|
cwd = taken.value;
|
|
7135
7576
|
i = taken.next;
|
|
7577
|
+
} else if (arg === "--bin") {
|
|
7578
|
+
const taken = takeValue(argv, i, arg);
|
|
7579
|
+
if ("error" in taken)
|
|
7580
|
+
return { ok: false, error: taken.error };
|
|
7581
|
+
const parsed = parseExternalCommandOverride(taken.value, "--bin", [
|
|
7582
|
+
"git"
|
|
7583
|
+
]);
|
|
7584
|
+
if (parsed.ok === false)
|
|
7585
|
+
return { ok: false, error: parsed.error };
|
|
7586
|
+
commandOverrides.push(parsed.override);
|
|
7587
|
+
i = taken.next;
|
|
7136
7588
|
} else if (VALUE_FLAGS4.has(arg)) {
|
|
7137
7589
|
const taken = takeValue(argv, i, arg);
|
|
7138
7590
|
if ("error" in taken)
|
|
@@ -7183,7 +7635,8 @@ function parseStatusArgs(argv) {
|
|
|
7183
7635
|
limit,
|
|
7184
7636
|
json: flags.has("--json")
|
|
7185
7637
|
},
|
|
7186
|
-
cwd
|
|
7638
|
+
cwd,
|
|
7639
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
7187
7640
|
}
|
|
7188
7641
|
};
|
|
7189
7642
|
}
|
|
@@ -7196,9 +7649,13 @@ function sumTotals(files) {
|
|
|
7196
7649
|
}
|
|
7197
7650
|
return { files: files.length, additions, deletions };
|
|
7198
7651
|
}
|
|
7199
|
-
function buildGroup(files) {
|
|
7652
|
+
function buildGroup(files, error) {
|
|
7200
7653
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
7201
|
-
return {
|
|
7654
|
+
return {
|
|
7655
|
+
files: sorted,
|
|
7656
|
+
totals: sumTotals(sorted),
|
|
7657
|
+
...error ? { error } : {}
|
|
7658
|
+
};
|
|
7202
7659
|
}
|
|
7203
7660
|
function metaPathKey(file) {
|
|
7204
7661
|
return `${file.old_path || ""}\x00${file.path}`;
|
|
@@ -7215,6 +7672,9 @@ function mergeMissingByPath(primary, fallback) {
|
|
|
7215
7672
|
}
|
|
7216
7673
|
return merged;
|
|
7217
7674
|
}
|
|
7675
|
+
function isMissingDiffBaseError(error) {
|
|
7676
|
+
return !!error && /ambiguous argument 'HEAD'|bad revision 'HEAD'/i.test(error);
|
|
7677
|
+
}
|
|
7218
7678
|
function joinCli(parts, serverUrl) {
|
|
7219
7679
|
const head = parts[0];
|
|
7220
7680
|
const tail = parts.slice(1).join(" ");
|
|
@@ -7259,6 +7719,8 @@ function formatGroupText(label, group) {
|
|
|
7259
7719
|
const totals = group.totals;
|
|
7260
7720
|
const summary = totals.files === 0 ? `${label}: 0 files` : `${label}: ${totals.files} files (+${totals.additions} / -${totals.deletions})`;
|
|
7261
7721
|
lines.push(summary);
|
|
7722
|
+
if (group.error)
|
|
7723
|
+
lines.push(` # error: ${group.error}`);
|
|
7262
7724
|
for (const file of group.files)
|
|
7263
7725
|
lines.push(formatChangedLine(file));
|
|
7264
7726
|
return lines;
|
|
@@ -7274,10 +7736,13 @@ function formatRecentText(commits) {
|
|
|
7274
7736
|
}
|
|
7275
7737
|
function buildStatusReport(opts) {
|
|
7276
7738
|
const { root, ref, limit } = opts;
|
|
7277
|
-
const
|
|
7278
|
-
const
|
|
7279
|
-
const
|
|
7280
|
-
const
|
|
7739
|
+
const stagedResult = fileMetaResult(["--cached"], root, false);
|
|
7740
|
+
const changedResult = fileMetaResult(["HEAD"], root, true);
|
|
7741
|
+
const worktreeFallbackResult = isMissingDiffBaseError(changedResult.error) ? fileMetaResult([], root, true) : null;
|
|
7742
|
+
const stagedFiles = stagedResult.files;
|
|
7743
|
+
const changedFiles = changedResult.error ? worktreeFallbackResult ? mergeMissingByPath(worktreeFallbackResult.files, stagedFiles) : [] : mergeMissingByPath(changedResult.files, stagedFiles);
|
|
7744
|
+
const changed = buildGroup(changedFiles, worktreeFallbackResult ? worktreeFallbackResult.error || stagedResult.error : changedResult.error || stagedResult.error);
|
|
7745
|
+
const staged = buildGroup(stagedFiles, stagedResult.error);
|
|
7281
7746
|
const history = commitHistory(root, { ref, skip: 0, limit });
|
|
7282
7747
|
const branch = currentBranch(root);
|
|
7283
7748
|
const remote = remoteWebUrl(root);
|
|
@@ -7325,7 +7790,7 @@ function runStatusCli(argv) {
|
|
|
7325
7790
|
console.error('Run "code-viewer status --help" for usage.');
|
|
7326
7791
|
process.exit(1);
|
|
7327
7792
|
}
|
|
7328
|
-
const { command, cwd } = parsed.args;
|
|
7793
|
+
const { command, cwd, commandOverrides = [] } = parsed.args;
|
|
7329
7794
|
if (command.kind === "help") {
|
|
7330
7795
|
console.log(STATUS_HELP);
|
|
7331
7796
|
return;
|
|
@@ -7334,6 +7799,15 @@ function runStatusCli(argv) {
|
|
|
7334
7799
|
console.log(STATUS_AGENT_HELP);
|
|
7335
7800
|
return;
|
|
7336
7801
|
}
|
|
7802
|
+
const commandConfig = configureExternalCommands({
|
|
7803
|
+
cwd: cwd || process.cwd(),
|
|
7804
|
+
cliOverrides: commandOverrides,
|
|
7805
|
+
allowedNames: ["git"]
|
|
7806
|
+
});
|
|
7807
|
+
if (commandConfig.ok === false) {
|
|
7808
|
+
console.error(commandConfig.error);
|
|
7809
|
+
process.exit(1);
|
|
7810
|
+
}
|
|
7337
7811
|
const root = resolveRepoRoot(cwd);
|
|
7338
7812
|
const report = buildStatusReport({
|
|
7339
7813
|
root,
|
|
@@ -7342,9 +7816,13 @@ function runStatusCli(argv) {
|
|
|
7342
7816
|
});
|
|
7343
7817
|
if (command.json) {
|
|
7344
7818
|
console.log(JSON.stringify(report, null, 2));
|
|
7819
|
+
if (report.changed.error || report.staged.error)
|
|
7820
|
+
process.exit(1);
|
|
7345
7821
|
return;
|
|
7346
7822
|
}
|
|
7347
7823
|
console.log(formatStatusReportText(report));
|
|
7824
|
+
if (report.changed.error || report.staged.error)
|
|
7825
|
+
process.exit(1);
|
|
7348
7826
|
}
|
|
7349
7827
|
var STATUS_DEFAULT_LIMIT = 10, STATUS_HARD_CAP_LIMIT = 100, STATUS_DEFAULT_REF = "HEAD", STATUS_HELP, STATUS_AGENT_HELP = `code-viewer status — agent guide
|
|
7350
7828
|
|
|
@@ -7376,11 +7854,11 @@ browser home page uses — no server, no SQLite, no docker required.
|
|
|
7376
7854
|
{ repoRoot, branch, remoteWebUrl, changed, staged, recentCommits,
|
|
7377
7855
|
nextCommands }
|
|
7378
7856
|
changed / staged share shape { files: GitFileMeta[], totals: { files,
|
|
7379
|
-
additions, deletions } } so AI can iterate both the same way.
|
|
7857
|
+
additions, deletions }, error? } so AI can iterate both the same way.
|
|
7380
7858
|
recentCommitsError is present when --ref cannot resolve; the command
|
|
7381
7859
|
still emits changed / staged / nextCommands and exits 0.
|
|
7382
|
-
- exit 0 on success. exit 1
|
|
7383
|
-
|
|
7860
|
+
- exit 0 on success. exit 1 on argument errors, when --cwd does not
|
|
7861
|
+
resolve to a directory, or when changed / staged git collection fails.
|
|
7384
7862
|
|
|
7385
7863
|
## Suggested usage
|
|
7386
7864
|
|
|
@@ -7390,16 +7868,18 @@ browser home page uses — no server, no SQLite, no docker required.
|
|
|
7390
7868
|
`, VALUE_FLAGS4, BOOL_FLAGS4;
|
|
7391
7869
|
var init_status_cli = __esm(() => {
|
|
7392
7870
|
init_cli_helpers();
|
|
7871
|
+
init_command_resolver();
|
|
7393
7872
|
init_git();
|
|
7394
7873
|
init_server_registry();
|
|
7395
7874
|
STATUS_HELP = `code-viewer status — snapshot the current repo for AI agents
|
|
7396
7875
|
|
|
7397
7876
|
Usage:
|
|
7398
|
-
code-viewer status [--cwd <repo>] [--ref <ref>] [--limit <N>] [--json]
|
|
7877
|
+
code-viewer status [--cwd <repo>] [--bin git=<path>] [--ref <ref>] [--limit <N>] [--json]
|
|
7399
7878
|
code-viewer status agent-help
|
|
7400
7879
|
|
|
7401
7880
|
Options:
|
|
7402
7881
|
--cwd <repo> Repository to inspect (default: current directory).
|
|
7882
|
+
--bin git=<p> Override git executable path.
|
|
7403
7883
|
--ref <ref> Ref to read recent commits from (default: ${STATUS_DEFAULT_REF}).
|
|
7404
7884
|
--limit <N> Recent commits to include (default: ${STATUS_DEFAULT_LIMIT}, max ${STATUS_HARD_CAP_LIMIT}).
|
|
7405
7885
|
--json Emit a structured JSON payload instead of plain text.
|
|
@@ -7928,7 +8408,22 @@ var init_spawn_runner = () => {};
|
|
|
7928
8408
|
// web-src/server/database/adapters/docker-utils.ts
|
|
7929
8409
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7930
8410
|
function isDockerComposeServiceUnavailableError(err) {
|
|
7931
|
-
return err instanceof DockerComposeServiceUnavailableError;
|
|
8411
|
+
return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError;
|
|
8412
|
+
}
|
|
8413
|
+
function dockerCommand() {
|
|
8414
|
+
return commandForExternal("docker");
|
|
8415
|
+
}
|
|
8416
|
+
function isDockerCommandUnavailableResult(result) {
|
|
8417
|
+
return isCommandNotFoundResult("docker", result);
|
|
8418
|
+
}
|
|
8419
|
+
function throwIfDockerCommandUnavailableResult(result) {
|
|
8420
|
+
if (isDockerCommandUnavailableResult(result)) {
|
|
8421
|
+
throw new DockerCommandUnavailableError;
|
|
8422
|
+
}
|
|
8423
|
+
}
|
|
8424
|
+
function throwIfCachedComposeDockerCommandUnavailable(cwd) {
|
|
8425
|
+
const failure = composePsCache.get(cwd)?.error || "";
|
|
8426
|
+
throwIfDockerCommandUnavailableResult({ code: 1, stderr: failure });
|
|
7932
8427
|
}
|
|
7933
8428
|
function parseComposePsOutput(stdout) {
|
|
7934
8429
|
const output = stdout.trim();
|
|
@@ -7942,18 +8437,21 @@ function parseComposePsOutput(stdout) {
|
|
|
7942
8437
|
}
|
|
7943
8438
|
return byService;
|
|
7944
8439
|
}
|
|
7945
|
-
function cacheComposePsFailure(cwd,
|
|
7946
|
-
const
|
|
8440
|
+
function cacheComposePsFailure(cwd, result, now) {
|
|
8441
|
+
const stderr = result.stderr || "";
|
|
8442
|
+
const failureTtl = isDockerCommandUnavailableResult(result) || /docker daemon|cannot connect|is the docker daemon running/i.test(stderr) ? COMPOSE_PS_FAILURE_TTL_MS : COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS;
|
|
7947
8443
|
composePsCache.set(cwd, {
|
|
7948
8444
|
containers: null,
|
|
7949
8445
|
positiveExpiresAt: now,
|
|
7950
|
-
negativeExpiresAt: now + failureTtl
|
|
8446
|
+
negativeExpiresAt: now + failureTtl,
|
|
8447
|
+
error: stderr
|
|
7951
8448
|
});
|
|
7952
8449
|
}
|
|
7953
8450
|
function runComposePsAsync(cwd, signal) {
|
|
7954
8451
|
throwIfAborted(signal, "docker compose ps aborted");
|
|
7955
8452
|
if (spawnSyncImpl !== spawnSync2) {
|
|
7956
|
-
const
|
|
8453
|
+
const command = dockerCommand();
|
|
8454
|
+
const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
|
|
7957
8455
|
encoding: "utf8",
|
|
7958
8456
|
timeout: 5000,
|
|
7959
8457
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -7961,19 +8459,21 @@ function runComposePsAsync(cwd, signal) {
|
|
|
7961
8459
|
});
|
|
7962
8460
|
return Promise.resolve({
|
|
7963
8461
|
stdout: String(proc.stdout || ""),
|
|
7964
|
-
stderr: String(proc.stderr || "")
|
|
8462
|
+
stderr: `${String(proc.stderr || "")}${proc.error ? `
|
|
8463
|
+
${proc.error.message}` : ""}`,
|
|
7965
8464
|
code: proc.status ?? 1
|
|
7966
8465
|
});
|
|
7967
8466
|
}
|
|
7968
8467
|
return spawnTextAsync({
|
|
7969
|
-
command:
|
|
8468
|
+
command: dockerCommand(),
|
|
7970
8469
|
args: ["compose", "ps", "--format", "json", "--status", "running"],
|
|
7971
8470
|
cwd,
|
|
7972
8471
|
timeoutMs: 5000,
|
|
7973
8472
|
signal,
|
|
7974
8473
|
killSignal: "SIGKILL",
|
|
7975
8474
|
abortMessage: "docker compose ps aborted",
|
|
7976
|
-
timeoutMessage: "docker compose ps timed out"
|
|
8475
|
+
timeoutMessage: "docker compose ps timed out",
|
|
8476
|
+
rejectOnError: false
|
|
7977
8477
|
});
|
|
7978
8478
|
}
|
|
7979
8479
|
async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
|
|
@@ -8002,11 +8502,14 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
8002
8502
|
} catch (err) {
|
|
8003
8503
|
if (isAbortLikeError(err, controller.signal))
|
|
8004
8504
|
throw err;
|
|
8005
|
-
cacheComposePsFailure(cwd,
|
|
8505
|
+
cacheComposePsFailure(cwd, {
|
|
8506
|
+
code: 1,
|
|
8507
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
8508
|
+
}, startedAt);
|
|
8006
8509
|
return null;
|
|
8007
8510
|
}
|
|
8008
8511
|
if (proc.code !== 0) {
|
|
8009
|
-
cacheComposePsFailure(cwd, proc
|
|
8512
|
+
cacheComposePsFailure(cwd, proc, startedAt);
|
|
8010
8513
|
return null;
|
|
8011
8514
|
}
|
|
8012
8515
|
try {
|
|
@@ -8021,7 +8524,8 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
8021
8524
|
composePsCache.set(cwd, {
|
|
8022
8525
|
containers: null,
|
|
8023
8526
|
positiveExpiresAt: startedAt,
|
|
8024
|
-
negativeExpiresAt: startedAt + COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS
|
|
8527
|
+
negativeExpiresAt: startedAt + COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS,
|
|
8528
|
+
error: "could not parse docker compose ps output"
|
|
8025
8529
|
});
|
|
8026
8530
|
return null;
|
|
8027
8531
|
}
|
|
@@ -8064,12 +8568,14 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
8064
8568
|
async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal) {
|
|
8065
8569
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8066
8570
|
if (!containerName) {
|
|
8571
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8067
8572
|
throw new DockerComposeServiceUnavailableError(serviceName, cwd);
|
|
8068
8573
|
}
|
|
8069
8574
|
return containerName;
|
|
8070
8575
|
}
|
|
8071
|
-
var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError;
|
|
8576
|
+
var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError;
|
|
8072
8577
|
var init_docker_utils = __esm(() => {
|
|
8578
|
+
init_command_resolver();
|
|
8073
8579
|
init_spawn_runner();
|
|
8074
8580
|
composePsCache = new Map;
|
|
8075
8581
|
composePsPending = new Map;
|
|
@@ -8085,6 +8591,13 @@ var init_docker_utils = __esm(() => {
|
|
|
8085
8591
|
this.cwd = cwd;
|
|
8086
8592
|
}
|
|
8087
8593
|
};
|
|
8594
|
+
DockerCommandUnavailableError = class DockerCommandUnavailableError extends Error {
|
|
8595
|
+
status = 503;
|
|
8596
|
+
constructor() {
|
|
8597
|
+
super(`${commandNotFoundDetail("docker")}. Install Docker or pass --bin docker=/absolute/path.`);
|
|
8598
|
+
this.name = "DockerCommandUnavailableError";
|
|
8599
|
+
}
|
|
8600
|
+
};
|
|
8088
8601
|
});
|
|
8089
8602
|
|
|
8090
8603
|
// web-src/server/database/adapters/sql-capture.ts
|
|
@@ -8135,7 +8648,7 @@ function fallbackDockerDatabases(defaultDb) {
|
|
|
8135
8648
|
function buildExecArgs(config, sql) {
|
|
8136
8649
|
if (config.kind === "postgresql") {
|
|
8137
8650
|
return [
|
|
8138
|
-
|
|
8651
|
+
dockerCommand(),
|
|
8139
8652
|
"exec",
|
|
8140
8653
|
"-i",
|
|
8141
8654
|
"-e",
|
|
@@ -8161,7 +8674,7 @@ function buildExecArgs(config, sql) {
|
|
|
8161
8674
|
];
|
|
8162
8675
|
}
|
|
8163
8676
|
return [
|
|
8164
|
-
|
|
8677
|
+
dockerCommand(),
|
|
8165
8678
|
"exec",
|
|
8166
8679
|
"-i",
|
|
8167
8680
|
"-e",
|
|
@@ -8186,7 +8699,8 @@ function execInContainer(config, sql, timeoutMs = 1e4) {
|
|
|
8186
8699
|
});
|
|
8187
8700
|
return {
|
|
8188
8701
|
stdout: proc.stdout || "",
|
|
8189
|
-
stderr: proc.stderr || ""
|
|
8702
|
+
stderr: `${proc.stderr || ""}${proc.error ? `
|
|
8703
|
+
${proc.error.message}` : ""}`,
|
|
8190
8704
|
code: proc.status ?? 1
|
|
8191
8705
|
};
|
|
8192
8706
|
}
|
|
@@ -8207,11 +8721,20 @@ async function readStreamText(stream) {
|
|
|
8207
8721
|
}
|
|
8208
8722
|
async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
|
|
8209
8723
|
throwIfAborted(signal, "query aborted");
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8724
|
+
let proc;
|
|
8725
|
+
try {
|
|
8726
|
+
proc = spawnFn(args, {
|
|
8727
|
+
stdin: "ignore",
|
|
8728
|
+
stdout: "pipe",
|
|
8729
|
+
stderr: "pipe"
|
|
8730
|
+
});
|
|
8731
|
+
} catch (err) {
|
|
8732
|
+
return {
|
|
8733
|
+
code: 1,
|
|
8734
|
+
stdout: "",
|
|
8735
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
8736
|
+
};
|
|
8737
|
+
}
|
|
8215
8738
|
let timedOut = false;
|
|
8216
8739
|
let aborted = false;
|
|
8217
8740
|
const abort = () => {
|
|
@@ -8261,13 +8784,17 @@ function execWithNodeSpawn(args, timeoutMs, signal) {
|
|
|
8261
8784
|
}
|
|
8262
8785
|
async function execInContainerAsync(config, sql, timeoutMs = 1e4, signal) {
|
|
8263
8786
|
recordSql(sql);
|
|
8264
|
-
|
|
8265
|
-
|
|
8787
|
+
let result;
|
|
8788
|
+
if (spawnSyncImpl2 !== spawnSync3) {
|
|
8789
|
+
result = execInContainer(config, sql, timeoutMs);
|
|
8790
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
8791
|
+
return result;
|
|
8792
|
+
}
|
|
8266
8793
|
const args = buildExecArgs(config, sql);
|
|
8267
8794
|
const bunSpawn = globalThis.Bun?.spawn;
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
return
|
|
8795
|
+
result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, timeoutMs, signal) : await execWithNodeSpawn(args, timeoutMs, signal);
|
|
8796
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
8797
|
+
return result;
|
|
8271
8798
|
}
|
|
8272
8799
|
function stripFinalLineBreak(text) {
|
|
8273
8800
|
if (text.endsWith(`\r
|
|
@@ -8798,14 +9325,22 @@ function createDockerAdapter(config) {
|
|
|
8798
9325
|
try {
|
|
8799
9326
|
const result = await execAsync(`SHOW CREATE TABLE ${tableIdentifier(table)}`, signal);
|
|
8800
9327
|
return result.rows.length > 0 ? result.rows[0][1] || "" : "";
|
|
8801
|
-
} catch {
|
|
9328
|
+
} catch (err) {
|
|
9329
|
+
if (isAbortLikeError(err, signal))
|
|
9330
|
+
throw err;
|
|
9331
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9332
|
+
throw err;
|
|
8802
9333
|
return "";
|
|
8803
9334
|
}
|
|
8804
9335
|
}
|
|
8805
9336
|
try {
|
|
8806
9337
|
const result = await execAsync(`SELECT 'CREATE TABLE ' || ${escapeSqlString(tableIdentifier(table))} || ' (...)' AS ddl`, signal);
|
|
8807
9338
|
return result.rows.length > 0 ? result.rows[0][0] || "" : "";
|
|
8808
|
-
} catch {
|
|
9339
|
+
} catch (err) {
|
|
9340
|
+
if (isAbortLikeError(err, signal))
|
|
9341
|
+
throw err;
|
|
9342
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9343
|
+
throw err;
|
|
8809
9344
|
return "";
|
|
8810
9345
|
}
|
|
8811
9346
|
},
|
|
@@ -8822,7 +9357,11 @@ function createDockerAdapter(config) {
|
|
|
8822
9357
|
name: row[0],
|
|
8823
9358
|
sql: row[1] || ""
|
|
8824
9359
|
}));
|
|
8825
|
-
} catch {
|
|
9360
|
+
} catch (err) {
|
|
9361
|
+
if (isAbortLikeError(err, signal))
|
|
9362
|
+
throw err;
|
|
9363
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9364
|
+
throw err;
|
|
8826
9365
|
return [];
|
|
8827
9366
|
}
|
|
8828
9367
|
},
|
|
@@ -8873,6 +9412,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
8873
9412
|
return [...cached.value];
|
|
8874
9413
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8875
9414
|
if (!containerName) {
|
|
9415
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8876
9416
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8877
9417
|
}
|
|
8878
9418
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || env.POSTGRES_USERNAME || env.MYSQL_USERNAME || env.USER || "root";
|
|
@@ -8906,6 +9446,8 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
8906
9446
|
} catch (err) {
|
|
8907
9447
|
if (isAbortLikeError(err, signal))
|
|
8908
9448
|
throw err;
|
|
9449
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9450
|
+
throw err;
|
|
8909
9451
|
const fallback = fallbackDockerDatabases(defaultDb);
|
|
8910
9452
|
if (fallback.length > 0)
|
|
8911
9453
|
return fallback;
|
|
@@ -8925,6 +9467,7 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
8925
9467
|
return [...cached.value];
|
|
8926
9468
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8927
9469
|
if (!containerName) {
|
|
9470
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8928
9471
|
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8929
9472
|
}
|
|
8930
9473
|
const config = {
|
|
@@ -8947,6 +9490,8 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
8947
9490
|
} catch (err) {
|
|
8948
9491
|
if (isAbortLikeError(err, signal))
|
|
8949
9492
|
throw err;
|
|
9493
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9494
|
+
throw err;
|
|
8950
9495
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8951
9496
|
}
|
|
8952
9497
|
}
|
|
@@ -9039,11 +9584,11 @@ __ES_STATUS__:%{http_code}
|
|
|
9039
9584
|
];
|
|
9040
9585
|
return { args, input: curlConfig };
|
|
9041
9586
|
}
|
|
9042
|
-
function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
|
|
9587
|
+
async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
|
|
9043
9588
|
throwIfAborted(signal, "elasticsearch request aborted");
|
|
9044
9589
|
const invocation = buildEsRequestInvocation(config, method, path, body);
|
|
9045
|
-
|
|
9046
|
-
command:
|
|
9590
|
+
const result = await spawnTextAsync({
|
|
9591
|
+
command: dockerCommand(),
|
|
9047
9592
|
args: invocation.args,
|
|
9048
9593
|
env: process.env,
|
|
9049
9594
|
input: invocation.input,
|
|
@@ -9053,6 +9598,8 @@ function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signa
|
|
|
9053
9598
|
timeoutMessage: `elasticsearch request timed out after ${timeoutMs}ms`,
|
|
9054
9599
|
rejectOnError: false
|
|
9055
9600
|
});
|
|
9601
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
9602
|
+
return result;
|
|
9056
9603
|
}
|
|
9057
9604
|
function parseEsResponse(stdout) {
|
|
9058
9605
|
const marker = "__ES_STATUS__:";
|
|
@@ -9400,11 +9947,11 @@ function buildRedisCliInvocation(config, args) {
|
|
|
9400
9947
|
const spawnEnv = hasPassword ? { ...process.env, REDISCLI_AUTH: config.password } : process.env;
|
|
9401
9948
|
return { args: dockerArgs, env: spawnEnv };
|
|
9402
9949
|
}
|
|
9403
|
-
function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
9950
|
+
async function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
9404
9951
|
throwIfAborted(signal, "redis-cli aborted");
|
|
9405
9952
|
const invocation = buildRedisCliInvocation(config, args);
|
|
9406
|
-
|
|
9407
|
-
command:
|
|
9953
|
+
const result = await spawnTextAsync({
|
|
9954
|
+
command: dockerCommand(),
|
|
9408
9955
|
args: invocation.args,
|
|
9409
9956
|
env: invocation.env,
|
|
9410
9957
|
timeoutMs,
|
|
@@ -9413,6 +9960,8 @@ function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
|
9413
9960
|
timeoutMessage: `redis-cli timed out after ${timeoutMs}ms`,
|
|
9414
9961
|
rejectOnError: false
|
|
9415
9962
|
});
|
|
9963
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
9964
|
+
return result;
|
|
9416
9965
|
}
|
|
9417
9966
|
function parseInfoKeyspace(stdout) {
|
|
9418
9967
|
const counts = new Map;
|
|
@@ -10655,14 +11204,19 @@ function timeoutReadableStream(body, signal) {
|
|
|
10655
11204
|
async function dockerCurlFetch(opts) {
|
|
10656
11205
|
const { args, input } = dockerCurlCommand(opts);
|
|
10657
11206
|
if (spawnSyncImplIsTestOverride) {
|
|
10658
|
-
const proc2 = spawnSyncImpl3(
|
|
11207
|
+
const proc2 = spawnSyncImpl3(dockerCommand(), args, {
|
|
10659
11208
|
encoding: "buffer",
|
|
10660
11209
|
input,
|
|
10661
11210
|
timeout: s3DockerCurlTimeoutMs,
|
|
10662
11211
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10663
11212
|
});
|
|
10664
11213
|
if ((proc2.status ?? 1) !== 0) {
|
|
10665
|
-
const stderr = new TextDecoder().decode(proc2.stderr || new Uint8Array).
|
|
11214
|
+
const stderr = new TextDecoder().decode(proc2.stderr || new Uint8Array).concat(proc2.error ? `
|
|
11215
|
+
${proc2.error.message}` : "").replace(/\s+/g, " ").trim();
|
|
11216
|
+
throwIfDockerCommandUnavailableResult({
|
|
11217
|
+
code: proc2.status ?? 1,
|
|
11218
|
+
stderr
|
|
11219
|
+
});
|
|
10666
11220
|
throw new S3HttpError(503, `S3 HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
10667
11221
|
}
|
|
10668
11222
|
return responseFromCurlOutput(new Uint8Array(proc2.stdout || new Uint8Array));
|
|
@@ -10671,16 +11225,21 @@ async function dockerCurlFetch(opts) {
|
|
|
10671
11225
|
throw new S3HttpError(503, "S3 HTTP transport aborted");
|
|
10672
11226
|
}
|
|
10673
11227
|
const proc = await spawnCollectAsync({
|
|
10674
|
-
command:
|
|
11228
|
+
command: dockerCommand(),
|
|
10675
11229
|
args,
|
|
10676
11230
|
input,
|
|
10677
11231
|
timeoutMs: s3DockerCurlTimeoutMs,
|
|
10678
11232
|
signal: opts.signal,
|
|
10679
11233
|
abortMessage: "S3 HTTP transport aborted",
|
|
10680
|
-
timeoutMessage: `docker exec curl timed out after ${s3DockerCurlTimeoutMs}ms
|
|
11234
|
+
timeoutMessage: `docker exec curl timed out after ${s3DockerCurlTimeoutMs}ms`,
|
|
11235
|
+
rejectOnError: false
|
|
10681
11236
|
});
|
|
10682
11237
|
if (proc.code !== 0) {
|
|
10683
11238
|
const stderr = new TextDecoder().decode(proc.stderr).replace(/\s+/g, " ").trim();
|
|
11239
|
+
throwIfDockerCommandUnavailableResult({
|
|
11240
|
+
code: proc.code,
|
|
11241
|
+
stderr
|
|
11242
|
+
});
|
|
10684
11243
|
throw new S3HttpError(503, `S3 HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
10685
11244
|
}
|
|
10686
11245
|
return responseFromCurlOutput(new Uint8Array(proc.stdout));
|
|
@@ -11329,14 +11888,14 @@ import {
|
|
|
11329
11888
|
existsSync as existsSync6,
|
|
11330
11889
|
openSync,
|
|
11331
11890
|
readSync,
|
|
11332
|
-
realpathSync as
|
|
11333
|
-
statSync as
|
|
11891
|
+
realpathSync as realpathSync4,
|
|
11892
|
+
statSync as statSync4
|
|
11334
11893
|
} from "node:fs";
|
|
11335
11894
|
import { lstat, open, readdir, readFile as readFile2, stat } from "node:fs/promises";
|
|
11336
|
-
import { basename, join as
|
|
11895
|
+
import { basename, join as join8, relative as relative3 } from "node:path";
|
|
11337
11896
|
function isSqliteFile(fullPath) {
|
|
11338
11897
|
try {
|
|
11339
|
-
const stat2 =
|
|
11898
|
+
const stat2 = statSync4(fullPath);
|
|
11340
11899
|
if (!stat2.isFile() || stat2.size < 16)
|
|
11341
11900
|
return false;
|
|
11342
11901
|
const buf = Buffer.alloc(16);
|
|
@@ -11404,7 +11963,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
11404
11963
|
return;
|
|
11405
11964
|
if (omitSet.has(entry.toLowerCase()))
|
|
11406
11965
|
continue;
|
|
11407
|
-
const full =
|
|
11966
|
+
const full = join8(dir, entry);
|
|
11408
11967
|
let entryStat;
|
|
11409
11968
|
try {
|
|
11410
11969
|
entryStat = await lstat(full);
|
|
@@ -11421,7 +11980,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
11421
11980
|
continue;
|
|
11422
11981
|
if (!await isSqliteFileAsync(full))
|
|
11423
11982
|
continue;
|
|
11424
|
-
const rel =
|
|
11983
|
+
const rel = relative3(cwd, full);
|
|
11425
11984
|
if (rel.startsWith("..") || rel.startsWith("/"))
|
|
11426
11985
|
continue;
|
|
11427
11986
|
results.push({
|
|
@@ -11448,18 +12007,18 @@ function validateDbPath(cwd, dbPath) {
|
|
|
11448
12007
|
const parts = dbPath.split(/[\\/]+/);
|
|
11449
12008
|
if (parts.some((p) => p === ".." || p.toLowerCase() === ".git" || p.toLowerCase() === ".code-viewer"))
|
|
11450
12009
|
return null;
|
|
11451
|
-
const full =
|
|
12010
|
+
const full = join8(cwd, dbPath);
|
|
11452
12011
|
if (!existsSync6(full))
|
|
11453
12012
|
return null;
|
|
11454
12013
|
let realCwd;
|
|
11455
12014
|
let realFull;
|
|
11456
12015
|
try {
|
|
11457
|
-
realCwd =
|
|
11458
|
-
realFull =
|
|
12016
|
+
realCwd = realpathSync4(cwd);
|
|
12017
|
+
realFull = realpathSync4(full);
|
|
11459
12018
|
} catch {
|
|
11460
12019
|
return null;
|
|
11461
12020
|
}
|
|
11462
|
-
const rel =
|
|
12021
|
+
const rel = relative3(realCwd, realFull);
|
|
11463
12022
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/"))
|
|
11464
12023
|
return null;
|
|
11465
12024
|
if (!isSqliteFile(realFull))
|
|
@@ -11585,7 +12144,7 @@ function resolveEnvValue(raw, composeDirEnv = {}) {
|
|
|
11585
12144
|
}
|
|
11586
12145
|
async function readDotenvAsync(composeDir) {
|
|
11587
12146
|
try {
|
|
11588
|
-
const content = await readFile2(
|
|
12147
|
+
const content = await readFile2(join8(composeDir, ".env"), "utf-8");
|
|
11589
12148
|
return parseDotenvContent(content);
|
|
11590
12149
|
} catch {
|
|
11591
12150
|
return {};
|
|
@@ -11707,7 +12266,7 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
|
|
|
11707
12266
|
for (let match = serviceRegex.exec(servicesBlock);match !== null; match = serviceRegex.exec(servicesBlock)) {
|
|
11708
12267
|
servicePositions.push({ name: match[1], start: match.index });
|
|
11709
12268
|
}
|
|
11710
|
-
const relDir =
|
|
12269
|
+
const relDir = relative3(cwd, composeDir);
|
|
11711
12270
|
const isRoot = relDir === "" || relDir === ".";
|
|
11712
12271
|
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
11713
12272
|
const filename = basename(filepath);
|
|
@@ -11807,7 +12366,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
11807
12366
|
if (depth > MAX_SCAN_DEPTH)
|
|
11808
12367
|
return;
|
|
11809
12368
|
for (const filename of COMPOSE_FILENAMES) {
|
|
11810
|
-
const filepath =
|
|
12369
|
+
const filepath = join8(dir, filename);
|
|
11811
12370
|
if (await pathExistsAsync(filepath)) {
|
|
11812
12371
|
await parseComposeFileAsync(filepath, dir, cwd, results);
|
|
11813
12372
|
break;
|
|
@@ -11830,7 +12389,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
11830
12389
|
return;
|
|
11831
12390
|
if (omitSet.has(entry.toLowerCase()))
|
|
11832
12391
|
continue;
|
|
11833
|
-
const full =
|
|
12392
|
+
const full = join8(dir, entry);
|
|
11834
12393
|
let entryStat;
|
|
11835
12394
|
try {
|
|
11836
12395
|
entryStat = await lstat(full);
|
|
@@ -11974,12 +12533,12 @@ import {
|
|
|
11974
12533
|
readdirSync as nodeReaddirSync,
|
|
11975
12534
|
watch as nodeWatch
|
|
11976
12535
|
} from "node:fs";
|
|
11977
|
-
import { join as
|
|
12536
|
+
import { join as join9, relative as relative4 } from "node:path";
|
|
11978
12537
|
function normalizeRelativePath(path) {
|
|
11979
12538
|
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
11980
12539
|
}
|
|
11981
12540
|
function isInsideRoot(root, path) {
|
|
11982
|
-
const rel =
|
|
12541
|
+
const rel = relative4(root, path).replace(/\\/g, "/");
|
|
11983
12542
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
11984
12543
|
}
|
|
11985
12544
|
function startWorktreeUpdateWatch(options) {
|
|
@@ -12017,7 +12576,7 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12017
12576
|
const pendingChangedPaths = new Set;
|
|
12018
12577
|
let watchLimitReported = false;
|
|
12019
12578
|
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
12020
|
-
const directoryRelativePath = (dir) => normalizeRelativePath(
|
|
12579
|
+
const directoryRelativePath = (dir) => normalizeRelativePath(relative4(options.root, dir));
|
|
12021
12580
|
const ignoredDirectory = (dir) => {
|
|
12022
12581
|
const rel = directoryRelativePath(dir);
|
|
12023
12582
|
return Boolean(rel && ignored(rel));
|
|
@@ -12083,7 +12642,7 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12083
12642
|
for (const entry of entries) {
|
|
12084
12643
|
if (!entry.isDirectory())
|
|
12085
12644
|
continue;
|
|
12086
|
-
const child =
|
|
12645
|
+
const child = join9(dir, entry.name);
|
|
12087
12646
|
if (ignoredDirectory(child))
|
|
12088
12647
|
continue;
|
|
12089
12648
|
children.push(child);
|
|
@@ -12167,10 +12726,10 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12167
12726
|
scheduleUpdate();
|
|
12168
12727
|
return;
|
|
12169
12728
|
}
|
|
12170
|
-
const changed = normalizeRelativePath(
|
|
12729
|
+
const changed = normalizeRelativePath(join9(rel, filename.toString()));
|
|
12171
12730
|
if (ignored(changed))
|
|
12172
12731
|
return;
|
|
12173
|
-
const fullChangedPath =
|
|
12732
|
+
const fullChangedPath = join9(options.root, changed);
|
|
12174
12733
|
if (!isInsideRoot(options.root, fullChangedPath))
|
|
12175
12734
|
return;
|
|
12176
12735
|
if (initialScanAsync) {
|
|
@@ -12219,9 +12778,9 @@ var init_worktree_watcher = __esm(() => {
|
|
|
12219
12778
|
});
|
|
12220
12779
|
|
|
12221
12780
|
// web-src/server/state-store.ts
|
|
12222
|
-
import { join as
|
|
12781
|
+
import { join as join10 } from "node:path";
|
|
12223
12782
|
function codeViewerPath(root, fileName) {
|
|
12224
|
-
return
|
|
12783
|
+
return join10(root, CODE_VIEWER_DIR2, fileName);
|
|
12225
12784
|
}
|
|
12226
12785
|
function isRecord(value) {
|
|
12227
12786
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -14142,9 +14701,9 @@ var init_handle_s3 = __esm(() => {
|
|
|
14142
14701
|
});
|
|
14143
14702
|
|
|
14144
14703
|
// web-src/server/database/query-history.ts
|
|
14145
|
-
import { join as
|
|
14704
|
+
import { join as join11 } from "node:path";
|
|
14146
14705
|
function historyFilePath(root) {
|
|
14147
|
-
return
|
|
14706
|
+
return join11(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
|
|
14148
14707
|
}
|
|
14149
14708
|
function emptyState() {
|
|
14150
14709
|
return { version: 1, entries: [] };
|
|
@@ -14294,9 +14853,9 @@ var init_query_history = __esm(() => {
|
|
|
14294
14853
|
// web-src/server/database/snapshot-store.ts
|
|
14295
14854
|
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
14296
14855
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
14297
|
-
import { join as
|
|
14856
|
+
import { join as join12 } from "node:path";
|
|
14298
14857
|
async function getStoreDb(cwd) {
|
|
14299
|
-
const dbPath =
|
|
14858
|
+
const dbPath = join12(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
|
|
14300
14859
|
if (storeDb && storeDbPath === dbPath)
|
|
14301
14860
|
return storeDb;
|
|
14302
14861
|
if (storeDb) {
|
|
@@ -14304,7 +14863,7 @@ async function getStoreDb(cwd) {
|
|
|
14304
14863
|
storeDb.close();
|
|
14305
14864
|
} catch {}
|
|
14306
14865
|
}
|
|
14307
|
-
mkdirSync3(
|
|
14866
|
+
mkdirSync3(join12(cwd, CODE_VIEWER_DIR4), { recursive: true });
|
|
14308
14867
|
const DbClass = await loadSqliteClass();
|
|
14309
14868
|
storeDb = new DbClass(dbPath);
|
|
14310
14869
|
storeDbPath = dbPath;
|
|
@@ -14910,9 +15469,9 @@ var init_snapshot_runner = __esm(() => {
|
|
|
14910
15469
|
});
|
|
14911
15470
|
|
|
14912
15471
|
// web-src/server/database/tabs-store.ts
|
|
14913
|
-
import { join as
|
|
15472
|
+
import { join as join13 } from "node:path";
|
|
14914
15473
|
function tabsFilePath(root) {
|
|
14915
|
-
return
|
|
15474
|
+
return join13(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
|
|
14916
15475
|
}
|
|
14917
15476
|
function emptyState2() {
|
|
14918
15477
|
return { version: 1, tabs: [], activeTabId: null };
|
|
@@ -16576,8 +17135,8 @@ var init_handle = __esm(() => {
|
|
|
16576
17135
|
});
|
|
16577
17136
|
|
|
16578
17137
|
// web-src/server/doctor.ts
|
|
16579
|
-
import { accessSync, constants, readFileSync as readFileSync5, statSync as
|
|
16580
|
-
import { dirname as
|
|
17138
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs";
|
|
17139
|
+
import { dirname as dirname4, join as join14, relative as relative5 } from "node:path";
|
|
16581
17140
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16582
17141
|
function statusWorse(a, b) {
|
|
16583
17142
|
const rank = { ok: 0, warn: 1, error: 2 };
|
|
@@ -16666,12 +17225,12 @@ function findCodeViewerPackageJson() {
|
|
|
16666
17225
|
try {
|
|
16667
17226
|
let cursor;
|
|
16668
17227
|
try {
|
|
16669
|
-
cursor =
|
|
17228
|
+
cursor = dirname4(fileURLToPath2(import.meta.url));
|
|
16670
17229
|
} catch {
|
|
16671
|
-
cursor =
|
|
17230
|
+
cursor = dirname4(process.argv[1] || ".");
|
|
16672
17231
|
}
|
|
16673
17232
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
16674
|
-
const candidate =
|
|
17233
|
+
const candidate = join14(cursor, "package.json");
|
|
16675
17234
|
try {
|
|
16676
17235
|
const raw = readFileSync5(candidate, "utf8");
|
|
16677
17236
|
const pkg = JSON.parse(raw);
|
|
@@ -16679,7 +17238,7 @@ function findCodeViewerPackageJson() {
|
|
|
16679
17238
|
return { version: pkg.version, path: candidate };
|
|
16680
17239
|
}
|
|
16681
17240
|
} catch {}
|
|
16682
|
-
const next =
|
|
17241
|
+
const next = dirname4(cursor);
|
|
16683
17242
|
if (next === cursor)
|
|
16684
17243
|
break;
|
|
16685
17244
|
cursor = next;
|
|
@@ -16767,9 +17326,9 @@ async function checkSqlite(cwd) {
|
|
|
16767
17326
|
return { id: "sqlite", title: "SQLite driver", rows };
|
|
16768
17327
|
}
|
|
16769
17328
|
async function trySnapshotDbOpen(cwd) {
|
|
16770
|
-
const dbPath =
|
|
17329
|
+
const dbPath = join14(cwd, SNAPSHOT_DB_REL);
|
|
16771
17330
|
try {
|
|
16772
|
-
|
|
17331
|
+
statSync5(dbPath);
|
|
16773
17332
|
} catch {
|
|
16774
17333
|
return { kind: "skipped" };
|
|
16775
17334
|
}
|
|
@@ -16788,17 +17347,17 @@ async function trySnapshotDbOpen(cwd) {
|
|
|
16788
17347
|
}
|
|
16789
17348
|
}
|
|
16790
17349
|
function checkSnapshotStore(cwd) {
|
|
16791
|
-
const dbPath =
|
|
16792
|
-
const dir =
|
|
17350
|
+
const dbPath = join14(cwd, SNAPSHOT_DB_REL);
|
|
17351
|
+
const dir = dirname4(dbPath);
|
|
16793
17352
|
let dirStatus = "ok";
|
|
16794
17353
|
let dirDetail = dir;
|
|
16795
17354
|
let dirHint;
|
|
16796
17355
|
try {
|
|
16797
|
-
|
|
17356
|
+
accessSync2(dir, constants2.W_OK);
|
|
16798
17357
|
dirDetail = `${dir} (writable)`;
|
|
16799
17358
|
} catch {
|
|
16800
17359
|
try {
|
|
16801
|
-
|
|
17360
|
+
statSync5(dir);
|
|
16802
17361
|
dirStatus = "error";
|
|
16803
17362
|
dirDetail = `${dir} (not writable)`;
|
|
16804
17363
|
dirHint = "Snapshot creation will fail until the directory is writable. " + "Check filesystem permissions on the .code-viewer directory.";
|
|
@@ -16808,7 +17367,7 @@ function checkSnapshotStore(cwd) {
|
|
|
16808
17367
|
}
|
|
16809
17368
|
let dbDetail = dbPath;
|
|
16810
17369
|
try {
|
|
16811
|
-
const stat2 =
|
|
17370
|
+
const stat2 = statSync5(dbPath);
|
|
16812
17371
|
dbDetail = `${dbPath} (${stat2.size.toLocaleString()} bytes)`;
|
|
16813
17372
|
} catch {
|
|
16814
17373
|
dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
|
|
@@ -16834,7 +17393,7 @@ function checkSnapshotStore(cwd) {
|
|
|
16834
17393
|
};
|
|
16835
17394
|
}
|
|
16836
17395
|
async function checkGit(cwd, signal) {
|
|
16837
|
-
const versionRes = await runCached(versionCache, TTL.version, "git", ["--version"], TIMEOUT.version, signal);
|
|
17396
|
+
const versionRes = await runCached(versionCache, TTL.version, commandForExternal("git"), ["--version"], TIMEOUT.version, signal);
|
|
16838
17397
|
if (!versionRes || versionRes.code !== 0) {
|
|
16839
17398
|
return {
|
|
16840
17399
|
id: "git",
|
|
@@ -16844,7 +17403,7 @@ async function checkGit(cwd, signal) {
|
|
|
16844
17403
|
id: "git.binary",
|
|
16845
17404
|
title: "git binary",
|
|
16846
17405
|
status: "error",
|
|
16847
|
-
detail: "
|
|
17406
|
+
detail: versionRes && isCommandNotFoundResult("git", versionRes) ? commandNotFoundDetail("git") : "git command failed",
|
|
16848
17407
|
hint: "git is required for diff, history, and blame features. Install git and ensure it is on PATH."
|
|
16849
17408
|
}
|
|
16850
17409
|
]
|
|
@@ -16858,11 +17417,11 @@ async function checkGit(cwd, signal) {
|
|
|
16858
17417
|
detail: firstLine2(versionRes.stdout)
|
|
16859
17418
|
}
|
|
16860
17419
|
];
|
|
16861
|
-
const repoCheck = await runCached(gitCache, TTL.gitRepo, "git", ["rev-parse", "--is-inside-work-tree"], TIMEOUT.git, signal, cwd);
|
|
17420
|
+
const repoCheck = await runCached(gitCache, TTL.gitRepo, commandForExternal("git"), ["rev-parse", "--is-inside-work-tree"], TIMEOUT.git, signal, cwd);
|
|
16862
17421
|
if (repoCheck && repoCheck.code === 0 && /true/.test(repoCheck.stdout)) {
|
|
16863
|
-
const topRes = await runCached(gitCache, TTL.gitRepo, "git", ["rev-parse", "--show-toplevel"], TIMEOUT.git, signal, cwd);
|
|
17422
|
+
const topRes = await runCached(gitCache, TTL.gitRepo, commandForExternal("git"), ["rev-parse", "--show-toplevel"], TIMEOUT.git, signal, cwd);
|
|
16864
17423
|
const top = topRes?.stdout.trim() || cwd;
|
|
16865
|
-
const insideCwd =
|
|
17424
|
+
const insideCwd = relative5(top, cwd) || ".";
|
|
16866
17425
|
rows.push({
|
|
16867
17426
|
id: "git.repo",
|
|
16868
17427
|
title: "Working tree",
|
|
@@ -16881,10 +17440,10 @@ async function checkGit(cwd, signal) {
|
|
|
16881
17440
|
return { id: "git", title: "Git", rows };
|
|
16882
17441
|
}
|
|
16883
17442
|
async function detectComposeBinary(signal) {
|
|
16884
|
-
const v2 = await runCached(versionCache, TTL.version, "docker", ["compose", "version", "--short"], TIMEOUT.version, signal);
|
|
17443
|
+
const v2 = await runCached(versionCache, TTL.version, commandForExternal("docker"), ["compose", "version", "--short"], TIMEOUT.version, signal);
|
|
16885
17444
|
if (v2 && v2.code === 0) {
|
|
16886
17445
|
return {
|
|
16887
|
-
cmd: { binary: "docker", subcommand: ["compose"] },
|
|
17446
|
+
cmd: { binary: commandForExternal("docker"), subcommand: ["compose"] },
|
|
16888
17447
|
v2Version: firstLine2(v2.stdout)
|
|
16889
17448
|
};
|
|
16890
17449
|
}
|
|
@@ -16937,13 +17496,13 @@ async function checkDocker(signal, discoveryResult) {
|
|
|
16937
17496
|
const summary = summarizeDockerSources(discoveryResult);
|
|
16938
17497
|
const dockerSourcesPresent = summary.total > 0;
|
|
16939
17498
|
const rows = [];
|
|
16940
|
-
const dockerVersion = await runCached(versionCache, TTL.version, "docker", ["--version"], TIMEOUT.version, signal);
|
|
17499
|
+
const dockerVersion = await runCached(versionCache, TTL.version, commandForExternal("docker"), ["--version"], TIMEOUT.version, signal);
|
|
16941
17500
|
const dockerOk = dockerVersion?.code === 0;
|
|
16942
17501
|
rows.push({
|
|
16943
17502
|
id: "docker.binary",
|
|
16944
17503
|
title: "docker CLI",
|
|
16945
17504
|
status: dockerOk ? "ok" : dockerSourcesPresent ? "error" : "warn",
|
|
16946
|
-
detail: dockerOk ? firstLine2(dockerVersion.stdout) : "
|
|
17505
|
+
detail: dockerOk ? firstLine2(dockerVersion.stdout) : dockerVersion && isCommandNotFoundResult("docker", dockerVersion) ? commandNotFoundDetail("docker") : "docker command failed",
|
|
16947
17506
|
...dockerOk ? {} : {
|
|
16948
17507
|
hint: dockerSourcesPresent ? "Compose files reference Docker services that need the docker CLI. Install Docker Desktop or the docker engine." : "docker is optional unless this project uses Docker compose data sources."
|
|
16949
17508
|
}
|
|
@@ -16974,7 +17533,7 @@ async function checkDocker(signal, discoveryResult) {
|
|
|
16974
17533
|
hint: "docker-compose v1 standalone is legacy. Prefer the Docker Compose v2 plugin (`docker compose ...`)."
|
|
16975
17534
|
});
|
|
16976
17535
|
}
|
|
16977
|
-
const dockerInfo = dockerOk ? await runCached(dockerInfoCache, TTL.dockerInfo, "docker", ["info", "--format", "{{.ServerVersion}}"], TIMEOUT.dockerInfo, signal) : null;
|
|
17536
|
+
const dockerInfo = dockerOk ? await runCached(dockerInfoCache, TTL.dockerInfo, commandForExternal("docker"), ["info", "--format", "{{.ServerVersion}}"], TIMEOUT.dockerInfo, signal) : null;
|
|
16978
17537
|
if (dockerOk) {
|
|
16979
17538
|
if (dockerInfo && dockerInfo.code === 0) {
|
|
16980
17539
|
rows.push({
|
|
@@ -17502,6 +18061,7 @@ async function handleDoctor(ctx) {
|
|
|
17502
18061
|
var SNAPSHOT_DB_REL = ".code-viewer/db-snapshots.sqlite", REQUIRED_NODE_MAJOR = 20, TTL, TIMEOUT, versionCache, gitCache, dockerInfoCache, CACHE_KEY_SEP = "\x01", composeConfigCache, composePsCache2, doctorGeneration = 0, DEFAULT_DATASTORE_PROBE_TIMEOUT_MS = 2000, DEFAULT_DATASTORE_CONNECTIVITY_DEPS;
|
|
17503
18062
|
var init_doctor = __esm(() => {
|
|
17504
18063
|
init_cli_helpers();
|
|
18064
|
+
init_command_resolver();
|
|
17505
18065
|
init_docker();
|
|
17506
18066
|
init_elasticsearch();
|
|
17507
18067
|
init_redis();
|
|
@@ -17549,6 +18109,7 @@ function parseDoctorCliArgs(argv) {
|
|
|
17549
18109
|
let cwd = process.cwd();
|
|
17550
18110
|
let port = 0;
|
|
17551
18111
|
let json2 = false;
|
|
18112
|
+
const commandOverrides = [];
|
|
17552
18113
|
for (let i = 0;i < argv.length; i++) {
|
|
17553
18114
|
const arg = argv[i];
|
|
17554
18115
|
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
@@ -17568,6 +18129,23 @@ function parseDoctorCliArgs(argv) {
|
|
|
17568
18129
|
cwd = next;
|
|
17569
18130
|
continue;
|
|
17570
18131
|
}
|
|
18132
|
+
if (arg === "--bin") {
|
|
18133
|
+
const next = argv[++i];
|
|
18134
|
+
if (!next) {
|
|
18135
|
+
return {
|
|
18136
|
+
kind: "error",
|
|
18137
|
+
message: "--bin requires <name>=<absolute-path>"
|
|
18138
|
+
};
|
|
18139
|
+
}
|
|
18140
|
+
const parsed = parseExternalCommandOverride(next, "--bin", [
|
|
18141
|
+
"git",
|
|
18142
|
+
"docker"
|
|
18143
|
+
]);
|
|
18144
|
+
if (parsed.ok === false)
|
|
18145
|
+
return { kind: "error", message: parsed.error };
|
|
18146
|
+
commandOverrides.push(parsed.override);
|
|
18147
|
+
continue;
|
|
18148
|
+
}
|
|
17571
18149
|
if (arg === "--port") {
|
|
17572
18150
|
const next = argv[++i];
|
|
17573
18151
|
if (!next)
|
|
@@ -17584,7 +18162,15 @@ function parseDoctorCliArgs(argv) {
|
|
|
17584
18162
|
}
|
|
17585
18163
|
return { kind: "error", message: `unknown argument: ${arg}` };
|
|
17586
18164
|
}
|
|
17587
|
-
return {
|
|
18165
|
+
return {
|
|
18166
|
+
kind: "run",
|
|
18167
|
+
args: {
|
|
18168
|
+
cwd,
|
|
18169
|
+
port,
|
|
18170
|
+
json: json2,
|
|
18171
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
18172
|
+
}
|
|
18173
|
+
};
|
|
17588
18174
|
}
|
|
17589
18175
|
function formatDoctorReportText(report) {
|
|
17590
18176
|
const lines = [];
|
|
@@ -17635,7 +18221,17 @@ async function runDoctorCli(argv) {
|
|
|
17635
18221
|
`);
|
|
17636
18222
|
return;
|
|
17637
18223
|
}
|
|
17638
|
-
const { cwd, port, json: json2 } = parsed.args;
|
|
18224
|
+
const { cwd, port, json: json2, commandOverrides = [] } = parsed.args;
|
|
18225
|
+
const commandConfig = configureExternalCommands({
|
|
18226
|
+
cwd,
|
|
18227
|
+
cliOverrides: commandOverrides,
|
|
18228
|
+
allowedNames: ["git", "docker"]
|
|
18229
|
+
});
|
|
18230
|
+
if (commandConfig.ok === false) {
|
|
18231
|
+
process.stderr.write(`code-viewer doctor: ${commandConfig.error}
|
|
18232
|
+
`);
|
|
18233
|
+
process.exit(2);
|
|
18234
|
+
}
|
|
17639
18235
|
const report = await buildDoctorReport({
|
|
17640
18236
|
cwd,
|
|
17641
18237
|
scopeOmitDirNames: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
|
|
@@ -17655,12 +18251,13 @@ async function runDoctorCli(argv) {
|
|
|
17655
18251
|
var DOCTOR_HELP = `code-viewer doctor — diagnose the current environment
|
|
17656
18252
|
|
|
17657
18253
|
Usage:
|
|
17658
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json]
|
|
18254
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
17659
18255
|
code-viewer doctor agent-help
|
|
17660
18256
|
|
|
17661
18257
|
Options:
|
|
17662
18258
|
--cwd <path> Working directory to inspect (default: process.cwd()).
|
|
17663
18259
|
--port <N> Listening port to mention in the report (default: 0 = no server).
|
|
18260
|
+
--bin <n>=<p> Override git/docker executable path. Repeatable.
|
|
17664
18261
|
--json Print the full DoctorReport as JSON instead of a summary.
|
|
17665
18262
|
--help, -h Show this help.
|
|
17666
18263
|
|
|
@@ -17672,6 +18269,7 @@ Exit codes:
|
|
|
17672
18269
|
2 invalid arguments
|
|
17673
18270
|
`, STATUS_SYMBOL;
|
|
17674
18271
|
var init_doctor_cli = __esm(() => {
|
|
18272
|
+
init_command_resolver();
|
|
17675
18273
|
init_doctor();
|
|
17676
18274
|
init_git();
|
|
17677
18275
|
STATUS_SYMBOL = {
|
|
@@ -17700,7 +18298,7 @@ function normalizeNewDirectoryName(name) {
|
|
|
17700
18298
|
|
|
17701
18299
|
// web-src/server/cache.ts
|
|
17702
18300
|
import { lstatSync as lstatSync3 } from "node:fs";
|
|
17703
|
-
import { join as
|
|
18301
|
+
import { join as join15 } from "node:path";
|
|
17704
18302
|
function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
|
|
17705
18303
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
17706
18304
|
}
|
|
@@ -17715,7 +18313,7 @@ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MA
|
|
|
17715
18313
|
}
|
|
17716
18314
|
function worktreeFileSignature(path, cwd) {
|
|
17717
18315
|
try {
|
|
17718
|
-
const stats = lstatSync3(
|
|
18316
|
+
const stats = lstatSync3(join15(cwd, path));
|
|
17719
18317
|
const inode = "ino" in stats ? stats.ino : 0;
|
|
17720
18318
|
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
17721
18319
|
} catch {
|
|
@@ -17761,12 +18359,12 @@ function startDevAssetReload(options) {
|
|
|
17761
18359
|
var init_dev_assets = () => {};
|
|
17762
18360
|
|
|
17763
18361
|
// web-src/server/search-service.ts
|
|
17764
|
-
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync6, realpathSync as
|
|
17765
|
-
import { join as
|
|
18362
|
+
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync6, realpathSync as realpathSync5 } from "node:fs";
|
|
18363
|
+
import { join as join16, relative as relative6 } from "node:path";
|
|
17766
18364
|
function rgAvailable(cwd) {
|
|
17767
18365
|
if (rgAvailableCache !== null)
|
|
17768
18366
|
return rgAvailableCache;
|
|
17769
|
-
const proc = runSync(["rg", "--version"], cwd);
|
|
18367
|
+
const proc = runSync([commandForExternal("rg"), "--version"], cwd);
|
|
17770
18368
|
rgAvailableCache = proc.code === 0;
|
|
17771
18369
|
return rgAvailableCache;
|
|
17772
18370
|
}
|
|
@@ -17783,18 +18381,18 @@ function safeWorktreePath(env, path) {
|
|
|
17783
18381
|
return null;
|
|
17784
18382
|
if (isGitInternalPath(path))
|
|
17785
18383
|
return null;
|
|
17786
|
-
const full =
|
|
18384
|
+
const full = join16(env.cwd, path);
|
|
17787
18385
|
if (!existsSync7(full))
|
|
17788
18386
|
return null;
|
|
17789
18387
|
let realCwd;
|
|
17790
18388
|
let realFull;
|
|
17791
18389
|
try {
|
|
17792
|
-
realCwd =
|
|
17793
|
-
realFull =
|
|
18390
|
+
realCwd = realpathSync5(env.cwd);
|
|
18391
|
+
realFull = realpathSync5(full);
|
|
17794
18392
|
} catch {
|
|
17795
18393
|
return null;
|
|
17796
18394
|
}
|
|
17797
|
-
const rel =
|
|
18395
|
+
const rel = relative6(realCwd, realFull);
|
|
17798
18396
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\"))
|
|
17799
18397
|
return null;
|
|
17800
18398
|
if (isGitInternalPath(rel))
|
|
@@ -17844,6 +18442,7 @@ function grepWorktree(env, req) {
|
|
|
17844
18442
|
if (rgAvailable(env.cwd)) {
|
|
17845
18443
|
const safePaths = paths.filter((path) => safeWorktreePath(env, path));
|
|
17846
18444
|
const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
|
|
18445
|
+
args[0] = commandForExternal("rg");
|
|
17847
18446
|
const proc = runSync(args, env.cwd, { timeout: 5000 });
|
|
17848
18447
|
const stdout = proc.stdout;
|
|
17849
18448
|
const matches2 = parseRgOutput(stdout, req.max, env.omitDirNames, env.excludeNames).filter((match) => isSafePath(match.path) && !isGitInternalPath(match.path) && !isSkippableSearchPath(match.path, env.omitDirNames, env.excludeNames) && !!safeWorktreePath(env, match.path));
|
|
@@ -17873,7 +18472,7 @@ function grepWorktree(env, req) {
|
|
|
17873
18472
|
function grepTreeRef(env, req) {
|
|
17874
18473
|
const safePaths = filterCallerPaths(env, req.paths);
|
|
17875
18474
|
const args = [
|
|
17876
|
-
"git",
|
|
18475
|
+
commandForExternal("git"),
|
|
17877
18476
|
"-c",
|
|
17878
18477
|
"core.quotepath=false",
|
|
17879
18478
|
"grep",
|
|
@@ -17899,6 +18498,17 @@ function grepTreeRef(env, req) {
|
|
|
17899
18498
|
};
|
|
17900
18499
|
}
|
|
17901
18500
|
function grepRepo(env, req) {
|
|
18501
|
+
const isWorktree = req.ref === "worktree" || req.ref === "";
|
|
18502
|
+
if (!isWorktree) {
|
|
18503
|
+
const refCheck = verifyTreeRefResult(req.ref, env.cwd);
|
|
18504
|
+
if (refCheck.ok !== true) {
|
|
18505
|
+
return {
|
|
18506
|
+
ok: false,
|
|
18507
|
+
error: refCheck.error,
|
|
18508
|
+
status: refCheck.status
|
|
18509
|
+
};
|
|
18510
|
+
}
|
|
18511
|
+
}
|
|
17902
18512
|
if (!req.query.trim()) {
|
|
17903
18513
|
return {
|
|
17904
18514
|
ok: true,
|
|
@@ -17910,24 +18520,32 @@ function grepRepo(env, req) {
|
|
|
17910
18520
|
}
|
|
17911
18521
|
};
|
|
17912
18522
|
}
|
|
17913
|
-
if (
|
|
18523
|
+
if (isWorktree) {
|
|
17914
18524
|
return { ok: true, value: grepWorktree(env, req) };
|
|
17915
18525
|
}
|
|
17916
|
-
if (!verifyTreeRef(req.ref, env.cwd)) {
|
|
17917
|
-
return { ok: false, error: "invalid target" };
|
|
17918
|
-
}
|
|
17919
18526
|
return { ok: true, value: grepTreeRef(env, req) };
|
|
17920
18527
|
}
|
|
17921
18528
|
function listRepoFiles(env, ref, generation) {
|
|
17922
|
-
if (ref !== "worktree" &&
|
|
17923
|
-
|
|
18529
|
+
if (ref !== "worktree" && ref !== "") {
|
|
18530
|
+
const refCheck = verifyTreeRefResult(ref, env.cwd);
|
|
18531
|
+
if (refCheck.ok !== true) {
|
|
18532
|
+
return {
|
|
18533
|
+
ok: false,
|
|
18534
|
+
error: refCheck.error,
|
|
18535
|
+
status: refCheck.status
|
|
18536
|
+
};
|
|
18537
|
+
}
|
|
17924
18538
|
}
|
|
17925
18539
|
const effectiveRef = ref || "worktree";
|
|
17926
|
-
const
|
|
18540
|
+
const tree = listTreeResult(effectiveRef, "", env.cwd, {
|
|
17927
18541
|
recursive: true,
|
|
17928
18542
|
omitDirNames: env.omitDirNames,
|
|
17929
18543
|
excludeNames: env.excludeNames
|
|
17930
|
-
})
|
|
18544
|
+
});
|
|
18545
|
+
if (tree.error) {
|
|
18546
|
+
return { ok: false, error: tree.error, status: tree.status };
|
|
18547
|
+
}
|
|
18548
|
+
const entries = tree.entries.filter((entry) => !isExcludedScopePath(entry.path, env.excludeNames));
|
|
17931
18549
|
return {
|
|
17932
18550
|
ok: true,
|
|
17933
18551
|
value: buildFileSearchList(effectiveRef, generation, entries)
|
|
@@ -17935,6 +18553,7 @@ function listRepoFiles(env, ref, generation) {
|
|
|
17935
18553
|
}
|
|
17936
18554
|
var rgAvailableCache = null;
|
|
17937
18555
|
var init_search_service = __esm(() => {
|
|
18556
|
+
init_command_resolver();
|
|
17938
18557
|
init_git();
|
|
17939
18558
|
init_runtime();
|
|
17940
18559
|
init_search();
|
|
@@ -17942,7 +18561,7 @@ var init_search_service = __esm(() => {
|
|
|
17942
18561
|
|
|
17943
18562
|
// web-src/server/mcp.ts
|
|
17944
18563
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
17945
|
-
import { join as
|
|
18564
|
+
import { join as join17 } from "node:path";
|
|
17946
18565
|
function defaultMcpTools(options = {}) {
|
|
17947
18566
|
return [
|
|
17948
18567
|
{
|
|
@@ -18481,7 +19100,10 @@ function runStatusTool(input, defaultCwd) {
|
|
|
18481
19100
|
}
|
|
18482
19101
|
try {
|
|
18483
19102
|
const report = buildStatusReport({ root: resolved.root, ref, limit });
|
|
18484
|
-
return {
|
|
19103
|
+
return {
|
|
19104
|
+
text: JSON.stringify(report, null, 2),
|
|
19105
|
+
isError: !!(report.changed.error || report.staged.error)
|
|
19106
|
+
};
|
|
18485
19107
|
} catch (err) {
|
|
18486
19108
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18487
19109
|
return { text: `status failed: ${detail}`, isError: true };
|
|
@@ -19413,7 +20035,7 @@ var init_mcp = __esm(() => {
|
|
|
19413
20035
|
init_search_cli();
|
|
19414
20036
|
init_search_service();
|
|
19415
20037
|
init_status_cli();
|
|
19416
|
-
PACKAGE_VERSION = JSON.parse(readFileSync7(
|
|
20038
|
+
PACKAGE_VERSION = JSON.parse(readFileSync7(join17(ROOT, "package.json"), "utf8")).version;
|
|
19417
20039
|
MCP_SERVER_INFO = {
|
|
19418
20040
|
name: "code-viewer",
|
|
19419
20041
|
title: "code-viewer",
|
|
@@ -19495,21 +20117,21 @@ var init_state_route = __esm(() => {
|
|
|
19495
20117
|
var exports_preview = {};
|
|
19496
20118
|
import {
|
|
19497
20119
|
closeSync as closeSync2,
|
|
19498
|
-
constants as
|
|
20120
|
+
constants as constants3,
|
|
19499
20121
|
existsSync as existsSync8,
|
|
19500
20122
|
lstatSync as lstatSync5,
|
|
19501
20123
|
mkdirSync as mkdirSync4,
|
|
19502
20124
|
openSync as openSync2,
|
|
19503
20125
|
readFileSync as readFileSync8,
|
|
19504
|
-
realpathSync as
|
|
20126
|
+
realpathSync as realpathSync6,
|
|
19505
20127
|
renameSync,
|
|
19506
|
-
statSync as
|
|
20128
|
+
statSync as statSync6,
|
|
19507
20129
|
unlinkSync as unlinkSync2,
|
|
19508
20130
|
watch,
|
|
19509
20131
|
writeFileSync as writeFileSync2
|
|
19510
20132
|
} from "node:fs";
|
|
19511
20133
|
import { homedir as homedir3 } from "node:os";
|
|
19512
|
-
import { basename as basename3, dirname as
|
|
20134
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join18, relative as relative7 } from "node:path";
|
|
19513
20135
|
function parseCli() {
|
|
19514
20136
|
const rest = [];
|
|
19515
20137
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -19518,15 +20140,15 @@ function parseCli() {
|
|
|
19518
20140
|
console.log(`code-viewer ${VERSION}
|
|
19519
20141
|
|
|
19520
20142
|
Usage:
|
|
19521
|
-
code-viewer [--cwd <repo>] [--port <port>] [--open] [git-diff-args...]
|
|
19522
|
-
code-viewer status [--cwd <repo>] [--ref <ref>] [--limit <N>] [--json]
|
|
20143
|
+
code-viewer [--cwd <repo>] [--port <port>] [--open] [--bin <name>=<path>] [git-diff-args...]
|
|
20144
|
+
code-viewer status [--cwd <repo>] [--bin git=<path>] [--ref <ref>] [--limit <N>] [--json]
|
|
19523
20145
|
code-viewer annotate <start|add|add-db|rename|edit|move|list|delete|clear> [options]
|
|
19524
|
-
code-viewer query <sources|schemas|schema|columns|ddl|exec|list|clear|snapshot|diff|search|redis|elasticsearch|s3> [options]
|
|
19525
|
-
code-viewer search code --term <text> [--ref <ref>] [--path <p>...] [--regex] [--max <n>] [--json]
|
|
19526
|
-
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json]
|
|
19527
|
-
code-viewer file <blame|history|show|diff> --path <p> [--ref <ref>] [...subcommand options] [--json]
|
|
20146
|
+
code-viewer query <sources|schemas|schema|columns|ddl|exec|list|clear|snapshot|diff|search|redis|elasticsearch|s3> [options] [--bin git=<path>]
|
|
20147
|
+
code-viewer search code --term <text> [--ref <ref>] [--path <p>...] [--regex] [--max <n>] [--json] [--bin git=<path>]
|
|
20148
|
+
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json] [--bin git=<path>]
|
|
20149
|
+
code-viewer file <blame|history|show|diff> --path <p> [--ref <ref>] [...subcommand options] [--json] [--bin git=<path>]
|
|
19528
20150
|
code-viewer skill install [--agent <list>] [--global]
|
|
19529
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json]
|
|
20151
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
19530
20152
|
code-viewer agent-help
|
|
19531
20153
|
code-viewer help
|
|
19532
20154
|
|
|
@@ -19562,9 +20184,8 @@ Examples:
|
|
|
19562
20184
|
process.exit(1);
|
|
19563
20185
|
}
|
|
19564
20186
|
try {
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
cwd = candidate === nextReal ? candidate : nextReal;
|
|
20187
|
+
cwd = realpathSync6(next);
|
|
20188
|
+
cwdWasExplicit = true;
|
|
19568
20189
|
} catch {
|
|
19569
20190
|
console.error("--cwd must point to an existing directory");
|
|
19570
20191
|
process.exit(1);
|
|
@@ -19579,6 +20200,18 @@ Examples:
|
|
|
19579
20200
|
listenPort = parsed;
|
|
19580
20201
|
} else if (arg === "--open") {
|
|
19581
20202
|
openAfterStart = true;
|
|
20203
|
+
} else if (arg === "--bin") {
|
|
20204
|
+
const next = process.argv[++i];
|
|
20205
|
+
if (!next) {
|
|
20206
|
+
console.error("--bin requires <name>=<absolute-path>");
|
|
20207
|
+
process.exit(1);
|
|
20208
|
+
}
|
|
20209
|
+
const parsed = parseExternalCommandOverride(next);
|
|
20210
|
+
if (parsed.ok === false) {
|
|
20211
|
+
console.error(parsed.error);
|
|
20212
|
+
process.exit(1);
|
|
20213
|
+
}
|
|
20214
|
+
commandOverrides.push(parsed.override);
|
|
19582
20215
|
} else if (arg === "--allow-upload") {} else if (arg === "--scope-omit-dir") {
|
|
19583
20216
|
const next = process.argv[++i];
|
|
19584
20217
|
if (!next) {
|
|
@@ -19595,6 +20228,21 @@ Examples:
|
|
|
19595
20228
|
}
|
|
19596
20229
|
if (rest.length)
|
|
19597
20230
|
cliArgs = rest;
|
|
20231
|
+
const commandConfig = configureExternalCommands({
|
|
20232
|
+
cwd,
|
|
20233
|
+
cliOverrides: commandOverrides
|
|
20234
|
+
});
|
|
20235
|
+
if (commandConfig.ok === false) {
|
|
20236
|
+
console.error(commandConfig.error);
|
|
20237
|
+
process.exit(1);
|
|
20238
|
+
}
|
|
20239
|
+
const candidate = repoRoot(cwd);
|
|
20240
|
+
if (cwdWasExplicit) {
|
|
20241
|
+
if (candidate === cwd)
|
|
20242
|
+
cwd = candidate;
|
|
20243
|
+
} else if (candidate) {
|
|
20244
|
+
cwd = candidate;
|
|
20245
|
+
}
|
|
19598
20246
|
warnIfLegacyConfigPresent();
|
|
19599
20247
|
if (scopeOmitDirCliOverride) {
|
|
19600
20248
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
@@ -19603,7 +20251,7 @@ Examples:
|
|
|
19603
20251
|
}
|
|
19604
20252
|
function warnIfLegacyConfigPresent() {
|
|
19605
20253
|
try {
|
|
19606
|
-
if (existsSync8(
|
|
20254
|
+
if (existsSync8(join18(cwd, ".code-viewer.json"))) {
|
|
19607
20255
|
console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
|
|
19608
20256
|
}
|
|
19609
20257
|
} catch {}
|
|
@@ -19710,7 +20358,7 @@ function staticFile(pathname) {
|
|
|
19710
20358
|
const spec = map[pathname];
|
|
19711
20359
|
if (!spec)
|
|
19712
20360
|
return null;
|
|
19713
|
-
const full =
|
|
20361
|
+
const full = join18(WEB_ROOT, spec[0]);
|
|
19714
20362
|
if (!existsSync8(full))
|
|
19715
20363
|
return text("not found", 404);
|
|
19716
20364
|
return new Response(readFileSync8(full), {
|
|
@@ -19718,17 +20366,17 @@ function staticFile(pathname) {
|
|
|
19718
20366
|
});
|
|
19719
20367
|
}
|
|
19720
20368
|
function buildRangeArgs(range) {
|
|
19721
|
-
const
|
|
20369
|
+
const refs = [];
|
|
19722
20370
|
if (range.from && range.from !== "worktree")
|
|
19723
|
-
|
|
20371
|
+
refs.push(range.from);
|
|
19724
20372
|
if (range.to && range.to !== "worktree")
|
|
19725
|
-
|
|
19726
|
-
return { args:
|
|
20373
|
+
refs.push(range.to);
|
|
20374
|
+
return { args: refs.length ? refs : cliArgs, refs };
|
|
19727
20375
|
}
|
|
19728
|
-
function includeUntracked(range,
|
|
20376
|
+
function includeUntracked(range, refs) {
|
|
19729
20377
|
const toWorktree = !range.to || range.to === "worktree";
|
|
19730
|
-
if (
|
|
19731
|
-
return toWorktree &&
|
|
20378
|
+
if (refs.length > 0)
|
|
20379
|
+
return toWorktree && refs.length < 2;
|
|
19732
20380
|
return cliArgs.length === 0 || cliArgs.length === 1 && cliArgs[0] === "HEAD";
|
|
19733
20381
|
}
|
|
19734
20382
|
function guessMediaKind(path) {
|
|
@@ -19815,11 +20463,13 @@ function computePayload(extras, range, pathFilter = "") {
|
|
|
19815
20463
|
generation
|
|
19816
20464
|
};
|
|
19817
20465
|
}
|
|
19818
|
-
const { args, refs
|
|
20466
|
+
const { args, refs } = buildRangeArgs(range);
|
|
19819
20467
|
const fullArgs = [...extras, ...args];
|
|
19820
|
-
const
|
|
19821
|
-
|
|
20468
|
+
const metaResult = fileMetaResult(fullArgs, cwd, false);
|
|
20469
|
+
const files = metaResult.files;
|
|
20470
|
+
if (!metaResult.error && includeUntracked(range, refs)) {
|
|
19822
20471
|
files.push(...untrackedMeta(cwd));
|
|
20472
|
+
}
|
|
19823
20473
|
const filteredFiles = pathFilter ? files.filter((file) => file.path === pathFilter || file.old_path === pathFilter) : files;
|
|
19824
20474
|
filteredFiles.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
19825
20475
|
filteredFiles.forEach((file, i) => {
|
|
@@ -19839,14 +20489,15 @@ function computePayload(extras, range, pathFilter = "") {
|
|
|
19839
20489
|
return acc;
|
|
19840
20490
|
}, { files: meta.length, additions: 0, deletions: 0 });
|
|
19841
20491
|
const toWorktree = !range.to || range.to === "worktree";
|
|
19842
|
-
const label =
|
|
20492
|
+
const label = refs.length ? `${refs.join(" .. ")}${toWorktree && refs.length === 1 ? " .. worktree" : ""}` : cliArgs.join(" ");
|
|
19843
20493
|
return {
|
|
19844
20494
|
files: meta,
|
|
19845
20495
|
totals,
|
|
19846
20496
|
range: label || "HEAD",
|
|
19847
20497
|
project: basename3(cwd),
|
|
19848
20498
|
branch: currentBranch(cwd) || undefined,
|
|
19849
|
-
generation
|
|
20499
|
+
generation,
|
|
20500
|
+
...metaResult.error ? { error: metaResult.error } : {}
|
|
19850
20501
|
};
|
|
19851
20502
|
}
|
|
19852
20503
|
function handleDiffJson(url) {
|
|
@@ -19962,12 +20613,12 @@ function safeWorktreePath2(path) {
|
|
|
19962
20613
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
19963
20614
|
}
|
|
19964
20615
|
function worktreePath(path) {
|
|
19965
|
-
return
|
|
20616
|
+
return join18(cwd, path);
|
|
19966
20617
|
}
|
|
19967
20618
|
function safeOpenWorktreePath(path) {
|
|
19968
20619
|
if (path === "") {
|
|
19969
20620
|
try {
|
|
19970
|
-
const realCwd =
|
|
20621
|
+
const realCwd = realpathSync6(cwd);
|
|
19971
20622
|
if (isGitInternalPath(realCwd))
|
|
19972
20623
|
return null;
|
|
19973
20624
|
return realCwd;
|
|
@@ -19978,7 +20629,7 @@ function safeOpenWorktreePath(path) {
|
|
|
19978
20629
|
return safeWorktreePath2(path);
|
|
19979
20630
|
}
|
|
19980
20631
|
function parentRepoPath(path) {
|
|
19981
|
-
const parent =
|
|
20632
|
+
const parent = dirname5(path);
|
|
19982
20633
|
return parent === "." ? "" : parent;
|
|
19983
20634
|
}
|
|
19984
20635
|
function isoDate(ms) {
|
|
@@ -19989,7 +20640,7 @@ function worktreeFileMetadata(path, knownSize) {
|
|
|
19989
20640
|
if (!full)
|
|
19990
20641
|
return {};
|
|
19991
20642
|
try {
|
|
19992
|
-
const stat2 =
|
|
20643
|
+
const stat2 = statSync6(full);
|
|
19993
20644
|
return {
|
|
19994
20645
|
size: knownSize ?? stat2.size,
|
|
19995
20646
|
created_at: isoDate(stat2.birthtimeMs),
|
|
@@ -20014,7 +20665,7 @@ function directoryMetadata(target, path) {
|
|
|
20014
20665
|
if (!full)
|
|
20015
20666
|
return {};
|
|
20016
20667
|
try {
|
|
20017
|
-
const stat2 =
|
|
20668
|
+
const stat2 = statSync6(full);
|
|
20018
20669
|
return {
|
|
20019
20670
|
created_at: isoDate(stat2.birthtimeMs),
|
|
20020
20671
|
updated_at: isoDate(stat2.mtimeMs)
|
|
@@ -20065,19 +20716,25 @@ function handleTree(url) {
|
|
|
20065
20716
|
return text("invalid path", 400);
|
|
20066
20717
|
if ((target === "worktree" || target === "") && isGitInternalPath(path))
|
|
20067
20718
|
return text("forbidden", 403);
|
|
20068
|
-
if (target !== "worktree"
|
|
20069
|
-
|
|
20719
|
+
if (target !== "worktree") {
|
|
20720
|
+
const refCheck = verifyTreeRefResult(target, cwd);
|
|
20721
|
+
if (refCheck.ok !== true)
|
|
20722
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20723
|
+
}
|
|
20070
20724
|
const recursive = url.searchParams.get("recursive") === "1";
|
|
20071
20725
|
if (invalidScopeOmitDirNamesQuery(url))
|
|
20072
20726
|
return text("invalid omit dirs", 400);
|
|
20073
20727
|
if (invalidScopeExcludeNamesQuery(url))
|
|
20074
20728
|
return text("invalid exclude names", 400);
|
|
20075
20729
|
const excludeNames = scopeExcludeNamesFromQuery(url);
|
|
20076
|
-
const
|
|
20730
|
+
const tree = listTreeResult(target, path, cwd, {
|
|
20077
20731
|
recursive,
|
|
20078
20732
|
omitDirNames: scopeOmitDirNamesFromQuery(url),
|
|
20079
20733
|
excludeNames
|
|
20080
|
-
})
|
|
20734
|
+
});
|
|
20735
|
+
if (tree.error)
|
|
20736
|
+
return text(tree.error, tree.status ?? 500);
|
|
20737
|
+
const entries = tree.entries.filter((entry) => !isExcludedScopePath(entry.path, excludeNames));
|
|
20081
20738
|
return json2({
|
|
20082
20739
|
ref: target,
|
|
20083
20740
|
path,
|
|
@@ -20141,7 +20798,7 @@ function handleFiles2(url) {
|
|
|
20141
20798
|
return json2(cached.body);
|
|
20142
20799
|
const result = listRepoFiles(currentSearchEnv(omitDirNames, excludeNames), target, generation);
|
|
20143
20800
|
if (result.ok !== true)
|
|
20144
|
-
return text(result.error, 400);
|
|
20801
|
+
return text(result.error, result.status ?? 400);
|
|
20145
20802
|
fileListCache.set(key, { generation, body: result.value });
|
|
20146
20803
|
return json2(result.value);
|
|
20147
20804
|
}
|
|
@@ -20165,7 +20822,7 @@ function handleGrep(url) {
|
|
|
20165
20822
|
max
|
|
20166
20823
|
});
|
|
20167
20824
|
if (result.ok !== true)
|
|
20168
|
-
return text(result.error, 400);
|
|
20825
|
+
return text(result.error, result.status ?? 400);
|
|
20169
20826
|
return json2(result.value);
|
|
20170
20827
|
}
|
|
20171
20828
|
function handleRefCommits(url) {
|
|
@@ -20174,7 +20831,10 @@ function handleRefCommits(url) {
|
|
|
20174
20831
|
const parsedSkip = Number(url.searchParams.get("skip") || "0");
|
|
20175
20832
|
const max = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : undefined;
|
|
20176
20833
|
const skip = Number.isFinite(parsedSkip) && parsedSkip > 0 ? parsedSkip : undefined;
|
|
20177
|
-
|
|
20834
|
+
const result = refCommitPageResult(cwd, { query, max, skip });
|
|
20835
|
+
if (result.error)
|
|
20836
|
+
return text(result.error, result.status ?? 500);
|
|
20837
|
+
return json2({ commits: result.commits, hasMore: result.hasMore });
|
|
20178
20838
|
}
|
|
20179
20839
|
function handleLog(url) {
|
|
20180
20840
|
const ref = url.searchParams.get("ref") || "HEAD";
|
|
@@ -20191,23 +20851,13 @@ function handleLog(url) {
|
|
|
20191
20851
|
...path ? { path } : {}
|
|
20192
20852
|
});
|
|
20193
20853
|
if (result.error)
|
|
20194
|
-
return text(result.error, 400);
|
|
20854
|
+
return text(result.error, result.status ?? 400);
|
|
20195
20855
|
const wantsWorktreeHead = path && skip === 0 && (ref === "worktree" || url.searchParams.get("worktree") === "1");
|
|
20196
20856
|
let commits = result.commits;
|
|
20197
20857
|
let hasWorktree = false;
|
|
20198
20858
|
if (wantsWorktreeHead) {
|
|
20199
|
-
const status =
|
|
20200
|
-
|
|
20201
|
-
"-c",
|
|
20202
|
-
"core.quotepath=false",
|
|
20203
|
-
"status",
|
|
20204
|
-
"--porcelain=v1",
|
|
20205
|
-
"-z",
|
|
20206
|
-
"--untracked-files=normal",
|
|
20207
|
-
"--",
|
|
20208
|
-
path
|
|
20209
|
-
], cwd);
|
|
20210
|
-
if (status.code === 0 && status.stdout.length > 0) {
|
|
20859
|
+
const status = statusPorcelainForPath(path, cwd);
|
|
20860
|
+
if (status.ok && status.stdout.length > 0) {
|
|
20211
20861
|
const parts = status.stdout.split("\x00").filter(Boolean);
|
|
20212
20862
|
if (parts.length > 0) {
|
|
20213
20863
|
hasWorktree = true;
|
|
@@ -20234,7 +20884,7 @@ function handleLog(url) {
|
|
|
20234
20884
|
}
|
|
20235
20885
|
function blamePathKey(p) {
|
|
20236
20886
|
try {
|
|
20237
|
-
const st =
|
|
20887
|
+
const st = statSync6(join18(cwd, p));
|
|
20238
20888
|
return `${st.mtimeMs}:${st.size}`;
|
|
20239
20889
|
} catch {
|
|
20240
20890
|
return "missing";
|
|
@@ -20266,10 +20916,12 @@ function handleFileBlame(url) {
|
|
|
20266
20916
|
if (base === "worktree") {
|
|
20267
20917
|
cacheKey = `worktree|${path}|${blamePathKey(path)}`;
|
|
20268
20918
|
} else {
|
|
20269
|
-
const resolved =
|
|
20270
|
-
if (resolved.
|
|
20271
|
-
|
|
20272
|
-
|
|
20919
|
+
const resolved = verifyCommit(normalized.ref, cwd);
|
|
20920
|
+
if (resolved.ok === false) {
|
|
20921
|
+
const status = resolved.error === commandNotFoundDetail("git") ? 503 : 400;
|
|
20922
|
+
return text(resolved.error || "unknown ref", status);
|
|
20923
|
+
}
|
|
20924
|
+
cacheKey = `HEAD|${path}|${resolved.sha}`;
|
|
20273
20925
|
}
|
|
20274
20926
|
const cached = blameCache.get(cacheKey);
|
|
20275
20927
|
if (cached) {
|
|
@@ -20280,6 +20932,8 @@ function handleFileBlame(url) {
|
|
|
20280
20932
|
return json2({ ...cached, base, ref, generation });
|
|
20281
20933
|
}
|
|
20282
20934
|
const result = blame(cwd, { path, ref: normalized.ref, base });
|
|
20935
|
+
if (result.error && result.status)
|
|
20936
|
+
return text(result.error, result.status);
|
|
20283
20937
|
if (!result.error)
|
|
20284
20938
|
rememberBlame(cacheKey, result);
|
|
20285
20939
|
return json2({ ...result, base, ref, generation });
|
|
@@ -20332,19 +20986,30 @@ function handleFileDiff(url) {
|
|
|
20332
20986
|
const cached = fileCache.get(cacheKey);
|
|
20333
20987
|
let diffText;
|
|
20334
20988
|
let errText = "";
|
|
20989
|
+
let errStatus;
|
|
20335
20990
|
if (cacheFresh(cached)) {
|
|
20336
20991
|
diffText = cached.diffText;
|
|
20337
20992
|
} else {
|
|
20338
20993
|
if (isUntracked) {
|
|
20339
|
-
|
|
20994
|
+
const res = untrackedFileDiff(extras, path, cwd);
|
|
20995
|
+
diffText = res.stdout || "";
|
|
20996
|
+
if (res.code !== 0) {
|
|
20997
|
+
errText = res.stderr;
|
|
20998
|
+
errStatus = res.status;
|
|
20999
|
+
}
|
|
20340
21000
|
} else {
|
|
20341
21001
|
const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
|
|
20342
21002
|
diffText = res.stdout || "";
|
|
20343
|
-
if (res.code !== 0)
|
|
21003
|
+
if (res.code !== 0) {
|
|
20344
21004
|
errText = res.stderr;
|
|
21005
|
+
errStatus = res.status;
|
|
21006
|
+
}
|
|
20345
21007
|
}
|
|
20346
|
-
|
|
21008
|
+
if (!errText)
|
|
21009
|
+
setTimedCacheEntry(fileCache, cacheKey, { diffText });
|
|
20347
21010
|
}
|
|
21011
|
+
if (errStatus)
|
|
21012
|
+
return text(errText || "diff failed", errStatus);
|
|
20348
21013
|
const mode = url.searchParams.get("mode") || "full";
|
|
20349
21014
|
const truncated = mode === "preview" ? truncateToNHunks(diffText, Number(url.searchParams.get("max_hunks")) || PREVIEW_HUNKS_DEFAULT, Number(url.searchParams.get("max_lines")) || PREVIEW_LINES_DEFAULT) : truncateToNHunks(diffText, 1e9);
|
|
20350
21015
|
const body = {
|
|
@@ -20365,7 +21030,7 @@ function handleFileDiff(url) {
|
|
|
20365
21030
|
}
|
|
20366
21031
|
function worktreeLineIndexSignature(full) {
|
|
20367
21032
|
try {
|
|
20368
|
-
const stat2 =
|
|
21033
|
+
const stat2 = statSync6(full);
|
|
20369
21034
|
return `size:${stat2.size}|mtime:${stat2.mtimeMs}|ctime:${stat2.ctimeMs}|ino:${stat2.ino || 0}`;
|
|
20370
21035
|
} catch {
|
|
20371
21036
|
return null;
|
|
@@ -20381,7 +21046,7 @@ async function getWorktreeLineIndex(full) {
|
|
|
20381
21046
|
lineIndexCache.set(full, cached);
|
|
20382
21047
|
return cached.index;
|
|
20383
21048
|
}
|
|
20384
|
-
const stat2 =
|
|
21049
|
+
const stat2 = statSync6(full);
|
|
20385
21050
|
if (stat2.size > LINE_INDEX_MAX_FILE_BYTES)
|
|
20386
21051
|
return null;
|
|
20387
21052
|
const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat2.size);
|
|
@@ -20526,8 +21191,9 @@ async function handleFileRange(url) {
|
|
|
20526
21191
|
};
|
|
20527
21192
|
return json2(body);
|
|
20528
21193
|
} else {
|
|
20529
|
-
|
|
20530
|
-
|
|
21194
|
+
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
21195
|
+
if (refCheck.ok !== true)
|
|
21196
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20531
21197
|
const oid = objectId(ref, path, cwd);
|
|
20532
21198
|
if (oid.code !== 0 || !oid.oid)
|
|
20533
21199
|
return text("not in ref", 404);
|
|
@@ -20557,8 +21223,9 @@ function handleRawFile(req, url) {
|
|
|
20557
21223
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
20558
21224
|
let body;
|
|
20559
21225
|
if (ref !== "worktree" && ref !== "") {
|
|
20560
|
-
|
|
20561
|
-
|
|
21226
|
+
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
21227
|
+
if (refCheck.ok !== true)
|
|
21228
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20562
21229
|
const size = rawFileSize(path, ref);
|
|
20563
21230
|
if (size == null)
|
|
20564
21231
|
return text("not in ref", 404);
|
|
@@ -20626,7 +21293,7 @@ function rawFileSize(path, ref) {
|
|
|
20626
21293
|
if (!full)
|
|
20627
21294
|
return null;
|
|
20628
21295
|
try {
|
|
20629
|
-
return
|
|
21296
|
+
return statSync6(full).size;
|
|
20630
21297
|
} catch {
|
|
20631
21298
|
return null;
|
|
20632
21299
|
}
|
|
@@ -20651,7 +21318,7 @@ function safeUploadFileName(name) {
|
|
|
20651
21318
|
return trimmed;
|
|
20652
21319
|
}
|
|
20653
21320
|
function uploadOpenFlags() {
|
|
20654
|
-
return
|
|
21321
|
+
return constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW || 0);
|
|
20655
21322
|
}
|
|
20656
21323
|
async function handleUploadFiles(req) {
|
|
20657
21324
|
if (!uploadEnabled)
|
|
@@ -20687,7 +21354,7 @@ async function handleUploadFiles(req) {
|
|
|
20687
21354
|
const realDir = safeOpenWorktreePath(dir);
|
|
20688
21355
|
if (!realDir)
|
|
20689
21356
|
return text("not found", 404);
|
|
20690
|
-
const stats =
|
|
21357
|
+
const stats = statSync6(realDir);
|
|
20691
21358
|
if (!stats.isDirectory())
|
|
20692
21359
|
return text("not a directory", 400);
|
|
20693
21360
|
const files = form.getAll("files").filter((item) => item instanceof File);
|
|
@@ -20711,8 +21378,8 @@ async function handleUploadFiles(req) {
|
|
|
20711
21378
|
total += file.size;
|
|
20712
21379
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
20713
21380
|
return text("upload too large", 413);
|
|
20714
|
-
const target =
|
|
20715
|
-
if (
|
|
21381
|
+
const target = join18(realDir, safeName);
|
|
21382
|
+
if (relative7(realDir, dirname5(target)) !== "")
|
|
20716
21383
|
return text("invalid filename", 400);
|
|
20717
21384
|
if (existsSync8(target))
|
|
20718
21385
|
return text("file exists", 409);
|
|
@@ -20833,9 +21500,9 @@ function triggerUpdate(changedPaths) {
|
|
|
20833
21500
|
sendSse("update", data);
|
|
20834
21501
|
}
|
|
20835
21502
|
function moveMacPathIntoTrash(path) {
|
|
20836
|
-
const trashDir =
|
|
21503
|
+
const trashDir = join18(homedir3(), ".Trash");
|
|
20837
21504
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
20838
|
-
const target =
|
|
21505
|
+
const target = join18(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
20839
21506
|
try {
|
|
20840
21507
|
mkdirSync4(trashDir, { recursive: true });
|
|
20841
21508
|
renameSync(path, target);
|
|
@@ -20877,11 +21544,11 @@ function restoreTrashPath(originalPath, trashPath) {
|
|
|
20877
21544
|
if (!existsSync8(trashPath))
|
|
20878
21545
|
return { ok: false, error: "trash item not found" };
|
|
20879
21546
|
try {
|
|
20880
|
-
const trashRoot =
|
|
20881
|
-
const trashRelative =
|
|
21547
|
+
const trashRoot = join18(homedir3(), ".Trash");
|
|
21548
|
+
const trashRelative = relative7(trashRoot, trashPath);
|
|
20882
21549
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
20883
21550
|
return { ok: false, error: "invalid trash handle" };
|
|
20884
|
-
mkdirSync4(
|
|
21551
|
+
mkdirSync4(dirname5(original), { recursive: true });
|
|
20885
21552
|
renameSync(trashPath, original);
|
|
20886
21553
|
return { ok: true };
|
|
20887
21554
|
} catch (error) {
|
|
@@ -20936,7 +21603,7 @@ async function handleOpenPath(req) {
|
|
|
20936
21603
|
const target = safeOpenWorktreePath(targetPath);
|
|
20937
21604
|
if (!target)
|
|
20938
21605
|
return text("not found", 404);
|
|
20939
|
-
const stats =
|
|
21606
|
+
const stats = statSync6(target);
|
|
20940
21607
|
if (!stats.isDirectory())
|
|
20941
21608
|
return text("not a directory", 400);
|
|
20942
21609
|
openOsPath(target);
|
|
@@ -21021,13 +21688,13 @@ async function handleCreateDirectory(req) {
|
|
|
21021
21688
|
const parent = safeOpenWorktreePath(dir);
|
|
21022
21689
|
if (!parent)
|
|
21023
21690
|
return text("not found", 404);
|
|
21024
|
-
const stats =
|
|
21691
|
+
const stats = statSync6(parent);
|
|
21025
21692
|
if (!stats.isDirectory())
|
|
21026
21693
|
return text("not a directory", 400);
|
|
21027
21694
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
21028
21695
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
21029
21696
|
return text("invalid target", 400);
|
|
21030
|
-
const target =
|
|
21697
|
+
const target = join18(parent, name);
|
|
21031
21698
|
if (existsSync8(target))
|
|
21032
21699
|
return text("already exists", 409);
|
|
21033
21700
|
try {
|
|
@@ -21328,11 +21995,12 @@ function restartWorktreeWatch() {
|
|
|
21328
21995
|
}
|
|
21329
21996
|
worktreeWatch = startScopedWorktreeWatch();
|
|
21330
21997
|
}
|
|
21331
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
21998
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
21332
21999
|
var init_preview = __esm(async () => {
|
|
21333
22000
|
init_routes();
|
|
21334
22001
|
init_annotations();
|
|
21335
22002
|
init_cache();
|
|
22003
|
+
init_command_resolver();
|
|
21336
22004
|
init_dev_assets();
|
|
21337
22005
|
init_doctor();
|
|
21338
22006
|
init_git();
|
|
@@ -21345,8 +22013,8 @@ var init_preview = __esm(async () => {
|
|
|
21345
22013
|
init_server_registry();
|
|
21346
22014
|
init_state_store();
|
|
21347
22015
|
init_worktree_watcher();
|
|
21348
|
-
WEB_ROOT =
|
|
21349
|
-
VERSION = JSON.parse(readFileSync8(
|
|
22016
|
+
WEB_ROOT = join18(ROOT, "web");
|
|
22017
|
+
VERSION = JSON.parse(readFileSync8(join18(ROOT, "package.json"), "utf8")).version;
|
|
21350
22018
|
DEFAULT_ARGS = ["HEAD"];
|
|
21351
22019
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
21352
22020
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -21390,8 +22058,9 @@ var init_preview = __esm(async () => {
|
|
|
21390
22058
|
".scss",
|
|
21391
22059
|
".html"
|
|
21392
22060
|
]);
|
|
21393
|
-
cwd =
|
|
22061
|
+
cwd = process.cwd();
|
|
21394
22062
|
cliArgs = DEFAULT_ARGS;
|
|
22063
|
+
commandOverrides = [];
|
|
21395
22064
|
scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
21396
22065
|
scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
21397
22066
|
scopeWatchLimit = DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
@@ -21474,8 +22143,12 @@ var init_preview = __esm(async () => {
|
|
|
21474
22143
|
return handleMcp(req);
|
|
21475
22144
|
if (url.pathname === "/_annotations")
|
|
21476
22145
|
return handleAnnotations(req);
|
|
21477
|
-
if (url.pathname === "/_refs")
|
|
21478
|
-
|
|
22146
|
+
if (url.pathname === "/_refs") {
|
|
22147
|
+
const result = refsResult(cwd);
|
|
22148
|
+
if (result.error)
|
|
22149
|
+
return text(result.error, result.status ?? 500);
|
|
22150
|
+
return json2(result.refs);
|
|
22151
|
+
}
|
|
21479
22152
|
if (url.pathname === "/refresh" && req.method === "POST") {
|
|
21480
22153
|
if (!sideEffectRequestAllowed(req))
|
|
21481
22154
|
return text("forbidden", 403);
|