@dotobokuri/fleet-console 1.28.0 → 1.30.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/dist/cli.mjs +209 -387
- package/dist/client/assets/{_baseUniq-0TpVlb56.js → _baseUniq-Dvbxpxj-.js} +1 -1
- package/dist/client/assets/{arc-BIRhKPNZ.js → arc-Cqerxs8I.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-CJzkivmk.js → architectureDiagram-Q4EWVU46-gNhmCKnU.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-PkVBy6UB.js → blockDiagram-DXYQGD6D-CM5HRpKC.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-TOO0Zc07.js → c4Diagram-AHTNJAMY-DxLhQuvg.js} +1 -1
- package/dist/client/assets/channel-DKkQpLWI.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-Ugm2Ht26.js → chunk-4BX2VUAB-HX6C_GAC.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-Byfwafb_.js → chunk-4TB4RGXK-CoBQUhS4.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-2I_nwtuS.js → chunk-55IACEB6-DWsWxGBm.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-JYP41aJo.js → chunk-EDXVE4YY-BXNRu1M4.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Duh-U1jY.js → chunk-FMBD7UC4-DzKgP1_A.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-64cqkTOO.js → chunk-OYMX7WX6-aqXdvKUD.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-engflZJB.js → chunk-QZHKN3VN-BbMqaUb0.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-BwnK_1vX.js → chunk-YZCP3GAM-D8Mo0nv8.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-CQurO5ix.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-CQurO5ix.js +1 -0
- package/dist/client/assets/clone-IodjsAGU.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-D43_BnnE.js → cose-bilkent-S5V4N54A-BOuVjjXz.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-Wh_p74YO.js → dagre-KV5264BT-BdUFzAj_.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-BNcxA6wC.js → diagram-5BDNPKRD-C0iFrsGo.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-HQdA06kX.js → diagram-G4DWMVQ6-CGxGeQZt.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-DlMdX_ng.js → diagram-MMDJMWI5-Dex5uqlA.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-C1hKAjZD.js → diagram-TYMM5635-O48PEpaS.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-CsSiuv9G.js → erDiagram-SMLLAGMA-B7PGw3YA.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-DfjQ3Ug9.js → flowDiagram-DWJPFMVM-CCXWJIMw.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-B0QHKePg.js → ganttDiagram-T4ZO3ILL-B26RghpR.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-_zejoqTH.js → gitGraphDiagram-UUTBAWPF-BJsjMn6A.js} +1 -1
- package/dist/client/assets/{graph-Dr6IPcji.js → graph-DuEL0RWH.js} +1 -1
- package/dist/client/assets/index-Dj775lUe.css +1 -0
- package/dist/client/assets/index-r6rDZQCs.js +451 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-dhruAwPt.js → infoDiagram-42DDH7IO-D32dz1SM.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DPoZZiH0.js → ishikawaDiagram-UXIWVN3A-4YPQjln0.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-DQk8gmrd.js → journeyDiagram-VCZTEJTY-BSvi22HM.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-B9rOgVFq.js → kanban-definition-6JOO6SKY-SozOXS7m.js} +1 -1
- package/dist/client/assets/{layout-D54BXsv2.js → layout-CNXntIRV.js} +1 -1
- package/dist/client/assets/{linear-DJxG7kFF.js → linear-9RZPFtO4.js} +1 -1
- package/dist/client/assets/{mermaid.core-BBFWA-zA.js → mermaid.core-kwH-UxKY.js} +4 -4
- package/dist/client/assets/{min-Dv_aLAYb.js → min-BdN2p8Q_.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-Dv5woCon.js → mindmap-definition-QFDTVHPH-H-3dJmH9.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-B_V_vCG8.js → pieDiagram-DEJITSTG-CspzYvcs.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-QHqq3tFD.js → quadrantDiagram-34T5L4WZ-D1C7e3-s.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-BhPhfMzK.js → requirementDiagram-MS252O5E-BJwKRtMi.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-D2-ZsaS_.js → sankeyDiagram-XADWPNL6-7oc42Z3P.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-Di0qgM3v.js → sequenceDiagram-FGHM5R23-TP5H_DnF.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-BAuqrwY8.js → stateDiagram-FHFEXIEX-BWnrNgJY.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Cmp8tobq.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-BcMGkC6T.js → timeline-definition-GMOUNBTQ-xXFvCsrr.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-C9dkwyKX.js → vennDiagram-DHZGUBPP-D5unm_iH.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-BLLCncPa.js → wardley-RL74JXVD-B-eT02rs.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-Yb9mlL0_.js → wardleyDiagram-NUSXRM2D-BTQ1Yqmb.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-BAHLdje7.js → xychartDiagram-5P7HB3ND-Bialvg0l.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/file-explorer/routes.mjs +199 -38
- package/dist/fleet-plugins/repository/routes.mjs +407 -108
- package/dist/fleet-plugins/skills/routes.mjs +60 -79
- package/dist/fleet-plugins/terminal/routes.mjs +577 -102
- package/package.json +1 -1
- package/dist/client/assets/channel-CqmujVSi.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DLXkYur6.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DLXkYur6.js +0 -1
- package/dist/client/assets/clone-KonJWRw9.js +0 -1
- package/dist/client/assets/index-CsmxZ8UQ.js +0 -453
- package/dist/client/assets/index-DC8tBAf_.css +0 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DhNebJyd.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
|
|
5
|
-
import
|
|
4
|
+
import path4 from 'path';
|
|
5
|
+
import fs2 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, path8, handler) {
|
|
12
|
+
ctx.registerRouter(normalizePath(path8), handler);
|
|
13
13
|
}
|
|
14
|
-
function normalizePath(
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
14
|
+
function normalizePath(path8) {
|
|
15
|
+
if (path8.length === 0 || path8 === "/") return "/";
|
|
16
|
+
return path8;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// ../../packages/core-process/src/hidden.ts
|
|
@@ -97,6 +97,72 @@ function runGit(args, opts) {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
function isPathContained(root, candidate) {
|
|
101
|
+
const relative = path4.relative(root, candidate);
|
|
102
|
+
return relative === "" || !path4.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path4.sep}`);
|
|
103
|
+
}
|
|
104
|
+
function isSelectableRepoRel(repoRel) {
|
|
105
|
+
if (repoRel === "") return true;
|
|
106
|
+
if (path4.isAbsolute(repoRel)) return false;
|
|
107
|
+
const normalized = path4.normalize(repoRel);
|
|
108
|
+
return !normalized.startsWith("-") && normalized !== ".." && !normalized.startsWith(`..${path4.sep}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ../fleet-plugins/repository/server/git-marker.ts
|
|
112
|
+
var GITDIR_PREFIX = "gitdir:";
|
|
113
|
+
async function resolveContainedGitDir(repoDir, realTheaterPath) {
|
|
114
|
+
const markerPath = path4.join(repoDir, ".git");
|
|
115
|
+
let marker;
|
|
116
|
+
try {
|
|
117
|
+
marker = await fs2.stat(markerPath);
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
let gitDir;
|
|
122
|
+
if (marker.isFile()) {
|
|
123
|
+
let content;
|
|
124
|
+
try {
|
|
125
|
+
content = await fs2.readFile(markerPath, "utf8");
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const line = content.split("\n").find((entry) => entry.trim().startsWith(GITDIR_PREFIX));
|
|
130
|
+
if (!line) return null;
|
|
131
|
+
const target = line.trim().slice(GITDIR_PREFIX.length).trim();
|
|
132
|
+
if (!target) return null;
|
|
133
|
+
gitDir = path4.resolve(repoDir, target);
|
|
134
|
+
} else if (marker.isDirectory()) {
|
|
135
|
+
gitDir = markerPath;
|
|
136
|
+
} else {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
let realGitDir;
|
|
140
|
+
try {
|
|
141
|
+
realGitDir = await fs2.realpath(gitDir);
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (!isPathContained(realTheaterPath, realGitDir)) return null;
|
|
146
|
+
let commonRaw = null;
|
|
147
|
+
try {
|
|
148
|
+
commonRaw = await fs2.readFile(path4.join(realGitDir, "commondir"), "utf8");
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
if (commonRaw !== null) {
|
|
152
|
+
const commonTarget = commonRaw.trim();
|
|
153
|
+
if (!commonTarget) return null;
|
|
154
|
+
let realCommonDir;
|
|
155
|
+
try {
|
|
156
|
+
realCommonDir = await fs2.realpath(path4.resolve(realGitDir, commonTarget));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (!isPathContained(realTheaterPath, realCommonDir)) return null;
|
|
161
|
+
}
|
|
162
|
+
return realGitDir;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../fleet-plugins/repository/server/diff.ts
|
|
100
166
|
function isPlainObject(value) {
|
|
101
167
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
102
168
|
}
|
|
@@ -154,24 +220,29 @@ function isNoHeadError(error) {
|
|
|
154
220
|
if (error.code !== "non_zero_exit") return false;
|
|
155
221
|
return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision");
|
|
156
222
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
223
|
+
var InvalidRepoError = class extends Error {
|
|
224
|
+
code = "invalid_repo";
|
|
225
|
+
};
|
|
226
|
+
async function resolveGitCwd(theaterPath, repoRel = "") {
|
|
227
|
+
if (repoRel === void 0 || repoRel === "") return { gitCwd: theaterPath };
|
|
228
|
+
if (typeof repoRel !== "string" || path4.isAbsolute(repoRel)) throw new InvalidRepoError("Repository path must be relative");
|
|
229
|
+
if (!isSelectableRepoRel(repoRel)) throw new InvalidRepoError("Repository path is invalid");
|
|
230
|
+
const resolved = path4.resolve(theaterPath, path4.normalize(repoRel));
|
|
231
|
+
if (!isPathContained(theaterPath, resolved)) throw new InvalidRepoError("Repository path escapes Theater");
|
|
162
232
|
let realTheater;
|
|
163
|
-
let
|
|
233
|
+
let realRepo;
|
|
164
234
|
try {
|
|
165
|
-
[realTheater,
|
|
166
|
-
fs.realpath(theaterPath),
|
|
167
|
-
fs.realpath(resolvedSub)
|
|
168
|
-
]);
|
|
235
|
+
[realTheater, realRepo] = await Promise.all([fs2.realpath(theaterPath), fs2.realpath(resolved)]);
|
|
169
236
|
} catch {
|
|
170
|
-
|
|
237
|
+
throw new InvalidRepoError("Repository path does not exist");
|
|
238
|
+
}
|
|
239
|
+
if (!isPathContained(realTheater, realRepo)) {
|
|
240
|
+
throw new InvalidRepoError("Repository path escapes Theater");
|
|
241
|
+
}
|
|
242
|
+
if (await resolveContainedGitDir(realRepo, realTheater) === null) {
|
|
243
|
+
throw new InvalidRepoError("Repository marker does not exist");
|
|
171
244
|
}
|
|
172
|
-
|
|
173
|
-
if (realSub !== realTheater && !realSub.startsWith(norm)) return null;
|
|
174
|
-
return { gitCwd: resolvedSub };
|
|
245
|
+
return { gitCwd: realRepo };
|
|
175
246
|
}
|
|
176
247
|
async function handleRepositoryChanged(req, res, ctx) {
|
|
177
248
|
if (req.method !== "POST") {
|
|
@@ -183,7 +254,7 @@ async function handleRepositoryChanged(req, res, ctx) {
|
|
|
183
254
|
return;
|
|
184
255
|
}
|
|
185
256
|
const body = await ctx.host.http.readJsonBody(req);
|
|
186
|
-
if (!isPlainObject(body)) {
|
|
257
|
+
if (!isPlainObject(body) || "subPath" in body) {
|
|
187
258
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
188
259
|
return;
|
|
189
260
|
}
|
|
@@ -197,11 +268,15 @@ async function handleRepositoryChanged(req, res, ctx) {
|
|
|
197
268
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
198
269
|
return;
|
|
199
270
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
271
|
+
let cwdResult;
|
|
272
|
+
try {
|
|
273
|
+
cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (error instanceof InvalidRepoError) {
|
|
276
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
205
280
|
}
|
|
206
281
|
const { gitCwd } = cwdResult;
|
|
207
282
|
try {
|
|
@@ -252,7 +327,7 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
252
327
|
return;
|
|
253
328
|
}
|
|
254
329
|
const body = await ctx.host.http.readJsonBody(req);
|
|
255
|
-
if (!isPlainObject(body) || typeof body.filePath !== "string") {
|
|
330
|
+
if (!isPlainObject(body) || "subPath" in body || typeof body.filePath !== "string") {
|
|
256
331
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
257
332
|
return;
|
|
258
333
|
}
|
|
@@ -271,20 +346,24 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
271
346
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
272
347
|
return;
|
|
273
348
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
349
|
+
let cwdResult;
|
|
350
|
+
try {
|
|
351
|
+
cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error instanceof InvalidRepoError) {
|
|
354
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
279
358
|
}
|
|
280
359
|
const { gitCwd } = cwdResult;
|
|
281
360
|
const rawFilePath = body.filePath;
|
|
282
|
-
const resolvedFilePath =
|
|
283
|
-
if (!resolvedFilePath.startsWith(gitCwd +
|
|
361
|
+
const resolvedFilePath = path4.resolve(gitCwd, rawFilePath);
|
|
362
|
+
if (!resolvedFilePath.startsWith(gitCwd + path4.sep) && resolvedFilePath !== gitCwd) {
|
|
284
363
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
285
364
|
return;
|
|
286
365
|
}
|
|
287
|
-
const relativePath =
|
|
366
|
+
const relativePath = path4.normalize(rawFilePath);
|
|
288
367
|
if (relativePath.startsWith("..")) {
|
|
289
368
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
290
369
|
return;
|
|
@@ -295,14 +374,14 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
295
374
|
let realFile;
|
|
296
375
|
try {
|
|
297
376
|
[realGitCwd, realFile] = await Promise.all([
|
|
298
|
-
|
|
299
|
-
|
|
377
|
+
fs2.realpath(gitCwd),
|
|
378
|
+
fs2.realpath(resolvedFilePath)
|
|
300
379
|
]);
|
|
301
380
|
} catch {
|
|
302
381
|
ctx.host.http.writeJson(res, 404, { error: "file_not_found" });
|
|
303
382
|
return;
|
|
304
383
|
}
|
|
305
|
-
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd +
|
|
384
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path4.sep)) {
|
|
306
385
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
307
386
|
return;
|
|
308
387
|
}
|
|
@@ -315,10 +394,10 @@ async function handleRepositoryFile(req, res, ctx) {
|
|
|
315
394
|
}
|
|
316
395
|
try {
|
|
317
396
|
const [realGitCwd, realFile] = await Promise.all([
|
|
318
|
-
|
|
319
|
-
|
|
397
|
+
fs2.realpath(gitCwd),
|
|
398
|
+
fs2.realpath(resolvedFilePath)
|
|
320
399
|
]);
|
|
321
|
-
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd +
|
|
400
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path4.sep)) {
|
|
322
401
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
323
402
|
return;
|
|
324
403
|
}
|
|
@@ -376,7 +455,7 @@ async function handleRepositoryCommit(req, res, ctx) {
|
|
|
376
455
|
return;
|
|
377
456
|
}
|
|
378
457
|
const body = await ctx.host.http.readJsonBody(req);
|
|
379
|
-
if (!isPlainObject2(body)) {
|
|
458
|
+
if (!isPlainObject2(body) || "subPath" in body) {
|
|
380
459
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
381
460
|
return;
|
|
382
461
|
}
|
|
@@ -395,11 +474,15 @@ async function handleRepositoryCommit(req, res, ctx) {
|
|
|
395
474
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
396
475
|
return;
|
|
397
476
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
477
|
+
let cwdResult;
|
|
478
|
+
try {
|
|
479
|
+
cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (error instanceof InvalidRepoError) {
|
|
482
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
throw error;
|
|
403
486
|
}
|
|
404
487
|
const { gitCwd } = cwdResult;
|
|
405
488
|
try {
|
|
@@ -434,9 +517,9 @@ function isPlainObject3(value) {
|
|
|
434
517
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
435
518
|
}
|
|
436
519
|
function resolveLiteralFilePath(gitCwd, rawPath) {
|
|
437
|
-
const relativePath =
|
|
438
|
-
const resolved =
|
|
439
|
-
if (relativePath === ".." || relativePath.startsWith(".." +
|
|
520
|
+
const relativePath = path4.normalize(rawPath);
|
|
521
|
+
const resolved = path4.resolve(gitCwd, relativePath);
|
|
522
|
+
if (relativePath === ".." || relativePath.startsWith(".." + path4.sep) || !resolved.startsWith(gitCwd + path4.sep)) return null;
|
|
440
523
|
return relativePath;
|
|
441
524
|
}
|
|
442
525
|
function literalPathspec2(relativePath) {
|
|
@@ -452,7 +535,7 @@ async function handleRepositoryCommitFile(req, res, ctx) {
|
|
|
452
535
|
return;
|
|
453
536
|
}
|
|
454
537
|
const body = await ctx.host.http.readJsonBody(req);
|
|
455
|
-
if (!isPlainObject3(body) || typeof body.theaterId !== "string" || typeof body.ref !== "string" || typeof body.filePath !== "string") {
|
|
538
|
+
if (!isPlainObject3(body) || "subPath" in body || typeof body.theaterId !== "string" || typeof body.ref !== "string" || typeof body.filePath !== "string") {
|
|
456
539
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
457
540
|
return;
|
|
458
541
|
}
|
|
@@ -473,10 +556,15 @@ async function handleRepositoryCommitFile(req, res, ctx) {
|
|
|
473
556
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
474
557
|
return;
|
|
475
558
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
559
|
+
let cwdResult;
|
|
560
|
+
try {
|
|
561
|
+
cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (error instanceof InvalidRepoError) {
|
|
564
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
throw error;
|
|
480
568
|
}
|
|
481
569
|
const relativePath = resolveLiteralFilePath(cwdResult.gitCwd, body.filePath);
|
|
482
570
|
const oldPath = typeof body.oldPath === "string" ? resolveLiteralFilePath(cwdResult.gitCwd, body.oldPath) : void 0;
|
|
@@ -542,7 +630,7 @@ function parseLogOutput(stdout) {
|
|
|
542
630
|
}
|
|
543
631
|
return commits;
|
|
544
632
|
}
|
|
545
|
-
|
|
633
|
+
function parseWorktreePorcelainEntries(stdout) {
|
|
546
634
|
const worktrees = [];
|
|
547
635
|
let worktreePath = null;
|
|
548
636
|
let sha = "";
|
|
@@ -573,10 +661,14 @@ async function parseWorktreePorcelain(stdout, currentWorktreePath) {
|
|
|
573
661
|
}
|
|
574
662
|
}
|
|
575
663
|
pushCurrent();
|
|
664
|
+
return worktrees;
|
|
665
|
+
}
|
|
666
|
+
async function parseWorktreePorcelain(stdout, currentWorktreePath) {
|
|
667
|
+
const worktrees = parseWorktreePorcelainEntries(stdout);
|
|
576
668
|
const normalizedCurrentWorktreePath = await normalizeWorktreePath(currentWorktreePath);
|
|
577
|
-
return Promise.all(worktrees.map(async ({ worktreePath
|
|
669
|
+
return Promise.all(worktrees.map(async ({ worktreePath, ...checkout }) => ({
|
|
578
670
|
...checkout,
|
|
579
|
-
isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(
|
|
671
|
+
isCurrent: normalizedCurrentWorktreePath !== "" && await normalizeWorktreePath(worktreePath) === normalizedCurrentWorktreePath
|
|
580
672
|
})));
|
|
581
673
|
}
|
|
582
674
|
function annotateHeadReachability(commits, revListStdout) {
|
|
@@ -592,7 +684,7 @@ function isNoHeadError2(error) {
|
|
|
592
684
|
async function normalizeWorktreePath(worktreePath) {
|
|
593
685
|
if (!worktreePath) return "";
|
|
594
686
|
try {
|
|
595
|
-
return await
|
|
687
|
+
return await fs2.realpath(worktreePath);
|
|
596
688
|
} catch {
|
|
597
689
|
return worktreePath;
|
|
598
690
|
}
|
|
@@ -623,7 +715,7 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
623
715
|
return;
|
|
624
716
|
}
|
|
625
717
|
const body = await ctx.host.http.readJsonBody(req);
|
|
626
|
-
if (!isPlainObject4(body)) {
|
|
718
|
+
if (!isPlainObject4(body) || "subPath" in body) {
|
|
627
719
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
628
720
|
return;
|
|
629
721
|
}
|
|
@@ -637,11 +729,15 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
637
729
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
638
730
|
return;
|
|
639
731
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
732
|
+
let cwdResult;
|
|
733
|
+
try {
|
|
734
|
+
cwdResult = await resolveGitCwd(theaterPath, body.repoRel);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
if (error instanceof InvalidRepoError) {
|
|
737
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
throw error;
|
|
645
741
|
}
|
|
646
742
|
const { gitCwd } = cwdResult;
|
|
647
743
|
const requestedRef = body.ref;
|
|
@@ -690,6 +786,8 @@ async function handleRepositoryLog(req, res, ctx) {
|
|
|
690
786
|
throw error;
|
|
691
787
|
}
|
|
692
788
|
}
|
|
789
|
+
|
|
790
|
+
// ../fleet-plugins/repository/server/refs.ts
|
|
693
791
|
function isObject(value) {
|
|
694
792
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
695
793
|
}
|
|
@@ -702,14 +800,6 @@ function parseRefItems(stdout, current) {
|
|
|
702
800
|
return ref && label ? [{ ref, label, current: ref === current }] : [];
|
|
703
801
|
});
|
|
704
802
|
}
|
|
705
|
-
async function readCurrentWorktreePath2(gitCwd) {
|
|
706
|
-
try {
|
|
707
|
-
return (await runGit(["rev-parse", "--show-toplevel"], { cwd: gitCwd })).stdout.trim();
|
|
708
|
-
} catch (error) {
|
|
709
|
-
if (error instanceof GitExecutorError) return "";
|
|
710
|
-
throw error;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
803
|
async function readStashes(gitCwd) {
|
|
714
804
|
try {
|
|
715
805
|
return (await runGit(["stash", "list", "--format=%gd%x00%s"], { cwd: gitCwd })).stdout;
|
|
@@ -718,28 +808,6 @@ async function readStashes(gitCwd) {
|
|
|
718
808
|
throw error;
|
|
719
809
|
}
|
|
720
810
|
}
|
|
721
|
-
async function parseWorktrees(stdout, currentWorktreePath, theaterPath, deps = {}) {
|
|
722
|
-
const records = stdout.split("\n\n").map((record) => record.split("\n"));
|
|
723
|
-
const realpath = deps.realpath ?? fs.realpath;
|
|
724
|
-
const normalizedCurrent = currentWorktreePath ? await realpath(currentWorktreePath).catch(() => currentWorktreePath) : "";
|
|
725
|
-
const realTheaterPath = await realpath(theaterPath).catch(() => null);
|
|
726
|
-
return Promise.all(records.flatMap((record) => {
|
|
727
|
-
const rawPath = record.find((line) => line.startsWith("worktree "))?.slice(9);
|
|
728
|
-
if (!rawPath) return [];
|
|
729
|
-
const branchRef = record.find((line) => line.startsWith("branch "))?.slice(7) ?? null;
|
|
730
|
-
const branch = branchRef?.startsWith("refs/heads/") ? branchRef.slice(11) : null;
|
|
731
|
-
return [{ rawPath, name: path2.basename(rawPath) || "worktree", branch }];
|
|
732
|
-
}).map(async ({ rawPath, ...item }) => {
|
|
733
|
-
const realWorktreePath = await realpath(rawPath).catch(() => null);
|
|
734
|
-
const currentPath = realWorktreePath ?? rawPath;
|
|
735
|
-
const contextRelPath = realTheaterPath !== null && realWorktreePath !== null && isWithinTheater(realWorktreePath, realTheaterPath) ? path2.relative(realTheaterPath, realWorktreePath).split(path2.sep).join("/") : null;
|
|
736
|
-
return { ...item, current: normalizedCurrent !== "" && currentPath === normalizedCurrent, contextRelPath };
|
|
737
|
-
}));
|
|
738
|
-
}
|
|
739
|
-
function isWithinTheater(candidate, theaterPath) {
|
|
740
|
-
const normalizedTheaterPath = theaterPath.endsWith(path2.sep) ? theaterPath : `${theaterPath}${path2.sep}`;
|
|
741
|
-
return candidate === theaterPath || candidate.startsWith(normalizedTheaterPath);
|
|
742
|
-
}
|
|
743
811
|
async function handleRepositoryRefs(req, res, ctx) {
|
|
744
812
|
if (req.method !== "POST") {
|
|
745
813
|
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
@@ -750,7 +818,7 @@ async function handleRepositoryRefs(req, res, ctx) {
|
|
|
750
818
|
return;
|
|
751
819
|
}
|
|
752
820
|
const body = await ctx.host.http.readJsonBody(req);
|
|
753
|
-
if (!isObject(body) || typeof body.theaterId !== "string") {
|
|
821
|
+
if (!isObject(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
754
822
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
755
823
|
return;
|
|
756
824
|
}
|
|
@@ -759,22 +827,25 @@ async function handleRepositoryRefs(req, res, ctx) {
|
|
|
759
827
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
760
828
|
return;
|
|
761
829
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
830
|
+
let resolved;
|
|
831
|
+
try {
|
|
832
|
+
resolved = await resolveGitCwd(theaterPath, body.repoRel);
|
|
833
|
+
} catch (error) {
|
|
834
|
+
if (error instanceof InvalidRepoError) {
|
|
835
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
throw error;
|
|
766
839
|
}
|
|
767
840
|
try {
|
|
768
|
-
const [head, local, remote, tags, stashes
|
|
841
|
+
const [head, local, remote, tags, stashes] = await Promise.all([
|
|
769
842
|
runGit(["symbolic-ref", "--quiet", "HEAD"], { cwd: resolved.gitCwd, allowExitCodes: [1] }),
|
|
770
843
|
runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/heads"], { cwd: resolved.gitCwd }),
|
|
771
844
|
runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/remotes"], { cwd: resolved.gitCwd }),
|
|
772
845
|
runGit(["for-each-ref", "--format=%(refname)%00%(refname:short)", "refs/tags"], { cwd: resolved.gitCwd }),
|
|
773
|
-
readStashes(resolved.gitCwd)
|
|
774
|
-
runGit(["worktree", "list", "--porcelain"], { cwd: resolved.gitCwd })
|
|
846
|
+
readStashes(resolved.gitCwd)
|
|
775
847
|
]);
|
|
776
848
|
const current = head.stdout.trim();
|
|
777
|
-
const currentWorktreePath = await readCurrentWorktreePath2(resolved.gitCwd);
|
|
778
849
|
ctx.host.http.writeJson(res, 200, {
|
|
779
850
|
branches: parseRefItems(local.stdout, current),
|
|
780
851
|
remotes: parseRefItems(remote.stdout, current),
|
|
@@ -782,12 +853,11 @@ async function handleRepositoryRefs(req, res, ctx) {
|
|
|
782
853
|
stashes: lines(stashes).map((line) => {
|
|
783
854
|
const [name, subject = ""] = line.split("\0");
|
|
784
855
|
return { name, subject };
|
|
785
|
-
})
|
|
786
|
-
worktrees: await parseWorktrees(worktrees.stdout, currentWorktreePath, theaterPath)
|
|
856
|
+
})
|
|
787
857
|
});
|
|
788
858
|
} catch (error) {
|
|
789
859
|
if (error instanceof GitExecutorError && error.code === "no_git_repo") {
|
|
790
|
-
ctx.host.http.writeJson(res, 200, { branches: [], remotes: [], tags: [], stashes: []
|
|
860
|
+
ctx.host.http.writeJson(res, 200, { branches: [], remotes: [], tags: [], stashes: [] });
|
|
791
861
|
return;
|
|
792
862
|
}
|
|
793
863
|
if (error instanceof GitExecutorError && error.code === "git_unavailable") {
|
|
@@ -797,11 +867,240 @@ async function handleRepositoryRefs(req, res, ctx) {
|
|
|
797
867
|
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
798
868
|
}
|
|
799
869
|
}
|
|
870
|
+
var REPOS_CAP = 200;
|
|
871
|
+
var HARD_CAP_DEPTH = 8;
|
|
872
|
+
var NESTED_BRANCH_CAP = 64;
|
|
873
|
+
var SCAN_VISIT_BUDGET = 1e4;
|
|
874
|
+
var DEFAULT_DEPTH = 3;
|
|
875
|
+
function isPlainObject5(value) {
|
|
876
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
877
|
+
}
|
|
878
|
+
async function resolveRepoBranch(repoDir) {
|
|
879
|
+
try {
|
|
880
|
+
const result = await runGit(["status", "--porcelain=v2", "--branch", "--untracked-files=no"], { cwd: repoDir });
|
|
881
|
+
const branch = result.stdout.split("\n").find((line) => line.startsWith("# branch.head "))?.slice(14).trim() ?? "";
|
|
882
|
+
const oid = result.stdout.split("\n").find((line) => line.startsWith("# branch.oid "))?.slice(13).trim() ?? "";
|
|
883
|
+
return branch === "(detached)" ? oid.slice(0, 7) : branch;
|
|
884
|
+
} catch {
|
|
885
|
+
return "";
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async function scanRepos(theaterPath, realTheaterPath, dir, currentDepth, maxDepth, repos, cap = REPOS_CAP, visits = { remaining: SCAN_VISIT_BUDGET }) {
|
|
889
|
+
if (repos.length >= cap) return true;
|
|
890
|
+
if (visits.remaining <= 0) return true;
|
|
891
|
+
visits.remaining -= 1;
|
|
892
|
+
const relPath = path4.relative(realTheaterPath, dir);
|
|
893
|
+
const gitDir = isSelectableRepoRel(relPath) ? await resolveContainedGitDir(dir, realTheaterPath) : null;
|
|
894
|
+
let linkedWorktree = false;
|
|
895
|
+
if (gitDir !== null) {
|
|
896
|
+
try {
|
|
897
|
+
await fs2.access(path4.join(gitDir, "commondir"));
|
|
898
|
+
linkedWorktree = true;
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (gitDir !== null && !linkedWorktree) {
|
|
903
|
+
repos.push({ relPath, name: relPath === "" ? path4.basename(realTheaterPath) : path4.basename(dir), repoDir: dir });
|
|
904
|
+
if (repos.length >= cap) return true;
|
|
905
|
+
}
|
|
906
|
+
if (currentDepth >= maxDepth) return false;
|
|
907
|
+
let dirHandle = null;
|
|
908
|
+
try {
|
|
909
|
+
dirHandle = await fs2.opendir(dir);
|
|
910
|
+
let dirent = await dirHandle.read();
|
|
911
|
+
while (dirent !== null) {
|
|
912
|
+
if (repos.length >= cap || visits.remaining <= 0) return true;
|
|
913
|
+
if (dirent.isDirectory() && dirent.name !== "node_modules" && dirent.name !== ".git") {
|
|
914
|
+
const childPath = path4.join(dir, dirent.name);
|
|
915
|
+
let realChildPath;
|
|
916
|
+
try {
|
|
917
|
+
realChildPath = await fs2.realpath(childPath);
|
|
918
|
+
} catch {
|
|
919
|
+
dirent = await dirHandle.read();
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (isPathContained(realTheaterPath, realChildPath)) {
|
|
923
|
+
const truncated = await scanRepos(realTheaterPath, realTheaterPath, realChildPath, currentDepth + 1, maxDepth, repos, cap, visits);
|
|
924
|
+
if (truncated) return true;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
dirent = await dirHandle.read();
|
|
928
|
+
}
|
|
929
|
+
return false;
|
|
930
|
+
} catch {
|
|
931
|
+
return false;
|
|
932
|
+
} finally {
|
|
933
|
+
if (dirHandle) {
|
|
934
|
+
try {
|
|
935
|
+
await dirHandle.close();
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function resolveNestedRepoCandidates(candidates, availableSlots, resolver = resolveRepoBranch) {
|
|
942
|
+
const resolved = [];
|
|
943
|
+
const selected = candidates.slice(0, Math.max(0, availableSlots));
|
|
944
|
+
for (const [index, candidate] of selected.entries()) {
|
|
945
|
+
resolved.push({
|
|
946
|
+
relPath: candidate.relPath,
|
|
947
|
+
name: candidate.name,
|
|
948
|
+
branch: index < NESTED_BRANCH_CAP ? await resolver(candidate.repoDir) : "",
|
|
949
|
+
kind: "nested"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return resolved;
|
|
953
|
+
}
|
|
954
|
+
async function handleRepositoryRepos(req, res, ctx) {
|
|
955
|
+
if (req.method !== "POST") {
|
|
956
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
960
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
964
|
+
if (!isPlainObject5(body) || typeof body.theaterId !== "string") {
|
|
965
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
969
|
+
if (!theaterPath) {
|
|
970
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
let realTheaterPath;
|
|
974
|
+
try {
|
|
975
|
+
realTheaterPath = await fs2.realpath(theaterPath);
|
|
976
|
+
} catch {
|
|
977
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const maxDepth = typeof body.maxDepth === "number" && Number.isFinite(body.maxDepth) ? Math.max(1, Math.min(HARD_CAP_DEPTH, Math.floor(body.maxDepth))) : DEFAULT_DEPTH;
|
|
981
|
+
const repos = [];
|
|
982
|
+
const seen = /* @__PURE__ */ new Set();
|
|
983
|
+
let truncated = false;
|
|
984
|
+
let rootIsRepo = await resolveContainedGitDir(realTheaterPath, realTheaterPath) !== null;
|
|
985
|
+
if (!rootIsRepo) {
|
|
986
|
+
try {
|
|
987
|
+
const inside = await runGit(["rev-parse", "--is-inside-work-tree"], { cwd: theaterPath });
|
|
988
|
+
rootIsRepo = inside.stdout.trim() === "true";
|
|
989
|
+
} catch {
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (rootIsRepo) {
|
|
993
|
+
repos.push({ relPath: "", name: path4.basename(theaterPath), branch: await resolveRepoBranch(theaterPath), kind: "root" });
|
|
994
|
+
seen.add("");
|
|
995
|
+
}
|
|
996
|
+
const scanned = [];
|
|
997
|
+
const scanCap = Math.max(0, REPOS_CAP - repos.length);
|
|
998
|
+
truncated = await scanRepos(realTheaterPath, realTheaterPath, realTheaterPath, 0, maxDepth, scanned, scanCap) || truncated;
|
|
999
|
+
const unseenNested = scanned.filter((candidate) => !seen.has(candidate.relPath));
|
|
1000
|
+
const remainingSlots = Math.max(0, REPOS_CAP - repos.length);
|
|
1001
|
+
if (unseenNested.length > remainingSlots) truncated = true;
|
|
1002
|
+
const nestedCandidates = await resolveNestedRepoCandidates(unseenNested, remainingSlots);
|
|
1003
|
+
for (const candidate of nestedCandidates) {
|
|
1004
|
+
if (repos.length >= REPOS_CAP) {
|
|
1005
|
+
truncated = true;
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
repos.push(candidate);
|
|
1009
|
+
seen.add(candidate.relPath);
|
|
1010
|
+
}
|
|
1011
|
+
const result = { repos, ...truncated ? { truncated: true } : {} };
|
|
1012
|
+
ctx.host.http.writeJson(res, 200, result);
|
|
1013
|
+
}
|
|
1014
|
+
function isPlainObject6(value) {
|
|
1015
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1016
|
+
}
|
|
1017
|
+
async function handleRepositoryWorktrees(req, res, ctx) {
|
|
1018
|
+
if (req.method !== "POST") {
|
|
1019
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
1023
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
1027
|
+
if (!isPlainObject6(body) || "subPath" in body || typeof body.theaterId !== "string") {
|
|
1028
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(body.theaterId);
|
|
1032
|
+
if (!theaterPath) {
|
|
1033
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
let gitCwd;
|
|
1037
|
+
try {
|
|
1038
|
+
({ gitCwd } = await resolveGitCwd(theaterPath, body.repoRel));
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
if (error instanceof InvalidRepoError) {
|
|
1041
|
+
ctx.host.http.writeJson(res, 400, { error: error.code });
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
throw error;
|
|
1045
|
+
}
|
|
1046
|
+
let realTheaterPath;
|
|
1047
|
+
let realGitCwd;
|
|
1048
|
+
try {
|
|
1049
|
+
[realTheaterPath, realGitCwd] = await Promise.all([fs2.realpath(theaterPath), fs2.realpath(gitCwd)]);
|
|
1050
|
+
} catch {
|
|
1051
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_repo" });
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
const result = await runGit(["worktree", "list", "--porcelain"], { cwd: gitCwd });
|
|
1056
|
+
const worktrees = [];
|
|
1057
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1058
|
+
for (const worktree of parseWorktreePorcelainEntries(result.stdout)) {
|
|
1059
|
+
let realWorktree;
|
|
1060
|
+
try {
|
|
1061
|
+
realWorktree = await fs2.realpath(worktree.worktreePath);
|
|
1062
|
+
} catch {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (!isPathContained(realTheaterPath, realWorktree)) continue;
|
|
1066
|
+
const relPath = path4.relative(realTheaterPath, realWorktree);
|
|
1067
|
+
if (!isSelectableRepoRel(relPath) || seen.has(relPath)) continue;
|
|
1068
|
+
if (await resolveContainedGitDir(realWorktree, realTheaterPath) === null) continue;
|
|
1069
|
+
worktrees.push({
|
|
1070
|
+
relPath,
|
|
1071
|
+
name: path4.basename(realWorktree),
|
|
1072
|
+
branch: worktree.branch ?? worktree.sha.slice(0, 7),
|
|
1073
|
+
current: realWorktree === realGitCwd
|
|
1074
|
+
});
|
|
1075
|
+
seen.add(relPath);
|
|
1076
|
+
}
|
|
1077
|
+
const payload = { worktrees };
|
|
1078
|
+
ctx.host.http.writeJson(res, 200, payload);
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
if (error instanceof GitExecutorError) {
|
|
1081
|
+
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
1082
|
+
ctx.host.http.writeJson(res, 422, { error: error.code });
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
ctx.host.http.writeJson(res, 500, { error: "git_failed" });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
throw error;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
800
1091
|
|
|
801
1092
|
// ../fleet-plugins/repository/routes.ts
|
|
802
1093
|
var routes_default = definePlugin({
|
|
803
1094
|
id: "repository",
|
|
804
1095
|
register(ctx) {
|
|
1096
|
+
registerRouter(ctx, "repos", async ({ req, res }) => {
|
|
1097
|
+
await handleRepositoryRepos(req, res, ctx);
|
|
1098
|
+
return true;
|
|
1099
|
+
});
|
|
1100
|
+
registerRouter(ctx, "worktrees", async ({ req, res }) => {
|
|
1101
|
+
await handleRepositoryWorktrees(req, res, ctx);
|
|
1102
|
+
return true;
|
|
1103
|
+
});
|
|
805
1104
|
registerRouter(ctx, "changed", async ({ req, res }) => {
|
|
806
1105
|
await handleRepositoryChanged(req, res, ctx);
|
|
807
1106
|
return true;
|