@dotobokuri/fleet-console 1.39.0 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/cli.d.ts +2 -1
- package/dist/cli.mjs +152 -1182
- package/dist/client/assets/{_baseUniq-BDfju2rW.js → _baseUniq-CImzMrtU.js} +1 -1
- package/dist/client/assets/{arc-DWcMv-ui.js → arc-CKb0rHjN.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-DhPv_Uba.js → architectureDiagram-Q4EWVU46-BaZipXSe.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-D7RYcNDP.js → blockDiagram-DXYQGD6D-D4sS9TjR.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-9_4dQBo3.js → c4Diagram-AHTNJAMY-DvjpMsKx.js} +1 -1
- package/dist/client/assets/channel-dSuGhtYS.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CQf5x2mT.js → chunk-4BX2VUAB-B47BryhY.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-eeG8BKQB.js → chunk-4TB4RGXK-DKIhSC52.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-1dkBlsDt.js → chunk-55IACEB6-B3Gvpef7.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-C5vcF-lT.js → chunk-EDXVE4YY-C4gFtvU8.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-CGelAuwP.js → chunk-FMBD7UC4-BADPieOa.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-CbKgVyop.js → chunk-OYMX7WX6-BUdtWGUY.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CT0ppG8R.js → chunk-QZHKN3VN-CMxkeGnM.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-BEf8DhxB.js → chunk-YZCP3GAM-Br6N7t8d.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DEN4mh7x.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DEN4mh7x.js +1 -0
- package/dist/client/assets/clone-BbTnmCoq.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-C6GtvpYi.js → cose-bilkent-S5V4N54A-D1Uw7i5_.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-DvrU2BgH.js → dagre-KV5264BT-kop0bHkM.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-Cy1qKKqt.js → diagram-5BDNPKRD-B9e41qNA.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-C4GGKnnD.js → diagram-G4DWMVQ6-Wew8X03k.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-By7JFsIw.js → diagram-MMDJMWI5-DGBb5Oqa.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-YwzVWpID.js → diagram-TYMM5635-CiVNsAz6.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-CDsNlmLk.js → erDiagram-SMLLAGMA-BdemYTgC.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-BuUpIzTa.js → flowDiagram-DWJPFMVM-CXC6yxhU.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-Dqv9t2x8.js → ganttDiagram-T4ZO3ILL-SMUpaiUg.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-DApQn2c7.js → gitGraphDiagram-UUTBAWPF-UaFgTHEh.js} +1 -1
- package/dist/client/assets/{graph-CyX-IC4n.js → graph-Ch8F0gMI.js} +1 -1
- package/dist/client/assets/index-D38r12i5.css +1 -0
- package/dist/client/assets/index-DkTcJcbj.js +452 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-DIf3qelw.js → infoDiagram-42DDH7IO-BHBzw0bu.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-CU3QtNdx.js → ishikawaDiagram-UXIWVN3A-CnDea8bw.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-Dfq4OBhW.js → journeyDiagram-VCZTEJTY-CI91m8BP.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-CWSe3eh7.js → kanban-definition-6JOO6SKY-D-rn-rG2.js} +1 -1
- package/dist/client/assets/{layout-CfdpDpEB.js → layout-Cd9zpG94.js} +1 -1
- package/dist/client/assets/{linear-BQA5wLDW.js → linear-Cucr1ojd.js} +1 -1
- package/dist/client/assets/{mermaid.core-BwJS5O4y.js → mermaid.core-G36P12JZ.js} +4 -4
- package/dist/client/assets/{min-Cg-Q6TB5.js → min-CzfCLG7v.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-BNXT0q7L.js → mindmap-definition-QFDTVHPH-4e1VjBV7.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-Cot4WNe7.js → pieDiagram-DEJITSTG-m2CGbU51.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-_YW3nZwc.js → quadrantDiagram-34T5L4WZ-BKNPGqMc.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-didEilrX.js → requirementDiagram-MS252O5E-D2BMwnpz.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-CIpzp5Ep.js → sankeyDiagram-XADWPNL6-Bm6hTqEh.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-CqvwD_CI.js → sequenceDiagram-FGHM5R23-Ds2FEGPj.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-D492KgCG.js → stateDiagram-FHFEXIEX-CrXn2Tb7.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BbzpXIlW.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-BpAwqH0H.js → timeline-definition-GMOUNBTQ-mw053fb5.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-DDMPO_HG.js → vennDiagram-DHZGUBPP-CAws0Vt2.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-TKEj0wFU.js → wardley-RL74JXVD-DGad2cmz.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-CLjBh2KA.js → wardleyDiagram-NUSXRM2D-BlIyft8I.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-DEn2Z9lG.js → xychartDiagram-5P7HB3ND-DHmSULAI.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/theme-boot.js +5 -4
- package/dist/fleet-plugins/file-explorer/routes.mjs +484 -56
- package/dist/fleet-plugins/ledger/routes.mjs +56 -80
- package/dist/fleet-plugins/quota/routes.mjs +19806 -0
- package/dist/fleet-plugins/repository/routes.mjs +274 -17
- package/dist/fleet-plugins/scuttlebutt/routes.mjs +6 -431
- package/dist/fleet-plugins/skills/routes.mjs +1 -79
- package/dist/fleet-plugins/terminal/routes.mjs +12449 -2974
- package/package.json +1 -1
- package/dist/client/assets/channel-Cjiuj1tM.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-B-_6FtAZ.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-B-_6FtAZ.js +0 -1
- package/dist/client/assets/clone-BF2H_qSP.js +0 -1
- package/dist/client/assets/index-RzoF0XVt.js +0 -452
- package/dist/client/assets/index-bog2t8Rz.css +0 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DUe9CEL6.js +0 -1
|
@@ -8,12 +8,12 @@ import fs2 from 'fs/promises';
|
|
|
8
8
|
function definePlugin(definition) {
|
|
9
9
|
return definition;
|
|
10
10
|
}
|
|
11
|
-
function registerRouter(ctx,
|
|
12
|
-
ctx.registerRouter(normalizePath(
|
|
11
|
+
function registerRouter(ctx, path10, handler) {
|
|
12
|
+
ctx.registerRouter(normalizePath(path10), handler);
|
|
13
13
|
}
|
|
14
|
-
function normalizePath(
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
14
|
+
function normalizePath(path10) {
|
|
15
|
+
if (path10.length === 0 || path10 === "/") return "/";
|
|
16
|
+
return path10;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// ../../packages/core-process/src/hidden.ts
|
|
@@ -36,9 +36,25 @@ var GitExecutorError = class extends Error {
|
|
|
36
36
|
};
|
|
37
37
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
38
38
|
var DEFAULT_MAX_BUFFER = 8 * 1024 * 1024;
|
|
39
|
+
var STDERR_MAX_BUFFER = 1024 * 1024;
|
|
40
|
+
var GIT_HARDENING_PREFIX = ["-c", "core.fsmonitor=false", "--no-optional-locks"];
|
|
41
|
+
function sanitizeGitEnvironment(environment) {
|
|
42
|
+
const sanitized = {};
|
|
43
|
+
for (const [key, value] of Object.entries(environment)) {
|
|
44
|
+
const normalizedKey = key.toUpperCase();
|
|
45
|
+
if (normalizedKey.startsWith("GIT_") || normalizedKey === "LC_ALL" || normalizedKey === "SSH_ASKPASS" || normalizedKey === "SSH_ASKPASS_REQUIRE") continue;
|
|
46
|
+
if (value !== void 0) sanitized[key] = value;
|
|
47
|
+
}
|
|
48
|
+
sanitized.GIT_OPTIONAL_LOCKS = "0";
|
|
49
|
+
sanitized.GIT_TERMINAL_PROMPT = "0";
|
|
50
|
+
sanitized.GIT_ALLOW_PROTOCOL = "ssh:git:http:https:file";
|
|
51
|
+
sanitized.LC_ALL = "C";
|
|
52
|
+
return sanitized;
|
|
53
|
+
}
|
|
39
54
|
function runGit(args, opts) {
|
|
40
55
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
41
56
|
const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
57
|
+
const hardenedArgs = [...GIT_HARDENING_PREFIX, ...args];
|
|
42
58
|
return new Promise((resolve, reject) => {
|
|
43
59
|
if (opts.signal?.aborted) {
|
|
44
60
|
reject(new GitExecutorError("timeout"));
|
|
@@ -46,7 +62,11 @@ function runGit(args, opts) {
|
|
|
46
62
|
}
|
|
47
63
|
let child;
|
|
48
64
|
try {
|
|
49
|
-
child = spawn("git",
|
|
65
|
+
child = spawn("git", hardenedArgs, withHidden({
|
|
66
|
+
cwd: opts.cwd,
|
|
67
|
+
env: sanitizeGitEnvironment(process.env),
|
|
68
|
+
shell: false
|
|
69
|
+
}));
|
|
50
70
|
} catch (error) {
|
|
51
71
|
const code = error.code === "ENOENT" ? "git_unavailable" : "spawn_failed";
|
|
52
72
|
reject(new GitExecutorError(code, String(error)));
|
|
@@ -55,7 +75,9 @@ function runGit(args, opts) {
|
|
|
55
75
|
const stdoutChunks = [];
|
|
56
76
|
const stderrChunks = [];
|
|
57
77
|
let stdoutLen = 0;
|
|
78
|
+
let stderrLen = 0;
|
|
58
79
|
let truncated = false;
|
|
80
|
+
let stderrTruncated = false;
|
|
59
81
|
let timedOut = false;
|
|
60
82
|
const abort = () => {
|
|
61
83
|
timedOut = true;
|
|
@@ -83,7 +105,16 @@ function runGit(args, opts) {
|
|
|
83
105
|
}
|
|
84
106
|
});
|
|
85
107
|
child.stderr.on("data", (chunk) => {
|
|
86
|
-
|
|
108
|
+
if (stderrTruncated) return;
|
|
109
|
+
const remaining = STDERR_MAX_BUFFER - stderrLen;
|
|
110
|
+
if (chunk.length >= remaining) {
|
|
111
|
+
stderrChunks.push(chunk.subarray(0, remaining));
|
|
112
|
+
stderrLen += remaining;
|
|
113
|
+
stderrTruncated = true;
|
|
114
|
+
} else {
|
|
115
|
+
stderrChunks.push(chunk);
|
|
116
|
+
stderrLen += chunk.length;
|
|
117
|
+
}
|
|
87
118
|
});
|
|
88
119
|
child.on("error", (error) => {
|
|
89
120
|
cleanup();
|
|
@@ -97,7 +128,7 @@ function runGit(args, opts) {
|
|
|
97
128
|
if (code !== 0) {
|
|
98
129
|
if (code !== null && (opts.allowExitCodes?.includes(code) ?? false)) {
|
|
99
130
|
const stdout2 = Buffer.concat(stdoutChunks).toString("utf8");
|
|
100
|
-
resolve({ stdout: stdout2, truncated });
|
|
131
|
+
resolve({ stdout: stdout2, stderr, truncated, stderrTruncated });
|
|
101
132
|
return;
|
|
102
133
|
}
|
|
103
134
|
const isNoRepo = stderr.toLowerCase().includes("not a git repository");
|
|
@@ -105,7 +136,7 @@ function runGit(args, opts) {
|
|
|
105
136
|
return;
|
|
106
137
|
}
|
|
107
138
|
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
108
|
-
resolve({ stdout, truncated });
|
|
139
|
+
resolve({ stdout, stderr, truncated, stderrTruncated });
|
|
109
140
|
});
|
|
110
141
|
});
|
|
111
142
|
}
|
|
@@ -796,10 +827,232 @@ async function handleRepositoryCompareFile(req, res, ctx) {
|
|
|
796
827
|
throw error;
|
|
797
828
|
}
|
|
798
829
|
}
|
|
830
|
+
var AUTO_FETCH_THROTTLE_MS = 3e5;
|
|
831
|
+
var autoFetchInFlight = /* @__PURE__ */ new Map();
|
|
832
|
+
var lastSuccessfulFetchAt = /* @__PURE__ */ new Map();
|
|
833
|
+
function isPlainObject6(value) {
|
|
834
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
835
|
+
}
|
|
836
|
+
function classifyFetchError(stderr) {
|
|
837
|
+
if (/permission denied|authentication failed|could not read username|terminal prompts disabled|repository not found/i.test(stderr)) {
|
|
838
|
+
return "auth_failed";
|
|
839
|
+
}
|
|
840
|
+
if (/could not resolve host|failed to connect|connection (refused|reset)|operation timed out|network is unreachable/i.test(stderr)) {
|
|
841
|
+
return "network";
|
|
842
|
+
}
|
|
843
|
+
if (/no remote repository specified|does not appear to be a git repository/i.test(stderr)) {
|
|
844
|
+
return "no_remote";
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
async function resolveGitIdentity(gitCwd) {
|
|
849
|
+
const [gitDirResult, commonDirResult] = await Promise.all([
|
|
850
|
+
runGit(["rev-parse", "--absolute-git-dir"], { cwd: gitCwd }),
|
|
851
|
+
runGit(["rev-parse", "--git-common-dir"], { cwd: gitCwd })
|
|
852
|
+
]);
|
|
853
|
+
const [gitDir, commonDir] = await Promise.all([
|
|
854
|
+
fs2.realpath(path4.resolve(gitCwd, gitDirResult.stdout.trim())),
|
|
855
|
+
fs2.realpath(path4.resolve(gitCwd, commonDirResult.stdout.trim()))
|
|
856
|
+
]);
|
|
857
|
+
return { gitDir, commonDir };
|
|
858
|
+
}
|
|
859
|
+
async function readLastFetchAt(gitDirs) {
|
|
860
|
+
let newest = null;
|
|
861
|
+
for (const gitDir of gitDirs) {
|
|
862
|
+
try {
|
|
863
|
+
const stats = await fs2.stat(path4.join(gitDir, "FETCH_HEAD"));
|
|
864
|
+
if (stats.size === 0) continue;
|
|
865
|
+
if (!newest || stats.mtime.getTime() > newest.getTime()) newest = stats.mtime;
|
|
866
|
+
} catch (error) {
|
|
867
|
+
if (error.code !== "ENOENT") throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return newest;
|
|
871
|
+
}
|
|
872
|
+
function countFetchProgress(stderr, marker) {
|
|
873
|
+
return stderr.split(/\r?\n/).filter((line) => line.includes(marker)).length;
|
|
874
|
+
}
|
|
875
|
+
function countFetchUpdatedRefs(stderr) {
|
|
876
|
+
return stderr.split(/\r?\n/).filter(
|
|
877
|
+
(line) => line.includes(" -> ") && /\S+\.\.\S+/.test(line) && !line.includes("[deleted]") && !line.includes("[new ")
|
|
878
|
+
).length;
|
|
879
|
+
}
|
|
880
|
+
var NoRemoteError = class extends Error {
|
|
881
|
+
};
|
|
882
|
+
var SAFE_CREDENTIAL_HELPERS = /* @__PURE__ */ new Set([
|
|
883
|
+
"cache",
|
|
884
|
+
"store",
|
|
885
|
+
"osxkeychain",
|
|
886
|
+
"wincred",
|
|
887
|
+
"libsecret",
|
|
888
|
+
"gnome-libsecret",
|
|
889
|
+
"manager",
|
|
890
|
+
"manager-core"
|
|
891
|
+
]);
|
|
892
|
+
async function resolveCredentialHelperArgs(gitCwd) {
|
|
893
|
+
const raw = (await runGit(["config", "--get-all", "credential.helper"], { cwd: gitCwd, allowExitCodes: [1] })).stdout;
|
|
894
|
+
const helpers = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
895
|
+
const kept = helpers.filter((helper) => {
|
|
896
|
+
if (!/^[A-Za-z0-9-]+$/.test(helper)) return false;
|
|
897
|
+
return SAFE_CREDENTIAL_HELPERS.has(helper) || SAFE_CREDENTIAL_HELPERS.has(helper.replace(/^git-credential-/, ""));
|
|
898
|
+
});
|
|
899
|
+
return ["-c", "credential.helper=", ...kept.flatMap((helper) => ["-c", `credential.helper=${helper}`]), "-c", "core.askPass="];
|
|
900
|
+
}
|
|
901
|
+
async function resolveDefaultRemote(gitCwd) {
|
|
902
|
+
const branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitCwd })).stdout.trim();
|
|
903
|
+
if (branch && branch !== "HEAD") {
|
|
904
|
+
const upstream = (await runGit(["config", "--get", `branch.${branch}.remote`], { cwd: gitCwd, allowExitCodes: [1] })).stdout.trim();
|
|
905
|
+
if (upstream && upstream !== ".") return upstream;
|
|
906
|
+
}
|
|
907
|
+
const remotes = (await runGit(["remote"], { cwd: gitCwd })).stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
908
|
+
if (remotes.includes("origin")) return "origin";
|
|
909
|
+
return remotes[0] ?? null;
|
|
910
|
+
}
|
|
911
|
+
async function readThrottleResult(gitDirs, commonDir) {
|
|
912
|
+
const fsNewest = await readLastFetchAt(gitDirs);
|
|
913
|
+
const recorded = lastSuccessfulFetchAt.get(commonDir);
|
|
914
|
+
let newest = fsNewest;
|
|
915
|
+
if (recorded !== void 0 && (!newest || recorded > newest.getTime())) newest = new Date(recorded);
|
|
916
|
+
if (!newest || Date.now() - newest.getTime() >= AUTO_FETCH_THROTTLE_MS) return null;
|
|
917
|
+
return { ok: true, skipped: "throttled", lastFetchAt: newest.toISOString() };
|
|
918
|
+
}
|
|
919
|
+
async function fetchRepository(gitCwd) {
|
|
920
|
+
const remote = await resolveDefaultRemote(gitCwd);
|
|
921
|
+
if (!remote) throw new NoRemoteError();
|
|
922
|
+
const credentialArgs = await resolveCredentialHelperArgs(gitCwd);
|
|
923
|
+
const result = await runGit([
|
|
924
|
+
"-c",
|
|
925
|
+
"core.sshCommand=ssh",
|
|
926
|
+
"-c",
|
|
927
|
+
// git 예약어 none만 프록시 우회다 — 다른 값은 프록시 "명령"으로 실행된다.
|
|
928
|
+
"core.gitProxy=none",
|
|
929
|
+
"-c",
|
|
930
|
+
// protocol.allow=user는 ext를 never에서 허용으로 뒤집는다(GIT_PROTOCOL_FROM_USER unset) —
|
|
931
|
+
// 기본 정책을 유지한 채 실행형 transport만 명시 차단한다.
|
|
932
|
+
"protocol.ext.allow=never",
|
|
933
|
+
"-c",
|
|
934
|
+
// ref transaction 훅(reference-transaction 등)의 zero-click 실행을 플랫폼 널 장치로 차단한다.
|
|
935
|
+
`core.hooksPath=${process.platform === "win32" ? "NUL" : "/dev/null"}`,
|
|
936
|
+
...credentialArgs,
|
|
937
|
+
"fetch",
|
|
938
|
+
remote,
|
|
939
|
+
// 명시 refspec이 configured refspec을 대체한다 — prune의 파괴 범위를
|
|
940
|
+
// refs/remotes/<remote>/ 아래로 고정해 로컬 refs를 보호한다.
|
|
941
|
+
`+refs/heads/*:refs/remotes/${remote}/*`,
|
|
942
|
+
// repo config의 remote.<name>.uploadpack이 로컬 transport에서 명령 실행되므로 표준 명령을 강제한다.
|
|
943
|
+
"--upload-pack=git-upload-pack",
|
|
944
|
+
// 하드닝된 상위 fetch를 빠져나가는 재귀(중첩 네트워크+하위 upload-pack)를 끊는다.
|
|
945
|
+
"--no-recurse-submodules",
|
|
946
|
+
"--prune",
|
|
947
|
+
"--no-tags",
|
|
948
|
+
// fetch.pruneTags=true인 저장소가 --prune과 결합해 로컬 전용 태그를 지우는 것을 막는다.
|
|
949
|
+
"--no-prune-tags"
|
|
950
|
+
], { cwd: gitCwd });
|
|
951
|
+
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
952
|
+
return {
|
|
953
|
+
ok: true,
|
|
954
|
+
fetchedAt,
|
|
955
|
+
lastFetchAt: fetchedAt,
|
|
956
|
+
pruned: countFetchProgress(result.stderr, " - [deleted] "),
|
|
957
|
+
newRefs: countFetchProgress(result.stderr, " * [new branch] "),
|
|
958
|
+
updatedRefs: countFetchUpdatedRefs(result.stderr)
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async function fetchHeadCandidates(identity) {
|
|
962
|
+
const candidates = /* @__PURE__ */ new Set([identity.gitDir, identity.commonDir]);
|
|
963
|
+
try {
|
|
964
|
+
const worktreesDir = path4.join(identity.commonDir, "worktrees");
|
|
965
|
+
for (const entry of await fs2.readdir(worktreesDir, { withFileTypes: true })) {
|
|
966
|
+
if (entry.isDirectory()) candidates.add(path4.join(worktreesDir, entry.name));
|
|
967
|
+
}
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (error.code !== "ENOENT") throw error;
|
|
970
|
+
}
|
|
971
|
+
return [...candidates];
|
|
972
|
+
}
|
|
973
|
+
async function runAutoFetch(gitCwd, identity) {
|
|
974
|
+
const throttled = await readThrottleResult(await fetchHeadCandidates(identity), identity.commonDir);
|
|
975
|
+
return throttled ?? fetchRepository(gitCwd);
|
|
976
|
+
}
|
|
977
|
+
async function fetchAutoSingleFlight(gitCwd, identity) {
|
|
978
|
+
const key = identity.commonDir;
|
|
979
|
+
const existing = autoFetchInFlight.get(key);
|
|
980
|
+
if (existing) {
|
|
981
|
+
await existing;
|
|
982
|
+
const throttled = await readThrottleResult(await fetchHeadCandidates(identity), identity.commonDir);
|
|
983
|
+
if (throttled) return throttled;
|
|
984
|
+
}
|
|
985
|
+
const operation = runAutoFetch(gitCwd, identity);
|
|
986
|
+
autoFetchInFlight.set(key, operation);
|
|
987
|
+
try {
|
|
988
|
+
return await operation;
|
|
989
|
+
} finally {
|
|
990
|
+
if (autoFetchInFlight.get(key) === operation) autoFetchInFlight.delete(key);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async function handleRepositoryFetch(req, res, ctx) {
|
|
994
|
+
if (req.method !== "POST") {
|
|
995
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
999
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
1003
|
+
if (!isPlainObject6(body) || "subPath" in body || typeof body.theaterId !== "string" || body.mode !== void 0 && body.mode !== "auto") {
|
|
1004
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
1008
|
+
if (!theaterPath) {
|
|
1009
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
let gitCwd;
|
|
1013
|
+
try {
|
|
1014
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
if (error instanceof InvalidRepoError) {
|
|
1017
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
1022
|
+
try {
|
|
1023
|
+
const identity = await resolveGitIdentity(gitCwd);
|
|
1024
|
+
const result = body.mode === "auto" ? await fetchAutoSingleFlight(gitCwd, identity) : await (autoFetchInFlight.get(identity.commonDir) ?? fetchRepository(gitCwd));
|
|
1025
|
+
if ("fetchedAt" in result) lastSuccessfulFetchAt.set(identity.commonDir, Date.parse(result.fetchedAt));
|
|
1026
|
+
ctx.host.http.writeJson(res, 200, result);
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
if (error instanceof NoRemoteError) {
|
|
1029
|
+
ctx.host.http.writeJson(res, 422, { error: "no_remote" });
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (error instanceof GitExecutorError) {
|
|
1033
|
+
if (error.code === "timeout") {
|
|
1034
|
+
ctx.host.http.writeJson(res, 422, { error: "timeout" });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const classified = classifyFetchError(error.stderr);
|
|
1038
|
+
if (classified) {
|
|
1039
|
+
ctx.host.http.writeJson(res, 422, { error: classified });
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
1043
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
throw error;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
799
1052
|
var WORKTREE_SHA_RE = /^[0-9a-f]{40}$/;
|
|
800
1053
|
var CANONICAL_REF_RE = /^refs\/(?:heads|remotes|tags)\//;
|
|
801
1054
|
var REF_METACHAR_RE = /[~^:?*\[\\\s\x00-\x1f\x7f]/;
|
|
802
|
-
function
|
|
1055
|
+
function isPlainObject7(value) {
|
|
803
1056
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
804
1057
|
}
|
|
805
1058
|
function isCanonicalRepositoryRef(ref) {
|
|
@@ -924,7 +1177,7 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
924
1177
|
return;
|
|
925
1178
|
}
|
|
926
1179
|
const body = await ctx.host.http.readJsonBody(req);
|
|
927
|
-
if (!
|
|
1180
|
+
if (!isPlainObject7(body) || "subPath" in body) {
|
|
928
1181
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
929
1182
|
return;
|
|
930
1183
|
}
|
|
@@ -1092,7 +1345,7 @@ var HARD_CAP_DEPTH = 8;
|
|
|
1092
1345
|
var NESTED_BRANCH_CAP = 64;
|
|
1093
1346
|
var SCAN_VISIT_BUDGET = 1e4;
|
|
1094
1347
|
var DEFAULT_DEPTH = 3;
|
|
1095
|
-
function
|
|
1348
|
+
function isPlainObject8(value) {
|
|
1096
1349
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1097
1350
|
}
|
|
1098
1351
|
async function resolveRepoBranch(repoDir) {
|
|
@@ -1181,7 +1434,7 @@ async function handleRepositoryRepos(req, res, ctx) {
|
|
|
1181
1434
|
return;
|
|
1182
1435
|
}
|
|
1183
1436
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1184
|
-
if (!
|
|
1437
|
+
if (!isPlainObject8(body) || typeof body.theaterId !== "string") {
|
|
1185
1438
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1186
1439
|
return;
|
|
1187
1440
|
}
|
|
@@ -1260,7 +1513,7 @@ async function handleRepositorySearch(req, res, ctx) {
|
|
|
1260
1513
|
return;
|
|
1261
1514
|
}
|
|
1262
1515
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1263
|
-
if (!
|
|
1516
|
+
if (!isPlainObject9(body) || typeof body.theaterId !== "string" || typeof body.query !== "string" || body.query.trim() === "" || !Number.isInteger(body.limit) || body.limit < 1 || body.limit > 8 || "subPath" in body || "ref" in body) {
|
|
1264
1517
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1265
1518
|
return;
|
|
1266
1519
|
}
|
|
@@ -1314,10 +1567,10 @@ async function handleRepositorySearch(req, res, ctx) {
|
|
|
1314
1567
|
throw error;
|
|
1315
1568
|
}
|
|
1316
1569
|
}
|
|
1317
|
-
function
|
|
1570
|
+
function isPlainObject9(value) {
|
|
1318
1571
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1319
1572
|
}
|
|
1320
|
-
function
|
|
1573
|
+
function isPlainObject10(value) {
|
|
1321
1574
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1322
1575
|
}
|
|
1323
1576
|
async function handleRepositoryWorktrees(req, res, ctx) {
|
|
@@ -1330,7 +1583,7 @@ async function handleRepositoryWorktrees(req, res, ctx) {
|
|
|
1330
1583
|
return;
|
|
1331
1584
|
}
|
|
1332
1585
|
const body = await ctx.host.http.readJsonBody(req);
|
|
1333
|
-
if (!
|
|
1586
|
+
if (!isPlainObject10(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
1334
1587
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1335
1588
|
return;
|
|
1336
1589
|
}
|
|
@@ -1411,6 +1664,10 @@ var routes_default = definePlugin({
|
|
|
1411
1664
|
await handleRepositoryChanged(req, res, ctx);
|
|
1412
1665
|
return true;
|
|
1413
1666
|
});
|
|
1667
|
+
registerRouter(ctx, "fetch", async ({ req, res }) => {
|
|
1668
|
+
await handleRepositoryFetch(req, res, ctx);
|
|
1669
|
+
return true;
|
|
1670
|
+
});
|
|
1414
1671
|
registerRouter(ctx, "file", async ({ req, res }) => {
|
|
1415
1672
|
await handleRepositoryFile(req, res, ctx);
|
|
1416
1673
|
return true;
|