@dotobokuri/fleet-console 1.16.1 → 1.17.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 +5 -2
- package/dist/cli.mjs +499 -299
- package/dist/client/assets/{_baseUniq-Bvbp_jlP.js → _baseUniq-24vRkv8i.js} +1 -1
- package/dist/client/assets/{arc-BIE5Buf8.js → arc-BTiCiQ57.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-D5XMG93D.js → architectureDiagram-Q4EWVU46-B24oDJvB.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-DF6L79td.js → blockDiagram-DXYQGD6D-BOAE3FdW.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-DhF1h0OW.js → c4Diagram-AHTNJAMY-C0W9raQx.js} +1 -1
- package/dist/client/assets/channel-yb1CkB_S.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-HIDhmpsY.js → chunk-4BX2VUAB-DvyhIkzo.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-WM3UvlRe.js → chunk-4TB4RGXK-CMV1UZLH.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D9kCAM0-.js → chunk-55IACEB6-CG75wbs4.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-CHGSf6YA.js → chunk-EDXVE4YY-9CjdVkNM.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-saa1UCmo.js → chunk-FMBD7UC4-i496V3zS.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-BrlxxClZ.js → chunk-OYMX7WX6-CeRfBoEk.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CVnd5MX8.js → chunk-QZHKN3VN-D1mXQVL_.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-BpsFNlbM.js → chunk-YZCP3GAM-DUXK6jpB.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DgNTT8Lb.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DgNTT8Lb.js +1 -0
- package/dist/client/assets/clone-BLvG1gy6.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-AdFIrmD1.js → cose-bilkent-S5V4N54A-Cw97IMZH.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-YPE_DPjG.js → dagre-KV5264BT-CyY9URRi.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-sW7JSe_o.js → diagram-5BDNPKRD-BEVj5KaK.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-CdoUA7HM.js → diagram-G4DWMVQ6-IXOT_4VL.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-Dr_Q44k2.js → diagram-MMDJMWI5-CO2p0poL.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-CgWLDdO4.js → diagram-TYMM5635-DvRDFMNM.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-BS5jKQII.js → erDiagram-SMLLAGMA-C5uteguB.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-DM_ZIfsi.js → flowDiagram-DWJPFMVM-JekEx60D.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-D1ASi2pt.js → ganttDiagram-T4ZO3ILL-DRKjnKDn.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-DTS8qu5f.js → gitGraphDiagram-UUTBAWPF-Dhnk3n7N.js} +1 -1
- package/dist/client/assets/{graph-BSP3V12O.js → graph-DE-kEMug.js} +1 -1
- package/dist/client/assets/index-BY_CeGCl.js +402 -0
- package/dist/client/assets/{index-DNtncJ2Y.css → index-CfNSyAUJ.css} +1 -1
- package/dist/client/assets/{infoDiagram-42DDH7IO-C6rS44Vv.js → infoDiagram-42DDH7IO-DG9M_7T9.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-BysMV0Yj.js → ishikawaDiagram-UXIWVN3A-CiP4wnqc.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-Cq1tDTjK.js → journeyDiagram-VCZTEJTY-DklSog3d.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-CV7fuoIo.js → kanban-definition-6JOO6SKY-BVB77h52.js} +1 -1
- package/dist/client/assets/{layout-mBwNdhT9.js → layout-CAevigwv.js} +1 -1
- package/dist/client/assets/{linear-B8Mqzhqu.js → linear-CB_shmWV.js} +1 -1
- package/dist/client/assets/{mermaid.core-mTVIRAe-.js → mermaid.core-DPLCTlKT.js} +4 -4
- package/dist/client/assets/{min-LkS4GBrL.js → min-Cimc7NFp.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-CuK4DHsN.js → mindmap-definition-QFDTVHPH-DnyowBeF.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-yKtYS0aG.js → pieDiagram-DEJITSTG-B1mr5ZWu.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-CQYBuUXw.js → quadrantDiagram-34T5L4WZ-CJla4J4E.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-DExCsGb_.js → requirementDiagram-MS252O5E-Bduy03Er.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-kqoN-VmQ.js → sankeyDiagram-XADWPNL6-G7I_cbR7.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-Dsk6zp0F.js → sequenceDiagram-FGHM5R23-D4Uf0SLt.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-Bg-kY2ci.js → stateDiagram-FHFEXIEX-Fr0IevW-.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DXt_OYu8.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-DkeszYPW.js → timeline-definition-GMOUNBTQ-CzR6FMOJ.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-a9aq_CaS.js → vennDiagram-DHZGUBPP-CCzsRm3-.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-BioIIFLl.js → wardley-RL74JXVD-C9U_HC7W.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-Bpy2nx7x.js → wardleyDiagram-NUSXRM2D-BkwkDUKo.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-N7vhpanC.js → xychartDiagram-5P7HB3ND-CZ45hysw.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/diff/routes.mjs +224 -57
- package/dist/fleet-plugins/file-explorer/routes.mjs +130 -0
- package/dist/fleet-plugins/skills/routes.mjs +17052 -0
- package/dist/fleet-plugins/terminal/routes.mjs +164 -67
- package/package.json +1 -1
- package/dist/client/assets/channel-BSjWiXu9.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DIEVc5gp.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DIEVc5gp.js +0 -1
- package/dist/client/assets/clone-VuQnnBA0.js +0 -1
- package/dist/client/assets/index-D3a51i1X.js +0 -402
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-Dlqv8bJ9.js +0 -1
|
@@ -7,12 +7,12 @@ import { spawn } from 'child_process';
|
|
|
7
7
|
function definePlugin(definition) {
|
|
8
8
|
return definition;
|
|
9
9
|
}
|
|
10
|
-
function registerRouter(ctx,
|
|
11
|
-
ctx.registerRouter(normalizePath(
|
|
10
|
+
function registerRouter(ctx, path3, handler) {
|
|
11
|
+
ctx.registerRouter(normalizePath(path3), handler);
|
|
12
12
|
}
|
|
13
|
-
function normalizePath(
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
13
|
+
function normalizePath(path3) {
|
|
14
|
+
if (path3.length === 0 || path3 === "/") return "/";
|
|
15
|
+
return path3;
|
|
16
16
|
}
|
|
17
17
|
var GitExecutorError = class extends Error {
|
|
18
18
|
code;
|
|
@@ -91,14 +91,13 @@ function runGit(args, opts) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// ../fleet-plugins/diff/server/diff.ts
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return REF_SAFE_RE.test(ref);
|
|
94
|
+
function isPlainObject(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
97
96
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
return ["
|
|
97
|
+
function normalizeNumstatPath(p) {
|
|
98
|
+
const match = /^(.*?)\{[^}]* => ([^}]*)\}(.*)$/.exec(p);
|
|
99
|
+
if (!match) return p;
|
|
100
|
+
return (match[1] ?? "") + (match[2] ?? "") + (match[3] ?? "");
|
|
102
101
|
}
|
|
103
102
|
function parseDiffFileList(nameStatusOutput, numstatOutput) {
|
|
104
103
|
const numstatMap = /* @__PURE__ */ new Map();
|
|
@@ -107,7 +106,7 @@ function parseDiffFileList(nameStatusOutput, numstatOutput) {
|
|
|
107
106
|
if (parts.length < 3) continue;
|
|
108
107
|
const [adds, dels, filePath] = parts;
|
|
109
108
|
if (!filePath) continue;
|
|
110
|
-
numstatMap.set(filePath, {
|
|
109
|
+
numstatMap.set(normalizeNumstatPath(filePath), {
|
|
111
110
|
additions: parseInt(adds ?? "0", 10) || 0,
|
|
112
111
|
deletions: parseInt(dels ?? "0", 10) || 0
|
|
113
112
|
});
|
|
@@ -135,8 +134,29 @@ async function fetchUntrackedFiles(cwd) {
|
|
|
135
134
|
deletions: 0
|
|
136
135
|
}));
|
|
137
136
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
137
|
+
function isNoHeadError(error) {
|
|
138
|
+
if (!(error instanceof GitExecutorError)) return false;
|
|
139
|
+
if (error.code !== "non_zero_exit") return false;
|
|
140
|
+
return error.stderr.includes("unknown revision") || error.stderr.includes("bad revision");
|
|
141
|
+
}
|
|
142
|
+
async function resolveGitCwd(theaterPath, rawSubPath) {
|
|
143
|
+
if (rawSubPath === "") return { gitCwd: theaterPath };
|
|
144
|
+
const resolvedSub = path.resolve(theaterPath, rawSubPath);
|
|
145
|
+
if (!resolvedSub.startsWith(theaterPath + path.sep) && resolvedSub !== theaterPath) return null;
|
|
146
|
+
if (path.normalize(rawSubPath).startsWith("..")) return null;
|
|
147
|
+
let realTheater;
|
|
148
|
+
let realSub;
|
|
149
|
+
try {
|
|
150
|
+
[realTheater, realSub] = await Promise.all([
|
|
151
|
+
fs.realpath(theaterPath),
|
|
152
|
+
fs.realpath(resolvedSub)
|
|
153
|
+
]);
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const norm = realTheater.endsWith(path.sep) ? realTheater : realTheater + path.sep;
|
|
158
|
+
if (realSub !== realTheater && !realSub.startsWith(norm)) return null;
|
|
159
|
+
return { gitCwd: resolvedSub };
|
|
140
160
|
}
|
|
141
161
|
async function handleDiffChanged(req, res, ctx) {
|
|
142
162
|
if (req.method !== "POST") {
|
|
@@ -152,43 +172,48 @@ async function handleDiffChanged(req, res, ctx) {
|
|
|
152
172
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
153
173
|
return;
|
|
154
174
|
}
|
|
155
|
-
const mode = body.mode;
|
|
156
|
-
if (mode !== "workdir" && mode !== "staged" && mode !== "commit") {
|
|
157
|
-
ctx.host.http.writeJson(res, 400, { error: "invalid_mode" });
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
175
|
const theaterId = body.theaterId;
|
|
161
176
|
if (typeof theaterId !== "string") {
|
|
162
177
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
163
178
|
return;
|
|
164
179
|
}
|
|
165
|
-
const rawRef = typeof body.ref === "string" ? body.ref : void 0;
|
|
166
|
-
if (rawRef !== void 0 && !isSafeGitRef(rawRef)) {
|
|
167
|
-
ctx.host.http.writeJson(res, 400, { error: "invalid_ref" });
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
180
|
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
171
181
|
if (!theaterPath) {
|
|
172
182
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
173
183
|
return;
|
|
174
184
|
}
|
|
185
|
+
const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
|
|
186
|
+
const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
|
|
187
|
+
if (!cwdResult) {
|
|
188
|
+
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const { gitCwd } = cwdResult;
|
|
175
192
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
let files;
|
|
194
|
+
let truncated = false;
|
|
195
|
+
try {
|
|
196
|
+
const [nameStatusResult, numstatResult] = await Promise.all([
|
|
197
|
+
runGit(["diff", "HEAD", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
|
|
198
|
+
runGit(["diff", "HEAD", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
|
|
199
|
+
]);
|
|
200
|
+
files = parseDiffFileList(nameStatusResult.stdout, numstatResult.stdout);
|
|
201
|
+
truncated = nameStatusResult.truncated || numstatResult.truncated;
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (!isNoHeadError(err)) throw err;
|
|
204
|
+
const [nsResult, nsNumstat] = await Promise.all([
|
|
205
|
+
runGit(["diff", "--cached", "--name-status", "--diff-filter=MADR"], { cwd: gitCwd }),
|
|
206
|
+
runGit(["diff", "--cached", "--numstat", "--diff-filter=MADR"], { cwd: gitCwd })
|
|
207
|
+
]);
|
|
208
|
+
files = parseDiffFileList(nsResult.stdout, nsNumstat.stdout);
|
|
209
|
+
truncated = nsResult.truncated || nsNumstat.truncated;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const untrackedFiles = await fetchUntrackedFiles(gitCwd);
|
|
213
|
+
files.push(...untrackedFiles);
|
|
214
|
+
} catch {
|
|
190
215
|
}
|
|
191
|
-
ctx.host.http.writeJson(res, 200, { files, truncated
|
|
216
|
+
ctx.host.http.writeJson(res, 200, { files, truncated });
|
|
192
217
|
} catch (error) {
|
|
193
218
|
if (error instanceof GitExecutorError) {
|
|
194
219
|
if (error.code === "no_git_repo" || error.code === "git_unavailable") {
|
|
@@ -216,7 +241,7 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
216
241
|
return;
|
|
217
242
|
}
|
|
218
243
|
const mode = body.mode;
|
|
219
|
-
if (mode !== "
|
|
244
|
+
if (mode !== "unified" && mode !== "untracked") {
|
|
220
245
|
ctx.host.http.writeJson(res, 400, { error: "invalid_mode" });
|
|
221
246
|
return;
|
|
222
247
|
}
|
|
@@ -225,53 +250,71 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
225
250
|
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
226
251
|
return;
|
|
227
252
|
}
|
|
228
|
-
const rawFilePath = body.filePath;
|
|
229
253
|
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
230
254
|
if (!theaterPath) {
|
|
231
255
|
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
232
256
|
return;
|
|
233
257
|
}
|
|
234
|
-
const
|
|
235
|
-
|
|
258
|
+
const rawSubPath = typeof body.subPath === "string" ? body.subPath : "";
|
|
259
|
+
const cwdResult = await resolveGitCwd(theaterPath, rawSubPath);
|
|
260
|
+
if (!cwdResult) {
|
|
236
261
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
237
262
|
return;
|
|
238
263
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
264
|
+
const { gitCwd } = cwdResult;
|
|
265
|
+
const rawFilePath = body.filePath;
|
|
266
|
+
const resolvedFilePath = path.resolve(gitCwd, rawFilePath);
|
|
267
|
+
if (!resolvedFilePath.startsWith(gitCwd + path.sep) && resolvedFilePath !== gitCwd) {
|
|
241
268
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
242
269
|
return;
|
|
243
270
|
}
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
ctx.host.http.writeJson(res,
|
|
271
|
+
const relativePath = path.normalize(rawFilePath);
|
|
272
|
+
if (relativePath.startsWith("..")) {
|
|
273
|
+
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
247
274
|
return;
|
|
248
275
|
}
|
|
249
276
|
try {
|
|
250
277
|
if (mode === "untracked") {
|
|
251
|
-
let
|
|
278
|
+
let realGitCwd;
|
|
252
279
|
let realFile;
|
|
253
280
|
try {
|
|
254
|
-
[
|
|
255
|
-
fs.realpath(
|
|
256
|
-
fs.realpath(
|
|
281
|
+
[realGitCwd, realFile] = await Promise.all([
|
|
282
|
+
fs.realpath(gitCwd),
|
|
283
|
+
fs.realpath(resolvedFilePath)
|
|
257
284
|
]);
|
|
258
285
|
} catch {
|
|
259
286
|
ctx.host.http.writeJson(res, 404, { error: "file_not_found" });
|
|
260
287
|
return;
|
|
261
288
|
}
|
|
262
|
-
if (realFile !==
|
|
289
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path.sep)) {
|
|
263
290
|
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
264
291
|
return;
|
|
265
292
|
}
|
|
266
293
|
const result2 = await runGit(
|
|
267
294
|
["diff", "--no-index", "--unified=3", "--", "/dev/null", relativePath],
|
|
268
|
-
{ cwd:
|
|
295
|
+
{ cwd: gitCwd, allowExitCodes: [1] }
|
|
269
296
|
);
|
|
270
297
|
ctx.host.http.writeJson(res, 200, { content: result2.stdout, truncated: result2.truncated });
|
|
271
298
|
return;
|
|
272
299
|
}
|
|
273
|
-
|
|
274
|
-
|
|
300
|
+
try {
|
|
301
|
+
const [realGitCwd, realFile] = await Promise.all([
|
|
302
|
+
fs.realpath(gitCwd),
|
|
303
|
+
fs.realpath(resolvedFilePath)
|
|
304
|
+
]);
|
|
305
|
+
if (realFile !== realGitCwd && !realFile.startsWith(realGitCwd + path.sep)) {
|
|
306
|
+
ctx.host.http.writeJson(res, 403, { error: "path_outside_theater" });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
let result;
|
|
312
|
+
try {
|
|
313
|
+
result = await runGit(["diff", "HEAD", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
314
|
+
} catch (err) {
|
|
315
|
+
if (!isNoHeadError(err)) throw err;
|
|
316
|
+
result = await runGit(["diff", "--cached", "--unified=3", "--", relativePath], { cwd: gitCwd });
|
|
317
|
+
}
|
|
275
318
|
ctx.host.http.writeJson(res, 200, { content: result.stdout, truncated: result.truncated });
|
|
276
319
|
} catch (error) {
|
|
277
320
|
if (error instanceof GitExecutorError) {
|
|
@@ -285,11 +328,135 @@ async function handleDiffFile(req, res, ctx) {
|
|
|
285
328
|
throw error;
|
|
286
329
|
}
|
|
287
330
|
}
|
|
331
|
+
var REPOS_CAP = 200;
|
|
332
|
+
var HARD_CAP_DEPTH = 8;
|
|
333
|
+
var DEFAULT_DEPTH = 3;
|
|
334
|
+
function isPlainObject2(value) {
|
|
335
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
336
|
+
}
|
|
337
|
+
async function resolveRepoBranch(repoDir) {
|
|
338
|
+
try {
|
|
339
|
+
const result = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoDir });
|
|
340
|
+
const branch = result.stdout.trim();
|
|
341
|
+
if (branch === "HEAD") {
|
|
342
|
+
const sha = await runGit(["rev-parse", "--short", "HEAD"], { cwd: repoDir });
|
|
343
|
+
return sha.stdout.trim() || "HEAD";
|
|
344
|
+
}
|
|
345
|
+
return branch || "HEAD";
|
|
346
|
+
} catch {
|
|
347
|
+
return "unknown";
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function scanRepos(theaterPath, realTheaterPath, dir, currentDepth, maxDepth, repos, cap = REPOS_CAP) {
|
|
351
|
+
if (repos.length >= cap) return true;
|
|
352
|
+
const gitPath = path.join(dir, ".git");
|
|
353
|
+
try {
|
|
354
|
+
await fs.stat(gitPath);
|
|
355
|
+
const relPath = path.relative(theaterPath, dir);
|
|
356
|
+
const name = relPath === "" ? path.basename(theaterPath) : path.basename(dir);
|
|
357
|
+
const branch = await resolveRepoBranch(dir);
|
|
358
|
+
repos.push({ relPath, name, branch });
|
|
359
|
+
if (repos.length >= cap) return true;
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
if (currentDepth >= maxDepth) return false;
|
|
363
|
+
let dirHandle = null;
|
|
364
|
+
try {
|
|
365
|
+
dirHandle = await fs.opendir(dir);
|
|
366
|
+
try {
|
|
367
|
+
let dirent = await dirHandle.read();
|
|
368
|
+
while (dirent !== null) {
|
|
369
|
+
if (repos.length >= cap) return true;
|
|
370
|
+
if (dirent.isDirectory()) {
|
|
371
|
+
const childName = dirent.name;
|
|
372
|
+
if (childName === "node_modules" || childName === ".git") {
|
|
373
|
+
dirent = await dirHandle.read();
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const childPath = path.join(dir, childName);
|
|
377
|
+
let realChildPath;
|
|
378
|
+
try {
|
|
379
|
+
realChildPath = await fs.realpath(childPath);
|
|
380
|
+
} catch {
|
|
381
|
+
dirent = await dirHandle.read();
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const normalizedReal = realTheaterPath.endsWith(path.sep) ? realTheaterPath : realTheaterPath + path.sep;
|
|
385
|
+
if (realChildPath !== realTheaterPath && !realChildPath.startsWith(normalizedReal)) {
|
|
386
|
+
dirent = await dirHandle.read();
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const truncated = await scanRepos(theaterPath, realTheaterPath, childPath, currentDepth + 1, maxDepth, repos, cap);
|
|
390
|
+
if (truncated) return true;
|
|
391
|
+
}
|
|
392
|
+
dirent = await dirHandle.read();
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
} finally {
|
|
396
|
+
await dirHandle.close();
|
|
397
|
+
}
|
|
398
|
+
} catch {
|
|
399
|
+
if (dirHandle) {
|
|
400
|
+
try {
|
|
401
|
+
await dirHandle.close();
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function handleDiffRepos(req, res, ctx) {
|
|
409
|
+
if (req.method !== "POST") {
|
|
410
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
414
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
418
|
+
if (!isPlainObject2(body)) {
|
|
419
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const theaterId = body.theaterId;
|
|
423
|
+
if (typeof theaterId !== "string") {
|
|
424
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
428
|
+
if (!theaterPath) {
|
|
429
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const rawDepth = body.maxDepth;
|
|
433
|
+
const maxDepth = typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 1 ? Math.min(HARD_CAP_DEPTH, Math.floor(rawDepth)) : DEFAULT_DEPTH;
|
|
434
|
+
let realTheaterPath;
|
|
435
|
+
try {
|
|
436
|
+
realTheaterPath = await fs.realpath(theaterPath);
|
|
437
|
+
} catch {
|
|
438
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const repos = [];
|
|
442
|
+
const truncated = await scanRepos(theaterPath, realTheaterPath, theaterPath, 0, maxDepth, repos);
|
|
443
|
+
repos.sort((a, b) => {
|
|
444
|
+
if (a.relPath === "") return -1;
|
|
445
|
+
if (b.relPath === "") return 1;
|
|
446
|
+
return a.relPath.localeCompare(b.relPath);
|
|
447
|
+
});
|
|
448
|
+
const result = { repos, ...truncated ? { truncated: true } : {} };
|
|
449
|
+
ctx.host.http.writeJson(res, 200, result);
|
|
450
|
+
}
|
|
288
451
|
|
|
289
452
|
// ../fleet-plugins/diff/routes.ts
|
|
290
453
|
var routes_default = definePlugin({
|
|
291
454
|
id: "diff",
|
|
292
455
|
register(ctx) {
|
|
456
|
+
registerRouter(ctx, "repos", async ({ req, res }) => {
|
|
457
|
+
await handleDiffRepos(req, res, ctx);
|
|
458
|
+
return true;
|
|
459
|
+
});
|
|
293
460
|
registerRouter(ctx, "changed", async ({ req, res }) => {
|
|
294
461
|
await handleDiffChanged(req, res, ctx);
|
|
295
462
|
return true;
|
|
@@ -300,6 +300,81 @@ function isWithinRoot2(resolved, root) {
|
|
|
300
300
|
const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
|
|
301
301
|
return resolved === root || resolved.startsWith(normalizedRoot);
|
|
302
302
|
}
|
|
303
|
+
var DEBOUNCE_MS = 200;
|
|
304
|
+
var watcherRegistry = createWatcherRegistry();
|
|
305
|
+
function createWatcherRegistry(watcherFactory = (p, opts, cb) => fs.watch(p, opts, (ev, fn) => cb(ev, typeof fn === "string" ? fn : null)), debounceMs = DEBOUNCE_MS) {
|
|
306
|
+
const entries = /* @__PURE__ */ new Map();
|
|
307
|
+
function makeUnsubscribe(theaterId, onChange, onState, entry) {
|
|
308
|
+
let called = false;
|
|
309
|
+
return () => {
|
|
310
|
+
if (called) return;
|
|
311
|
+
called = true;
|
|
312
|
+
entry.subscribers.delete(onChange);
|
|
313
|
+
entry.stateSubscribers.delete(onState);
|
|
314
|
+
entry.subscriberCount--;
|
|
315
|
+
if (entry.subscriberCount <= 0) {
|
|
316
|
+
for (const t of entry.debounceTimers.values()) clearTimeout(t);
|
|
317
|
+
entry.watcher.close();
|
|
318
|
+
if (entries.get(theaterId) === entry) entries.delete(theaterId);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function subscribe(theaterId, theaterPath, onChange, onState) {
|
|
323
|
+
const existing = entries.get(theaterId);
|
|
324
|
+
if (existing) {
|
|
325
|
+
existing.subscribers.add(onChange);
|
|
326
|
+
existing.stateSubscribers.add(onState);
|
|
327
|
+
existing.subscriberCount++;
|
|
328
|
+
onState("watching");
|
|
329
|
+
return makeUnsubscribe(theaterId, onChange, onState, existing);
|
|
330
|
+
}
|
|
331
|
+
const debounceTimers = /* @__PURE__ */ new Map();
|
|
332
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
333
|
+
const stateSubscribers = /* @__PURE__ */ new Set();
|
|
334
|
+
let watcher;
|
|
335
|
+
try {
|
|
336
|
+
watcher = watcherFactory(theaterPath, { recursive: true }, (_, filename) => {
|
|
337
|
+
const relDir = computeRelDir(filename);
|
|
338
|
+
const pending = debounceTimers.get(relDir);
|
|
339
|
+
if (pending) clearTimeout(pending);
|
|
340
|
+
debounceTimers.set(
|
|
341
|
+
relDir,
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
debounceTimers.delete(relDir);
|
|
344
|
+
for (const sub of subscribers) sub(relDir);
|
|
345
|
+
}, debounceMs)
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
} catch {
|
|
349
|
+
onState("degraded");
|
|
350
|
+
return () => {
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const entry = { watcher, subscribers, stateSubscribers, subscriberCount: 1, debounceTimers };
|
|
354
|
+
watcher.on("error", () => {
|
|
355
|
+
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
356
|
+
debounceTimers.clear();
|
|
357
|
+
try {
|
|
358
|
+
watcher.close();
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
if (entries.get(theaterId) === entry) entries.delete(theaterId);
|
|
362
|
+
for (const notify of stateSubscribers) notify("degraded");
|
|
363
|
+
});
|
|
364
|
+
entries.set(theaterId, entry);
|
|
365
|
+
subscribers.add(onChange);
|
|
366
|
+
stateSubscribers.add(onState);
|
|
367
|
+
onState("watching");
|
|
368
|
+
return makeUnsubscribe(theaterId, onChange, onState, entry);
|
|
369
|
+
}
|
|
370
|
+
return { subscribe };
|
|
371
|
+
}
|
|
372
|
+
function computeRelDir(filename) {
|
|
373
|
+
if (!filename) return "";
|
|
374
|
+
const lastSep = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
375
|
+
if (lastSep <= 0) return "";
|
|
376
|
+
return filename.slice(0, lastSep);
|
|
377
|
+
}
|
|
303
378
|
|
|
304
379
|
// ../fleet-plugins/file-explorer/server/handlers.ts
|
|
305
380
|
function isPlainObject(value) {
|
|
@@ -423,6 +498,57 @@ async function handleFilesImage(req, res, ctx) {
|
|
|
423
498
|
throw error;
|
|
424
499
|
}
|
|
425
500
|
}
|
|
501
|
+
function handleFilesWatch(req, res, ctx) {
|
|
502
|
+
if (req.method !== "GET") {
|
|
503
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
507
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const url = readUrl(req);
|
|
511
|
+
const theaterId = url.searchParams.get("theaterId");
|
|
512
|
+
if (!theaterId) {
|
|
513
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
517
|
+
if (!theaterPath) {
|
|
518
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
res.writeHead(200, {
|
|
522
|
+
"Content-Type": "text/event-stream",
|
|
523
|
+
"Cache-Control": "no-cache",
|
|
524
|
+
"Connection": "keep-alive",
|
|
525
|
+
"X-Accel-Buffering": "no"
|
|
526
|
+
});
|
|
527
|
+
res.flushHeaders();
|
|
528
|
+
function sendEvent(name, data) {
|
|
529
|
+
try {
|
|
530
|
+
res.write(`event: ${name}
|
|
531
|
+
data: ${data}
|
|
532
|
+
|
|
533
|
+
`);
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const unsubscribe = watcherRegistry.subscribe(
|
|
538
|
+
theaterId,
|
|
539
|
+
theaterPath,
|
|
540
|
+
// 개행 포함 파일명이 SSE 필드 경계를 깨지 않도록 JSON으로 프레이밍한다
|
|
541
|
+
(relDir) => sendEvent("change", JSON.stringify(relDir)),
|
|
542
|
+
(state) => sendEvent("state", state)
|
|
543
|
+
);
|
|
544
|
+
req.on("close", () => {
|
|
545
|
+
unsubscribe();
|
|
546
|
+
try {
|
|
547
|
+
res.end();
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
426
552
|
|
|
427
553
|
// ../fleet-plugins/file-explorer/routes.ts
|
|
428
554
|
var routes_default = definePlugin({
|
|
@@ -440,6 +566,10 @@ var routes_default = definePlugin({
|
|
|
440
566
|
await handleFilesImage(req, res, ctx);
|
|
441
567
|
return true;
|
|
442
568
|
});
|
|
569
|
+
registerRouter(ctx, "files/watch", ({ req, res }) => {
|
|
570
|
+
handleFilesWatch(req, res, ctx);
|
|
571
|
+
return true;
|
|
572
|
+
});
|
|
443
573
|
}
|
|
444
574
|
});
|
|
445
575
|
|