@dotobokuri/fleet-console 1.23.0 → 1.24.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/AGENTS.md +41 -257
- package/README.md +8 -0
- package/dist/cli.d.ts +11 -1
- package/dist/cli.mjs +1738 -453
- package/dist/client/assets/{_baseUniq-BjOe5yYF.js → _baseUniq-yooYbTgN.js} +1 -1
- package/dist/client/assets/{arc-bFUuO3jf.js → arc-D4mme5Ts.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-Ba7nimQq.js → architectureDiagram-Q4EWVU46-BAJOviEX.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-CQGf47hl.js → blockDiagram-DXYQGD6D-DuObpxQ3.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-DZF_cAK4.js → c4Diagram-AHTNJAMY-BOT-SD34.js} +1 -1
- package/dist/client/assets/channel-TZYyAq1v.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-DjUUHSQ5.js → chunk-4BX2VUAB-BF_aMTes.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-Dvqti8bd.js → chunk-4TB4RGXK-M4brBcnD.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-2BxiBvkp.js → chunk-55IACEB6-CrG-4b2Z.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-CqCjol5O.js → chunk-EDXVE4YY-BxDSygH1.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-eoCfIbly.js → chunk-FMBD7UC4-Bh7MEX7E.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-CoC86V_1.js → chunk-OYMX7WX6-DBnBwxer.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CJQA5mlQ.js → chunk-QZHKN3VN-bjAogQwW.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-C5C4kbLu.js → chunk-YZCP3GAM-l4FGlk_c.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-Dh6hZUl4.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-Dh6hZUl4.js +1 -0
- package/dist/client/assets/clone-DgWGnVWH.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CahuunpL.js → cose-bilkent-S5V4N54A-Bm5QA4zo.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-Cbv5kSi5.js → dagre-KV5264BT-PzXQN-5Q.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-CNm40ZGB.js → diagram-5BDNPKRD-KhaAKPtX.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-DrlGNT3i.js → diagram-G4DWMVQ6-DBbjfiR8.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-r8Y2oqmw.js → diagram-MMDJMWI5-QWro7PRX.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-CMzEhXup.js → diagram-TYMM5635-B9LqWrbF.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-UFjUUMPz.js → erDiagram-SMLLAGMA-b0VkMMN4.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-Dd1STmYc.js → flowDiagram-DWJPFMVM-edxBAbe6.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-CHHU0hVd.js → ganttDiagram-T4ZO3ILL-ks2Gg1_V.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-vj2HukAF.js → gitGraphDiagram-UUTBAWPF-DymUyYNl.js} +1 -1
- package/dist/client/assets/{graph-DhjfEbEb.js → graph-B-B_mqfh.js} +1 -1
- package/dist/client/assets/index-BgLzfg-v.css +1 -0
- package/dist/client/assets/index-pDnb-q6f.js +380 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-CC7AEgDm.js → infoDiagram-42DDH7IO-B5iLIYAc.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DlRw2B9G.js → ishikawaDiagram-UXIWVN3A-DnztLLn7.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-BbRXANKQ.js → journeyDiagram-VCZTEJTY-BKDRfH1Y.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-DtvdCxGN.js → kanban-definition-6JOO6SKY-DPB3Phvr.js} +1 -1
- package/dist/client/assets/{layout-Dg5cvw5v.js → layout-BEeRhYTG.js} +1 -1
- package/dist/client/assets/{linear-BvVlQLqF.js → linear-QzmfWKSq.js} +1 -1
- package/dist/client/assets/{mermaid.core-DzrV8sC-.js → mermaid.core-BVka5dgm.js} +4 -4
- package/dist/client/assets/{min-DNGQ1Ciz.js → min-CfXk9kSN.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-DxJCNZbg.js → mindmap-definition-QFDTVHPH-tGrGhr6Z.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-B75XHG-s.js → pieDiagram-DEJITSTG-ZRBqRiRK.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-CL4yoF0m.js → quadrantDiagram-34T5L4WZ-VCINZgct.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-kGV4vcr1.js → requirementDiagram-MS252O5E-DFLzADh3.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-DoA4u7wI.js → sankeyDiagram-XADWPNL6-BbouN8Ji.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-ZpNDf1bS.js → sequenceDiagram-FGHM5R23-B58_LF0R.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-BzgKcVAG.js → stateDiagram-FHFEXIEX-pk-OHHuL.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-xsN122JA.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-B_DkUDsD.js → timeline-definition-GMOUNBTQ-4jQdCvaj.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-MCUnnsKb.js → vennDiagram-DHZGUBPP-CUr1fH6N.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-D_JAMSxj.js → wardley-RL74JXVD-D0cfvvNA.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-BHCwZGLq.js → wardleyDiagram-NUSXRM2D-D0l_Rp_Z.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-FbGwbD3V.js → xychartDiagram-5P7HB3ND-BdR8OEuJ.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/desktop-protocol.d.ts +49 -0
- package/dist/desktop-protocol.mjs +99 -0
- package/dist/fleet-plugins/diff/routes.mjs +124 -225
- package/dist/fleet-plugins/skills/routes.mjs +218 -82
- package/dist/fleet-plugins/terminal/routes.mjs +190 -176
- package/package.json +7 -2
- package/dist/client/assets/channel-D2BahVp1.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DsDAVLhS.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DsDAVLhS.js +0 -1
- package/dist/client/assets/clone-CdZy2Xzf.js +0 -1
- package/dist/client/assets/index-Dq9EKm2G.css +0 -1
- package/dist/client/assets/index-DtzbOdd7.js +0 -380
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-yRK5hzZ4.js +0 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import 'fs';
|
|
4
|
-
import
|
|
4
|
+
import path2 from 'path';
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
6
|
|
|
7
7
|
// sdk/plugin/node.ts
|
|
8
8
|
function definePlugin(definition) {
|
|
9
9
|
return definition;
|
|
10
10
|
}
|
|
11
|
-
function registerRouter(ctx,
|
|
12
|
-
ctx.registerRouter(normalizePath(
|
|
11
|
+
function registerRouter(ctx, path3, handler) {
|
|
12
|
+
ctx.registerRouter(normalizePath(path3), handler);
|
|
13
13
|
}
|
|
14
|
-
function normalizePath(
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
14
|
+
function normalizePath(path3) {
|
|
15
|
+
if (path3.length === 0 || path3 === "/") return "/";
|
|
16
|
+
return path3;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// ../../packages/core-process/src/hidden.ts
|
|
@@ -132,7 +132,7 @@ function parseDiffFileList(nameStatusOutput, numstatOutput) {
|
|
|
132
132
|
return files;
|
|
133
133
|
}
|
|
134
134
|
async function fetchUntrackedFiles(cwd) {
|
|
135
|
-
const result = await runGit(["ls-files", "--others", "--exclude-standard"], { cwd });
|
|
135
|
+
const result = await runGit(["ls-files", "--others", "--exclude-standard", "--", "."], { cwd });
|
|
136
136
|
return result.stdout.split("\n").filter((p) => p.trim()).map((p) => ({
|
|
137
137
|
path: p,
|
|
138
138
|
status: "U",
|
|
@@ -140,6 +140,9 @@ async function fetchUntrackedFiles(cwd) {
|
|
|
140
140
|
deletions: 0
|
|
141
141
|
}));
|
|
142
142
|
}
|
|
143
|
+
async function ensureGitRepository(cwd) {
|
|
144
|
+
await runGit(["rev-parse", "--is-inside-work-tree"], { cwd });
|
|
145
|
+
}
|
|
143
146
|
function isNoHeadError(error) {
|
|
144
147
|
if (!(error instanceof GitExecutorError)) return false;
|
|
145
148
|
if (error.code !== "non_zero_exit") return false;
|
|
@@ -147,9 +150,9 @@ function isNoHeadError(error) {
|
|
|
147
150
|
}
|
|
148
151
|
async function resolveGitCwd(theaterPath, rawSubPath) {
|
|
149
152
|
if (rawSubPath === "") return { gitCwd: theaterPath };
|
|
150
|
-
const resolvedSub =
|
|
151
|
-
if (!resolvedSub.startsWith(theaterPath +
|
|
152
|
-
if (
|
|
153
|
+
const resolvedSub = path2.resolve(theaterPath, rawSubPath);
|
|
154
|
+
if (!resolvedSub.startsWith(theaterPath + path2.sep) && resolvedSub !== theaterPath) return null;
|
|
155
|
+
if (path2.normalize(rawSubPath).startsWith("..")) return null;
|
|
153
156
|
let realTheater;
|
|
154
157
|
let realSub;
|
|
155
158
|
try {
|
|
@@ -160,7 +163,7 @@ async function resolveGitCwd(theaterPath, rawSubPath) {
|
|
|
160
163
|
} catch {
|
|
161
164
|
return null;
|
|
162
165
|
}
|
|
163
|
-
const norm = realTheater.endsWith(
|
|
166
|
+
const norm = realTheater.endsWith(path2.sep) ? realTheater : realTheater + path2.sep;
|
|
164
167
|
if (realSub !== realTheater && !realSub.startsWith(norm)) return null;
|
|
165
168
|
return { gitCwd: resolvedSub };
|
|
166
169
|
}
|
|
@@ -196,20 +199,21 @@ async function handleDiffChanged(req, res, ctx) {
|
|
|
196
199
|
}
|
|
197
200
|
const { gitCwd } = cwdResult;
|
|
198
201
|
try {
|
|
202
|
+
await ensureGitRepository(gitCwd);
|
|
199
203
|
let files;
|
|
200
204
|
let truncated = false;
|
|
201
205
|
try {
|
|
202
206
|
const [nameStatusResult, numstatResult] = await Promise.all([
|
|
203
|
-
runGit(["diff", "HEAD", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
|
|
204
|
-
runGit(["diff", "HEAD", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
|
|
207
|
+
runGit(["diff", "HEAD", "--relative", "--name-status", "--diff-filter=MADR", "--", "."], { cwd: gitCwd }),
|
|
208
|
+
runGit(["diff", "HEAD", "--relative", "--numstat", "--diff-filter=MADR", "--", "."], { cwd: gitCwd })
|
|
205
209
|
]);
|
|
206
210
|
files = parseDiffFileList(nameStatusResult.stdout, numstatResult.stdout);
|
|
207
211
|
truncated = nameStatusResult.truncated || numstatResult.truncated;
|
|
208
212
|
} catch (err) {
|
|
209
213
|
if (!isNoHeadError(err)) throw err;
|
|
210
214
|
const [nsResult, nsNumstat] = await Promise.all([
|
|
211
|
-
runGit(["diff", "--cached", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
|
|
212
|
-
runGit(["diff", "--cached", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
|
|
215
|
+
runGit(["diff", "--cached", "--relative", "--name-status", "--diff-filter=MADR", "--", "."], { cwd: gitCwd }),
|
|
216
|
+
runGit(["diff", "--cached", "--relative", "--numstat", "--diff-filter=MADR", "--", "."], { cwd: gitCwd })
|
|
213
217
|
]);
|
|
214
218
|
files = parseDiffFileList(nsResult.stdout, nsNumstat.stdout);
|
|
215
219
|
truncated = nsResult.truncated || nsNumstat.truncated;
|
|
@@ -269,12 +273,12 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
269
273
|
}
|
|
270
274
|
const { gitCwd } = cwdResult;
|
|
271
275
|
const rawFilePath = body.filePath;
|
|
272
|
-
const resolvedFilePath =
|
|
273
|
-
if (!resolvedFilePath.startsWith(gitCwd +
|
|
276
|
+
const resolvedFilePath = path2.resolve(gitCwd, rawFilePath);
|
|
277
|
+
if (!resolvedFilePath.startsWith(gitCwd + path2.sep) && resolvedFilePath !== gitCwd) {
|
|
274
278
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
275
279
|
return;
|
|
276
280
|
}
|
|
277
|
-
const relativePath =
|
|
281
|
+
const relativePath = path2.normalize(rawFilePath);
|
|
278
282
|
if (relativePath.startsWith("..")) {
|
|
279
283
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
280
284
|
return;
|
|
@@ -292,12 +296,12 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
292
296
|
ctx.host.http.writeJson(res, 404, { error: "file_not_found" });
|
|
293
297
|
return;
|
|
294
298
|
}
|
|
295
|
-
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd +
|
|
299
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
|
|
296
300
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
297
301
|
return;
|
|
298
302
|
}
|
|
299
303
|
const result2 = await runGit(
|
|
300
|
-
["diff", "--no-index", "--unified=3", "--", "/dev/null", relativePath],
|
|
304
|
+
["diff", "--no-index", "--relative", "--unified=3", "--", "/dev/null", relativePath],
|
|
301
305
|
{ cwd: gitCwd, allowExitCodes: [1] }
|
|
302
306
|
);
|
|
303
307
|
ctx.host.http.writeJson(res, 200, { content: result2.stdout, truncated: result2.truncated });
|
|
@@ -308,7 +312,7 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
308
312
|
fs.realpath(gitCwd),
|
|
309
313
|
fs.realpath(resolvedFilePath)
|
|
310
314
|
]);
|
|
311
|
-
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd +
|
|
315
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path2.sep)) {
|
|
312
316
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
313
317
|
return;
|
|
314
318
|
}
|
|
@@ -316,10 +320,10 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
316
320
|
}
|
|
317
321
|
let result;
|
|
318
322
|
try {
|
|
319
|
-
result = await runGit(["diff", "HEAD", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
323
|
+
result = await runGit(["diff", "HEAD", "--relative", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
320
324
|
} catch (err) {
|
|
321
325
|
if (!isNoHeadError(err)) throw err;
|
|
322
|
-
result = await runGit(["diff", "--cached", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
326
|
+
result = await runGit(["diff", "--cached", "--relative", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
323
327
|
}
|
|
324
328
|
ctx.host.http.writeJson(res, 200, { content: result.stdout, truncated: result.truncated });
|
|
325
329
|
} catch (error) {
|
|
@@ -377,7 +381,7 @@ async function handleDiffCommit(req, res, ctx) {
|
|
|
377
381
|
}
|
|
378
382
|
const { gitCwd } = cwdResult;
|
|
379
383
|
try {
|
|
380
|
-
const result = await runGit(["show", ref, "--unified=3", "--format="], { cwd: gitCwd });
|
|
384
|
+
const result = await runGit(["show", ref, "--relative", "--unified=3", "--format=", "--", "."], { cwd: gitCwd });
|
|
381
385
|
ctx.host.http.writeJson(res, 200, { content: result.stdout, ...result.truncated ? { truncated: true } : {} });
|
|
382
386
|
} catch (error) {
|
|
383
387
|
if (error instanceof GitExecutorError) {
|
|
@@ -391,8 +395,7 @@ async function handleDiffCommit(req, res, ctx) {
|
|
|
391
395
|
throw error;
|
|
392
396
|
}
|
|
393
397
|
}
|
|
394
|
-
|
|
395
|
-
// ../fleet-plugins/diff/server/log.ts
|
|
398
|
+
var WORKTREE_SHA_RE = /^[0-9a-f]{40}$/;
|
|
396
399
|
function isPlainObject3(value) {
|
|
397
400
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
398
401
|
}
|
|
@@ -410,30 +413,96 @@ function parseLogOutput(stdout) {
|
|
|
410
413
|
const subject = fields[2] ?? "";
|
|
411
414
|
const authorName = fields[3] ?? "";
|
|
412
415
|
const relTime = fields[4] ?? "";
|
|
413
|
-
const
|
|
414
|
-
const
|
|
416
|
+
const authorAt = Number.parseInt(fields[5] ?? "0", 10);
|
|
417
|
+
const refsRaw = fields[6] ?? "";
|
|
418
|
+
const parentsRaw = fields[7] ?? "";
|
|
415
419
|
if (!fullHash) continue;
|
|
416
420
|
const refs = refsRaw.split(",").map((r) => r.trim()).filter(Boolean);
|
|
417
421
|
const parents = parentsRaw.split(" ").map((p) => p.trim()).filter(Boolean);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
commits.push({ shortHash, fullHash, subject, authorName, relTime, refs, parents, additions, deletions });
|
|
422
|
+
commits.push({
|
|
423
|
+
shortHash,
|
|
424
|
+
fullHash,
|
|
425
|
+
subject,
|
|
426
|
+
authorName,
|
|
427
|
+
relTime,
|
|
428
|
+
authorAt: Number.isFinite(authorAt) ? authorAt : 0,
|
|
429
|
+
refs,
|
|
430
|
+
parents,
|
|
431
|
+
onHead: true
|
|
432
|
+
});
|
|
430
433
|
}
|
|
431
434
|
return commits;
|
|
432
435
|
}
|
|
436
|
+
async function parseWorktreePorcelain(stdout, currentWorktreePath) {
|
|
437
|
+
const worktrees = [];
|
|
438
|
+
let worktreePath = null;
|
|
439
|
+
let sha = "";
|
|
440
|
+
let branch = null;
|
|
441
|
+
let prunable = false;
|
|
442
|
+
const pushCurrent = () => {
|
|
443
|
+
if (!worktreePath || !sha || prunable || /^0+$/.test(sha)) return;
|
|
444
|
+
worktrees.push({
|
|
445
|
+
sha,
|
|
446
|
+
branch,
|
|
447
|
+
worktreePath
|
|
448
|
+
});
|
|
449
|
+
};
|
|
450
|
+
for (const line of stdout.split("\n")) {
|
|
451
|
+
if (line.startsWith("worktree ")) {
|
|
452
|
+
pushCurrent();
|
|
453
|
+
worktreePath = line.slice(9);
|
|
454
|
+
sha = "";
|
|
455
|
+
branch = null;
|
|
456
|
+
prunable = false;
|
|
457
|
+
} else if (line.startsWith("HEAD ")) {
|
|
458
|
+
sha = line.slice(5);
|
|
459
|
+
} else if (line.startsWith("branch ")) {
|
|
460
|
+
const ref = line.slice(7);
|
|
461
|
+
branch = ref.startsWith("refs/heads/") ? ref.slice(11) : ref;
|
|
462
|
+
} else if (line === "prunable" || line.startsWith("prunable ")) {
|
|
463
|
+
prunable = true;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
pushCurrent();
|
|
467
|
+
const normalizedCurrentWorktreePath = await normalizeWorktreePath(currentWorktreePath);
|
|
468
|
+
return Promise.all(worktrees.map(async ({ worktreePath: worktreePath2, ...checkout }) => ({
|
|
469
|
+
...checkout,
|
|
470
|
+
isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(worktreePath2) === normalizedCurrentWorktreePath
|
|
471
|
+
})));
|
|
472
|
+
}
|
|
473
|
+
function annotateHeadReachability(commits, revListStdout) {
|
|
474
|
+
const reachable = new Set(revListStdout.split("\n").map((line) => line.trim()).filter(Boolean));
|
|
475
|
+
if (reachable.size === 0) return commits;
|
|
476
|
+
return commits.map((commit) => ({ ...commit, onHead: reachable.has(commit.fullHash) }));
|
|
477
|
+
}
|
|
433
478
|
function isNoHeadError2(error) {
|
|
434
479
|
if (!(error instanceof GitExecutorError)) return false;
|
|
435
480
|
if (error.code !== "non_zero_exit") return false;
|
|
436
|
-
return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision");
|
|
481
|
+
return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision") || error.stderr.includes("does not have any commits");
|
|
482
|
+
}
|
|
483
|
+
async function normalizeWorktreePath(worktreePath) {
|
|
484
|
+
if (!worktreePath) return "";
|
|
485
|
+
try {
|
|
486
|
+
return await fs.realpath(worktreePath);
|
|
487
|
+
} catch {
|
|
488
|
+
return worktreePath;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async function readHeadRevList(gitCwd) {
|
|
492
|
+
try {
|
|
493
|
+
return (await runGit(["rev-list", "-n", "1000", "HEAD"], { cwd: gitCwd })).stdout;
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error instanceof GitExecutorError) return "";
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function readCurrentWorktreePath(gitCwd) {
|
|
500
|
+
try {
|
|
501
|
+
return (await runGit(["rev-parse", "--show-toplevel"], { cwd: gitCwd })).stdout.trim();
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (error instanceof GitExecutorError) return "";
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
437
506
|
}
|
|
438
507
|
async function handleDiffLog(req, res, ctx) {
|
|
439
508
|
if (req.method !== "POST") {
|
|
@@ -467,16 +536,25 @@ async function handleDiffLog(req, res, ctx) {
|
|
|
467
536
|
}
|
|
468
537
|
const { gitCwd } = cwdResult;
|
|
469
538
|
try {
|
|
539
|
+
const [worktrees, currentWorktreePath, headRevList] = await Promise.all([
|
|
540
|
+
runGit(["worktree", "list", "--porcelain"], { cwd: gitCwd }),
|
|
541
|
+
readCurrentWorktreePath(gitCwd),
|
|
542
|
+
readHeadRevList(gitCwd)
|
|
543
|
+
]);
|
|
544
|
+
const checkouts = await parseWorktreePorcelain(worktrees.stdout, currentWorktreePath);
|
|
545
|
+
const worktreeRevs = [...new Set(checkouts.map((checkout) => checkout.sha))].filter((sha) => WORKTREE_SHA_RE.test(sha) && !/^0+$/.test(sha));
|
|
546
|
+
const headRevs = headRevList ? ["HEAD"] : [];
|
|
470
547
|
const result = await runGit(
|
|
471
|
-
|
|
548
|
+
// --all은 refs/stash·refs/notes까지 그래프에 유입시키므로 브랜치/태그/원격 + 현재 HEAD + 워크트리 HEAD로 한정한다
|
|
549
|
+
["log", "--branches", "--tags", "--remotes", "--date-order", "-n", "200", "--relative", "--decorate=full", "--pretty=format:%x1e%H%x00%h%x00%s%x00%an%x00%ar%x00%at%x00%D%x00%P", ...headRevs, ...worktreeRevs, "--", "."],
|
|
472
550
|
{ cwd: gitCwd }
|
|
473
551
|
);
|
|
474
|
-
const commits = parseLogOutput(result.stdout);
|
|
475
|
-
ctx.host.http.writeJson(res, 200, { commits, ...result.truncated ? { truncated: true } : {} });
|
|
552
|
+
const commits = annotateHeadReachability(parseLogOutput(result.stdout), headRevList);
|
|
553
|
+
ctx.host.http.writeJson(res, 200, { commits, checkouts, ...result.truncated ? { truncated: true } : {} });
|
|
476
554
|
} catch (error) {
|
|
477
555
|
if (error instanceof GitExecutorError) {
|
|
478
556
|
if (error.code === "no_git_repo") {
|
|
479
|
-
ctx.host.http.writeJson(res, 200, { commits: [] });
|
|
557
|
+
ctx.host.http.writeJson(res, 200, { commits: [], checkouts: [] });
|
|
480
558
|
return;
|
|
481
559
|
}
|
|
482
560
|
if (error.code === "git_unavailable") {
|
|
@@ -484,7 +562,7 @@ async function handleDiffLog(req, res, ctx) {
|
|
|
484
562
|
return;
|
|
485
563
|
}
|
|
486
564
|
if (isNoHeadError2(error)) {
|
|
487
|
-
ctx.host.http.writeJson(res, 200, { commits: [] });
|
|
565
|
+
ctx.host.http.writeJson(res, 200, { commits: [], checkouts: [] });
|
|
488
566
|
return;
|
|
489
567
|
}
|
|
490
568
|
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
@@ -493,190 +571,11 @@ async function handleDiffLog(req, res, ctx) {
|
|
|
493
571
|
throw error;
|
|
494
572
|
}
|
|
495
573
|
}
|
|
496
|
-
var REPOS_CAP = 200;
|
|
497
|
-
var HARD_CAP_DEPTH = 8;
|
|
498
|
-
var DEFAULT_DEPTH = 3;
|
|
499
|
-
function isPlainObject4(value) {
|
|
500
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
501
|
-
}
|
|
502
|
-
async function resolveRepoBranch(repoDir) {
|
|
503
|
-
try {
|
|
504
|
-
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoDir });
|
|
505
|
-
const branch = result.stdout.trim();
|
|
506
|
-
if (branch === "HEAD") {
|
|
507
|
-
const sha = await runGit(["rev-parse", "--short", "HEAD"], { cwd: repoDir });
|
|
508
|
-
return sha.stdout.trim() || "HEAD";
|
|
509
|
-
}
|
|
510
|
-
return branch || "HEAD";
|
|
511
|
-
} catch {
|
|
512
|
-
return "unknown";
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async function detectWorktreeParent(dir, gitFileContent) {
|
|
516
|
-
const match = gitFileContent.trim().match(/^gitdir:\s*(.+)$/m);
|
|
517
|
-
if (!match) return null;
|
|
518
|
-
const rawPointer = match[1].trim();
|
|
519
|
-
const resolvedGitdir = path3.normalize(
|
|
520
|
-
path3.isAbsolute(rawPointer) ? rawPointer : path3.resolve(dir, rawPointer)
|
|
521
|
-
);
|
|
522
|
-
if (resolvedGitdir.includes(`${path3.sep}modules${path3.sep}`)) {
|
|
523
|
-
return null;
|
|
524
|
-
}
|
|
525
|
-
const worktreesSegment = `${path3.sep}.git${path3.sep}worktrees${path3.sep}`;
|
|
526
|
-
const wtIdx = resolvedGitdir.indexOf(worktreesSegment);
|
|
527
|
-
if (wtIdx === -1) {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
const parentAbsRaw = resolvedGitdir.slice(0, wtIdx);
|
|
531
|
-
let parentAbs;
|
|
532
|
-
try {
|
|
533
|
-
parentAbs = await fs.realpath(parentAbsRaw);
|
|
534
|
-
} catch {
|
|
535
|
-
parentAbs = path3.normalize(parentAbsRaw);
|
|
536
|
-
}
|
|
537
|
-
return { isWorktree: true, parentAbs };
|
|
538
|
-
}
|
|
539
|
-
async function scanRepos(theaterPath, realTheaterPath, dir, currentDepth, maxDepth, repos, cap = REPOS_CAP) {
|
|
540
|
-
if (repos.length >= cap) return true;
|
|
541
|
-
const gitPath = path3.join(dir, ".git");
|
|
542
|
-
try {
|
|
543
|
-
const gitStat = await fs.stat(gitPath);
|
|
544
|
-
const relPath = path3.relative(theaterPath, dir);
|
|
545
|
-
const name = relPath === "" ? path3.basename(theaterPath) : path3.basename(dir);
|
|
546
|
-
const branch = await resolveRepoBranch(dir);
|
|
547
|
-
if (gitStat.isFile()) {
|
|
548
|
-
const content = await fs.readFile(gitPath, "utf8");
|
|
549
|
-
const worktreeResult = await detectWorktreeParent(dir, content);
|
|
550
|
-
if (worktreeResult) {
|
|
551
|
-
repos.push({ relPath, name, branch, isWorktree: true, _wtParentAbs: worktreeResult.parentAbs });
|
|
552
|
-
} else {
|
|
553
|
-
repos.push({ relPath, name, branch });
|
|
554
|
-
}
|
|
555
|
-
} else {
|
|
556
|
-
repos.push({ relPath, name, branch });
|
|
557
|
-
}
|
|
558
|
-
if (repos.length >= cap) return true;
|
|
559
|
-
} catch {
|
|
560
|
-
}
|
|
561
|
-
if (currentDepth >= maxDepth) return false;
|
|
562
|
-
let dirHandle = null;
|
|
563
|
-
try {
|
|
564
|
-
dirHandle = await fs.opendir(dir);
|
|
565
|
-
try {
|
|
566
|
-
let dirent = await dirHandle.read();
|
|
567
|
-
while (dirent !== null) {
|
|
568
|
-
if (repos.length >= cap) return true;
|
|
569
|
-
if (dirent.isDirectory()) {
|
|
570
|
-
const childName = dirent.name;
|
|
571
|
-
if (childName === "node_modules" || childName === ".git") {
|
|
572
|
-
dirent = await dirHandle.read();
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
const childPath = path3.join(dir, childName);
|
|
576
|
-
let realChildPath;
|
|
577
|
-
try {
|
|
578
|
-
realChildPath = await fs.realpath(childPath);
|
|
579
|
-
} catch {
|
|
580
|
-
dirent = await dirHandle.read();
|
|
581
|
-
continue;
|
|
582
|
-
}
|
|
583
|
-
const normalizedReal = realTheaterPath.endsWith(path3.sep) ? realTheaterPath : realTheaterPath + path3.sep;
|
|
584
|
-
if (realChildPath !== realTheaterPath && !realChildPath.startsWith(normalizedReal)) {
|
|
585
|
-
dirent = await dirHandle.read();
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
const truncated = await scanRepos(theaterPath, realTheaterPath, childPath, currentDepth + 1, maxDepth, repos, cap);
|
|
589
|
-
if (truncated) return true;
|
|
590
|
-
}
|
|
591
|
-
dirent = await dirHandle.read();
|
|
592
|
-
}
|
|
593
|
-
return false;
|
|
594
|
-
} finally {
|
|
595
|
-
await dirHandle.close();
|
|
596
|
-
}
|
|
597
|
-
} catch {
|
|
598
|
-
if (dirHandle) {
|
|
599
|
-
try {
|
|
600
|
-
await dirHandle.close();
|
|
601
|
-
} catch {
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return false;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
function resolveWorktreeParents(raw, realTheaterPath) {
|
|
608
|
-
const absToRelPath = /* @__PURE__ */ new Map();
|
|
609
|
-
for (const entry of raw) {
|
|
610
|
-
const absDir = path3.normalize(
|
|
611
|
-
entry.relPath === "" ? realTheaterPath : path3.join(realTheaterPath, entry.relPath)
|
|
612
|
-
);
|
|
613
|
-
absToRelPath.set(absDir, entry.relPath);
|
|
614
|
-
}
|
|
615
|
-
return raw.map((entry) => {
|
|
616
|
-
const { _wtParentAbs, ...rest } = entry;
|
|
617
|
-
if (_wtParentAbs && entry.isWorktree) {
|
|
618
|
-
const normalizedParent = path3.normalize(_wtParentAbs);
|
|
619
|
-
const parentRelPath = absToRelPath.get(normalizedParent);
|
|
620
|
-
if (parentRelPath !== void 0) {
|
|
621
|
-
return { ...rest, worktreeOf: parentRelPath };
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return rest;
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
async function handleDiffRepos(req, res, ctx) {
|
|
628
|
-
if (req.method !== "POST") {
|
|
629
|
-
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
633
|
-
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
const body = await ctx.host.http.readJsonBody(req);
|
|
637
|
-
if (!isPlainObject4(body)) {
|
|
638
|
-
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
const theaterId = body.theaterId;
|
|
642
|
-
if (typeof theaterId !== "string") {
|
|
643
|
-
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
647
|
-
if (!theaterPath) {
|
|
648
|
-
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
const rawDepth = body.maxDepth;
|
|
652
|
-
const maxDepth = typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 1 ? Math.min(HARD_CAP_DEPTH, Math.floor(rawDepth)) : DEFAULT_DEPTH;
|
|
653
|
-
let realTheaterPath;
|
|
654
|
-
try {
|
|
655
|
-
realTheaterPath = await fs.realpath(theaterPath);
|
|
656
|
-
} catch {
|
|
657
|
-
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
const raw = [];
|
|
661
|
-
const truncated = await scanRepos(theaterPath, realTheaterPath, theaterPath, 0, maxDepth, raw);
|
|
662
|
-
const resolved = resolveWorktreeParents(raw, realTheaterPath);
|
|
663
|
-
resolved.sort((a, b) => {
|
|
664
|
-
if (a.relPath === "") return -1;
|
|
665
|
-
if (b.relPath === "") return 1;
|
|
666
|
-
return a.relPath.localeCompare(b.relPath);
|
|
667
|
-
});
|
|
668
|
-
const result = { repos: resolved, ...truncated ? { truncated: true } : {} };
|
|
669
|
-
ctx.host.http.writeJson(res, 200, result);
|
|
670
|
-
}
|
|
671
574
|
|
|
672
575
|
// ../fleet-plugins/diff/routes.ts
|
|
673
576
|
var routes_default = definePlugin({
|
|
674
577
|
id: "diff",
|
|
675
578
|
register(ctx) {
|
|
676
|
-
registerRouter(ctx, "repos", async ({ req, res }) => {
|
|
677
|
-
await handleDiffRepos(req, res, ctx);
|
|
678
|
-
return true;
|
|
679
|
-
});
|
|
680
579
|
registerRouter(ctx, "changed", async ({ req, res }) => {
|
|
681
580
|
await handleDiffChanged(req, res, ctx);
|
|
682
581
|
return true;
|