@youtyan/code-viewer 0.6.1 → 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 +24 -4
- package/dist/code-viewer.js +1036 -312
- package/package.json +1 -1
- package/web/app.js +2053 -266
- package/web/index.html +40 -13
- package/web/style.css +787 -41
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
|
}
|
|
@@ -1438,7 +1768,19 @@ function omittedWorktreeDirectoryReason(name, omitDirNames) {
|
|
|
1438
1768
|
return "internal";
|
|
1439
1769
|
return omitDirNames.has(name) ? "heavy" : undefined;
|
|
1440
1770
|
}
|
|
1441
|
-
function
|
|
1771
|
+
function worktreeSubmodulePaths(cwd) {
|
|
1772
|
+
if (!existsSync(join3(cwd, ".gitmodules")))
|
|
1773
|
+
return new Set;
|
|
1774
|
+
const res = run(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
|
|
1775
|
+
if (res.code !== 0)
|
|
1776
|
+
return new Set;
|
|
1777
|
+
return new Set(res.stdout.split(`
|
|
1778
|
+
`).map((line) => {
|
|
1779
|
+
const split = line.indexOf(" ");
|
|
1780
|
+
return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
|
|
1781
|
+
}).filter(Boolean));
|
|
1782
|
+
}
|
|
1783
|
+
function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
|
|
1442
1784
|
if (excludeNames.has(name.toLowerCase()))
|
|
1443
1785
|
return {
|
|
1444
1786
|
name,
|
|
@@ -1446,25 +1788,26 @@ function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, exc
|
|
|
1446
1788
|
type: isDirectory ? "tree" : "blob"
|
|
1447
1789
|
};
|
|
1448
1790
|
const entryPath = base ? `${base}/${name}` : name;
|
|
1449
|
-
const type = isDirectory ? hasDotGitEntry(
|
|
1791
|
+
const type = isDirectory ? hasDotGitEntry(join3(dir, name)) ? "commit" : "tree" : "blob";
|
|
1450
1792
|
const omittedReason = type === "tree" ? omittedWorktreeDirectoryReason(name, omitDirNames) : undefined;
|
|
1793
|
+
const submodule = type === "commit" && submodulePaths.has(entryPath) ? true : undefined;
|
|
1794
|
+
const baseEntry = submodule ? { name, path: entryPath, type, submodule } : { name, path: entryPath, type };
|
|
1451
1795
|
return omittedReason ? {
|
|
1452
|
-
|
|
1453
|
-
path: entryPath,
|
|
1454
|
-
type,
|
|
1796
|
+
...baseEntry,
|
|
1455
1797
|
children_omitted: true,
|
|
1456
1798
|
children_omitted_reason: omittedReason
|
|
1457
|
-
} :
|
|
1799
|
+
} : baseEntry;
|
|
1458
1800
|
}
|
|
1459
1801
|
function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
1460
1802
|
const base = normalizeTreePath(path);
|
|
1461
|
-
const root =
|
|
1803
|
+
const root = join3(cwd, base);
|
|
1462
1804
|
const omitDirNameSet = new Set(omitDirNames);
|
|
1463
1805
|
const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
|
|
1806
|
+
const submodulePaths = worktreeSubmodulePaths(cwd);
|
|
1464
1807
|
let directEntries;
|
|
1465
1808
|
try {
|
|
1466
1809
|
const dirents = readdirSync(root, { withFileTypes: true });
|
|
1467
|
-
directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet)).filter((entry) => entry.path));
|
|
1810
|
+
directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
|
|
1468
1811
|
} catch {
|
|
1469
1812
|
return [];
|
|
1470
1813
|
}
|
|
@@ -1504,7 +1847,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
1504
1847
|
if (excludeNameSet.has(entry.name.toLowerCase()))
|
|
1505
1848
|
continue;
|
|
1506
1849
|
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1507
|
-
const full =
|
|
1850
|
+
const full = join3(dir, entry.name);
|
|
1508
1851
|
if (entry.isDirectory()) {
|
|
1509
1852
|
const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
|
|
1510
1853
|
if (omittedReason) {
|
|
@@ -1536,7 +1879,7 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
1536
1879
|
}
|
|
1537
1880
|
function hasDotGitEntry(dir) {
|
|
1538
1881
|
try {
|
|
1539
|
-
lstatSync(
|
|
1882
|
+
lstatSync(join3(dir, ".git"));
|
|
1540
1883
|
return true;
|
|
1541
1884
|
} catch (err) {
|
|
1542
1885
|
return !!err && typeof err === "object" && "code" in err && err.code !== "ENOENT";
|
|
@@ -1599,14 +1942,20 @@ function listTree(ref, path, cwd, options = {}) {
|
|
|
1599
1942
|
stderr: ""
|
|
1600
1943
|
};
|
|
1601
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
|
+
}
|
|
1602
1951
|
function untrackedMeta(cwd) {
|
|
1603
1952
|
return untracked(cwd).flatMap((path) => {
|
|
1604
|
-
const full =
|
|
1953
|
+
const full = join3(cwd, path);
|
|
1605
1954
|
let binary = false;
|
|
1606
1955
|
let lines = 0;
|
|
1607
1956
|
let fileExists = false;
|
|
1608
1957
|
try {
|
|
1609
|
-
fileExists = existsSync(full) &&
|
|
1958
|
+
fileExists = existsSync(full) && statSync2(full).isFile();
|
|
1610
1959
|
} catch {
|
|
1611
1960
|
fileExists = false;
|
|
1612
1961
|
}
|
|
@@ -1632,11 +1981,15 @@ function untrackedMeta(cwd) {
|
|
|
1632
1981
|
];
|
|
1633
1982
|
});
|
|
1634
1983
|
}
|
|
1635
|
-
function
|
|
1636
|
-
const ns =
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
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) => {
|
|
1640
1993
|
const stats = byPath.get(file.path);
|
|
1641
1994
|
return {
|
|
1642
1995
|
...file,
|
|
@@ -1645,11 +1998,13 @@ function fileMeta(args, cwd, includeUntracked = false) {
|
|
|
1645
1998
|
binary: stats?.binary || false
|
|
1646
1999
|
};
|
|
1647
2000
|
});
|
|
1648
|
-
return
|
|
2001
|
+
return {
|
|
2002
|
+
files: includeUntracked ? files.concat(untrackedMeta(cwd)) : files
|
|
2003
|
+
};
|
|
1649
2004
|
}
|
|
1650
2005
|
function fileDiffText(args, path, cwd) {
|
|
1651
2006
|
const paths = Array.isArray(path) ? path : [path];
|
|
1652
|
-
|
|
2007
|
+
const res = run([
|
|
1653
2008
|
"git",
|
|
1654
2009
|
"-c",
|
|
1655
2010
|
"core.quotepath=false",
|
|
@@ -1661,9 +2016,13 @@ function fileDiffText(args, path, cwd) {
|
|
|
1661
2016
|
"--",
|
|
1662
2017
|
...paths
|
|
1663
2018
|
], cwd);
|
|
2019
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2020
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2021
|
+
}
|
|
2022
|
+
return res;
|
|
1664
2023
|
}
|
|
1665
2024
|
function untrackedFileDiff(extras, path, cwd) {
|
|
1666
|
-
|
|
2025
|
+
const res = run([
|
|
1667
2026
|
"git",
|
|
1668
2027
|
"-c",
|
|
1669
2028
|
"core.quotepath=false",
|
|
@@ -1675,6 +2034,10 @@ function untrackedFileDiff(extras, path, cwd) {
|
|
|
1675
2034
|
"/dev/null",
|
|
1676
2035
|
path
|
|
1677
2036
|
], cwd);
|
|
2037
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2038
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2039
|
+
}
|
|
2040
|
+
return res;
|
|
1678
2041
|
}
|
|
1679
2042
|
function splitHunks(diffText) {
|
|
1680
2043
|
if (!diffText)
|
|
@@ -1751,6 +2114,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
1751
2114
|
}
|
|
1752
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;
|
|
1753
2116
|
var init_git = __esm(() => {
|
|
2117
|
+
init_command_resolver();
|
|
1754
2118
|
init_runtime();
|
|
1755
2119
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
1756
2120
|
"node_modules",
|
|
@@ -1807,16 +2171,16 @@ import {
|
|
|
1807
2171
|
writeFileSync
|
|
1808
2172
|
} from "node:fs";
|
|
1809
2173
|
import { homedir } from "node:os";
|
|
1810
|
-
import { join as
|
|
2174
|
+
import { join as join4 } from "node:path";
|
|
1811
2175
|
function registryDir() {
|
|
1812
2176
|
const override = process.env.CODE_VIEWER_TEST_SERVER_REGISTRY_DIR;
|
|
1813
2177
|
if (override)
|
|
1814
2178
|
return override;
|
|
1815
|
-
return
|
|
2179
|
+
return join4(homedir(), ".cache", "code-viewer", "servers");
|
|
1816
2180
|
}
|
|
1817
2181
|
function serverRegistryFilePath(root) {
|
|
1818
2182
|
const hash = createHash("sha256").update(root).digest("hex").slice(0, 16);
|
|
1819
|
-
return
|
|
2183
|
+
return join4(registryDir(), `${hash}.json`);
|
|
1820
2184
|
}
|
|
1821
2185
|
function writeServerRegistry(entry) {
|
|
1822
2186
|
try {
|
|
@@ -1857,7 +2221,7 @@ function removeServerRegistry(root, pid) {
|
|
|
1857
2221
|
var init_server_registry = () => {};
|
|
1858
2222
|
|
|
1859
2223
|
// web-src/server/cli-helpers.ts
|
|
1860
|
-
import { realpathSync } from "node:fs";
|
|
2224
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
1861
2225
|
function takeValue(argv, index, flag) {
|
|
1862
2226
|
const value = argv[index + 1];
|
|
1863
2227
|
if (value === undefined)
|
|
@@ -1905,14 +2269,21 @@ function validateRepoRelativePathValue(value, flag) {
|
|
|
1905
2269
|
}
|
|
1906
2270
|
function resolveRepoRootSafe(cwdOption) {
|
|
1907
2271
|
const base = cwdOption || process.cwd();
|
|
2272
|
+
let baseReal;
|
|
1908
2273
|
try {
|
|
1909
|
-
|
|
2274
|
+
baseReal = realpathSync2(base);
|
|
1910
2275
|
} catch {
|
|
1911
2276
|
return {
|
|
1912
2277
|
ok: false,
|
|
1913
2278
|
error: `--cwd must point to an existing directory: ${base}`
|
|
1914
2279
|
};
|
|
1915
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 };
|
|
1916
2287
|
}
|
|
1917
2288
|
function resolveRepoRoot(cwdOption) {
|
|
1918
2289
|
const result = resolveRepoRootSafe(cwdOption);
|
|
@@ -3011,8 +3382,8 @@ __export(exports_file_cli, {
|
|
|
3011
3382
|
FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
|
|
3012
3383
|
FILE_AGENT_HELP: () => FILE_AGENT_HELP
|
|
3013
3384
|
});
|
|
3014
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as
|
|
3015
|
-
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";
|
|
3016
3387
|
function validatePath(value) {
|
|
3017
3388
|
return validateRepoRelativePathValue(value, "--path");
|
|
3018
3389
|
}
|
|
@@ -3030,6 +3401,7 @@ function parseFileArgs(argv) {
|
|
|
3030
3401
|
let cwd;
|
|
3031
3402
|
const options = new Map;
|
|
3032
3403
|
const flags = new Set;
|
|
3404
|
+
const commandOverrides = [];
|
|
3033
3405
|
for (let i = 0;i < argv.length; i++) {
|
|
3034
3406
|
const arg = argv[i];
|
|
3035
3407
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -3041,6 +3413,17 @@ function parseFileArgs(argv) {
|
|
|
3041
3413
|
return { ok: false, error: taken.error };
|
|
3042
3414
|
cwd = taken.value;
|
|
3043
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;
|
|
3044
3427
|
} else if (VALUE_FLAGS.has(arg)) {
|
|
3045
3428
|
const taken = takeValue(argv, i, arg);
|
|
3046
3429
|
if ("error" in taken)
|
|
@@ -3102,7 +3485,8 @@ function parseFileArgs(argv) {
|
|
|
3102
3485
|
base,
|
|
3103
3486
|
json
|
|
3104
3487
|
},
|
|
3105
|
-
cwd
|
|
3488
|
+
cwd,
|
|
3489
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3106
3490
|
}
|
|
3107
3491
|
};
|
|
3108
3492
|
}
|
|
@@ -3140,7 +3524,8 @@ function parseFileArgs(argv) {
|
|
|
3140
3524
|
query,
|
|
3141
3525
|
json
|
|
3142
3526
|
},
|
|
3143
|
-
cwd
|
|
3527
|
+
cwd,
|
|
3528
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3144
3529
|
}
|
|
3145
3530
|
};
|
|
3146
3531
|
}
|
|
@@ -3179,7 +3564,8 @@ function parseFileArgs(argv) {
|
|
|
3179
3564
|
end,
|
|
3180
3565
|
json
|
|
3181
3566
|
},
|
|
3182
|
-
cwd
|
|
3567
|
+
cwd,
|
|
3568
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3183
3569
|
}
|
|
3184
3570
|
};
|
|
3185
3571
|
}
|
|
@@ -3268,7 +3654,8 @@ function parseFileArgs(argv) {
|
|
|
3268
3654
|
maxLines,
|
|
3269
3655
|
json
|
|
3270
3656
|
},
|
|
3271
|
-
cwd
|
|
3657
|
+
cwd,
|
|
3658
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
3272
3659
|
}
|
|
3273
3660
|
};
|
|
3274
3661
|
}
|
|
@@ -3370,13 +3757,13 @@ function sliceLines(text, start, end) {
|
|
|
3370
3757
|
function safeWorktreePathFromRoot(root, path) {
|
|
3371
3758
|
if (validatePath(path))
|
|
3372
3759
|
return null;
|
|
3373
|
-
const full =
|
|
3760
|
+
const full = join5(root, path);
|
|
3374
3761
|
if (!existsSync3(full))
|
|
3375
3762
|
return null;
|
|
3376
3763
|
try {
|
|
3377
|
-
const realRoot =
|
|
3378
|
-
const realFull =
|
|
3379
|
-
const rel =
|
|
3764
|
+
const realRoot = realpathSync3(root);
|
|
3765
|
+
const realFull = realpathSync3(full);
|
|
3766
|
+
const rel = relative2(realRoot, realFull);
|
|
3380
3767
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\")) {
|
|
3381
3768
|
return null;
|
|
3382
3769
|
}
|
|
@@ -3400,7 +3787,7 @@ function readShowText(root, command) {
|
|
|
3400
3787
|
};
|
|
3401
3788
|
}
|
|
3402
3789
|
try {
|
|
3403
|
-
const stat =
|
|
3790
|
+
const stat = statSync3(full);
|
|
3404
3791
|
if (!stat.isFile()) {
|
|
3405
3792
|
return { code: 1, stdout: "", stderr: "not a file" };
|
|
3406
3793
|
}
|
|
@@ -3453,12 +3840,12 @@ function runShow(root, command) {
|
|
|
3453
3840
|
}
|
|
3454
3841
|
}
|
|
3455
3842
|
function buildFileDiffRangeArgs(from, to) {
|
|
3456
|
-
const
|
|
3843
|
+
const refs = [];
|
|
3457
3844
|
if (from && from !== "worktree")
|
|
3458
|
-
|
|
3845
|
+
refs.push(from);
|
|
3459
3846
|
if (to && to !== "worktree")
|
|
3460
|
-
|
|
3461
|
-
return
|
|
3847
|
+
refs.push(to);
|
|
3848
|
+
return refs;
|
|
3462
3849
|
}
|
|
3463
3850
|
function buildFileDiffReport(root, command) {
|
|
3464
3851
|
const base = {
|
|
@@ -3537,7 +3924,7 @@ async function runFileCli(argv) {
|
|
|
3537
3924
|
console.error('Run "code-viewer file --help" for usage.');
|
|
3538
3925
|
process.exit(1);
|
|
3539
3926
|
}
|
|
3540
|
-
const { command, cwd } = parsed.args;
|
|
3927
|
+
const { command, cwd, commandOverrides = [] } = parsed.args;
|
|
3541
3928
|
if (command.kind === "help") {
|
|
3542
3929
|
console.log(FILE_HELP);
|
|
3543
3930
|
return;
|
|
@@ -3546,6 +3933,15 @@ async function runFileCli(argv) {
|
|
|
3546
3933
|
console.log(FILE_AGENT_HELP);
|
|
3547
3934
|
return;
|
|
3548
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
|
+
}
|
|
3549
3945
|
const root = resolveRepoRoot(cwd);
|
|
3550
3946
|
if (command.kind === "blame")
|
|
3551
3947
|
return runBlame(root, command);
|
|
@@ -3559,13 +3955,14 @@ async function runFileCli(argv) {
|
|
|
3559
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";
|
|
3560
3956
|
var init_file_cli = __esm(() => {
|
|
3561
3957
|
init_cli_helpers();
|
|
3958
|
+
init_command_resolver();
|
|
3562
3959
|
init_git();
|
|
3563
3960
|
FILE_HELP = `code-viewer file — inspect a path's blame, history, contents, or diff
|
|
3564
3961
|
|
|
3565
3962
|
Usage:
|
|
3566
|
-
code-viewer file blame --path <path> [--ref <ref>] [--base <worktree|HEAD>] [--json] [--cwd <dir>]
|
|
3567
|
-
code-viewer file history --path <path> [--ref <ref>] [--limit <n>] [--skip <n>] [--query <text>] [--json] [--cwd <dir>]
|
|
3568
|
-
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>]
|
|
3569
3966
|
code-viewer file diff --path <path> [--from <ref>] [--to <ref>] [--old-path <path>] [--untracked]
|
|
3570
3967
|
[--ignore-ws] [--ignore-blank] [--max-hunks <n>] [--max-lines <n>] [--full] [--json] [--cwd <dir>]
|
|
3571
3968
|
code-viewer file --help
|
|
@@ -3578,6 +3975,7 @@ Common options:
|
|
|
3578
3975
|
HEAD. Single-line, no NUL, no leading "-". blame/show accept
|
|
3579
3976
|
the literal "worktree" to mean the working tree.
|
|
3580
3977
|
--cwd <dir> Repository to target (default: process.cwd()).
|
|
3978
|
+
--bin git=<p> Override git executable path.
|
|
3581
3979
|
--json Emit a structured JSON payload instead of plain text.
|
|
3582
3980
|
--help, -h Show this help.
|
|
3583
3981
|
|
|
@@ -3644,7 +4042,8 @@ Diff Viewer views.
|
|
|
3644
4042
|
- Run from inside the repository, or pass --cwd <repo>.
|
|
3645
4043
|
- No code-viewer server is required (this command reads git refs or the
|
|
3646
4044
|
worktree directly).
|
|
3647
|
-
- 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.
|
|
3648
4047
|
|
|
3649
4048
|
## How to call
|
|
3650
4049
|
|
|
@@ -3889,6 +4288,7 @@ function parseQueryArgs(argv) {
|
|
|
3889
4288
|
let server;
|
|
3890
4289
|
const options = new Map;
|
|
3891
4290
|
const flags = new Set;
|
|
4291
|
+
const commandOverrides = [];
|
|
3892
4292
|
for (let i = 0;i < argv.length; i++) {
|
|
3893
4293
|
const arg = argv[i];
|
|
3894
4294
|
if (arg === "--help" || arg === "-h")
|
|
@@ -3902,6 +4302,17 @@ function parseQueryArgs(argv) {
|
|
|
3902
4302
|
else
|
|
3903
4303
|
server = taken.value;
|
|
3904
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;
|
|
3905
4316
|
} else if (VALUE_FLAGS2.has(arg)) {
|
|
3906
4317
|
const taken = takeValue(argv, i, arg);
|
|
3907
4318
|
if ("error" in taken)
|
|
@@ -3919,7 +4330,11 @@ function parseQueryArgs(argv) {
|
|
|
3919
4330
|
const subcommand = rest[0];
|
|
3920
4331
|
if (!subcommand)
|
|
3921
4332
|
return { ok: true, args: { command: { kind: "help" } } };
|
|
3922
|
-
const globalArgs = {
|
|
4333
|
+
const globalArgs = {
|
|
4334
|
+
cwd,
|
|
4335
|
+
server,
|
|
4336
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
4337
|
+
};
|
|
3923
4338
|
if (subcommand === "agent-help") {
|
|
3924
4339
|
return { ok: true, args: { command: { kind: "agent-help" } } };
|
|
3925
4340
|
}
|
|
@@ -4694,7 +5109,7 @@ async function runQueryCli(argv) {
|
|
|
4694
5109
|
console.error('Run "code-viewer query --help" for usage.');
|
|
4695
5110
|
process.exit(1);
|
|
4696
5111
|
}
|
|
4697
|
-
const { command, cwd, server } = parsed.args;
|
|
5112
|
+
const { command, cwd, server, commandOverrides = [] } = parsed.args;
|
|
4698
5113
|
if (command.kind === "help") {
|
|
4699
5114
|
console.log(QUERY_HELP);
|
|
4700
5115
|
return;
|
|
@@ -4703,7 +5118,16 @@ async function runQueryCli(argv) {
|
|
|
4703
5118
|
console.log(QUERY_AGENT_HELP);
|
|
4704
5119
|
return;
|
|
4705
5120
|
}
|
|
4706
|
-
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);
|
|
4707
5131
|
const serverUrl = await ensureServerUrl(root, server, "/");
|
|
4708
5132
|
if (command.kind === "sources")
|
|
4709
5133
|
return runSources(serverUrl, command);
|
|
@@ -5548,6 +5972,8 @@ Usage:
|
|
|
5548
5972
|
Global options:
|
|
5549
5973
|
--cwd <dir> repository directory (default: current directory)
|
|
5550
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.
|
|
5551
5977
|
|
|
5552
5978
|
Examples:
|
|
5553
5979
|
code-viewer query sources --json
|
|
@@ -5953,6 +6379,7 @@ object bytes (text-shaped objects are previewable via \`s3 text\`).
|
|
|
5953
6379
|
var init_query_cli = __esm(() => {
|
|
5954
6380
|
init_routes();
|
|
5955
6381
|
init_cli_helpers();
|
|
6382
|
+
init_command_resolver();
|
|
5956
6383
|
init_cli_helpers();
|
|
5957
6384
|
VALUE_FLAGS2 = new Set([
|
|
5958
6385
|
"--db",
|
|
@@ -6445,6 +6872,7 @@ function parseSearchArgs(argv) {
|
|
|
6445
6872
|
const options = new Map;
|
|
6446
6873
|
const paths = [];
|
|
6447
6874
|
const flags = new Set;
|
|
6875
|
+
const commandOverrides = [];
|
|
6448
6876
|
for (let i = 0;i < argv.length; i++) {
|
|
6449
6877
|
const arg = argv[i];
|
|
6450
6878
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -6459,6 +6887,17 @@ function parseSearchArgs(argv) {
|
|
|
6459
6887
|
else
|
|
6460
6888
|
server = taken.value;
|
|
6461
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;
|
|
6462
6901
|
} else if (REPEATABLE_VALUE_FLAGS.has(arg)) {
|
|
6463
6902
|
const taken = takeValue(argv, i, arg);
|
|
6464
6903
|
if ("error" in taken)
|
|
@@ -6546,7 +6985,8 @@ function parseSearchArgs(argv) {
|
|
|
6546
6985
|
json: flags.has("--json")
|
|
6547
6986
|
},
|
|
6548
6987
|
cwd,
|
|
6549
|
-
server
|
|
6988
|
+
server,
|
|
6989
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
6550
6990
|
}
|
|
6551
6991
|
};
|
|
6552
6992
|
}
|
|
@@ -6581,7 +7021,8 @@ function parseSearchArgs(argv) {
|
|
|
6581
7021
|
json: flags.has("--json")
|
|
6582
7022
|
},
|
|
6583
7023
|
cwd,
|
|
6584
|
-
server
|
|
7024
|
+
server,
|
|
7025
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
6585
7026
|
}
|
|
6586
7027
|
};
|
|
6587
7028
|
}
|
|
@@ -6673,7 +7114,7 @@ async function runSearchCli(argv) {
|
|
|
6673
7114
|
console.error('Run "code-viewer search --help" for usage.');
|
|
6674
7115
|
process.exit(1);
|
|
6675
7116
|
}
|
|
6676
|
-
const { command, cwd, server } = parsed.args;
|
|
7117
|
+
const { command, cwd, server, commandOverrides = [] } = parsed.args;
|
|
6677
7118
|
if (command.kind === "help") {
|
|
6678
7119
|
console.log(SEARCH_HELP);
|
|
6679
7120
|
return;
|
|
@@ -6682,7 +7123,16 @@ async function runSearchCli(argv) {
|
|
|
6682
7123
|
console.log(SEARCH_AGENT_HELP);
|
|
6683
7124
|
return;
|
|
6684
7125
|
}
|
|
6685
|
-
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);
|
|
6686
7136
|
const serverUrl = await ensureServerUrl(root, server, "/");
|
|
6687
7137
|
if (command.kind === "code")
|
|
6688
7138
|
return runCode(serverUrl, command);
|
|
@@ -6692,15 +7142,16 @@ async function runSearchCli(argv) {
|
|
|
6692
7142
|
var FILE_NAME_SEARCH_DEFAULT_MAX = 50, SEARCH_HELP, SEARCH_AGENT_HELP, VALUE_FLAGS3, REPEATABLE_VALUE_FLAGS, BOOL_FLAGS3;
|
|
6693
7143
|
var init_search_cli = __esm(() => {
|
|
6694
7144
|
init_cli_helpers();
|
|
7145
|
+
init_command_resolver();
|
|
6695
7146
|
init_search();
|
|
6696
7147
|
SEARCH_HELP = `code-viewer search — text and filename search across the worktree or a git ref
|
|
6697
7148
|
|
|
6698
7149
|
Usage:
|
|
6699
7150
|
code-viewer search code --term <text> [--ref <ref>] [--path <path>...]
|
|
6700
7151
|
[--regex] [--max <n>] [--json]
|
|
6701
|
-
[--cwd <dir>] [--server <url>]
|
|
7152
|
+
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
6702
7153
|
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json]
|
|
6703
|
-
[--cwd <dir>] [--server <url>]
|
|
7154
|
+
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
6704
7155
|
code-viewer search --help
|
|
6705
7156
|
code-viewer search agent-help
|
|
6706
7157
|
|
|
@@ -6713,6 +7164,8 @@ Common options:
|
|
|
6713
7164
|
--json Emit a structured JSON payload instead of plain lines.
|
|
6714
7165
|
--cwd <dir> Repository to target (default: process.cwd()).
|
|
6715
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.
|
|
6716
7169
|
--help, -h Show this help.
|
|
6717
7170
|
|
|
6718
7171
|
search code only:
|
|
@@ -6839,24 +7292,24 @@ Parse failures and unreachable servers exit 1.
|
|
|
6839
7292
|
|
|
6840
7293
|
// web-src/server/root.ts
|
|
6841
7294
|
import { existsSync as existsSync4 } from "node:fs";
|
|
6842
|
-
import { dirname as
|
|
7295
|
+
import { dirname as dirname3, join as join6, normalize } from "node:path";
|
|
6843
7296
|
import { fileURLToPath } from "node:url";
|
|
6844
7297
|
function findRoot(start) {
|
|
6845
7298
|
let current = start;
|
|
6846
7299
|
for (let i = 0;i < 5; i++) {
|
|
6847
|
-
if (existsSync4(
|
|
7300
|
+
if (existsSync4(join6(current, "package.json")) && existsSync4(join6(current, "web"))) {
|
|
6848
7301
|
return normalize(current);
|
|
6849
7302
|
}
|
|
6850
|
-
const parent =
|
|
7303
|
+
const parent = dirname3(current);
|
|
6851
7304
|
if (parent === current)
|
|
6852
7305
|
break;
|
|
6853
7306
|
current = parent;
|
|
6854
7307
|
}
|
|
6855
|
-
return normalize(
|
|
7308
|
+
return normalize(join6(start, "..", ".."));
|
|
6856
7309
|
}
|
|
6857
7310
|
var ROOT;
|
|
6858
7311
|
var init_root = __esm(() => {
|
|
6859
|
-
ROOT = findRoot(
|
|
7312
|
+
ROOT = findRoot(dirname3(fileURLToPath(import.meta.url)));
|
|
6860
7313
|
});
|
|
6861
7314
|
|
|
6862
7315
|
// web-src/server/skill-cli.ts
|
|
@@ -6871,7 +7324,7 @@ __export(exports_skill_cli, {
|
|
|
6871
7324
|
});
|
|
6872
7325
|
import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
6873
7326
|
import { homedir as homedir2 } from "node:os";
|
|
6874
|
-
import { join as
|
|
7327
|
+
import { join as join7, resolve } from "node:path";
|
|
6875
7328
|
function parseAgentList(value) {
|
|
6876
7329
|
if (value === "all")
|
|
6877
7330
|
return [...AGENT_NAMES];
|
|
@@ -6931,7 +7384,7 @@ function parseSkillArgs(argv) {
|
|
|
6931
7384
|
function discoverBundledSkills(skillsRoot) {
|
|
6932
7385
|
if (!existsSync5(skillsRoot))
|
|
6933
7386
|
return [];
|
|
6934
|
-
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();
|
|
6935
7388
|
}
|
|
6936
7389
|
function installSkill(args, deps) {
|
|
6937
7390
|
const skills = discoverBundledSkills(deps.skillsRoot);
|
|
@@ -6945,8 +7398,8 @@ function installSkill(args, deps) {
|
|
|
6945
7398
|
const results = [];
|
|
6946
7399
|
for (const agent of args.agents) {
|
|
6947
7400
|
for (const skill of skills) {
|
|
6948
|
-
const sourceDir =
|
|
6949
|
-
const target =
|
|
7401
|
+
const sourceDir = join7(deps.skillsRoot, skill);
|
|
7402
|
+
const target = join7(base, AGENT_SKILL_DIRS[agent], "skills", skill);
|
|
6950
7403
|
const action = existsSync5(target) ? "updated" : "installed";
|
|
6951
7404
|
try {
|
|
6952
7405
|
mkdirSync2(target, { recursive: true });
|
|
@@ -6975,7 +7428,7 @@ function runSkillCli(argv) {
|
|
|
6975
7428
|
return;
|
|
6976
7429
|
}
|
|
6977
7430
|
const result = installSkill(parsed.args, {
|
|
6978
|
-
skillsRoot:
|
|
7431
|
+
skillsRoot: join7(ROOT, "skills"),
|
|
6979
7432
|
homeDir: homedir2(),
|
|
6980
7433
|
projectDir: process.cwd()
|
|
6981
7434
|
});
|
|
@@ -7109,6 +7562,7 @@ function parseStatusArgs(argv) {
|
|
|
7109
7562
|
const options = new Map;
|
|
7110
7563
|
const flags = new Set;
|
|
7111
7564
|
const positional = [];
|
|
7565
|
+
const commandOverrides = [];
|
|
7112
7566
|
for (let i = 0;i < argv.length; i++) {
|
|
7113
7567
|
const arg = argv[i];
|
|
7114
7568
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -7120,6 +7574,17 @@ function parseStatusArgs(argv) {
|
|
|
7120
7574
|
return { ok: false, error: taken.error };
|
|
7121
7575
|
cwd = taken.value;
|
|
7122
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;
|
|
7123
7588
|
} else if (VALUE_FLAGS4.has(arg)) {
|
|
7124
7589
|
const taken = takeValue(argv, i, arg);
|
|
7125
7590
|
if ("error" in taken)
|
|
@@ -7170,7 +7635,8 @@ function parseStatusArgs(argv) {
|
|
|
7170
7635
|
limit,
|
|
7171
7636
|
json: flags.has("--json")
|
|
7172
7637
|
},
|
|
7173
|
-
cwd
|
|
7638
|
+
cwd,
|
|
7639
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
7174
7640
|
}
|
|
7175
7641
|
};
|
|
7176
7642
|
}
|
|
@@ -7183,9 +7649,13 @@ function sumTotals(files) {
|
|
|
7183
7649
|
}
|
|
7184
7650
|
return { files: files.length, additions, deletions };
|
|
7185
7651
|
}
|
|
7186
|
-
function buildGroup(files) {
|
|
7652
|
+
function buildGroup(files, error) {
|
|
7187
7653
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
7188
|
-
return {
|
|
7654
|
+
return {
|
|
7655
|
+
files: sorted,
|
|
7656
|
+
totals: sumTotals(sorted),
|
|
7657
|
+
...error ? { error } : {}
|
|
7658
|
+
};
|
|
7189
7659
|
}
|
|
7190
7660
|
function metaPathKey(file) {
|
|
7191
7661
|
return `${file.old_path || ""}\x00${file.path}`;
|
|
@@ -7202,6 +7672,9 @@ function mergeMissingByPath(primary, fallback) {
|
|
|
7202
7672
|
}
|
|
7203
7673
|
return merged;
|
|
7204
7674
|
}
|
|
7675
|
+
function isMissingDiffBaseError(error) {
|
|
7676
|
+
return !!error && /ambiguous argument 'HEAD'|bad revision 'HEAD'/i.test(error);
|
|
7677
|
+
}
|
|
7205
7678
|
function joinCli(parts, serverUrl) {
|
|
7206
7679
|
const head = parts[0];
|
|
7207
7680
|
const tail = parts.slice(1).join(" ");
|
|
@@ -7246,6 +7719,8 @@ function formatGroupText(label, group) {
|
|
|
7246
7719
|
const totals = group.totals;
|
|
7247
7720
|
const summary = totals.files === 0 ? `${label}: 0 files` : `${label}: ${totals.files} files (+${totals.additions} / -${totals.deletions})`;
|
|
7248
7721
|
lines.push(summary);
|
|
7722
|
+
if (group.error)
|
|
7723
|
+
lines.push(` # error: ${group.error}`);
|
|
7249
7724
|
for (const file of group.files)
|
|
7250
7725
|
lines.push(formatChangedLine(file));
|
|
7251
7726
|
return lines;
|
|
@@ -7261,10 +7736,13 @@ function formatRecentText(commits) {
|
|
|
7261
7736
|
}
|
|
7262
7737
|
function buildStatusReport(opts) {
|
|
7263
7738
|
const { root, ref, limit } = opts;
|
|
7264
|
-
const
|
|
7265
|
-
const
|
|
7266
|
-
const
|
|
7267
|
-
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);
|
|
7268
7746
|
const history = commitHistory(root, { ref, skip: 0, limit });
|
|
7269
7747
|
const branch = currentBranch(root);
|
|
7270
7748
|
const remote = remoteWebUrl(root);
|
|
@@ -7312,7 +7790,7 @@ function runStatusCli(argv) {
|
|
|
7312
7790
|
console.error('Run "code-viewer status --help" for usage.');
|
|
7313
7791
|
process.exit(1);
|
|
7314
7792
|
}
|
|
7315
|
-
const { command, cwd } = parsed.args;
|
|
7793
|
+
const { command, cwd, commandOverrides = [] } = parsed.args;
|
|
7316
7794
|
if (command.kind === "help") {
|
|
7317
7795
|
console.log(STATUS_HELP);
|
|
7318
7796
|
return;
|
|
@@ -7321,6 +7799,15 @@ function runStatusCli(argv) {
|
|
|
7321
7799
|
console.log(STATUS_AGENT_HELP);
|
|
7322
7800
|
return;
|
|
7323
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
|
+
}
|
|
7324
7811
|
const root = resolveRepoRoot(cwd);
|
|
7325
7812
|
const report = buildStatusReport({
|
|
7326
7813
|
root,
|
|
@@ -7329,9 +7816,13 @@ function runStatusCli(argv) {
|
|
|
7329
7816
|
});
|
|
7330
7817
|
if (command.json) {
|
|
7331
7818
|
console.log(JSON.stringify(report, null, 2));
|
|
7819
|
+
if (report.changed.error || report.staged.error)
|
|
7820
|
+
process.exit(1);
|
|
7332
7821
|
return;
|
|
7333
7822
|
}
|
|
7334
7823
|
console.log(formatStatusReportText(report));
|
|
7824
|
+
if (report.changed.error || report.staged.error)
|
|
7825
|
+
process.exit(1);
|
|
7335
7826
|
}
|
|
7336
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
|
|
7337
7828
|
|
|
@@ -7363,11 +7854,11 @@ browser home page uses — no server, no SQLite, no docker required.
|
|
|
7363
7854
|
{ repoRoot, branch, remoteWebUrl, changed, staged, recentCommits,
|
|
7364
7855
|
nextCommands }
|
|
7365
7856
|
changed / staged share shape { files: GitFileMeta[], totals: { files,
|
|
7366
|
-
additions, deletions } } so AI can iterate both the same way.
|
|
7857
|
+
additions, deletions }, error? } so AI can iterate both the same way.
|
|
7367
7858
|
recentCommitsError is present when --ref cannot resolve; the command
|
|
7368
7859
|
still emits changed / staged / nextCommands and exits 0.
|
|
7369
|
-
- exit 0 on success. exit 1
|
|
7370
|
-
|
|
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.
|
|
7371
7862
|
|
|
7372
7863
|
## Suggested usage
|
|
7373
7864
|
|
|
@@ -7377,16 +7868,18 @@ browser home page uses — no server, no SQLite, no docker required.
|
|
|
7377
7868
|
`, VALUE_FLAGS4, BOOL_FLAGS4;
|
|
7378
7869
|
var init_status_cli = __esm(() => {
|
|
7379
7870
|
init_cli_helpers();
|
|
7871
|
+
init_command_resolver();
|
|
7380
7872
|
init_git();
|
|
7381
7873
|
init_server_registry();
|
|
7382
7874
|
STATUS_HELP = `code-viewer status — snapshot the current repo for AI agents
|
|
7383
7875
|
|
|
7384
7876
|
Usage:
|
|
7385
|
-
code-viewer status [--cwd <repo>] [--ref <ref>] [--limit <N>] [--json]
|
|
7877
|
+
code-viewer status [--cwd <repo>] [--bin git=<path>] [--ref <ref>] [--limit <N>] [--json]
|
|
7386
7878
|
code-viewer status agent-help
|
|
7387
7879
|
|
|
7388
7880
|
Options:
|
|
7389
7881
|
--cwd <repo> Repository to inspect (default: current directory).
|
|
7882
|
+
--bin git=<p> Override git executable path.
|
|
7390
7883
|
--ref <ref> Ref to read recent commits from (default: ${STATUS_DEFAULT_REF}).
|
|
7391
7884
|
--limit <N> Recent commits to include (default: ${STATUS_DEFAULT_LIMIT}, max ${STATUS_HARD_CAP_LIMIT}).
|
|
7392
7885
|
--json Emit a structured JSON payload instead of plain text.
|
|
@@ -7915,7 +8408,22 @@ var init_spawn_runner = () => {};
|
|
|
7915
8408
|
// web-src/server/database/adapters/docker-utils.ts
|
|
7916
8409
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7917
8410
|
function isDockerComposeServiceUnavailableError(err) {
|
|
7918
|
-
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 });
|
|
7919
8427
|
}
|
|
7920
8428
|
function parseComposePsOutput(stdout) {
|
|
7921
8429
|
const output = stdout.trim();
|
|
@@ -7929,18 +8437,21 @@ function parseComposePsOutput(stdout) {
|
|
|
7929
8437
|
}
|
|
7930
8438
|
return byService;
|
|
7931
8439
|
}
|
|
7932
|
-
function cacheComposePsFailure(cwd,
|
|
7933
|
-
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;
|
|
7934
8443
|
composePsCache.set(cwd, {
|
|
7935
8444
|
containers: null,
|
|
7936
8445
|
positiveExpiresAt: now,
|
|
7937
|
-
negativeExpiresAt: now + failureTtl
|
|
8446
|
+
negativeExpiresAt: now + failureTtl,
|
|
8447
|
+
error: stderr
|
|
7938
8448
|
});
|
|
7939
8449
|
}
|
|
7940
8450
|
function runComposePsAsync(cwd, signal) {
|
|
7941
8451
|
throwIfAborted(signal, "docker compose ps aborted");
|
|
7942
8452
|
if (spawnSyncImpl !== spawnSync2) {
|
|
7943
|
-
const
|
|
8453
|
+
const command = dockerCommand();
|
|
8454
|
+
const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
|
|
7944
8455
|
encoding: "utf8",
|
|
7945
8456
|
timeout: 5000,
|
|
7946
8457
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -7948,19 +8459,21 @@ function runComposePsAsync(cwd, signal) {
|
|
|
7948
8459
|
});
|
|
7949
8460
|
return Promise.resolve({
|
|
7950
8461
|
stdout: String(proc.stdout || ""),
|
|
7951
|
-
stderr: String(proc.stderr || "")
|
|
8462
|
+
stderr: `${String(proc.stderr || "")}${proc.error ? `
|
|
8463
|
+
${proc.error.message}` : ""}`,
|
|
7952
8464
|
code: proc.status ?? 1
|
|
7953
8465
|
});
|
|
7954
8466
|
}
|
|
7955
8467
|
return spawnTextAsync({
|
|
7956
|
-
command:
|
|
8468
|
+
command: dockerCommand(),
|
|
7957
8469
|
args: ["compose", "ps", "--format", "json", "--status", "running"],
|
|
7958
8470
|
cwd,
|
|
7959
8471
|
timeoutMs: 5000,
|
|
7960
8472
|
signal,
|
|
7961
8473
|
killSignal: "SIGKILL",
|
|
7962
8474
|
abortMessage: "docker compose ps aborted",
|
|
7963
|
-
timeoutMessage: "docker compose ps timed out"
|
|
8475
|
+
timeoutMessage: "docker compose ps timed out",
|
|
8476
|
+
rejectOnError: false
|
|
7964
8477
|
});
|
|
7965
8478
|
}
|
|
7966
8479
|
async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
|
|
@@ -7989,11 +8502,14 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
7989
8502
|
} catch (err) {
|
|
7990
8503
|
if (isAbortLikeError(err, controller.signal))
|
|
7991
8504
|
throw err;
|
|
7992
|
-
cacheComposePsFailure(cwd,
|
|
8505
|
+
cacheComposePsFailure(cwd, {
|
|
8506
|
+
code: 1,
|
|
8507
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
8508
|
+
}, startedAt);
|
|
7993
8509
|
return null;
|
|
7994
8510
|
}
|
|
7995
8511
|
if (proc.code !== 0) {
|
|
7996
|
-
cacheComposePsFailure(cwd, proc
|
|
8512
|
+
cacheComposePsFailure(cwd, proc, startedAt);
|
|
7997
8513
|
return null;
|
|
7998
8514
|
}
|
|
7999
8515
|
try {
|
|
@@ -8008,7 +8524,8 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
8008
8524
|
composePsCache.set(cwd, {
|
|
8009
8525
|
containers: null,
|
|
8010
8526
|
positiveExpiresAt: startedAt,
|
|
8011
|
-
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"
|
|
8012
8529
|
});
|
|
8013
8530
|
return null;
|
|
8014
8531
|
}
|
|
@@ -8051,12 +8568,14 @@ async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal)
|
|
|
8051
8568
|
async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal) {
|
|
8052
8569
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8053
8570
|
if (!containerName) {
|
|
8571
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8054
8572
|
throw new DockerComposeServiceUnavailableError(serviceName, cwd);
|
|
8055
8573
|
}
|
|
8056
8574
|
return containerName;
|
|
8057
8575
|
}
|
|
8058
|
-
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;
|
|
8059
8577
|
var init_docker_utils = __esm(() => {
|
|
8578
|
+
init_command_resolver();
|
|
8060
8579
|
init_spawn_runner();
|
|
8061
8580
|
composePsCache = new Map;
|
|
8062
8581
|
composePsPending = new Map;
|
|
@@ -8072,6 +8591,13 @@ var init_docker_utils = __esm(() => {
|
|
|
8072
8591
|
this.cwd = cwd;
|
|
8073
8592
|
}
|
|
8074
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
|
+
};
|
|
8075
8601
|
});
|
|
8076
8602
|
|
|
8077
8603
|
// web-src/server/database/adapters/sql-capture.ts
|
|
@@ -8122,7 +8648,7 @@ function fallbackDockerDatabases(defaultDb) {
|
|
|
8122
8648
|
function buildExecArgs(config, sql) {
|
|
8123
8649
|
if (config.kind === "postgresql") {
|
|
8124
8650
|
return [
|
|
8125
|
-
|
|
8651
|
+
dockerCommand(),
|
|
8126
8652
|
"exec",
|
|
8127
8653
|
"-i",
|
|
8128
8654
|
"-e",
|
|
@@ -8148,7 +8674,7 @@ function buildExecArgs(config, sql) {
|
|
|
8148
8674
|
];
|
|
8149
8675
|
}
|
|
8150
8676
|
return [
|
|
8151
|
-
|
|
8677
|
+
dockerCommand(),
|
|
8152
8678
|
"exec",
|
|
8153
8679
|
"-i",
|
|
8154
8680
|
"-e",
|
|
@@ -8173,7 +8699,8 @@ function execInContainer(config, sql, timeoutMs = 1e4) {
|
|
|
8173
8699
|
});
|
|
8174
8700
|
return {
|
|
8175
8701
|
stdout: proc.stdout || "",
|
|
8176
|
-
stderr: proc.stderr || ""
|
|
8702
|
+
stderr: `${proc.stderr || ""}${proc.error ? `
|
|
8703
|
+
${proc.error.message}` : ""}`,
|
|
8177
8704
|
code: proc.status ?? 1
|
|
8178
8705
|
};
|
|
8179
8706
|
}
|
|
@@ -8194,11 +8721,20 @@ async function readStreamText(stream) {
|
|
|
8194
8721
|
}
|
|
8195
8722
|
async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
|
|
8196
8723
|
throwIfAborted(signal, "query aborted");
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
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
|
+
}
|
|
8202
8738
|
let timedOut = false;
|
|
8203
8739
|
let aborted = false;
|
|
8204
8740
|
const abort = () => {
|
|
@@ -8248,13 +8784,17 @@ function execWithNodeSpawn(args, timeoutMs, signal) {
|
|
|
8248
8784
|
}
|
|
8249
8785
|
async function execInContainerAsync(config, sql, timeoutMs = 1e4, signal) {
|
|
8250
8786
|
recordSql(sql);
|
|
8251
|
-
|
|
8252
|
-
|
|
8787
|
+
let result;
|
|
8788
|
+
if (spawnSyncImpl2 !== spawnSync3) {
|
|
8789
|
+
result = execInContainer(config, sql, timeoutMs);
|
|
8790
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
8791
|
+
return result;
|
|
8792
|
+
}
|
|
8253
8793
|
const args = buildExecArgs(config, sql);
|
|
8254
8794
|
const bunSpawn = globalThis.Bun?.spawn;
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
return
|
|
8795
|
+
result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, timeoutMs, signal) : await execWithNodeSpawn(args, timeoutMs, signal);
|
|
8796
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
8797
|
+
return result;
|
|
8258
8798
|
}
|
|
8259
8799
|
function stripFinalLineBreak(text) {
|
|
8260
8800
|
if (text.endsWith(`\r
|
|
@@ -8785,14 +9325,22 @@ function createDockerAdapter(config) {
|
|
|
8785
9325
|
try {
|
|
8786
9326
|
const result = await execAsync(`SHOW CREATE TABLE ${tableIdentifier(table)}`, signal);
|
|
8787
9327
|
return result.rows.length > 0 ? result.rows[0][1] || "" : "";
|
|
8788
|
-
} catch {
|
|
9328
|
+
} catch (err) {
|
|
9329
|
+
if (isAbortLikeError(err, signal))
|
|
9330
|
+
throw err;
|
|
9331
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9332
|
+
throw err;
|
|
8789
9333
|
return "";
|
|
8790
9334
|
}
|
|
8791
9335
|
}
|
|
8792
9336
|
try {
|
|
8793
9337
|
const result = await execAsync(`SELECT 'CREATE TABLE ' || ${escapeSqlString(tableIdentifier(table))} || ' (...)' AS ddl`, signal);
|
|
8794
9338
|
return result.rows.length > 0 ? result.rows[0][0] || "" : "";
|
|
8795
|
-
} catch {
|
|
9339
|
+
} catch (err) {
|
|
9340
|
+
if (isAbortLikeError(err, signal))
|
|
9341
|
+
throw err;
|
|
9342
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9343
|
+
throw err;
|
|
8796
9344
|
return "";
|
|
8797
9345
|
}
|
|
8798
9346
|
},
|
|
@@ -8809,7 +9357,11 @@ function createDockerAdapter(config) {
|
|
|
8809
9357
|
name: row[0],
|
|
8810
9358
|
sql: row[1] || ""
|
|
8811
9359
|
}));
|
|
8812
|
-
} catch {
|
|
9360
|
+
} catch (err) {
|
|
9361
|
+
if (isAbortLikeError(err, signal))
|
|
9362
|
+
throw err;
|
|
9363
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9364
|
+
throw err;
|
|
8813
9365
|
return [];
|
|
8814
9366
|
}
|
|
8815
9367
|
},
|
|
@@ -8860,6 +9412,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
8860
9412
|
return [...cached.value];
|
|
8861
9413
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8862
9414
|
if (!containerName) {
|
|
9415
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8863
9416
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8864
9417
|
}
|
|
8865
9418
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || env.POSTGRES_USERNAME || env.MYSQL_USERNAME || env.USER || "root";
|
|
@@ -8893,6 +9446,8 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
8893
9446
|
} catch (err) {
|
|
8894
9447
|
if (isAbortLikeError(err, signal))
|
|
8895
9448
|
throw err;
|
|
9449
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9450
|
+
throw err;
|
|
8896
9451
|
const fallback = fallbackDockerDatabases(defaultDb);
|
|
8897
9452
|
if (fallback.length > 0)
|
|
8898
9453
|
return fallback;
|
|
@@ -8912,6 +9467,7 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
8912
9467
|
return [...cached.value];
|
|
8913
9468
|
const containerName = await resolveRunningComposeContainerNameAsync(serviceName, cwd, signal);
|
|
8914
9469
|
if (!containerName) {
|
|
9470
|
+
throwIfCachedComposeDockerCommandUnavailable(cwd);
|
|
8915
9471
|
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8916
9472
|
}
|
|
8917
9473
|
const config = {
|
|
@@ -8934,6 +9490,8 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
8934
9490
|
} catch (err) {
|
|
8935
9491
|
if (isAbortLikeError(err, signal))
|
|
8936
9492
|
throw err;
|
|
9493
|
+
if (err instanceof DockerCommandUnavailableError)
|
|
9494
|
+
throw err;
|
|
8937
9495
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
8938
9496
|
}
|
|
8939
9497
|
}
|
|
@@ -9026,11 +9584,11 @@ __ES_STATUS__:%{http_code}
|
|
|
9026
9584
|
];
|
|
9027
9585
|
return { args, input: curlConfig };
|
|
9028
9586
|
}
|
|
9029
|
-
function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
|
|
9587
|
+
async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
|
|
9030
9588
|
throwIfAborted(signal, "elasticsearch request aborted");
|
|
9031
9589
|
const invocation = buildEsRequestInvocation(config, method, path, body);
|
|
9032
|
-
|
|
9033
|
-
command:
|
|
9590
|
+
const result = await spawnTextAsync({
|
|
9591
|
+
command: dockerCommand(),
|
|
9034
9592
|
args: invocation.args,
|
|
9035
9593
|
env: process.env,
|
|
9036
9594
|
input: invocation.input,
|
|
@@ -9040,6 +9598,8 @@ function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signa
|
|
|
9040
9598
|
timeoutMessage: `elasticsearch request timed out after ${timeoutMs}ms`,
|
|
9041
9599
|
rejectOnError: false
|
|
9042
9600
|
});
|
|
9601
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
9602
|
+
return result;
|
|
9043
9603
|
}
|
|
9044
9604
|
function parseEsResponse(stdout) {
|
|
9045
9605
|
const marker = "__ES_STATUS__:";
|
|
@@ -9387,11 +9947,11 @@ function buildRedisCliInvocation(config, args) {
|
|
|
9387
9947
|
const spawnEnv = hasPassword ? { ...process.env, REDISCLI_AUTH: config.password } : process.env;
|
|
9388
9948
|
return { args: dockerArgs, env: spawnEnv };
|
|
9389
9949
|
}
|
|
9390
|
-
function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
9950
|
+
async function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
9391
9951
|
throwIfAborted(signal, "redis-cli aborted");
|
|
9392
9952
|
const invocation = buildRedisCliInvocation(config, args);
|
|
9393
|
-
|
|
9394
|
-
command:
|
|
9953
|
+
const result = await spawnTextAsync({
|
|
9954
|
+
command: dockerCommand(),
|
|
9395
9955
|
args: invocation.args,
|
|
9396
9956
|
env: invocation.env,
|
|
9397
9957
|
timeoutMs,
|
|
@@ -9400,6 +9960,8 @@ function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
|
9400
9960
|
timeoutMessage: `redis-cli timed out after ${timeoutMs}ms`,
|
|
9401
9961
|
rejectOnError: false
|
|
9402
9962
|
});
|
|
9963
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
9964
|
+
return result;
|
|
9403
9965
|
}
|
|
9404
9966
|
function parseInfoKeyspace(stdout) {
|
|
9405
9967
|
const counts = new Map;
|
|
@@ -10642,14 +11204,19 @@ function timeoutReadableStream(body, signal) {
|
|
|
10642
11204
|
async function dockerCurlFetch(opts) {
|
|
10643
11205
|
const { args, input } = dockerCurlCommand(opts);
|
|
10644
11206
|
if (spawnSyncImplIsTestOverride) {
|
|
10645
|
-
const proc2 = spawnSyncImpl3(
|
|
11207
|
+
const proc2 = spawnSyncImpl3(dockerCommand(), args, {
|
|
10646
11208
|
encoding: "buffer",
|
|
10647
11209
|
input,
|
|
10648
11210
|
timeout: s3DockerCurlTimeoutMs,
|
|
10649
11211
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10650
11212
|
});
|
|
10651
11213
|
if ((proc2.status ?? 1) !== 0) {
|
|
10652
|
-
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
|
+
});
|
|
10653
11220
|
throw new S3HttpError(503, `S3 HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
10654
11221
|
}
|
|
10655
11222
|
return responseFromCurlOutput(new Uint8Array(proc2.stdout || new Uint8Array));
|
|
@@ -10658,16 +11225,21 @@ async function dockerCurlFetch(opts) {
|
|
|
10658
11225
|
throw new S3HttpError(503, "S3 HTTP transport aborted");
|
|
10659
11226
|
}
|
|
10660
11227
|
const proc = await spawnCollectAsync({
|
|
10661
|
-
command:
|
|
11228
|
+
command: dockerCommand(),
|
|
10662
11229
|
args,
|
|
10663
11230
|
input,
|
|
10664
11231
|
timeoutMs: s3DockerCurlTimeoutMs,
|
|
10665
11232
|
signal: opts.signal,
|
|
10666
11233
|
abortMessage: "S3 HTTP transport aborted",
|
|
10667
|
-
timeoutMessage: `docker exec curl timed out after ${s3DockerCurlTimeoutMs}ms
|
|
11234
|
+
timeoutMessage: `docker exec curl timed out after ${s3DockerCurlTimeoutMs}ms`,
|
|
11235
|
+
rejectOnError: false
|
|
10668
11236
|
});
|
|
10669
11237
|
if (proc.code !== 0) {
|
|
10670
11238
|
const stderr = new TextDecoder().decode(proc.stderr).replace(/\s+/g, " ").trim();
|
|
11239
|
+
throwIfDockerCommandUnavailableResult({
|
|
11240
|
+
code: proc.code,
|
|
11241
|
+
stderr
|
|
11242
|
+
});
|
|
10671
11243
|
throw new S3HttpError(503, `S3 HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
10672
11244
|
}
|
|
10673
11245
|
return responseFromCurlOutput(new Uint8Array(proc.stdout));
|
|
@@ -11316,14 +11888,14 @@ import {
|
|
|
11316
11888
|
existsSync as existsSync6,
|
|
11317
11889
|
openSync,
|
|
11318
11890
|
readSync,
|
|
11319
|
-
realpathSync as
|
|
11320
|
-
statSync as
|
|
11891
|
+
realpathSync as realpathSync4,
|
|
11892
|
+
statSync as statSync4
|
|
11321
11893
|
} from "node:fs";
|
|
11322
11894
|
import { lstat, open, readdir, readFile as readFile2, stat } from "node:fs/promises";
|
|
11323
|
-
import { basename, join as
|
|
11895
|
+
import { basename, join as join8, relative as relative3 } from "node:path";
|
|
11324
11896
|
function isSqliteFile(fullPath) {
|
|
11325
11897
|
try {
|
|
11326
|
-
const stat2 =
|
|
11898
|
+
const stat2 = statSync4(fullPath);
|
|
11327
11899
|
if (!stat2.isFile() || stat2.size < 16)
|
|
11328
11900
|
return false;
|
|
11329
11901
|
const buf = Buffer.alloc(16);
|
|
@@ -11391,7 +11963,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
11391
11963
|
return;
|
|
11392
11964
|
if (omitSet.has(entry.toLowerCase()))
|
|
11393
11965
|
continue;
|
|
11394
|
-
const full =
|
|
11966
|
+
const full = join8(dir, entry);
|
|
11395
11967
|
let entryStat;
|
|
11396
11968
|
try {
|
|
11397
11969
|
entryStat = await lstat(full);
|
|
@@ -11408,7 +11980,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
11408
11980
|
continue;
|
|
11409
11981
|
if (!await isSqliteFileAsync(full))
|
|
11410
11982
|
continue;
|
|
11411
|
-
const rel =
|
|
11983
|
+
const rel = relative3(cwd, full);
|
|
11412
11984
|
if (rel.startsWith("..") || rel.startsWith("/"))
|
|
11413
11985
|
continue;
|
|
11414
11986
|
results.push({
|
|
@@ -11435,18 +12007,18 @@ function validateDbPath(cwd, dbPath) {
|
|
|
11435
12007
|
const parts = dbPath.split(/[\\/]+/);
|
|
11436
12008
|
if (parts.some((p) => p === ".." || p.toLowerCase() === ".git" || p.toLowerCase() === ".code-viewer"))
|
|
11437
12009
|
return null;
|
|
11438
|
-
const full =
|
|
12010
|
+
const full = join8(cwd, dbPath);
|
|
11439
12011
|
if (!existsSync6(full))
|
|
11440
12012
|
return null;
|
|
11441
12013
|
let realCwd;
|
|
11442
12014
|
let realFull;
|
|
11443
12015
|
try {
|
|
11444
|
-
realCwd =
|
|
11445
|
-
realFull =
|
|
12016
|
+
realCwd = realpathSync4(cwd);
|
|
12017
|
+
realFull = realpathSync4(full);
|
|
11446
12018
|
} catch {
|
|
11447
12019
|
return null;
|
|
11448
12020
|
}
|
|
11449
|
-
const rel =
|
|
12021
|
+
const rel = relative3(realCwd, realFull);
|
|
11450
12022
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/"))
|
|
11451
12023
|
return null;
|
|
11452
12024
|
if (!isSqliteFile(realFull))
|
|
@@ -11572,7 +12144,7 @@ function resolveEnvValue(raw, composeDirEnv = {}) {
|
|
|
11572
12144
|
}
|
|
11573
12145
|
async function readDotenvAsync(composeDir) {
|
|
11574
12146
|
try {
|
|
11575
|
-
const content = await readFile2(
|
|
12147
|
+
const content = await readFile2(join8(composeDir, ".env"), "utf-8");
|
|
11576
12148
|
return parseDotenvContent(content);
|
|
11577
12149
|
} catch {
|
|
11578
12150
|
return {};
|
|
@@ -11694,7 +12266,7 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
|
|
|
11694
12266
|
for (let match = serviceRegex.exec(servicesBlock);match !== null; match = serviceRegex.exec(servicesBlock)) {
|
|
11695
12267
|
servicePositions.push({ name: match[1], start: match.index });
|
|
11696
12268
|
}
|
|
11697
|
-
const relDir =
|
|
12269
|
+
const relDir = relative3(cwd, composeDir);
|
|
11698
12270
|
const isRoot = relDir === "" || relDir === ".";
|
|
11699
12271
|
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
11700
12272
|
const filename = basename(filepath);
|
|
@@ -11794,7 +12366,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
11794
12366
|
if (depth > MAX_SCAN_DEPTH)
|
|
11795
12367
|
return;
|
|
11796
12368
|
for (const filename of COMPOSE_FILENAMES) {
|
|
11797
|
-
const filepath =
|
|
12369
|
+
const filepath = join8(dir, filename);
|
|
11798
12370
|
if (await pathExistsAsync(filepath)) {
|
|
11799
12371
|
await parseComposeFileAsync(filepath, dir, cwd, results);
|
|
11800
12372
|
break;
|
|
@@ -11817,7 +12389,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
11817
12389
|
return;
|
|
11818
12390
|
if (omitSet.has(entry.toLowerCase()))
|
|
11819
12391
|
continue;
|
|
11820
|
-
const full =
|
|
12392
|
+
const full = join8(dir, entry);
|
|
11821
12393
|
let entryStat;
|
|
11822
12394
|
try {
|
|
11823
12395
|
entryStat = await lstat(full);
|
|
@@ -11961,12 +12533,12 @@ import {
|
|
|
11961
12533
|
readdirSync as nodeReaddirSync,
|
|
11962
12534
|
watch as nodeWatch
|
|
11963
12535
|
} from "node:fs";
|
|
11964
|
-
import { join as
|
|
12536
|
+
import { join as join9, relative as relative4 } from "node:path";
|
|
11965
12537
|
function normalizeRelativePath(path) {
|
|
11966
12538
|
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
11967
12539
|
}
|
|
11968
12540
|
function isInsideRoot(root, path) {
|
|
11969
|
-
const rel =
|
|
12541
|
+
const rel = relative4(root, path).replace(/\\/g, "/");
|
|
11970
12542
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
11971
12543
|
}
|
|
11972
12544
|
function startWorktreeUpdateWatch(options) {
|
|
@@ -12004,7 +12576,7 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12004
12576
|
const pendingChangedPaths = new Set;
|
|
12005
12577
|
let watchLimitReported = false;
|
|
12006
12578
|
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
12007
|
-
const directoryRelativePath = (dir) => normalizeRelativePath(
|
|
12579
|
+
const directoryRelativePath = (dir) => normalizeRelativePath(relative4(options.root, dir));
|
|
12008
12580
|
const ignoredDirectory = (dir) => {
|
|
12009
12581
|
const rel = directoryRelativePath(dir);
|
|
12010
12582
|
return Boolean(rel && ignored(rel));
|
|
@@ -12070,7 +12642,7 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12070
12642
|
for (const entry of entries) {
|
|
12071
12643
|
if (!entry.isDirectory())
|
|
12072
12644
|
continue;
|
|
12073
|
-
const child =
|
|
12645
|
+
const child = join9(dir, entry.name);
|
|
12074
12646
|
if (ignoredDirectory(child))
|
|
12075
12647
|
continue;
|
|
12076
12648
|
children.push(child);
|
|
@@ -12154,10 +12726,10 @@ function startWorktreeUpdateWatch(options) {
|
|
|
12154
12726
|
scheduleUpdate();
|
|
12155
12727
|
return;
|
|
12156
12728
|
}
|
|
12157
|
-
const changed = normalizeRelativePath(
|
|
12729
|
+
const changed = normalizeRelativePath(join9(rel, filename.toString()));
|
|
12158
12730
|
if (ignored(changed))
|
|
12159
12731
|
return;
|
|
12160
|
-
const fullChangedPath =
|
|
12732
|
+
const fullChangedPath = join9(options.root, changed);
|
|
12161
12733
|
if (!isInsideRoot(options.root, fullChangedPath))
|
|
12162
12734
|
return;
|
|
12163
12735
|
if (initialScanAsync) {
|
|
@@ -12206,9 +12778,9 @@ var init_worktree_watcher = __esm(() => {
|
|
|
12206
12778
|
});
|
|
12207
12779
|
|
|
12208
12780
|
// web-src/server/state-store.ts
|
|
12209
|
-
import { join as
|
|
12781
|
+
import { join as join10 } from "node:path";
|
|
12210
12782
|
function codeViewerPath(root, fileName) {
|
|
12211
|
-
return
|
|
12783
|
+
return join10(root, CODE_VIEWER_DIR2, fileName);
|
|
12212
12784
|
}
|
|
12213
12785
|
function isRecord(value) {
|
|
12214
12786
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -14129,9 +14701,9 @@ var init_handle_s3 = __esm(() => {
|
|
|
14129
14701
|
});
|
|
14130
14702
|
|
|
14131
14703
|
// web-src/server/database/query-history.ts
|
|
14132
|
-
import { join as
|
|
14704
|
+
import { join as join11 } from "node:path";
|
|
14133
14705
|
function historyFilePath(root) {
|
|
14134
|
-
return
|
|
14706
|
+
return join11(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
|
|
14135
14707
|
}
|
|
14136
14708
|
function emptyState() {
|
|
14137
14709
|
return { version: 1, entries: [] };
|
|
@@ -14281,9 +14853,9 @@ var init_query_history = __esm(() => {
|
|
|
14281
14853
|
// web-src/server/database/snapshot-store.ts
|
|
14282
14854
|
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
14283
14855
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
14284
|
-
import { join as
|
|
14856
|
+
import { join as join12 } from "node:path";
|
|
14285
14857
|
async function getStoreDb(cwd) {
|
|
14286
|
-
const dbPath =
|
|
14858
|
+
const dbPath = join12(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
|
|
14287
14859
|
if (storeDb && storeDbPath === dbPath)
|
|
14288
14860
|
return storeDb;
|
|
14289
14861
|
if (storeDb) {
|
|
@@ -14291,7 +14863,7 @@ async function getStoreDb(cwd) {
|
|
|
14291
14863
|
storeDb.close();
|
|
14292
14864
|
} catch {}
|
|
14293
14865
|
}
|
|
14294
|
-
mkdirSync3(
|
|
14866
|
+
mkdirSync3(join12(cwd, CODE_VIEWER_DIR4), { recursive: true });
|
|
14295
14867
|
const DbClass = await loadSqliteClass();
|
|
14296
14868
|
storeDb = new DbClass(dbPath);
|
|
14297
14869
|
storeDbPath = dbPath;
|
|
@@ -14897,9 +15469,9 @@ var init_snapshot_runner = __esm(() => {
|
|
|
14897
15469
|
});
|
|
14898
15470
|
|
|
14899
15471
|
// web-src/server/database/tabs-store.ts
|
|
14900
|
-
import { join as
|
|
15472
|
+
import { join as join13 } from "node:path";
|
|
14901
15473
|
function tabsFilePath(root) {
|
|
14902
|
-
return
|
|
15474
|
+
return join13(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
|
|
14903
15475
|
}
|
|
14904
15476
|
function emptyState2() {
|
|
14905
15477
|
return { version: 1, tabs: [], activeTabId: null };
|
|
@@ -15437,6 +16009,38 @@ async function handleTable(cwd, url, omitDirNames, signal) {
|
|
|
15437
16009
|
return handleError("database", "read table", err, signal);
|
|
15438
16010
|
}
|
|
15439
16011
|
}
|
|
16012
|
+
async function handleTableCount(cwd, url, omitDirNames, signal) {
|
|
16013
|
+
const r = await resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"), signal);
|
|
16014
|
+
if (r instanceof Response)
|
|
16015
|
+
return r;
|
|
16016
|
+
const table = url.searchParams.get("table");
|
|
16017
|
+
if (!table)
|
|
16018
|
+
return textError("missing table parameter", 400);
|
|
16019
|
+
try {
|
|
16020
|
+
const adapter = await getAdapter(r, cwd, signal);
|
|
16021
|
+
const { result, executedSql } = await captureSql(async () => {
|
|
16022
|
+
const db = asAsync(adapter);
|
|
16023
|
+
const tables = await db.tables(signal);
|
|
16024
|
+
const entry = tables.find((candidate) => candidate.name === table);
|
|
16025
|
+
if (!entry)
|
|
16026
|
+
throw new Error(`unknown table: ${table}`);
|
|
16027
|
+
if (entry.type !== "table")
|
|
16028
|
+
return { rowCount: null };
|
|
16029
|
+
const counts = await db.tableRowCounts([table], signal);
|
|
16030
|
+
return { rowCount: counts.get(table) ?? null };
|
|
16031
|
+
});
|
|
16032
|
+
const body = {
|
|
16033
|
+
dbId: r.dbId,
|
|
16034
|
+
...r.schema ? { schema: r.schema } : {},
|
|
16035
|
+
table,
|
|
16036
|
+
rowCount: result.rowCount,
|
|
16037
|
+
executedSql
|
|
16038
|
+
};
|
|
16039
|
+
return json(body);
|
|
16040
|
+
} catch (err) {
|
|
16041
|
+
return handleError("database", "read table count", err, signal);
|
|
16042
|
+
}
|
|
16043
|
+
}
|
|
15440
16044
|
function makeHistoryId() {
|
|
15441
16045
|
return makeId("qh");
|
|
15442
16046
|
}
|
|
@@ -16365,6 +16969,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
16365
16969
|
methods: ["GET"],
|
|
16366
16970
|
handler: () => handleTable(cwd, url, omitDirNames, req.signal)
|
|
16367
16971
|
},
|
|
16972
|
+
"/_db/table-count": {
|
|
16973
|
+
methods: ["GET"],
|
|
16974
|
+
handler: () => handleTableCount(cwd, url, omitDirNames, req.signal)
|
|
16975
|
+
},
|
|
16368
16976
|
"/_db/columns": {
|
|
16369
16977
|
methods: ["GET"],
|
|
16370
16978
|
handler: () => handleColumns(cwd, url, omitDirNames, req.signal)
|
|
@@ -16527,8 +17135,8 @@ var init_handle = __esm(() => {
|
|
|
16527
17135
|
});
|
|
16528
17136
|
|
|
16529
17137
|
// web-src/server/doctor.ts
|
|
16530
|
-
import { accessSync, constants, readFileSync as readFileSync5, statSync as
|
|
16531
|
-
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";
|
|
16532
17140
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16533
17141
|
function statusWorse(a, b) {
|
|
16534
17142
|
const rank = { ok: 0, warn: 1, error: 2 };
|
|
@@ -16617,12 +17225,12 @@ function findCodeViewerPackageJson() {
|
|
|
16617
17225
|
try {
|
|
16618
17226
|
let cursor;
|
|
16619
17227
|
try {
|
|
16620
|
-
cursor =
|
|
17228
|
+
cursor = dirname4(fileURLToPath2(import.meta.url));
|
|
16621
17229
|
} catch {
|
|
16622
|
-
cursor =
|
|
17230
|
+
cursor = dirname4(process.argv[1] || ".");
|
|
16623
17231
|
}
|
|
16624
17232
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
16625
|
-
const candidate =
|
|
17233
|
+
const candidate = join14(cursor, "package.json");
|
|
16626
17234
|
try {
|
|
16627
17235
|
const raw = readFileSync5(candidate, "utf8");
|
|
16628
17236
|
const pkg = JSON.parse(raw);
|
|
@@ -16630,7 +17238,7 @@ function findCodeViewerPackageJson() {
|
|
|
16630
17238
|
return { version: pkg.version, path: candidate };
|
|
16631
17239
|
}
|
|
16632
17240
|
} catch {}
|
|
16633
|
-
const next =
|
|
17241
|
+
const next = dirname4(cursor);
|
|
16634
17242
|
if (next === cursor)
|
|
16635
17243
|
break;
|
|
16636
17244
|
cursor = next;
|
|
@@ -16718,9 +17326,9 @@ async function checkSqlite(cwd) {
|
|
|
16718
17326
|
return { id: "sqlite", title: "SQLite driver", rows };
|
|
16719
17327
|
}
|
|
16720
17328
|
async function trySnapshotDbOpen(cwd) {
|
|
16721
|
-
const dbPath =
|
|
17329
|
+
const dbPath = join14(cwd, SNAPSHOT_DB_REL);
|
|
16722
17330
|
try {
|
|
16723
|
-
|
|
17331
|
+
statSync5(dbPath);
|
|
16724
17332
|
} catch {
|
|
16725
17333
|
return { kind: "skipped" };
|
|
16726
17334
|
}
|
|
@@ -16739,17 +17347,17 @@ async function trySnapshotDbOpen(cwd) {
|
|
|
16739
17347
|
}
|
|
16740
17348
|
}
|
|
16741
17349
|
function checkSnapshotStore(cwd) {
|
|
16742
|
-
const dbPath =
|
|
16743
|
-
const dir =
|
|
17350
|
+
const dbPath = join14(cwd, SNAPSHOT_DB_REL);
|
|
17351
|
+
const dir = dirname4(dbPath);
|
|
16744
17352
|
let dirStatus = "ok";
|
|
16745
17353
|
let dirDetail = dir;
|
|
16746
17354
|
let dirHint;
|
|
16747
17355
|
try {
|
|
16748
|
-
|
|
17356
|
+
accessSync2(dir, constants2.W_OK);
|
|
16749
17357
|
dirDetail = `${dir} (writable)`;
|
|
16750
17358
|
} catch {
|
|
16751
17359
|
try {
|
|
16752
|
-
|
|
17360
|
+
statSync5(dir);
|
|
16753
17361
|
dirStatus = "error";
|
|
16754
17362
|
dirDetail = `${dir} (not writable)`;
|
|
16755
17363
|
dirHint = "Snapshot creation will fail until the directory is writable. " + "Check filesystem permissions on the .code-viewer directory.";
|
|
@@ -16759,7 +17367,7 @@ function checkSnapshotStore(cwd) {
|
|
|
16759
17367
|
}
|
|
16760
17368
|
let dbDetail = dbPath;
|
|
16761
17369
|
try {
|
|
16762
|
-
const stat2 =
|
|
17370
|
+
const stat2 = statSync5(dbPath);
|
|
16763
17371
|
dbDetail = `${dbPath} (${stat2.size.toLocaleString()} bytes)`;
|
|
16764
17372
|
} catch {
|
|
16765
17373
|
dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
|
|
@@ -16785,7 +17393,7 @@ function checkSnapshotStore(cwd) {
|
|
|
16785
17393
|
};
|
|
16786
17394
|
}
|
|
16787
17395
|
async function checkGit(cwd, signal) {
|
|
16788
|
-
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);
|
|
16789
17397
|
if (!versionRes || versionRes.code !== 0) {
|
|
16790
17398
|
return {
|
|
16791
17399
|
id: "git",
|
|
@@ -16795,7 +17403,7 @@ async function checkGit(cwd, signal) {
|
|
|
16795
17403
|
id: "git.binary",
|
|
16796
17404
|
title: "git binary",
|
|
16797
17405
|
status: "error",
|
|
16798
|
-
detail: "
|
|
17406
|
+
detail: versionRes && isCommandNotFoundResult("git", versionRes) ? commandNotFoundDetail("git") : "git command failed",
|
|
16799
17407
|
hint: "git is required for diff, history, and blame features. Install git and ensure it is on PATH."
|
|
16800
17408
|
}
|
|
16801
17409
|
]
|
|
@@ -16809,11 +17417,11 @@ async function checkGit(cwd, signal) {
|
|
|
16809
17417
|
detail: firstLine2(versionRes.stdout)
|
|
16810
17418
|
}
|
|
16811
17419
|
];
|
|
16812
|
-
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);
|
|
16813
17421
|
if (repoCheck && repoCheck.code === 0 && /true/.test(repoCheck.stdout)) {
|
|
16814
|
-
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);
|
|
16815
17423
|
const top = topRes?.stdout.trim() || cwd;
|
|
16816
|
-
const insideCwd =
|
|
17424
|
+
const insideCwd = relative5(top, cwd) || ".";
|
|
16817
17425
|
rows.push({
|
|
16818
17426
|
id: "git.repo",
|
|
16819
17427
|
title: "Working tree",
|
|
@@ -16832,10 +17440,10 @@ async function checkGit(cwd, signal) {
|
|
|
16832
17440
|
return { id: "git", title: "Git", rows };
|
|
16833
17441
|
}
|
|
16834
17442
|
async function detectComposeBinary(signal) {
|
|
16835
|
-
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);
|
|
16836
17444
|
if (v2 && v2.code === 0) {
|
|
16837
17445
|
return {
|
|
16838
|
-
cmd: { binary: "docker", subcommand: ["compose"] },
|
|
17446
|
+
cmd: { binary: commandForExternal("docker"), subcommand: ["compose"] },
|
|
16839
17447
|
v2Version: firstLine2(v2.stdout)
|
|
16840
17448
|
};
|
|
16841
17449
|
}
|
|
@@ -16888,13 +17496,13 @@ async function checkDocker(signal, discoveryResult) {
|
|
|
16888
17496
|
const summary = summarizeDockerSources(discoveryResult);
|
|
16889
17497
|
const dockerSourcesPresent = summary.total > 0;
|
|
16890
17498
|
const rows = [];
|
|
16891
|
-
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);
|
|
16892
17500
|
const dockerOk = dockerVersion?.code === 0;
|
|
16893
17501
|
rows.push({
|
|
16894
17502
|
id: "docker.binary",
|
|
16895
17503
|
title: "docker CLI",
|
|
16896
17504
|
status: dockerOk ? "ok" : dockerSourcesPresent ? "error" : "warn",
|
|
16897
|
-
detail: dockerOk ? firstLine2(dockerVersion.stdout) : "
|
|
17505
|
+
detail: dockerOk ? firstLine2(dockerVersion.stdout) : dockerVersion && isCommandNotFoundResult("docker", dockerVersion) ? commandNotFoundDetail("docker") : "docker command failed",
|
|
16898
17506
|
...dockerOk ? {} : {
|
|
16899
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."
|
|
16900
17508
|
}
|
|
@@ -16925,7 +17533,7 @@ async function checkDocker(signal, discoveryResult) {
|
|
|
16925
17533
|
hint: "docker-compose v1 standalone is legacy. Prefer the Docker Compose v2 plugin (`docker compose ...`)."
|
|
16926
17534
|
});
|
|
16927
17535
|
}
|
|
16928
|
-
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;
|
|
16929
17537
|
if (dockerOk) {
|
|
16930
17538
|
if (dockerInfo && dockerInfo.code === 0) {
|
|
16931
17539
|
rows.push({
|
|
@@ -17453,6 +18061,7 @@ async function handleDoctor(ctx) {
|
|
|
17453
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;
|
|
17454
18062
|
var init_doctor = __esm(() => {
|
|
17455
18063
|
init_cli_helpers();
|
|
18064
|
+
init_command_resolver();
|
|
17456
18065
|
init_docker();
|
|
17457
18066
|
init_elasticsearch();
|
|
17458
18067
|
init_redis();
|
|
@@ -17500,6 +18109,7 @@ function parseDoctorCliArgs(argv) {
|
|
|
17500
18109
|
let cwd = process.cwd();
|
|
17501
18110
|
let port = 0;
|
|
17502
18111
|
let json2 = false;
|
|
18112
|
+
const commandOverrides = [];
|
|
17503
18113
|
for (let i = 0;i < argv.length; i++) {
|
|
17504
18114
|
const arg = argv[i];
|
|
17505
18115
|
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
@@ -17519,6 +18129,23 @@ function parseDoctorCliArgs(argv) {
|
|
|
17519
18129
|
cwd = next;
|
|
17520
18130
|
continue;
|
|
17521
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
|
+
}
|
|
17522
18149
|
if (arg === "--port") {
|
|
17523
18150
|
const next = argv[++i];
|
|
17524
18151
|
if (!next)
|
|
@@ -17535,7 +18162,15 @@ function parseDoctorCliArgs(argv) {
|
|
|
17535
18162
|
}
|
|
17536
18163
|
return { kind: "error", message: `unknown argument: ${arg}` };
|
|
17537
18164
|
}
|
|
17538
|
-
return {
|
|
18165
|
+
return {
|
|
18166
|
+
kind: "run",
|
|
18167
|
+
args: {
|
|
18168
|
+
cwd,
|
|
18169
|
+
port,
|
|
18170
|
+
json: json2,
|
|
18171
|
+
...commandOverrides.length ? { commandOverrides } : {}
|
|
18172
|
+
}
|
|
18173
|
+
};
|
|
17539
18174
|
}
|
|
17540
18175
|
function formatDoctorReportText(report) {
|
|
17541
18176
|
const lines = [];
|
|
@@ -17586,7 +18221,17 @@ async function runDoctorCli(argv) {
|
|
|
17586
18221
|
`);
|
|
17587
18222
|
return;
|
|
17588
18223
|
}
|
|
17589
|
-
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
|
+
}
|
|
17590
18235
|
const report = await buildDoctorReport({
|
|
17591
18236
|
cwd,
|
|
17592
18237
|
scopeOmitDirNames: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
|
|
@@ -17606,12 +18251,13 @@ async function runDoctorCli(argv) {
|
|
|
17606
18251
|
var DOCTOR_HELP = `code-viewer doctor — diagnose the current environment
|
|
17607
18252
|
|
|
17608
18253
|
Usage:
|
|
17609
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json]
|
|
18254
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
17610
18255
|
code-viewer doctor agent-help
|
|
17611
18256
|
|
|
17612
18257
|
Options:
|
|
17613
18258
|
--cwd <path> Working directory to inspect (default: process.cwd()).
|
|
17614
18259
|
--port <N> Listening port to mention in the report (default: 0 = no server).
|
|
18260
|
+
--bin <n>=<p> Override git/docker executable path. Repeatable.
|
|
17615
18261
|
--json Print the full DoctorReport as JSON instead of a summary.
|
|
17616
18262
|
--help, -h Show this help.
|
|
17617
18263
|
|
|
@@ -17623,6 +18269,7 @@ Exit codes:
|
|
|
17623
18269
|
2 invalid arguments
|
|
17624
18270
|
`, STATUS_SYMBOL;
|
|
17625
18271
|
var init_doctor_cli = __esm(() => {
|
|
18272
|
+
init_command_resolver();
|
|
17626
18273
|
init_doctor();
|
|
17627
18274
|
init_git();
|
|
17628
18275
|
STATUS_SYMBOL = {
|
|
@@ -17651,7 +18298,7 @@ function normalizeNewDirectoryName(name) {
|
|
|
17651
18298
|
|
|
17652
18299
|
// web-src/server/cache.ts
|
|
17653
18300
|
import { lstatSync as lstatSync3 } from "node:fs";
|
|
17654
|
-
import { join as
|
|
18301
|
+
import { join as join15 } from "node:path";
|
|
17655
18302
|
function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
|
|
17656
18303
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
17657
18304
|
}
|
|
@@ -17666,7 +18313,7 @@ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MA
|
|
|
17666
18313
|
}
|
|
17667
18314
|
function worktreeFileSignature(path, cwd) {
|
|
17668
18315
|
try {
|
|
17669
|
-
const stats = lstatSync3(
|
|
18316
|
+
const stats = lstatSync3(join15(cwd, path));
|
|
17670
18317
|
const inode = "ino" in stats ? stats.ino : 0;
|
|
17671
18318
|
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
17672
18319
|
} catch {
|
|
@@ -17712,12 +18359,12 @@ function startDevAssetReload(options) {
|
|
|
17712
18359
|
var init_dev_assets = () => {};
|
|
17713
18360
|
|
|
17714
18361
|
// web-src/server/search-service.ts
|
|
17715
|
-
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync6, realpathSync as
|
|
17716
|
-
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";
|
|
17717
18364
|
function rgAvailable(cwd) {
|
|
17718
18365
|
if (rgAvailableCache !== null)
|
|
17719
18366
|
return rgAvailableCache;
|
|
17720
|
-
const proc = runSync(["rg", "--version"], cwd);
|
|
18367
|
+
const proc = runSync([commandForExternal("rg"), "--version"], cwd);
|
|
17721
18368
|
rgAvailableCache = proc.code === 0;
|
|
17722
18369
|
return rgAvailableCache;
|
|
17723
18370
|
}
|
|
@@ -17734,18 +18381,18 @@ function safeWorktreePath(env, path) {
|
|
|
17734
18381
|
return null;
|
|
17735
18382
|
if (isGitInternalPath(path))
|
|
17736
18383
|
return null;
|
|
17737
|
-
const full =
|
|
18384
|
+
const full = join16(env.cwd, path);
|
|
17738
18385
|
if (!existsSync7(full))
|
|
17739
18386
|
return null;
|
|
17740
18387
|
let realCwd;
|
|
17741
18388
|
let realFull;
|
|
17742
18389
|
try {
|
|
17743
|
-
realCwd =
|
|
17744
|
-
realFull =
|
|
18390
|
+
realCwd = realpathSync5(env.cwd);
|
|
18391
|
+
realFull = realpathSync5(full);
|
|
17745
18392
|
} catch {
|
|
17746
18393
|
return null;
|
|
17747
18394
|
}
|
|
17748
|
-
const rel =
|
|
18395
|
+
const rel = relative6(realCwd, realFull);
|
|
17749
18396
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/") || rel.startsWith("\\"))
|
|
17750
18397
|
return null;
|
|
17751
18398
|
if (isGitInternalPath(rel))
|
|
@@ -17795,6 +18442,7 @@ function grepWorktree(env, req) {
|
|
|
17795
18442
|
if (rgAvailable(env.cwd)) {
|
|
17796
18443
|
const safePaths = paths.filter((path) => safeWorktreePath(env, path));
|
|
17797
18444
|
const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
|
|
18445
|
+
args[0] = commandForExternal("rg");
|
|
17798
18446
|
const proc = runSync(args, env.cwd, { timeout: 5000 });
|
|
17799
18447
|
const stdout = proc.stdout;
|
|
17800
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));
|
|
@@ -17824,7 +18472,7 @@ function grepWorktree(env, req) {
|
|
|
17824
18472
|
function grepTreeRef(env, req) {
|
|
17825
18473
|
const safePaths = filterCallerPaths(env, req.paths);
|
|
17826
18474
|
const args = [
|
|
17827
|
-
"git",
|
|
18475
|
+
commandForExternal("git"),
|
|
17828
18476
|
"-c",
|
|
17829
18477
|
"core.quotepath=false",
|
|
17830
18478
|
"grep",
|
|
@@ -17850,6 +18498,17 @@ function grepTreeRef(env, req) {
|
|
|
17850
18498
|
};
|
|
17851
18499
|
}
|
|
17852
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
|
+
}
|
|
17853
18512
|
if (!req.query.trim()) {
|
|
17854
18513
|
return {
|
|
17855
18514
|
ok: true,
|
|
@@ -17861,24 +18520,32 @@ function grepRepo(env, req) {
|
|
|
17861
18520
|
}
|
|
17862
18521
|
};
|
|
17863
18522
|
}
|
|
17864
|
-
if (
|
|
18523
|
+
if (isWorktree) {
|
|
17865
18524
|
return { ok: true, value: grepWorktree(env, req) };
|
|
17866
18525
|
}
|
|
17867
|
-
if (!verifyTreeRef(req.ref, env.cwd)) {
|
|
17868
|
-
return { ok: false, error: "invalid target" };
|
|
17869
|
-
}
|
|
17870
18526
|
return { ok: true, value: grepTreeRef(env, req) };
|
|
17871
18527
|
}
|
|
17872
18528
|
function listRepoFiles(env, ref, generation) {
|
|
17873
|
-
if (ref !== "worktree" &&
|
|
17874
|
-
|
|
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
|
+
}
|
|
17875
18538
|
}
|
|
17876
18539
|
const effectiveRef = ref || "worktree";
|
|
17877
|
-
const
|
|
18540
|
+
const tree = listTreeResult(effectiveRef, "", env.cwd, {
|
|
17878
18541
|
recursive: true,
|
|
17879
18542
|
omitDirNames: env.omitDirNames,
|
|
17880
18543
|
excludeNames: env.excludeNames
|
|
17881
|
-
})
|
|
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));
|
|
17882
18549
|
return {
|
|
17883
18550
|
ok: true,
|
|
17884
18551
|
value: buildFileSearchList(effectiveRef, generation, entries)
|
|
@@ -17886,6 +18553,7 @@ function listRepoFiles(env, ref, generation) {
|
|
|
17886
18553
|
}
|
|
17887
18554
|
var rgAvailableCache = null;
|
|
17888
18555
|
var init_search_service = __esm(() => {
|
|
18556
|
+
init_command_resolver();
|
|
17889
18557
|
init_git();
|
|
17890
18558
|
init_runtime();
|
|
17891
18559
|
init_search();
|
|
@@ -17893,7 +18561,7 @@ var init_search_service = __esm(() => {
|
|
|
17893
18561
|
|
|
17894
18562
|
// web-src/server/mcp.ts
|
|
17895
18563
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
17896
|
-
import { join as
|
|
18564
|
+
import { join as join17 } from "node:path";
|
|
17897
18565
|
function defaultMcpTools(options = {}) {
|
|
17898
18566
|
return [
|
|
17899
18567
|
{
|
|
@@ -18432,7 +19100,10 @@ function runStatusTool(input, defaultCwd) {
|
|
|
18432
19100
|
}
|
|
18433
19101
|
try {
|
|
18434
19102
|
const report = buildStatusReport({ root: resolved.root, ref, limit });
|
|
18435
|
-
return {
|
|
19103
|
+
return {
|
|
19104
|
+
text: JSON.stringify(report, null, 2),
|
|
19105
|
+
isError: !!(report.changed.error || report.staged.error)
|
|
19106
|
+
};
|
|
18436
19107
|
} catch (err) {
|
|
18437
19108
|
const detail = err instanceof Error ? err.message : String(err);
|
|
18438
19109
|
return { text: `status failed: ${detail}`, isError: true };
|
|
@@ -19364,7 +20035,7 @@ var init_mcp = __esm(() => {
|
|
|
19364
20035
|
init_search_cli();
|
|
19365
20036
|
init_search_service();
|
|
19366
20037
|
init_status_cli();
|
|
19367
|
-
PACKAGE_VERSION = JSON.parse(readFileSync7(
|
|
20038
|
+
PACKAGE_VERSION = JSON.parse(readFileSync7(join17(ROOT, "package.json"), "utf8")).version;
|
|
19368
20039
|
MCP_SERVER_INFO = {
|
|
19369
20040
|
name: "code-viewer",
|
|
19370
20041
|
title: "code-viewer",
|
|
@@ -19446,21 +20117,21 @@ var init_state_route = __esm(() => {
|
|
|
19446
20117
|
var exports_preview = {};
|
|
19447
20118
|
import {
|
|
19448
20119
|
closeSync as closeSync2,
|
|
19449
|
-
constants as
|
|
20120
|
+
constants as constants3,
|
|
19450
20121
|
existsSync as existsSync8,
|
|
19451
20122
|
lstatSync as lstatSync5,
|
|
19452
20123
|
mkdirSync as mkdirSync4,
|
|
19453
20124
|
openSync as openSync2,
|
|
19454
20125
|
readFileSync as readFileSync8,
|
|
19455
|
-
realpathSync as
|
|
20126
|
+
realpathSync as realpathSync6,
|
|
19456
20127
|
renameSync,
|
|
19457
|
-
statSync as
|
|
20128
|
+
statSync as statSync6,
|
|
19458
20129
|
unlinkSync as unlinkSync2,
|
|
19459
20130
|
watch,
|
|
19460
20131
|
writeFileSync as writeFileSync2
|
|
19461
20132
|
} from "node:fs";
|
|
19462
20133
|
import { homedir as homedir3 } from "node:os";
|
|
19463
|
-
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";
|
|
19464
20135
|
function parseCli() {
|
|
19465
20136
|
const rest = [];
|
|
19466
20137
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -19469,15 +20140,15 @@ function parseCli() {
|
|
|
19469
20140
|
console.log(`code-viewer ${VERSION}
|
|
19470
20141
|
|
|
19471
20142
|
Usage:
|
|
19472
|
-
code-viewer [--cwd <repo>] [--port <port>] [--open] [git-diff-args...]
|
|
19473
|
-
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]
|
|
19474
20145
|
code-viewer annotate <start|add|add-db|rename|edit|move|list|delete|clear> [options]
|
|
19475
|
-
code-viewer query <sources|schemas|schema|columns|ddl|exec|list|clear|snapshot|diff|search|redis|elasticsearch|s3> [options]
|
|
19476
|
-
code-viewer search code --term <text> [--ref <ref>] [--path <p>...] [--regex] [--max <n>] [--json]
|
|
19477
|
-
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json]
|
|
19478
|
-
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>]
|
|
19479
20150
|
code-viewer skill install [--agent <list>] [--global]
|
|
19480
|
-
code-viewer doctor [--cwd <path>] [--port <N>] [--json]
|
|
20151
|
+
code-viewer doctor [--cwd <path>] [--port <N>] [--json] [--bin <git|docker>=<path>]
|
|
19481
20152
|
code-viewer agent-help
|
|
19482
20153
|
code-viewer help
|
|
19483
20154
|
|
|
@@ -19513,9 +20184,8 @@ Examples:
|
|
|
19513
20184
|
process.exit(1);
|
|
19514
20185
|
}
|
|
19515
20186
|
try {
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
cwd = candidate === nextReal ? candidate : nextReal;
|
|
20187
|
+
cwd = realpathSync6(next);
|
|
20188
|
+
cwdWasExplicit = true;
|
|
19519
20189
|
} catch {
|
|
19520
20190
|
console.error("--cwd must point to an existing directory");
|
|
19521
20191
|
process.exit(1);
|
|
@@ -19530,6 +20200,18 @@ Examples:
|
|
|
19530
20200
|
listenPort = parsed;
|
|
19531
20201
|
} else if (arg === "--open") {
|
|
19532
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);
|
|
19533
20215
|
} else if (arg === "--allow-upload") {} else if (arg === "--scope-omit-dir") {
|
|
19534
20216
|
const next = process.argv[++i];
|
|
19535
20217
|
if (!next) {
|
|
@@ -19546,6 +20228,21 @@ Examples:
|
|
|
19546
20228
|
}
|
|
19547
20229
|
if (rest.length)
|
|
19548
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
|
+
}
|
|
19549
20246
|
warnIfLegacyConfigPresent();
|
|
19550
20247
|
if (scopeOmitDirCliOverride) {
|
|
19551
20248
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
@@ -19554,7 +20251,7 @@ Examples:
|
|
|
19554
20251
|
}
|
|
19555
20252
|
function warnIfLegacyConfigPresent() {
|
|
19556
20253
|
try {
|
|
19557
|
-
if (existsSync8(
|
|
20254
|
+
if (existsSync8(join18(cwd, ".code-viewer.json"))) {
|
|
19558
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.");
|
|
19559
20256
|
}
|
|
19560
20257
|
} catch {}
|
|
@@ -19661,7 +20358,7 @@ function staticFile(pathname) {
|
|
|
19661
20358
|
const spec = map[pathname];
|
|
19662
20359
|
if (!spec)
|
|
19663
20360
|
return null;
|
|
19664
|
-
const full =
|
|
20361
|
+
const full = join18(WEB_ROOT, spec[0]);
|
|
19665
20362
|
if (!existsSync8(full))
|
|
19666
20363
|
return text("not found", 404);
|
|
19667
20364
|
return new Response(readFileSync8(full), {
|
|
@@ -19669,17 +20366,17 @@ function staticFile(pathname) {
|
|
|
19669
20366
|
});
|
|
19670
20367
|
}
|
|
19671
20368
|
function buildRangeArgs(range) {
|
|
19672
|
-
const
|
|
20369
|
+
const refs = [];
|
|
19673
20370
|
if (range.from && range.from !== "worktree")
|
|
19674
|
-
|
|
20371
|
+
refs.push(range.from);
|
|
19675
20372
|
if (range.to && range.to !== "worktree")
|
|
19676
|
-
|
|
19677
|
-
return { args:
|
|
20373
|
+
refs.push(range.to);
|
|
20374
|
+
return { args: refs.length ? refs : cliArgs, refs };
|
|
19678
20375
|
}
|
|
19679
|
-
function includeUntracked(range,
|
|
20376
|
+
function includeUntracked(range, refs) {
|
|
19680
20377
|
const toWorktree = !range.to || range.to === "worktree";
|
|
19681
|
-
if (
|
|
19682
|
-
return toWorktree &&
|
|
20378
|
+
if (refs.length > 0)
|
|
20379
|
+
return toWorktree && refs.length < 2;
|
|
19683
20380
|
return cliArgs.length === 0 || cliArgs.length === 1 && cliArgs[0] === "HEAD";
|
|
19684
20381
|
}
|
|
19685
20382
|
function guessMediaKind(path) {
|
|
@@ -19766,11 +20463,13 @@ function computePayload(extras, range, pathFilter = "") {
|
|
|
19766
20463
|
generation
|
|
19767
20464
|
};
|
|
19768
20465
|
}
|
|
19769
|
-
const { args, refs
|
|
20466
|
+
const { args, refs } = buildRangeArgs(range);
|
|
19770
20467
|
const fullArgs = [...extras, ...args];
|
|
19771
|
-
const
|
|
19772
|
-
|
|
20468
|
+
const metaResult = fileMetaResult(fullArgs, cwd, false);
|
|
20469
|
+
const files = metaResult.files;
|
|
20470
|
+
if (!metaResult.error && includeUntracked(range, refs)) {
|
|
19773
20471
|
files.push(...untrackedMeta(cwd));
|
|
20472
|
+
}
|
|
19774
20473
|
const filteredFiles = pathFilter ? files.filter((file) => file.path === pathFilter || file.old_path === pathFilter) : files;
|
|
19775
20474
|
filteredFiles.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
19776
20475
|
filteredFiles.forEach((file, i) => {
|
|
@@ -19790,14 +20489,15 @@ function computePayload(extras, range, pathFilter = "") {
|
|
|
19790
20489
|
return acc;
|
|
19791
20490
|
}, { files: meta.length, additions: 0, deletions: 0 });
|
|
19792
20491
|
const toWorktree = !range.to || range.to === "worktree";
|
|
19793
|
-
const label =
|
|
20492
|
+
const label = refs.length ? `${refs.join(" .. ")}${toWorktree && refs.length === 1 ? " .. worktree" : ""}` : cliArgs.join(" ");
|
|
19794
20493
|
return {
|
|
19795
20494
|
files: meta,
|
|
19796
20495
|
totals,
|
|
19797
20496
|
range: label || "HEAD",
|
|
19798
20497
|
project: basename3(cwd),
|
|
19799
20498
|
branch: currentBranch(cwd) || undefined,
|
|
19800
|
-
generation
|
|
20499
|
+
generation,
|
|
20500
|
+
...metaResult.error ? { error: metaResult.error } : {}
|
|
19801
20501
|
};
|
|
19802
20502
|
}
|
|
19803
20503
|
function handleDiffJson(url) {
|
|
@@ -19913,12 +20613,12 @@ function safeWorktreePath2(path) {
|
|
|
19913
20613
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
19914
20614
|
}
|
|
19915
20615
|
function worktreePath(path) {
|
|
19916
|
-
return
|
|
20616
|
+
return join18(cwd, path);
|
|
19917
20617
|
}
|
|
19918
20618
|
function safeOpenWorktreePath(path) {
|
|
19919
20619
|
if (path === "") {
|
|
19920
20620
|
try {
|
|
19921
|
-
const realCwd =
|
|
20621
|
+
const realCwd = realpathSync6(cwd);
|
|
19922
20622
|
if (isGitInternalPath(realCwd))
|
|
19923
20623
|
return null;
|
|
19924
20624
|
return realCwd;
|
|
@@ -19929,7 +20629,7 @@ function safeOpenWorktreePath(path) {
|
|
|
19929
20629
|
return safeWorktreePath2(path);
|
|
19930
20630
|
}
|
|
19931
20631
|
function parentRepoPath(path) {
|
|
19932
|
-
const parent =
|
|
20632
|
+
const parent = dirname5(path);
|
|
19933
20633
|
return parent === "." ? "" : parent;
|
|
19934
20634
|
}
|
|
19935
20635
|
function isoDate(ms) {
|
|
@@ -19940,7 +20640,7 @@ function worktreeFileMetadata(path, knownSize) {
|
|
|
19940
20640
|
if (!full)
|
|
19941
20641
|
return {};
|
|
19942
20642
|
try {
|
|
19943
|
-
const stat2 =
|
|
20643
|
+
const stat2 = statSync6(full);
|
|
19944
20644
|
return {
|
|
19945
20645
|
size: knownSize ?? stat2.size,
|
|
19946
20646
|
created_at: isoDate(stat2.birthtimeMs),
|
|
@@ -19965,7 +20665,7 @@ function directoryMetadata(target, path) {
|
|
|
19965
20665
|
if (!full)
|
|
19966
20666
|
return {};
|
|
19967
20667
|
try {
|
|
19968
|
-
const stat2 =
|
|
20668
|
+
const stat2 = statSync6(full);
|
|
19969
20669
|
return {
|
|
19970
20670
|
created_at: isoDate(stat2.birthtimeMs),
|
|
19971
20671
|
updated_at: isoDate(stat2.mtimeMs)
|
|
@@ -19983,6 +20683,8 @@ function fileMetadataForTarget(target, path) {
|
|
|
19983
20683
|
function attachTreeEntryMetadata(target, entry) {
|
|
19984
20684
|
if (entry.type === "tree")
|
|
19985
20685
|
return { ...entry, ...directoryMetadata(target, entry.path) };
|
|
20686
|
+
if (entry.type === "commit" && !entry.submodule && (target === "worktree" || target === ""))
|
|
20687
|
+
return { ...entry, ...directoryMetadata(target, entry.path) };
|
|
19986
20688
|
if (entry.type !== "blob")
|
|
19987
20689
|
return entry;
|
|
19988
20690
|
return { ...entry, ...fileMetadataForTarget(target, entry.path) };
|
|
@@ -20014,19 +20716,25 @@ function handleTree(url) {
|
|
|
20014
20716
|
return text("invalid path", 400);
|
|
20015
20717
|
if ((target === "worktree" || target === "") && isGitInternalPath(path))
|
|
20016
20718
|
return text("forbidden", 403);
|
|
20017
|
-
if (target !== "worktree"
|
|
20018
|
-
|
|
20719
|
+
if (target !== "worktree") {
|
|
20720
|
+
const refCheck = verifyTreeRefResult(target, cwd);
|
|
20721
|
+
if (refCheck.ok !== true)
|
|
20722
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20723
|
+
}
|
|
20019
20724
|
const recursive = url.searchParams.get("recursive") === "1";
|
|
20020
20725
|
if (invalidScopeOmitDirNamesQuery(url))
|
|
20021
20726
|
return text("invalid omit dirs", 400);
|
|
20022
20727
|
if (invalidScopeExcludeNamesQuery(url))
|
|
20023
20728
|
return text("invalid exclude names", 400);
|
|
20024
20729
|
const excludeNames = scopeExcludeNamesFromQuery(url);
|
|
20025
|
-
const
|
|
20730
|
+
const tree = listTreeResult(target, path, cwd, {
|
|
20026
20731
|
recursive,
|
|
20027
20732
|
omitDirNames: scopeOmitDirNamesFromQuery(url),
|
|
20028
20733
|
excludeNames
|
|
20029
|
-
})
|
|
20734
|
+
});
|
|
20735
|
+
if (tree.error)
|
|
20736
|
+
return text(tree.error, tree.status ?? 500);
|
|
20737
|
+
const entries = tree.entries.filter((entry) => !isExcludedScopePath(entry.path, excludeNames));
|
|
20030
20738
|
return json2({
|
|
20031
20739
|
ref: target,
|
|
20032
20740
|
path,
|
|
@@ -20090,7 +20798,7 @@ function handleFiles2(url) {
|
|
|
20090
20798
|
return json2(cached.body);
|
|
20091
20799
|
const result = listRepoFiles(currentSearchEnv(omitDirNames, excludeNames), target, generation);
|
|
20092
20800
|
if (result.ok !== true)
|
|
20093
|
-
return text(result.error, 400);
|
|
20801
|
+
return text(result.error, result.status ?? 400);
|
|
20094
20802
|
fileListCache.set(key, { generation, body: result.value });
|
|
20095
20803
|
return json2(result.value);
|
|
20096
20804
|
}
|
|
@@ -20114,7 +20822,7 @@ function handleGrep(url) {
|
|
|
20114
20822
|
max
|
|
20115
20823
|
});
|
|
20116
20824
|
if (result.ok !== true)
|
|
20117
|
-
return text(result.error, 400);
|
|
20825
|
+
return text(result.error, result.status ?? 400);
|
|
20118
20826
|
return json2(result.value);
|
|
20119
20827
|
}
|
|
20120
20828
|
function handleRefCommits(url) {
|
|
@@ -20123,7 +20831,10 @@ function handleRefCommits(url) {
|
|
|
20123
20831
|
const parsedSkip = Number(url.searchParams.get("skip") || "0");
|
|
20124
20832
|
const max = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : undefined;
|
|
20125
20833
|
const skip = Number.isFinite(parsedSkip) && parsedSkip > 0 ? parsedSkip : undefined;
|
|
20126
|
-
|
|
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 });
|
|
20127
20838
|
}
|
|
20128
20839
|
function handleLog(url) {
|
|
20129
20840
|
const ref = url.searchParams.get("ref") || "HEAD";
|
|
@@ -20140,23 +20851,13 @@ function handleLog(url) {
|
|
|
20140
20851
|
...path ? { path } : {}
|
|
20141
20852
|
});
|
|
20142
20853
|
if (result.error)
|
|
20143
|
-
return text(result.error, 400);
|
|
20854
|
+
return text(result.error, result.status ?? 400);
|
|
20144
20855
|
const wantsWorktreeHead = path && skip === 0 && (ref === "worktree" || url.searchParams.get("worktree") === "1");
|
|
20145
20856
|
let commits = result.commits;
|
|
20146
20857
|
let hasWorktree = false;
|
|
20147
20858
|
if (wantsWorktreeHead) {
|
|
20148
|
-
const status =
|
|
20149
|
-
|
|
20150
|
-
"-c",
|
|
20151
|
-
"core.quotepath=false",
|
|
20152
|
-
"status",
|
|
20153
|
-
"--porcelain=v1",
|
|
20154
|
-
"-z",
|
|
20155
|
-
"--untracked-files=normal",
|
|
20156
|
-
"--",
|
|
20157
|
-
path
|
|
20158
|
-
], cwd);
|
|
20159
|
-
if (status.code === 0 && status.stdout.length > 0) {
|
|
20859
|
+
const status = statusPorcelainForPath(path, cwd);
|
|
20860
|
+
if (status.ok && status.stdout.length > 0) {
|
|
20160
20861
|
const parts = status.stdout.split("\x00").filter(Boolean);
|
|
20161
20862
|
if (parts.length > 0) {
|
|
20162
20863
|
hasWorktree = true;
|
|
@@ -20183,7 +20884,7 @@ function handleLog(url) {
|
|
|
20183
20884
|
}
|
|
20184
20885
|
function blamePathKey(p) {
|
|
20185
20886
|
try {
|
|
20186
|
-
const st =
|
|
20887
|
+
const st = statSync6(join18(cwd, p));
|
|
20187
20888
|
return `${st.mtimeMs}:${st.size}`;
|
|
20188
20889
|
} catch {
|
|
20189
20890
|
return "missing";
|
|
@@ -20215,10 +20916,12 @@ function handleFileBlame(url) {
|
|
|
20215
20916
|
if (base === "worktree") {
|
|
20216
20917
|
cacheKey = `worktree|${path}|${blamePathKey(path)}`;
|
|
20217
20918
|
} else {
|
|
20218
|
-
const resolved =
|
|
20219
|
-
if (resolved.
|
|
20220
|
-
|
|
20221
|
-
|
|
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}`;
|
|
20222
20925
|
}
|
|
20223
20926
|
const cached = blameCache.get(cacheKey);
|
|
20224
20927
|
if (cached) {
|
|
@@ -20229,6 +20932,8 @@ function handleFileBlame(url) {
|
|
|
20229
20932
|
return json2({ ...cached, base, ref, generation });
|
|
20230
20933
|
}
|
|
20231
20934
|
const result = blame(cwd, { path, ref: normalized.ref, base });
|
|
20935
|
+
if (result.error && result.status)
|
|
20936
|
+
return text(result.error, result.status);
|
|
20232
20937
|
if (!result.error)
|
|
20233
20938
|
rememberBlame(cacheKey, result);
|
|
20234
20939
|
return json2({ ...result, base, ref, generation });
|
|
@@ -20281,19 +20986,30 @@ function handleFileDiff(url) {
|
|
|
20281
20986
|
const cached = fileCache.get(cacheKey);
|
|
20282
20987
|
let diffText;
|
|
20283
20988
|
let errText = "";
|
|
20989
|
+
let errStatus;
|
|
20284
20990
|
if (cacheFresh(cached)) {
|
|
20285
20991
|
diffText = cached.diffText;
|
|
20286
20992
|
} else {
|
|
20287
20993
|
if (isUntracked) {
|
|
20288
|
-
|
|
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
|
+
}
|
|
20289
21000
|
} else {
|
|
20290
21001
|
const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
|
|
20291
21002
|
diffText = res.stdout || "";
|
|
20292
|
-
if (res.code !== 0)
|
|
21003
|
+
if (res.code !== 0) {
|
|
20293
21004
|
errText = res.stderr;
|
|
21005
|
+
errStatus = res.status;
|
|
21006
|
+
}
|
|
20294
21007
|
}
|
|
20295
|
-
|
|
21008
|
+
if (!errText)
|
|
21009
|
+
setTimedCacheEntry(fileCache, cacheKey, { diffText });
|
|
20296
21010
|
}
|
|
21011
|
+
if (errStatus)
|
|
21012
|
+
return text(errText || "diff failed", errStatus);
|
|
20297
21013
|
const mode = url.searchParams.get("mode") || "full";
|
|
20298
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);
|
|
20299
21015
|
const body = {
|
|
@@ -20314,7 +21030,7 @@ function handleFileDiff(url) {
|
|
|
20314
21030
|
}
|
|
20315
21031
|
function worktreeLineIndexSignature(full) {
|
|
20316
21032
|
try {
|
|
20317
|
-
const stat2 =
|
|
21033
|
+
const stat2 = statSync6(full);
|
|
20318
21034
|
return `size:${stat2.size}|mtime:${stat2.mtimeMs}|ctime:${stat2.ctimeMs}|ino:${stat2.ino || 0}`;
|
|
20319
21035
|
} catch {
|
|
20320
21036
|
return null;
|
|
@@ -20330,7 +21046,7 @@ async function getWorktreeLineIndex(full) {
|
|
|
20330
21046
|
lineIndexCache.set(full, cached);
|
|
20331
21047
|
return cached.index;
|
|
20332
21048
|
}
|
|
20333
|
-
const stat2 =
|
|
21049
|
+
const stat2 = statSync6(full);
|
|
20334
21050
|
if (stat2.size > LINE_INDEX_MAX_FILE_BYTES)
|
|
20335
21051
|
return null;
|
|
20336
21052
|
const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat2.size);
|
|
@@ -20475,8 +21191,9 @@ async function handleFileRange(url) {
|
|
|
20475
21191
|
};
|
|
20476
21192
|
return json2(body);
|
|
20477
21193
|
} else {
|
|
20478
|
-
|
|
20479
|
-
|
|
21194
|
+
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
21195
|
+
if (refCheck.ok !== true)
|
|
21196
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20480
21197
|
const oid = objectId(ref, path, cwd);
|
|
20481
21198
|
if (oid.code !== 0 || !oid.oid)
|
|
20482
21199
|
return text("not in ref", 404);
|
|
@@ -20506,8 +21223,9 @@ function handleRawFile(req, url) {
|
|
|
20506
21223
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
20507
21224
|
let body;
|
|
20508
21225
|
if (ref !== "worktree" && ref !== "") {
|
|
20509
|
-
|
|
20510
|
-
|
|
21226
|
+
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
21227
|
+
if (refCheck.ok !== true)
|
|
21228
|
+
return text(refCheck.error, refCheck.status ?? 400);
|
|
20511
21229
|
const size = rawFileSize(path, ref);
|
|
20512
21230
|
if (size == null)
|
|
20513
21231
|
return text("not in ref", 404);
|
|
@@ -20575,7 +21293,7 @@ function rawFileSize(path, ref) {
|
|
|
20575
21293
|
if (!full)
|
|
20576
21294
|
return null;
|
|
20577
21295
|
try {
|
|
20578
|
-
return
|
|
21296
|
+
return statSync6(full).size;
|
|
20579
21297
|
} catch {
|
|
20580
21298
|
return null;
|
|
20581
21299
|
}
|
|
@@ -20600,7 +21318,7 @@ function safeUploadFileName(name) {
|
|
|
20600
21318
|
return trimmed;
|
|
20601
21319
|
}
|
|
20602
21320
|
function uploadOpenFlags() {
|
|
20603
|
-
return
|
|
21321
|
+
return constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW || 0);
|
|
20604
21322
|
}
|
|
20605
21323
|
async function handleUploadFiles(req) {
|
|
20606
21324
|
if (!uploadEnabled)
|
|
@@ -20636,7 +21354,7 @@ async function handleUploadFiles(req) {
|
|
|
20636
21354
|
const realDir = safeOpenWorktreePath(dir);
|
|
20637
21355
|
if (!realDir)
|
|
20638
21356
|
return text("not found", 404);
|
|
20639
|
-
const stats =
|
|
21357
|
+
const stats = statSync6(realDir);
|
|
20640
21358
|
if (!stats.isDirectory())
|
|
20641
21359
|
return text("not a directory", 400);
|
|
20642
21360
|
const files = form.getAll("files").filter((item) => item instanceof File);
|
|
@@ -20660,8 +21378,8 @@ async function handleUploadFiles(req) {
|
|
|
20660
21378
|
total += file.size;
|
|
20661
21379
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
20662
21380
|
return text("upload too large", 413);
|
|
20663
|
-
const target =
|
|
20664
|
-
if (
|
|
21381
|
+
const target = join18(realDir, safeName);
|
|
21382
|
+
if (relative7(realDir, dirname5(target)) !== "")
|
|
20665
21383
|
return text("invalid filename", 400);
|
|
20666
21384
|
if (existsSync8(target))
|
|
20667
21385
|
return text("file exists", 409);
|
|
@@ -20782,9 +21500,9 @@ function triggerUpdate(changedPaths) {
|
|
|
20782
21500
|
sendSse("update", data);
|
|
20783
21501
|
}
|
|
20784
21502
|
function moveMacPathIntoTrash(path) {
|
|
20785
|
-
const trashDir =
|
|
21503
|
+
const trashDir = join18(homedir3(), ".Trash");
|
|
20786
21504
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
20787
|
-
const target =
|
|
21505
|
+
const target = join18(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
20788
21506
|
try {
|
|
20789
21507
|
mkdirSync4(trashDir, { recursive: true });
|
|
20790
21508
|
renameSync(path, target);
|
|
@@ -20826,11 +21544,11 @@ function restoreTrashPath(originalPath, trashPath) {
|
|
|
20826
21544
|
if (!existsSync8(trashPath))
|
|
20827
21545
|
return { ok: false, error: "trash item not found" };
|
|
20828
21546
|
try {
|
|
20829
|
-
const trashRoot =
|
|
20830
|
-
const trashRelative =
|
|
21547
|
+
const trashRoot = join18(homedir3(), ".Trash");
|
|
21548
|
+
const trashRelative = relative7(trashRoot, trashPath);
|
|
20831
21549
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
20832
21550
|
return { ok: false, error: "invalid trash handle" };
|
|
20833
|
-
mkdirSync4(
|
|
21551
|
+
mkdirSync4(dirname5(original), { recursive: true });
|
|
20834
21552
|
renameSync(trashPath, original);
|
|
20835
21553
|
return { ok: true };
|
|
20836
21554
|
} catch (error) {
|
|
@@ -20885,7 +21603,7 @@ async function handleOpenPath(req) {
|
|
|
20885
21603
|
const target = safeOpenWorktreePath(targetPath);
|
|
20886
21604
|
if (!target)
|
|
20887
21605
|
return text("not found", 404);
|
|
20888
|
-
const stats =
|
|
21606
|
+
const stats = statSync6(target);
|
|
20889
21607
|
if (!stats.isDirectory())
|
|
20890
21608
|
return text("not a directory", 400);
|
|
20891
21609
|
openOsPath(target);
|
|
@@ -20970,13 +21688,13 @@ async function handleCreateDirectory(req) {
|
|
|
20970
21688
|
const parent = safeOpenWorktreePath(dir);
|
|
20971
21689
|
if (!parent)
|
|
20972
21690
|
return text("not found", 404);
|
|
20973
|
-
const stats =
|
|
21691
|
+
const stats = statSync6(parent);
|
|
20974
21692
|
if (!stats.isDirectory())
|
|
20975
21693
|
return text("not a directory", 400);
|
|
20976
21694
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
20977
21695
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
20978
21696
|
return text("invalid target", 400);
|
|
20979
|
-
const target =
|
|
21697
|
+
const target = join18(parent, name);
|
|
20980
21698
|
if (existsSync8(target))
|
|
20981
21699
|
return text("already exists", 409);
|
|
20982
21700
|
try {
|
|
@@ -21277,11 +21995,12 @@ function restartWorktreeWatch() {
|
|
|
21277
21995
|
}
|
|
21278
21996
|
worktreeWatch = startScopedWorktreeWatch();
|
|
21279
21997
|
}
|
|
21280
|
-
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;
|
|
21281
21999
|
var init_preview = __esm(async () => {
|
|
21282
22000
|
init_routes();
|
|
21283
22001
|
init_annotations();
|
|
21284
22002
|
init_cache();
|
|
22003
|
+
init_command_resolver();
|
|
21285
22004
|
init_dev_assets();
|
|
21286
22005
|
init_doctor();
|
|
21287
22006
|
init_git();
|
|
@@ -21294,8 +22013,8 @@ var init_preview = __esm(async () => {
|
|
|
21294
22013
|
init_server_registry();
|
|
21295
22014
|
init_state_store();
|
|
21296
22015
|
init_worktree_watcher();
|
|
21297
|
-
WEB_ROOT =
|
|
21298
|
-
VERSION = JSON.parse(readFileSync8(
|
|
22016
|
+
WEB_ROOT = join18(ROOT, "web");
|
|
22017
|
+
VERSION = JSON.parse(readFileSync8(join18(ROOT, "package.json"), "utf8")).version;
|
|
21299
22018
|
DEFAULT_ARGS = ["HEAD"];
|
|
21300
22019
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
21301
22020
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -21339,8 +22058,9 @@ var init_preview = __esm(async () => {
|
|
|
21339
22058
|
".scss",
|
|
21340
22059
|
".html"
|
|
21341
22060
|
]);
|
|
21342
|
-
cwd =
|
|
22061
|
+
cwd = process.cwd();
|
|
21343
22062
|
cliArgs = DEFAULT_ARGS;
|
|
22063
|
+
commandOverrides = [];
|
|
21344
22064
|
scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
21345
22065
|
scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
21346
22066
|
scopeWatchLimit = DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
@@ -21423,8 +22143,12 @@ var init_preview = __esm(async () => {
|
|
|
21423
22143
|
return handleMcp(req);
|
|
21424
22144
|
if (url.pathname === "/_annotations")
|
|
21425
22145
|
return handleAnnotations(req);
|
|
21426
|
-
if (url.pathname === "/_refs")
|
|
21427
|
-
|
|
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
|
+
}
|
|
21428
22152
|
if (url.pathname === "/refresh" && req.method === "POST") {
|
|
21429
22153
|
if (!sideEffectRequestAllowed(req))
|
|
21430
22154
|
return text("forbidden", 403);
|