@dotobokuri/fleet-console 1.14.0 → 1.16.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 +160 -23
- package/README.md +18 -16
- package/dist/cli.d.ts +5 -20
- package/dist/cli.mjs +14176 -22199
- package/dist/client/assets/{_baseUniq--pkWMMvp.js → _baseUniq-Dl47hoBn.js} +1 -1
- package/dist/client/assets/{arc-DIAmGa-L.js → arc-gTL65oBG.js} +1 -1
- package/dist/client/assets/{architectureDiagram-Q4EWVU46-C1rKH9j8.js → architectureDiagram-Q4EWVU46-B4ysOekM.js} +1 -1
- package/dist/client/assets/{blockDiagram-DXYQGD6D-pUE19ZBm.js → blockDiagram-DXYQGD6D-Cn9GJTRY.js} +1 -1
- package/dist/client/assets/{c4Diagram-AHTNJAMY-D9ojglXD.js → c4Diagram-AHTNJAMY-BIub3g5q.js} +1 -1
- package/dist/client/assets/channel-kIwNtTP-.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-BbHyV-q7.js → chunk-4BX2VUAB-DFHyuJK_.js} +1 -1
- package/dist/client/assets/{chunk-4TB4RGXK-DvK0LRYh.js → chunk-4TB4RGXK-CV_laQzm.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CyZurxBS.js → chunk-55IACEB6-Cs5LeHXe.js} +1 -1
- package/dist/client/assets/{chunk-EDXVE4YY-OXfpLkhv.js → chunk-EDXVE4YY-D_LyOot5.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bp492nAE.js → chunk-FMBD7UC4-B1dz5YRM.js} +1 -1
- package/dist/client/assets/{chunk-OYMX7WX6-Dp06iRbU.js → chunk-OYMX7WX6-CT0D_NEN.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CGFb41af.js → chunk-QZHKN3VN-CWNbvQat.js} +1 -1
- package/dist/client/assets/{chunk-YZCP3GAM-CPemnVAa.js → chunk-YZCP3GAM-C_fp6KvG.js} +1 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DGNebVYt.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DGNebVYt.js +1 -0
- package/dist/client/assets/clone-DFRxH9FR.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DzLbihnc.js → cose-bilkent-S5V4N54A-B6xq1D1R.js} +1 -1
- package/dist/client/assets/{dagre-KV5264BT-gIu_mIwG.js → dagre-KV5264BT-_leuW1pD.js} +1 -1
- package/dist/client/assets/{diagram-5BDNPKRD-BQtHJXn-.js → diagram-5BDNPKRD-4wH1x31-.js} +1 -1
- package/dist/client/assets/{diagram-G4DWMVQ6-Dvu5rGtf.js → diagram-G4DWMVQ6-nlP8pQsq.js} +1 -1
- package/dist/client/assets/{diagram-MMDJMWI5-DffZ4K1f.js → diagram-MMDJMWI5-CWgyNhRW.js} +1 -1
- package/dist/client/assets/{diagram-TYMM5635-CgIAxnvO.js → diagram-TYMM5635-DfGqjepr.js} +1 -1
- package/dist/client/assets/{erDiagram-SMLLAGMA-uThpoDTJ.js → erDiagram-SMLLAGMA-CMpgXQfn.js} +1 -1
- package/dist/client/assets/{flowDiagram-DWJPFMVM-CSgTIvvw.js → flowDiagram-DWJPFMVM-BaLRDaS7.js} +1 -1
- package/dist/client/assets/{ganttDiagram-T4ZO3ILL-B-QOGtBC.js → ganttDiagram-T4ZO3ILL-CAuXJ2xz.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-Cb_lpM0D.js → gitGraphDiagram-UUTBAWPF-KsfuOuTW.js} +1 -1
- package/dist/client/assets/{graph-BSnp6JSF.js → graph-3375lXck.js} +1 -1
- package/dist/client/assets/index-BRniWsxl.css +1 -0
- package/dist/client/assets/index-OEz1YKt3.js +361 -0
- package/dist/client/assets/{infoDiagram-42DDH7IO-BfdmGjc0.js → infoDiagram-42DDH7IO-GYhjUtOi.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-B7IxhAyp.js → ishikawaDiagram-UXIWVN3A-DfjzfJ87.js} +1 -1
- package/dist/client/assets/{journeyDiagram-VCZTEJTY-BLcjOlni.js → journeyDiagram-VCZTEJTY-CjF_oAe2.js} +1 -1
- package/dist/client/assets/{kanban-definition-6JOO6SKY-D7ZIyiF_.js → kanban-definition-6JOO6SKY-DkIK9azu.js} +1 -1
- package/dist/client/assets/{layout-D3OG5NbT.js → layout-g4DCAcmJ.js} +1 -1
- package/dist/client/assets/{linear-BvWfoqQq.js → linear-BXtneTLA.js} +1 -1
- package/dist/client/assets/{mermaid.core-cauuvPfO.js → mermaid.core-CFCyolqU.js} +4 -4
- package/dist/client/assets/{min-BlleJrkQ.js → min-BOlS-Kpi.js} +1 -1
- package/dist/client/assets/{mindmap-definition-QFDTVHPH-Clkt11oj.js → mindmap-definition-QFDTVHPH-pM2WtK3x.js} +1 -1
- package/dist/client/assets/{pieDiagram-DEJITSTG-DdFtIn-F.js → pieDiagram-DEJITSTG-DDIwX7uR.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-34T5L4WZ-D-s3xVqQ.js → quadrantDiagram-34T5L4WZ-BXaToAll.js} +1 -1
- package/dist/client/assets/{requirementDiagram-MS252O5E-CAOgs_ga.js → requirementDiagram-MS252O5E-DcniSPVW.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-XADWPNL6-CwNe6T7F.js → sankeyDiagram-XADWPNL6-BRSO8kbj.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-FGHM5R23-a2KprIq8.js → sequenceDiagram-FGHM5R23-CUIcqpcf.js} +1 -1
- package/dist/client/assets/{stateDiagram-FHFEXIEX-zhB1rn0b.js → stateDiagram-FHFEXIEX-DwQwUYPD.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-oBGjXrxd.js +1 -0
- package/dist/client/assets/{timeline-definition-GMOUNBTQ-BlA9nDAP.js → timeline-definition-GMOUNBTQ-DNrziHKp.js} +1 -1
- package/dist/client/assets/{vennDiagram-DHZGUBPP-CWLLKd9C.js → vennDiagram-DHZGUBPP-BEVuQytw.js} +1 -1
- package/dist/client/assets/{wardley-RL74JXVD-B9fBOng-.js → wardley-RL74JXVD-Cwp-Pzm_.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-NUSXRM2D-B7b_tMc3.js → wardleyDiagram-NUSXRM2D-Cd3w820H.js} +1 -1
- package/dist/client/assets/{xychartDiagram-5P7HB3ND-D9kDqkfZ.js → xychartDiagram-5P7HB3ND-CSVs_RZN.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/fleet-plugins/diff/routes.mjs +304 -0
- package/dist/fleet-plugins/file-explorer/routes.mjs +446 -0
- package/dist/{cli-bin.mjs → fleet-plugins/terminal/routes.mjs} +17244 -21250
- package/package.json +7 -6
- package/dist/cli-bin.d.ts +0 -2
- package/dist/client/assets/channel-BSqChC9B.js +0 -1
- package/dist/client/assets/classDiagram-6PBFFD2Q-DLPKVOP5.js +0 -1
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-DLPKVOP5.js +0 -1
- package/dist/client/assets/clone-TN0j087k.js +0 -1
- package/dist/client/assets/index-BsgvDcDS.js +0 -777
- package/dist/client/assets/index-DouvozK2.css +0 -1
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-8QLfjXWZ.js +0 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path2 from 'path';
|
|
4
|
+
|
|
5
|
+
// sdk/plugin/node.ts
|
|
6
|
+
function definePlugin(definition) {
|
|
7
|
+
return definition;
|
|
8
|
+
}
|
|
9
|
+
function registerRouter(ctx, path4, handler) {
|
|
10
|
+
ctx.registerRouter(normalizePath(path4), handler);
|
|
11
|
+
}
|
|
12
|
+
function normalizePath(path4) {
|
|
13
|
+
if (path4.length === 0 || path4 === "/") return "/";
|
|
14
|
+
return path4;
|
|
15
|
+
}
|
|
16
|
+
var FileReadError = class extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code) {
|
|
19
|
+
super(code);
|
|
20
|
+
this.name = "FileReadError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var FILE_SIZE_CAP = 1024 * 1024;
|
|
25
|
+
var BINARY_CHECK_BYTES = 8192;
|
|
26
|
+
var BINARY_NUL_THRESHOLD = 0.05;
|
|
27
|
+
var EXT_LANG_MAP = {
|
|
28
|
+
".ts": "typescript",
|
|
29
|
+
".tsx": "typescript",
|
|
30
|
+
".js": "javascript",
|
|
31
|
+
".jsx": "javascript",
|
|
32
|
+
".mjs": "javascript",
|
|
33
|
+
".cjs": "javascript",
|
|
34
|
+
".json": "json",
|
|
35
|
+
".json5": "json",
|
|
36
|
+
".md": "markdown",
|
|
37
|
+
".mdx": "markdown",
|
|
38
|
+
".html": "html",
|
|
39
|
+
".htm": "html",
|
|
40
|
+
".css": "css",
|
|
41
|
+
".scss": "scss",
|
|
42
|
+
".sass": "sass",
|
|
43
|
+
".less": "less",
|
|
44
|
+
".py": "python",
|
|
45
|
+
".go": "go",
|
|
46
|
+
".rs": "rust",
|
|
47
|
+
".sh": "bash",
|
|
48
|
+
".bash": "bash",
|
|
49
|
+
".zsh": "bash",
|
|
50
|
+
".yaml": "yaml",
|
|
51
|
+
".yml": "yaml",
|
|
52
|
+
".toml": "toml",
|
|
53
|
+
".xml": "xml",
|
|
54
|
+
".svg": "xml",
|
|
55
|
+
".sql": "sql",
|
|
56
|
+
".rb": "ruby",
|
|
57
|
+
".java": "java",
|
|
58
|
+
".kt": "kotlin",
|
|
59
|
+
".swift": "swift",
|
|
60
|
+
".c": "c",
|
|
61
|
+
".h": "c",
|
|
62
|
+
".cpp": "cpp",
|
|
63
|
+
".cc": "cpp",
|
|
64
|
+
".hpp": "cpp",
|
|
65
|
+
".dockerfile": "dockerfile",
|
|
66
|
+
".gitignore": "plaintext",
|
|
67
|
+
".env": "plaintext",
|
|
68
|
+
".txt": "plaintext"
|
|
69
|
+
};
|
|
70
|
+
async function readFileForTheater(theaterPath, relativePath) {
|
|
71
|
+
const resolved = path2.resolve(theaterPath, relativePath);
|
|
72
|
+
if (!isWithinRoot(resolved, theaterPath)) throw new FileReadError("path_outside_theater");
|
|
73
|
+
let realResolved;
|
|
74
|
+
let realRoot;
|
|
75
|
+
try {
|
|
76
|
+
[realResolved, realRoot] = await Promise.all([
|
|
77
|
+
fs.promises.realpath(resolved),
|
|
78
|
+
fs.promises.realpath(theaterPath)
|
|
79
|
+
]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const code = error.code;
|
|
82
|
+
if (code === "EACCES" || code === "EPERM") throw new FileReadError("forbidden");
|
|
83
|
+
throw new FileReadError("not_found");
|
|
84
|
+
}
|
|
85
|
+
if (!isWithinRoot(realResolved, realRoot)) throw new FileReadError("path_outside_theater");
|
|
86
|
+
let stat;
|
|
87
|
+
try {
|
|
88
|
+
stat = await fs.promises.stat(realResolved);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const code = error.code;
|
|
91
|
+
if (code === "EACCES" || code === "EPERM") throw new FileReadError("forbidden");
|
|
92
|
+
throw new FileReadError("not_found");
|
|
93
|
+
}
|
|
94
|
+
if (!stat.isFile()) throw new FileReadError("not_a_file");
|
|
95
|
+
if (stat.size > FILE_SIZE_CAP) {
|
|
96
|
+
const fd = await fs.promises.open(realResolved, "r");
|
|
97
|
+
const buffer2 = Buffer.alloc(FILE_SIZE_CAP);
|
|
98
|
+
const { bytesRead } = await fd.read(buffer2, 0, FILE_SIZE_CAP, 0);
|
|
99
|
+
await fd.close();
|
|
100
|
+
const chunk = buffer2.subarray(0, bytesRead);
|
|
101
|
+
if (isBinaryBuffer(chunk)) throw new FileReadError("binary_file");
|
|
102
|
+
return {
|
|
103
|
+
relativePath: path2.relative(realRoot, realResolved),
|
|
104
|
+
content: chunk.toString("utf8"),
|
|
105
|
+
lang: detectLang(realResolved),
|
|
106
|
+
truncated: true
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const buffer = await fs.promises.readFile(realResolved);
|
|
110
|
+
if (isBinaryBuffer(buffer)) throw new FileReadError("binary_file");
|
|
111
|
+
return {
|
|
112
|
+
relativePath: path2.relative(realRoot, realResolved),
|
|
113
|
+
content: buffer.toString("utf8"),
|
|
114
|
+
lang: detectLang(realResolved)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function isWithinRoot(resolved, root) {
|
|
118
|
+
const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
|
|
119
|
+
return resolved === root || resolved.startsWith(normalizedRoot);
|
|
120
|
+
}
|
|
121
|
+
function isBinaryBuffer(buffer) {
|
|
122
|
+
const checkLen = Math.min(buffer.length, BINARY_CHECK_BYTES);
|
|
123
|
+
if (checkLen === 0) return false;
|
|
124
|
+
let nulCount = 0;
|
|
125
|
+
for (let i = 0; i < checkLen; i++) {
|
|
126
|
+
if (buffer[i] === 0) nulCount++;
|
|
127
|
+
}
|
|
128
|
+
return nulCount / checkLen > BINARY_NUL_THRESHOLD;
|
|
129
|
+
}
|
|
130
|
+
function detectLang(filePath) {
|
|
131
|
+
const ext = path2.extname(filePath).toLowerCase();
|
|
132
|
+
return EXT_LANG_MAP[ext] ?? "plaintext";
|
|
133
|
+
}
|
|
134
|
+
var FolderBrowserError = class extends Error {
|
|
135
|
+
code;
|
|
136
|
+
constructor(code) {
|
|
137
|
+
super(code);
|
|
138
|
+
this.name = "FolderBrowserError";
|
|
139
|
+
this.code = code;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var DIRECTORY_ENTRY_CAP = 500;
|
|
143
|
+
async function listTheaterContents(theaterPath, relativePath, deps = {}) {
|
|
144
|
+
const opendir = deps.opendir ?? fs.promises.opendir;
|
|
145
|
+
const stat = deps.stat ?? fs.promises.stat;
|
|
146
|
+
const targetAbs = path2.resolve(theaterPath, relativePath);
|
|
147
|
+
const normalizedRoot = theaterPath.endsWith(path2.sep) ? theaterPath : theaterPath + path2.sep;
|
|
148
|
+
if (targetAbs !== theaterPath && !targetAbs.startsWith(normalizedRoot)) {
|
|
149
|
+
throw new FolderBrowserError("forbidden");
|
|
150
|
+
}
|
|
151
|
+
let realRoot;
|
|
152
|
+
let realTargetAbs;
|
|
153
|
+
try {
|
|
154
|
+
[realRoot, realTargetAbs] = await Promise.all([
|
|
155
|
+
fs.promises.realpath(theaterPath),
|
|
156
|
+
fs.promises.realpath(targetAbs)
|
|
157
|
+
]);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw mapFsError(error);
|
|
160
|
+
}
|
|
161
|
+
const realNormalizedRoot = realRoot.endsWith(path2.sep) ? realRoot : realRoot + path2.sep;
|
|
162
|
+
if (realTargetAbs !== realRoot && !realTargetAbs.startsWith(realNormalizedRoot)) {
|
|
163
|
+
throw new FolderBrowserError("forbidden");
|
|
164
|
+
}
|
|
165
|
+
const targetStat = await statDirectory(realTargetAbs, stat);
|
|
166
|
+
if (!targetStat.isDirectory()) throw new FolderBrowserError("invalid_path");
|
|
167
|
+
const entries = [];
|
|
168
|
+
const truncated = await collectContentsEntries(realTargetAbs, realRoot, opendir, stat, entries);
|
|
169
|
+
entries.sort((a, b) => {
|
|
170
|
+
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
171
|
+
return a.name.localeCompare(b.name);
|
|
172
|
+
});
|
|
173
|
+
const rel = path2.relative(realRoot, realTargetAbs);
|
|
174
|
+
const parentRel = rel === "" ? null : path2.relative(realRoot, path2.dirname(realTargetAbs));
|
|
175
|
+
return {
|
|
176
|
+
relativePath: rel,
|
|
177
|
+
parentRelativePath: parentRel === "" ? null : parentRel ?? null,
|
|
178
|
+
entries,
|
|
179
|
+
...truncated ? { truncated: true } : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function statDirectory(targetPath, stat) {
|
|
183
|
+
try {
|
|
184
|
+
return await stat(targetPath);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw mapFsError(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function collectContentsEntries(targetPath, theaterPath, opendir, stat, entries) {
|
|
190
|
+
const directory = await openDirectory(targetPath, opendir);
|
|
191
|
+
try {
|
|
192
|
+
let dirent = await directory.read();
|
|
193
|
+
while (dirent !== null) {
|
|
194
|
+
if (entries.length >= DIRECTORY_ENTRY_CAP) return true;
|
|
195
|
+
const entry = await toContentsEntry(targetPath, theaterPath, dirent, stat);
|
|
196
|
+
if (entry !== null) entries.push(entry);
|
|
197
|
+
dirent = await directory.read();
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw mapFsError(error);
|
|
202
|
+
} finally {
|
|
203
|
+
await directory.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function openDirectory(targetPath, opendir) {
|
|
207
|
+
try {
|
|
208
|
+
return await opendir(targetPath);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
throw mapFsError(error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function toContentsEntry(targetPath, theaterPath, dirent, stat) {
|
|
214
|
+
const entryPath = path2.join(targetPath, dirent.name);
|
|
215
|
+
const rel = path2.relative(theaterPath, entryPath);
|
|
216
|
+
if (dirent.isDirectory()) return { name: dirent.name, relativePath: rel, kind: "dir" };
|
|
217
|
+
if (dirent.isFile()) return { name: dirent.name, relativePath: rel, kind: "file" };
|
|
218
|
+
if (dirent.isSymbolicLink()) {
|
|
219
|
+
try {
|
|
220
|
+
const realEntryPath = await fs.promises.realpath(entryPath);
|
|
221
|
+
const realNormalizedRoot = theaterPath.endsWith(path2.sep) ? theaterPath : theaterPath + path2.sep;
|
|
222
|
+
if (realEntryPath !== theaterPath && !realEntryPath.startsWith(realNormalizedRoot)) return null;
|
|
223
|
+
const s = await stat(realEntryPath);
|
|
224
|
+
if (s.isDirectory()) return { name: dirent.name, relativePath: rel, kind: "dir" };
|
|
225
|
+
if (s.isFile()) return { name: dirent.name, relativePath: rel, kind: "file" };
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function mapFsError(error) {
|
|
233
|
+
const code = error.code;
|
|
234
|
+
if (code === "EACCES" || code === "EPERM") return new FolderBrowserError("forbidden");
|
|
235
|
+
if (code === "ENOENT" || code === "ENOTDIR") return new FolderBrowserError("not_found");
|
|
236
|
+
return new FolderBrowserError("invalid_path");
|
|
237
|
+
}
|
|
238
|
+
var ImageServeError = class extends Error {
|
|
239
|
+
code;
|
|
240
|
+
constructor(code) {
|
|
241
|
+
super(code);
|
|
242
|
+
this.name = "ImageServeError";
|
|
243
|
+
this.code = code;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var IMAGE_SIZE_CAP = 4 * 1024 * 1024;
|
|
247
|
+
var MIME_ALLOWLIST = {
|
|
248
|
+
".png": "image/png",
|
|
249
|
+
".jpg": "image/jpeg",
|
|
250
|
+
".jpeg": "image/jpeg",
|
|
251
|
+
".webp": "image/webp",
|
|
252
|
+
".gif": "image/gif"
|
|
253
|
+
};
|
|
254
|
+
var IMAGE_SECURITY_HEADERS = {
|
|
255
|
+
"X-Content-Type-Options": "nosniff",
|
|
256
|
+
"Referrer-Policy": "no-referrer",
|
|
257
|
+
"Cache-Control": "private, no-store"
|
|
258
|
+
};
|
|
259
|
+
async function readImageForTheater(theaterPath, relativePath) {
|
|
260
|
+
const resolved = path2.resolve(theaterPath, relativePath);
|
|
261
|
+
if (!isWithinRoot2(resolved, theaterPath)) throw new ImageServeError("path_outside_theater");
|
|
262
|
+
const ext = path2.extname(resolved).toLowerCase();
|
|
263
|
+
const mimeType = MIME_ALLOWLIST[ext];
|
|
264
|
+
if (!mimeType) throw new ImageServeError("mime_not_allowed");
|
|
265
|
+
let realResolved;
|
|
266
|
+
let realRoot;
|
|
267
|
+
try {
|
|
268
|
+
[realResolved, realRoot] = await Promise.all([
|
|
269
|
+
fs.promises.realpath(resolved),
|
|
270
|
+
fs.promises.realpath(theaterPath)
|
|
271
|
+
]);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const code = error.code;
|
|
274
|
+
if (code === "EACCES" || code === "EPERM") throw new ImageServeError("forbidden");
|
|
275
|
+
throw new ImageServeError("not_found");
|
|
276
|
+
}
|
|
277
|
+
if (!isWithinRoot2(realResolved, realRoot)) throw new ImageServeError("path_outside_theater");
|
|
278
|
+
let stat;
|
|
279
|
+
try {
|
|
280
|
+
stat = await fs.promises.stat(realResolved);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const code = error.code;
|
|
283
|
+
if (code === "EACCES" || code === "EPERM") throw new ImageServeError("forbidden");
|
|
284
|
+
throw new ImageServeError("not_found");
|
|
285
|
+
}
|
|
286
|
+
if (!stat.isFile()) throw new ImageServeError("not_a_file");
|
|
287
|
+
if (stat.size > IMAGE_SIZE_CAP) throw new ImageServeError("size_exceeded");
|
|
288
|
+
const buffer = await fs.promises.readFile(realResolved);
|
|
289
|
+
return { mimeType, buffer };
|
|
290
|
+
}
|
|
291
|
+
function writeImageResponse(res, result) {
|
|
292
|
+
res.writeHead(200, {
|
|
293
|
+
...IMAGE_SECURITY_HEADERS,
|
|
294
|
+
"Content-Type": result.mimeType,
|
|
295
|
+
"Content-Length": result.buffer.length
|
|
296
|
+
});
|
|
297
|
+
res.end(result.buffer);
|
|
298
|
+
}
|
|
299
|
+
function isWithinRoot2(resolved, root) {
|
|
300
|
+
const normalizedRoot = root.endsWith(path2.sep) ? root : root + path2.sep;
|
|
301
|
+
return resolved === root || resolved.startsWith(normalizedRoot);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ../fleet-plugins/file-explorer/server/handlers.ts
|
|
305
|
+
function isPlainObject(value) {
|
|
306
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
307
|
+
}
|
|
308
|
+
function readUrl(req) {
|
|
309
|
+
return new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
310
|
+
}
|
|
311
|
+
async function handleFilesList(req, res, ctx) {
|
|
312
|
+
if (req.method !== "POST") {
|
|
313
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
317
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
321
|
+
if (!isPlainObject(body)) {
|
|
322
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const rawTheaterId = body.theaterId;
|
|
326
|
+
if (typeof rawTheaterId !== "string") {
|
|
327
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const rawRel = body.relativePath;
|
|
331
|
+
const relPath = rawRel === void 0 || rawRel === null ? "" : typeof rawRel === "string" ? rawRel : null;
|
|
332
|
+
if (relPath === null) {
|
|
333
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_path" });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(rawTheaterId);
|
|
337
|
+
if (!theaterPath) {
|
|
338
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const result = await listTheaterContents(theaterPath, relPath);
|
|
343
|
+
ctx.host.http.writeJson(res, 200, result);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (error instanceof FolderBrowserError) {
|
|
346
|
+
const httpStatus = error.code === "forbidden" ? 403 : error.code === "not_found" ? 404 : 400;
|
|
347
|
+
ctx.host.http.writeJson(res, httpStatus, { error: error.code });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function handleFilesRead(req, res, ctx) {
|
|
354
|
+
if (req.method !== "POST") {
|
|
355
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
359
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const body = await ctx.host.http.readJsonBody(req);
|
|
363
|
+
if (!isPlainObject(body) || typeof body.relativePath !== "string") {
|
|
364
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_path" });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const theaterId = body.theaterId;
|
|
368
|
+
if (typeof theaterId !== "string") {
|
|
369
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
373
|
+
if (!theaterPath) {
|
|
374
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const result = await readFileForTheater(theaterPath, body.relativePath);
|
|
379
|
+
ctx.host.http.writeJson(res, 200, result);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (error instanceof FileReadError) {
|
|
382
|
+
const httpStatus = error.code === "path_outside_theater" || error.code === "forbidden" ? 403 : error.code === "binary_file" ? 422 : 404;
|
|
383
|
+
ctx.host.http.writeJson(res, httpStatus, { error: error.code });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function handleFilesImage(req, res, ctx) {
|
|
390
|
+
if (req.method !== "GET") {
|
|
391
|
+
ctx.host.http.writeJson(res, 405, { error: "Method not allowed" });
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!ctx.host.security.isTerminalAuthorized(req)) {
|
|
395
|
+
ctx.host.http.writeJson(res, 401, { error: "unauthorized" });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const url = readUrl(req);
|
|
399
|
+
const theaterId = url.searchParams.get("theaterId");
|
|
400
|
+
const relPath = url.searchParams.get("path");
|
|
401
|
+
if (!theaterId) {
|
|
402
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_request" });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (!relPath) {
|
|
406
|
+
ctx.host.http.writeJson(res, 400, { error: "invalid_path" });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const theaterPath = ctx.host.paths.resolveTheaterPath(theaterId);
|
|
410
|
+
if (!theaterPath) {
|
|
411
|
+
ctx.host.http.writeJson(res, 404, { error: "theater_not_found" });
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const result = await readImageForTheater(theaterPath, relPath);
|
|
416
|
+
writeImageResponse(res, result);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (error instanceof ImageServeError) {
|
|
419
|
+
const httpStatus = error.code === "path_outside_theater" || error.code === "mime_not_allowed" || error.code === "forbidden" ? 403 : error.code === "size_exceeded" ? 413 : 404;
|
|
420
|
+
ctx.host.http.writeJson(res, httpStatus, { error: error.code });
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ../fleet-plugins/file-explorer/routes.ts
|
|
428
|
+
var routes_default = definePlugin({
|
|
429
|
+
id: "file-explorer",
|
|
430
|
+
register(ctx) {
|
|
431
|
+
registerRouter(ctx, "files/list", async ({ req, res }) => {
|
|
432
|
+
await handleFilesList(req, res, ctx);
|
|
433
|
+
return true;
|
|
434
|
+
});
|
|
435
|
+
registerRouter(ctx, "files/read", async ({ req, res }) => {
|
|
436
|
+
await handleFilesRead(req, res, ctx);
|
|
437
|
+
return true;
|
|
438
|
+
});
|
|
439
|
+
registerRouter(ctx, "files/image", async ({ req, res }) => {
|
|
440
|
+
await handleFilesImage(req, res, ctx);
|
|
441
|
+
return true;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
export { routes_default as default };
|