@elementspanel/epanel-plugin-file 0.0.0-stage → 1.0.1
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/LICENSE +201 -0
- package/README.md +4 -2
- package/daemon/backend/index.cjs +4395 -0
- package/daemon/plugin.json +6 -0
- package/package.json +61 -4
- package/panel/backend/index.cjs +513 -0
- package/panel/frontend/assets/style-0-style-9-_panel-plugin-build-entry_file-eb11377f.css +1 -0
- package/panel/frontend/panel-plugin-file-b9399327.js +1 -0
- package/panel/plugin.json +7 -0
|
@@ -0,0 +1,4395 @@
|
|
|
1
|
+
/******/ (() => { // webpackBootstrap
|
|
2
|
+
/******/ "use strict";
|
|
3
|
+
/******/ var __webpack_modules__ = ({
|
|
4
|
+
|
|
5
|
+
/***/ "../common/src/array.ts"
|
|
6
|
+
(__unused_webpack_module, exports) {
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10
|
+
exports.arrayUnique = arrayUnique;
|
|
11
|
+
function arrayUnique(arr, felidName) {
|
|
12
|
+
if (!felidName)
|
|
13
|
+
return Array.from(new Set(arr));
|
|
14
|
+
const map = new Map();
|
|
15
|
+
return arr.filter((v) => !map.has(v[felidName]) && map.set(v[felidName], v));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/***/ },
|
|
20
|
+
|
|
21
|
+
/***/ "../common/src/docker_utils.ts"
|
|
22
|
+
(__unused_webpack_module, exports) {
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
26
|
+
exports.normalizeDockerArchitecture = normalizeDockerArchitecture;
|
|
27
|
+
exports.normalizeDockerOS = normalizeDockerOS;
|
|
28
|
+
exports.normalizeDockerPlatform = normalizeDockerPlatform;
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes Docker platform architecture names to standard format
|
|
31
|
+
* @param architecture The architecture string from Docker manifest
|
|
32
|
+
* @returns Normalized architecture string (amd64, arm64, arm, or original if not recognized)
|
|
33
|
+
*/
|
|
34
|
+
function normalizeDockerArchitecture(architecture) {
|
|
35
|
+
if (!architecture) {
|
|
36
|
+
return "amd64"; // default fallback
|
|
37
|
+
}
|
|
38
|
+
// Return original if not one of the standard architectures
|
|
39
|
+
return architecture;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalizes Docker platform OS name
|
|
43
|
+
* @param os The OS string from Docker manifest
|
|
44
|
+
* @returns Normalized OS string (defaults to "linux" if not provided)
|
|
45
|
+
*/
|
|
46
|
+
function normalizeDockerOS(os) {
|
|
47
|
+
return os || "linux";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Normalizes a Docker platform from manifest format to standard platform string
|
|
51
|
+
* @param platform Platform object from Docker manifest with os and architecture
|
|
52
|
+
* @returns Normalized platform string in format "os/arch" (e.g., "linux/amd64")
|
|
53
|
+
*/
|
|
54
|
+
function normalizeDockerPlatform(platform) {
|
|
55
|
+
const os = normalizeDockerOS(platform.os);
|
|
56
|
+
const arch = normalizeDockerArchitecture(platform.architecture);
|
|
57
|
+
return `${os}/${arch}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/***/ },
|
|
62
|
+
|
|
63
|
+
/***/ "../common/src/global_variable.ts"
|
|
64
|
+
(__unused_webpack_module, exports) {
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
68
|
+
class GlobalVariable {
|
|
69
|
+
static map = new Map();
|
|
70
|
+
static set(k, v) {
|
|
71
|
+
GlobalVariable.map.set(k, v);
|
|
72
|
+
}
|
|
73
|
+
static get(k, def) {
|
|
74
|
+
if (GlobalVariable.map.has(k)) {
|
|
75
|
+
return GlobalVariable.map.get(k);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return def;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
static del(k) {
|
|
82
|
+
return GlobalVariable.map.delete(k);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports["default"] = GlobalVariable;
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/***/ },
|
|
89
|
+
|
|
90
|
+
/***/ "../common/src/index.ts"
|
|
91
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
95
|
+
if (k2 === undefined) k2 = k;
|
|
96
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
97
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
98
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
99
|
+
}
|
|
100
|
+
Object.defineProperty(o, k2, desc);
|
|
101
|
+
}) : (function(o, m, k, k2) {
|
|
102
|
+
if (k2 === undefined) k2 = k;
|
|
103
|
+
o[k2] = m[k];
|
|
104
|
+
}));
|
|
105
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
106
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
107
|
+
};
|
|
108
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
109
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
110
|
+
};
|
|
111
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
112
|
+
exports.StorageSubsystem = exports.MCServerStatus = exports.InstanceStreamListener = exports.GlobalVariable = exports.normalizeDockerPlatform = exports.normalizeDockerOS = exports.normalizeDockerArchitecture = exports.removeTrail = exports.uninstallNpmPlugin = exports.isEssentialNpmPlugin = exports.npmPluginOperationPending = exports.npmPluginRoots = exports.listNpmPlugins = exports.installNpmPlugin = exports.currentProjectRoot = exports.currentPackageRoot = exports.sortPlugins = exports.resolvePluginEntry = exports.readPluginManifest = exports.createFrontendPluginMetadata = exports.discoverPluginsFromRoots = exports.discoverPlugins = exports.arrayUnique = exports.toText = exports.toNumber = exports.toBoolean = exports.supposeValue = exports.isEmpty = exports.configureEntityParams = exports.systemInfo = exports.QueryWrapper = exports.QueryMapWrapper = exports.MySqlSource = exports.LocalFileSource = exports.killProcess = exports.ProcessWrapper = void 0;
|
|
113
|
+
const global_variable_1 = __importDefault(__webpack_require__("../common/src/global_variable.ts"));
|
|
114
|
+
exports.GlobalVariable = global_variable_1.default;
|
|
115
|
+
const instance_stream_1 = __importDefault(__webpack_require__("../common/src/instance_stream.ts"));
|
|
116
|
+
exports.InstanceStreamListener = instance_stream_1.default;
|
|
117
|
+
const system_storage_1 = __importDefault(__webpack_require__("../common/src/system_storage.ts"));
|
|
118
|
+
exports.StorageSubsystem = system_storage_1.default;
|
|
119
|
+
const mcping_1 = __importDefault(__webpack_require__("../common/src/mcping.ts"));
|
|
120
|
+
exports.MCServerStatus = mcping_1.default;
|
|
121
|
+
var process_tools_1 = __webpack_require__("../common/src/process_tools.ts");
|
|
122
|
+
Object.defineProperty(exports, "ProcessWrapper", ({ enumerable: true, get: function () { return process_tools_1.ProcessWrapper; } }));
|
|
123
|
+
Object.defineProperty(exports, "killProcess", ({ enumerable: true, get: function () { return process_tools_1.killProcess; } }));
|
|
124
|
+
var query_wrapper_1 = __webpack_require__("../common/src/query_wrapper.ts");
|
|
125
|
+
Object.defineProperty(exports, "LocalFileSource", ({ enumerable: true, get: function () { return query_wrapper_1.LocalFileSource; } }));
|
|
126
|
+
Object.defineProperty(exports, "MySqlSource", ({ enumerable: true, get: function () { return query_wrapper_1.MySqlSource; } }));
|
|
127
|
+
Object.defineProperty(exports, "QueryMapWrapper", ({ enumerable: true, get: function () { return query_wrapper_1.QueryMapWrapper; } }));
|
|
128
|
+
Object.defineProperty(exports, "QueryWrapper", ({ enumerable: true, get: function () { return query_wrapper_1.QueryWrapper; } }));
|
|
129
|
+
var system_info_1 = __webpack_require__("../common/src/system_info.ts");
|
|
130
|
+
Object.defineProperty(exports, "systemInfo", ({ enumerable: true, get: function () { return system_info_1.systemInfo; } }));
|
|
131
|
+
var typecheck_1 = __webpack_require__("../common/src/typecheck.ts");
|
|
132
|
+
Object.defineProperty(exports, "configureEntityParams", ({ enumerable: true, get: function () { return typecheck_1.configureEntityParams; } }));
|
|
133
|
+
Object.defineProperty(exports, "isEmpty", ({ enumerable: true, get: function () { return typecheck_1.isEmpty; } }));
|
|
134
|
+
Object.defineProperty(exports, "supposeValue", ({ enumerable: true, get: function () { return typecheck_1.supposeValue; } }));
|
|
135
|
+
Object.defineProperty(exports, "toBoolean", ({ enumerable: true, get: function () { return typecheck_1.toBoolean; } }));
|
|
136
|
+
Object.defineProperty(exports, "toNumber", ({ enumerable: true, get: function () { return typecheck_1.toNumber; } }));
|
|
137
|
+
Object.defineProperty(exports, "toText", ({ enumerable: true, get: function () { return typecheck_1.toText; } }));
|
|
138
|
+
var array_1 = __webpack_require__("../common/src/array.ts");
|
|
139
|
+
Object.defineProperty(exports, "arrayUnique", ({ enumerable: true, get: function () { return array_1.arrayUnique; } }));
|
|
140
|
+
__exportStar(__webpack_require__("../common/src/plugin_contract.ts"), exports);
|
|
141
|
+
__exportStar(__webpack_require__("../common/src/plugin_overrides.ts"), exports);
|
|
142
|
+
__exportStar(__webpack_require__("../common/src/plugin_lifecycle.ts"), exports);
|
|
143
|
+
__exportStar(__webpack_require__("../common/src/plugin_revision.ts"), exports);
|
|
144
|
+
__exportStar(__webpack_require__("../common/src/plugin_modules.ts"), exports);
|
|
145
|
+
__exportStar(__webpack_require__("../common/src/plugin_watch.ts"), exports);
|
|
146
|
+
__exportStar(__webpack_require__("../common/src/runtime_paths.ts"), exports);
|
|
147
|
+
var plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
|
|
148
|
+
Object.defineProperty(exports, "discoverPlugins", ({ enumerable: true, get: function () { return plugin_manifest_1.discoverPlugins; } }));
|
|
149
|
+
Object.defineProperty(exports, "discoverPluginsFromRoots", ({ enumerable: true, get: function () { return plugin_manifest_1.discoverPluginsFromRoots; } }));
|
|
150
|
+
Object.defineProperty(exports, "createFrontendPluginMetadata", ({ enumerable: true, get: function () { return plugin_manifest_1.createFrontendPluginMetadata; } }));
|
|
151
|
+
Object.defineProperty(exports, "readPluginManifest", ({ enumerable: true, get: function () { return plugin_manifest_1.readPluginManifest; } }));
|
|
152
|
+
Object.defineProperty(exports, "resolvePluginEntry", ({ enumerable: true, get: function () { return plugin_manifest_1.resolvePluginEntry; } }));
|
|
153
|
+
Object.defineProperty(exports, "sortPlugins", ({ enumerable: true, get: function () { return plugin_manifest_1.sortPlugins; } }));
|
|
154
|
+
var plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
|
|
155
|
+
Object.defineProperty(exports, "currentPackageRoot", ({ enumerable: true, get: function () { return plugin_npm_1.currentPackageRoot; } }));
|
|
156
|
+
Object.defineProperty(exports, "currentProjectRoot", ({ enumerable: true, get: function () { return plugin_npm_1.currentProjectRoot; } }));
|
|
157
|
+
Object.defineProperty(exports, "installNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.installNpmPlugin; } }));
|
|
158
|
+
Object.defineProperty(exports, "listNpmPlugins", ({ enumerable: true, get: function () { return plugin_npm_1.listNpmPlugins; } }));
|
|
159
|
+
Object.defineProperty(exports, "npmPluginRoots", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginRoots; } }));
|
|
160
|
+
Object.defineProperty(exports, "npmPluginOperationPending", ({ enumerable: true, get: function () { return plugin_npm_1.npmPluginOperationPending; } }));
|
|
161
|
+
Object.defineProperty(exports, "isEssentialNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.isEssentialNpmPlugin; } }));
|
|
162
|
+
Object.defineProperty(exports, "uninstallNpmPlugin", ({ enumerable: true, get: function () { return plugin_npm_1.uninstallNpmPlugin; } }));
|
|
163
|
+
var string_utils_1 = __webpack_require__("../common/src/string_utils.ts");
|
|
164
|
+
Object.defineProperty(exports, "removeTrail", ({ enumerable: true, get: function () { return string_utils_1.removeTrail; } }));
|
|
165
|
+
var docker_utils_1 = __webpack_require__("../common/src/docker_utils.ts");
|
|
166
|
+
Object.defineProperty(exports, "normalizeDockerArchitecture", ({ enumerable: true, get: function () { return docker_utils_1.normalizeDockerArchitecture; } }));
|
|
167
|
+
Object.defineProperty(exports, "normalizeDockerOS", ({ enumerable: true, get: function () { return docker_utils_1.normalizeDockerOS; } }));
|
|
168
|
+
Object.defineProperty(exports, "normalizeDockerPlatform", ({ enumerable: true, get: function () { return docker_utils_1.normalizeDockerPlatform; } }));
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/***/ },
|
|
172
|
+
|
|
173
|
+
/***/ "../common/src/instance_stream.ts"
|
|
174
|
+
(__unused_webpack_module, exports) {
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178
|
+
// Application instance data stream forwarding adapter
|
|
179
|
+
class InstanceStreamListener {
|
|
180
|
+
listenMap = new Map();
|
|
181
|
+
disconnectHandlers = new Map();
|
|
182
|
+
requestForward(socket, instanceUuid) {
|
|
183
|
+
const sockets = this.listenMap.get(instanceUuid) ?? [];
|
|
184
|
+
if (sockets.some((listener) => listener.id === socket.id))
|
|
185
|
+
return;
|
|
186
|
+
sockets.push(socket);
|
|
187
|
+
this.listenMap.set(instanceUuid, sockets);
|
|
188
|
+
if (!this.disconnectHandlers.has(socket)) {
|
|
189
|
+
const disconnect = () => {
|
|
190
|
+
for (const uuid of this.listenMap.keys())
|
|
191
|
+
this.cannelForward(socket, uuid);
|
|
192
|
+
};
|
|
193
|
+
this.disconnectHandlers.set(socket, disconnect);
|
|
194
|
+
socket.once("disconnect", disconnect);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
cannelForward(socket, instanceUuid) {
|
|
198
|
+
const remaining = this.listenMap
|
|
199
|
+
.get(instanceUuid)
|
|
200
|
+
?.filter((listener) => listener.id !== socket.id);
|
|
201
|
+
if (remaining?.length)
|
|
202
|
+
this.listenMap.set(instanceUuid, remaining);
|
|
203
|
+
else
|
|
204
|
+
this.listenMap.delete(instanceUuid);
|
|
205
|
+
if (![...this.listenMap.values()].some((sockets) => sockets.includes(socket))) {
|
|
206
|
+
const disconnect = this.disconnectHandlers.get(socket);
|
|
207
|
+
if (disconnect)
|
|
208
|
+
socket.off("disconnect", disconnect);
|
|
209
|
+
this.disconnectHandlers.delete(socket);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
forward(instanceUuid, data) {
|
|
213
|
+
this.forwardViaCallback(instanceUuid, (socket) => socket.emit("instance/stdout", data));
|
|
214
|
+
}
|
|
215
|
+
forwardViaCallback(instanceUuid, callback) {
|
|
216
|
+
for (const socket of this.listenMap.get(instanceUuid) ?? []) {
|
|
217
|
+
if (socket.connected)
|
|
218
|
+
callback(socket);
|
|
219
|
+
else
|
|
220
|
+
this.cannelForward(socket, instanceUuid);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
hasListenInstance(instanceUuid) {
|
|
224
|
+
return this.listenMap.get(instanceUuid)?.some((socket) => socket.connected) ?? false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exports["default"] = InstanceStreamListener;
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
/***/ },
|
|
231
|
+
|
|
232
|
+
/***/ "../common/src/mcping.ts"
|
|
233
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
237
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
238
|
+
};
|
|
239
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
240
|
+
const net_1 = __importDefault(__webpack_require__("net"));
|
|
241
|
+
class PingMinecraftServer {
|
|
242
|
+
port;
|
|
243
|
+
host;
|
|
244
|
+
status;
|
|
245
|
+
client;
|
|
246
|
+
pending;
|
|
247
|
+
constructor(port, host) {
|
|
248
|
+
this.port = port;
|
|
249
|
+
this.host = host;
|
|
250
|
+
this.status = this.offlineStatus();
|
|
251
|
+
}
|
|
252
|
+
offlineStatus() {
|
|
253
|
+
return {
|
|
254
|
+
online: false,
|
|
255
|
+
host: this.host,
|
|
256
|
+
port: this.port,
|
|
257
|
+
version: "",
|
|
258
|
+
motd: "",
|
|
259
|
+
current_players: 0,
|
|
260
|
+
max_players: 0,
|
|
261
|
+
latency: 0
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
getStatus() {
|
|
265
|
+
if (this.pending)
|
|
266
|
+
return this.pending;
|
|
267
|
+
this.status = this.offlineStatus();
|
|
268
|
+
this.pending = new Promise((resolve, reject) => {
|
|
269
|
+
const startedAt = Date.now();
|
|
270
|
+
const client = net_1.default.connect({ host: this.host, port: this.port });
|
|
271
|
+
this.client = client;
|
|
272
|
+
let received = Buffer.alloc(0);
|
|
273
|
+
let settled = false;
|
|
274
|
+
const finish = (error) => {
|
|
275
|
+
if (settled)
|
|
276
|
+
return;
|
|
277
|
+
settled = true;
|
|
278
|
+
clearTimeout(timeout);
|
|
279
|
+
client.destroy();
|
|
280
|
+
this.client = undefined;
|
|
281
|
+
if (error)
|
|
282
|
+
reject(error);
|
|
283
|
+
else
|
|
284
|
+
resolve(this.status);
|
|
285
|
+
};
|
|
286
|
+
const timeout = setTimeout(() => finish(new Error("Minecraft status request timed out")), 15000);
|
|
287
|
+
client.once("connect", () => client.write(Buffer.from([0xfe, 0x01])));
|
|
288
|
+
client.on("data", (chunk) => {
|
|
289
|
+
if (settled)
|
|
290
|
+
return;
|
|
291
|
+
try {
|
|
292
|
+
received = Buffer.concat([received, chunk]);
|
|
293
|
+
// Legacy status packets contain an unsigned UTF-16 character count.
|
|
294
|
+
if (received.length > 3 + 0xffff * 2)
|
|
295
|
+
throw new Error("Minecraft status response is too large");
|
|
296
|
+
if (received.length < 3)
|
|
297
|
+
return;
|
|
298
|
+
if (received[0] !== 0xff)
|
|
299
|
+
throw new Error("Invalid Minecraft status packet");
|
|
300
|
+
const length = 3 + received.readUInt16BE(1) * 2;
|
|
301
|
+
if (received.length < length)
|
|
302
|
+
return;
|
|
303
|
+
const fields = Buffer.from(received.subarray(3, length))
|
|
304
|
+
.swap16()
|
|
305
|
+
.toString("utf16le")
|
|
306
|
+
.split("\0");
|
|
307
|
+
if (fields.length !== 6 ||
|
|
308
|
+
fields[0] !== "§1" ||
|
|
309
|
+
!/^\d+$/.test(fields[4]) ||
|
|
310
|
+
!/^\d+$/.test(fields[5])) {
|
|
311
|
+
throw new Error("Invalid Minecraft status response");
|
|
312
|
+
}
|
|
313
|
+
const currentPlayers = Number(fields[4]);
|
|
314
|
+
const maxPlayers = Number(fields[5]);
|
|
315
|
+
if (!Number.isSafeInteger(currentPlayers) || !Number.isSafeInteger(maxPlayers)) {
|
|
316
|
+
throw new Error("Invalid Minecraft player count");
|
|
317
|
+
}
|
|
318
|
+
this.status = {
|
|
319
|
+
host: this.host,
|
|
320
|
+
port: this.port,
|
|
321
|
+
online: true,
|
|
322
|
+
version: fields[2],
|
|
323
|
+
motd: fields[3],
|
|
324
|
+
current_players: currentPlayers,
|
|
325
|
+
max_players: maxPlayers,
|
|
326
|
+
latency: Date.now() - startedAt
|
|
327
|
+
};
|
|
328
|
+
finish();
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
client.once("error", finish);
|
|
335
|
+
client.once("close", () => finish(received.length ? new Error("Incomplete Minecraft status response") : undefined));
|
|
336
|
+
}).finally(() => {
|
|
337
|
+
this.pending = undefined;
|
|
338
|
+
});
|
|
339
|
+
return this.pending;
|
|
340
|
+
}
|
|
341
|
+
asyncStatus() {
|
|
342
|
+
return this.getStatus();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
exports["default"] = PingMinecraftServer;
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
/***/ },
|
|
349
|
+
|
|
350
|
+
/***/ "../common/src/plugin_contract.ts"
|
|
351
|
+
(__unused_webpack_module, exports) {
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
355
|
+
exports.PLUGIN_SDK_VERSION = exports.PLUGIN_API_VERSION = void 0;
|
|
356
|
+
exports.validatePluginCompatibility = validatePluginCompatibility;
|
|
357
|
+
exports.resolvePluginDependencyOrder = resolvePluginDependencyOrder;
|
|
358
|
+
exports.createPluginQueue = createPluginQueue;
|
|
359
|
+
exports.validatePluginSettings = validatePluginSettings;
|
|
360
|
+
/** Versioned contract shared by the host, plugin compiler and package registry. */
|
|
361
|
+
exports.PLUGIN_API_VERSION = 1;
|
|
362
|
+
exports.PLUGIN_SDK_VERSION = "1.0.0";
|
|
363
|
+
/** Omitted metadata denotes a legacy package. Unknown versions never execute. */
|
|
364
|
+
function validatePluginCompatibility(value) {
|
|
365
|
+
if (value === undefined)
|
|
366
|
+
return;
|
|
367
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
368
|
+
throw new Error("Invalid ElementsPanel plugin compatibility metadata.");
|
|
369
|
+
const contract = value;
|
|
370
|
+
if (contract.api !== exports.PLUGIN_API_VERSION || (contract.sdk !== undefined && contract.sdk !== 1))
|
|
371
|
+
throw new Error(`Unsupported ElementsPanel plugin API/SDK: ${JSON.stringify(value)}`);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Stable topological ordering shared by plugin hosts.
|
|
375
|
+
*
|
|
376
|
+
* Invalid nodes are kept out of `ordered`: direct missing dependencies are
|
|
377
|
+
* reported first, then anything depending on an invalid node, and finally
|
|
378
|
+
* cycles (plus nodes blocked behind a cycle). Independent nodes retain the
|
|
379
|
+
* caller's priority/id comparator.
|
|
380
|
+
*/
|
|
381
|
+
function resolvePluginDependencyOrder(items, options) {
|
|
382
|
+
const sorted = [...items].sort(options.compare);
|
|
383
|
+
const byId = new Map(sorted.map((item) => [options.id(item), item]));
|
|
384
|
+
const dependencies = new Map(sorted.map((item) => [
|
|
385
|
+
options.id(item),
|
|
386
|
+
[...new Set(options.dependencies(item) || [])].filter(Boolean)
|
|
387
|
+
]));
|
|
388
|
+
const issues = new Map();
|
|
389
|
+
for (const item of sorted) {
|
|
390
|
+
const id = options.id(item);
|
|
391
|
+
const missing = dependencies.get(id).filter((dependency) => !byId.has(dependency));
|
|
392
|
+
if (!missing.length)
|
|
393
|
+
continue;
|
|
394
|
+
issues.set(id, {
|
|
395
|
+
type: "missing-dependency",
|
|
396
|
+
dependencies: missing,
|
|
397
|
+
message: `Missing frontend plugin dependencies: ${missing.join(", ")}`
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const markUnavailable = () => {
|
|
401
|
+
let changed = false;
|
|
402
|
+
for (const item of sorted) {
|
|
403
|
+
const id = options.id(item);
|
|
404
|
+
if (issues.has(id))
|
|
405
|
+
continue;
|
|
406
|
+
const unavailable = dependencies.get(id).filter((dependency) => issues.has(dependency));
|
|
407
|
+
if (!unavailable.length)
|
|
408
|
+
continue;
|
|
409
|
+
issues.set(id, {
|
|
410
|
+
type: "dependency-unavailable",
|
|
411
|
+
dependencies: unavailable,
|
|
412
|
+
message: `Frontend plugin dependencies are unavailable: ${unavailable.join(", ")}`
|
|
413
|
+
});
|
|
414
|
+
changed = true;
|
|
415
|
+
}
|
|
416
|
+
return changed;
|
|
417
|
+
};
|
|
418
|
+
while (markUnavailable()) {
|
|
419
|
+
// Propagate direct graph failures before sorting the remaining nodes.
|
|
420
|
+
}
|
|
421
|
+
const candidates = sorted.filter((item) => !issues.has(options.id(item)));
|
|
422
|
+
const candidateIds = new Set(candidates.map(options.id));
|
|
423
|
+
const indegree = new Map(candidates.map((item) => [options.id(item), 0]));
|
|
424
|
+
const dependents = new Map();
|
|
425
|
+
for (const item of candidates) {
|
|
426
|
+
const id = options.id(item);
|
|
427
|
+
for (const dependency of dependencies.get(id)) {
|
|
428
|
+
if (!candidateIds.has(dependency))
|
|
429
|
+
continue;
|
|
430
|
+
indegree.set(id, indegree.get(id) + 1);
|
|
431
|
+
const bucket = dependents.get(dependency) || [];
|
|
432
|
+
bucket.push(item);
|
|
433
|
+
dependents.set(dependency, bucket);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const ready = candidates.filter((item) => indegree.get(options.id(item)) === 0);
|
|
437
|
+
ready.sort(options.compare);
|
|
438
|
+
const ordered = [];
|
|
439
|
+
while (ready.length) {
|
|
440
|
+
const item = ready.shift();
|
|
441
|
+
const id = options.id(item);
|
|
442
|
+
ordered.push(item);
|
|
443
|
+
for (const dependent of dependents.get(id) || []) {
|
|
444
|
+
const dependentId = options.id(dependent);
|
|
445
|
+
const next = indegree.get(dependentId) - 1;
|
|
446
|
+
indegree.set(dependentId, next);
|
|
447
|
+
if (next === 0) {
|
|
448
|
+
ready.push(dependent);
|
|
449
|
+
ready.sort(options.compare);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const unresolved = candidates.filter((item) => !ordered.includes(item));
|
|
454
|
+
if (unresolved.length) {
|
|
455
|
+
const unresolvedIds = new Set(unresolved.map(options.id));
|
|
456
|
+
for (const item of unresolved) {
|
|
457
|
+
const id = options.id(item);
|
|
458
|
+
const blockedBy = dependencies.get(id).filter((dependency) => unresolvedIds.has(dependency));
|
|
459
|
+
issues.set(id, {
|
|
460
|
+
type: "dependency-cycle",
|
|
461
|
+
dependencies: blockedBy,
|
|
462
|
+
message: `Frontend plugin dependency cycle: ${[id, ...blockedBy].join(" -> ")}`
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { ordered, issues };
|
|
467
|
+
}
|
|
468
|
+
/** A queue survives failures, so a failed change cannot block subsequent repairs. */
|
|
469
|
+
function createPluginQueue() {
|
|
470
|
+
let tail = Promise.resolve();
|
|
471
|
+
return (operation) => {
|
|
472
|
+
const next = tail.then(operation);
|
|
473
|
+
tail = next.catch(() => undefined);
|
|
474
|
+
return next;
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/** Validate the form contract on the server before invoking plugin-owned validation. */
|
|
478
|
+
function validatePluginSettings(fields, values) {
|
|
479
|
+
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
480
|
+
throw new Error("Invalid plugin settings.");
|
|
481
|
+
const input = values;
|
|
482
|
+
for (const field of fields) {
|
|
483
|
+
if (!field.key || field.type === "link")
|
|
484
|
+
continue;
|
|
485
|
+
const value = input[field.key];
|
|
486
|
+
if (value === undefined || value === null) {
|
|
487
|
+
if (field.required)
|
|
488
|
+
throw new Error(`${field.title || field.key}: required`);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (field.type === "list") {
|
|
492
|
+
if (!Array.isArray(value) || value.length > (field.maxItems ?? 100))
|
|
493
|
+
throw new Error(`${field.title || field.key}: invalid list`);
|
|
494
|
+
for (const item of value)
|
|
495
|
+
validatePluginSettings(field.fields || [], item);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const valid = field.type === "number"
|
|
499
|
+
? typeof value === "number" &&
|
|
500
|
+
Number.isFinite(value) &&
|
|
501
|
+
(field.min === undefined || value >= field.min) &&
|
|
502
|
+
(field.max === undefined || value <= field.max)
|
|
503
|
+
: field.type === "boolean"
|
|
504
|
+
? typeof value === "boolean"
|
|
505
|
+
: field.type === "select"
|
|
506
|
+
? field.options?.some((option) => option.value === value)
|
|
507
|
+
: typeof value === "string";
|
|
508
|
+
if (!valid)
|
|
509
|
+
throw new Error(`${field.title || field.key}: invalid value`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
/***/ },
|
|
515
|
+
|
|
516
|
+
/***/ "../common/src/plugin_lifecycle.ts"
|
|
517
|
+
(__unused_webpack_module, exports) {
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
521
|
+
exports.trackPluginDisposal = trackPluginDisposal;
|
|
522
|
+
/** Cordis 3 starts async disposers without awaiting them. Track them per host. */
|
|
523
|
+
function trackPluginDisposal(ctx) {
|
|
524
|
+
const pending = new Set();
|
|
525
|
+
const failures = [];
|
|
526
|
+
const tracked = new WeakSet();
|
|
527
|
+
const scopes = new WeakSet();
|
|
528
|
+
const observe = (scope) => {
|
|
529
|
+
if (scopes.has(scope))
|
|
530
|
+
return;
|
|
531
|
+
scopes.add(scope);
|
|
532
|
+
const reset = scope.reset;
|
|
533
|
+
scope.reset = function () {
|
|
534
|
+
this.disposables = this.disposables.map((dispose) => {
|
|
535
|
+
if (tracked.has(dispose))
|
|
536
|
+
return dispose;
|
|
537
|
+
const wrapper = (...args) => {
|
|
538
|
+
let result;
|
|
539
|
+
try {
|
|
540
|
+
result = dispose(...args);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
failures.push(error);
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
if (result && typeof result.then === "function") {
|
|
547
|
+
const task = Promise.resolve(result)
|
|
548
|
+
.catch((error) => {
|
|
549
|
+
failures.push(error);
|
|
550
|
+
})
|
|
551
|
+
.finally(() => pending.delete(task));
|
|
552
|
+
pending.add(task);
|
|
553
|
+
}
|
|
554
|
+
return result;
|
|
555
|
+
};
|
|
556
|
+
// Cordis marks persistent disposers with a symbol; preserve its semantics.
|
|
557
|
+
for (const key of Object.getOwnPropertySymbols(dispose))
|
|
558
|
+
Object.defineProperty(wrapper, key, Object.getOwnPropertyDescriptor(dispose, key));
|
|
559
|
+
tracked.add(wrapper);
|
|
560
|
+
return wrapper;
|
|
561
|
+
});
|
|
562
|
+
return reset.call(this);
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
ctx.on("internal/runtime", observe);
|
|
566
|
+
ctx.on("internal/fork", observe);
|
|
567
|
+
return async () => {
|
|
568
|
+
do {
|
|
569
|
+
await ctx.events.flush();
|
|
570
|
+
await Promise.all([...pending]);
|
|
571
|
+
} while (pending.size);
|
|
572
|
+
if (failures.length) {
|
|
573
|
+
const errors = failures.splice(0);
|
|
574
|
+
throw new Error(`Plugin cleanup failed: ${errors.map(String).join("; ")}`);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
/***/ },
|
|
581
|
+
|
|
582
|
+
/***/ "../common/src/plugin_manifest.ts"
|
|
583
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
587
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
588
|
+
};
|
|
589
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
590
|
+
exports.createFrontendPluginMetadata = createFrontendPluginMetadata;
|
|
591
|
+
exports.readPluginManifest = readPluginManifest;
|
|
592
|
+
exports.resolvePluginEntry = resolvePluginEntry;
|
|
593
|
+
exports.discoverPlugins = discoverPlugins;
|
|
594
|
+
exports.discoverPluginsFromRoots = discoverPluginsFromRoots;
|
|
595
|
+
exports.sortPlugins = sortPlugins;
|
|
596
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
597
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
598
|
+
function stringArray(value) {
|
|
599
|
+
if (!Array.isArray(value))
|
|
600
|
+
return undefined;
|
|
601
|
+
const result = value.filter((item) => typeof item === "string" && !!item.trim());
|
|
602
|
+
return result.length ? [...new Set(result.map((item) => item.trim()))] : undefined;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Redacts a full plugin manifest to the fields a browser is allowed to see.
|
|
606
|
+
* Backend `config` may contain credentials, so only `frontendConfig` crosses
|
|
607
|
+
* this boundary.
|
|
608
|
+
*/
|
|
609
|
+
function createFrontendPluginMetadata(manifest, extras = {}) {
|
|
610
|
+
const frontendInject = stringArray(manifest.frontendInject);
|
|
611
|
+
return {
|
|
612
|
+
id: manifest.id,
|
|
613
|
+
...(manifest.version === undefined ? {} : { version: manifest.version }),
|
|
614
|
+
...(manifest.description === undefined ? {} : { description: manifest.description }),
|
|
615
|
+
...(manifest.priority === undefined ? {} : { priority: manifest.priority }),
|
|
616
|
+
...(manifest.elements === undefined ? {} : { elements: manifest.elements }),
|
|
617
|
+
...(frontendInject === undefined ? {} : { frontendInject }),
|
|
618
|
+
...(manifest.frontendImmediate === undefined
|
|
619
|
+
? {}
|
|
620
|
+
: { frontendImmediate: Boolean(manifest.frontendImmediate) }),
|
|
621
|
+
...(manifest.frontendRequired === undefined
|
|
622
|
+
? {}
|
|
623
|
+
: { frontendRequired: Boolean(manifest.frontendRequired) }),
|
|
624
|
+
...(manifest.frontendConfig === undefined ? {} : { config: manifest.frontendConfig }),
|
|
625
|
+
...(extras.restartRequired === undefined ? {} : extras)
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const MANIFEST_FILES = ["plugin.json", "manifest.json", "package.json"];
|
|
629
|
+
const ENTRY_FIELDS = ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"];
|
|
630
|
+
function objectValue(value) {
|
|
631
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
632
|
+
? value
|
|
633
|
+
: {};
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Source plugins keep package.json beside plugin.json. Published npm plugins
|
|
637
|
+
* keep it at the package root, with runtime metadata under elementspanel.<side>.
|
|
638
|
+
* Stop at the first package boundary so a host's metadata cannot reach a plugin.
|
|
639
|
+
*/
|
|
640
|
+
function readPackageMetadata(directory, id) {
|
|
641
|
+
const pluginDirectory = node_path_1.default.resolve(directory);
|
|
642
|
+
let current = pluginDirectory;
|
|
643
|
+
while (true) {
|
|
644
|
+
const filename = node_path_1.default.join(current, "package.json");
|
|
645
|
+
if (node_fs_1.default.existsSync(filename)) {
|
|
646
|
+
const { elementspanel, ...metadata } = objectValue(JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")));
|
|
647
|
+
const { id: packageId, panel, daemon, ...shared } = objectValue(elementspanel);
|
|
648
|
+
const definition = [panel, daemon]
|
|
649
|
+
.map(objectValue)
|
|
650
|
+
.find((side) => typeof side.directory === "string" &&
|
|
651
|
+
node_path_1.default.resolve(current, side.directory) === pluginDirectory);
|
|
652
|
+
if (current !== pluginDirectory && (packageId !== id || !definition))
|
|
653
|
+
return {};
|
|
654
|
+
const { directory: _directory, folder: _folder, ...sideMetadata } = definition ?? {};
|
|
655
|
+
const result = { ...metadata, ...shared, ...sideMetadata };
|
|
656
|
+
// npm's main/entry fields do not define a side's execution entry.
|
|
657
|
+
for (const field of ["dual", ...ENTRY_FIELDS])
|
|
658
|
+
delete result[field];
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
661
|
+
const parent = node_path_1.default.dirname(current);
|
|
662
|
+
if (parent === current)
|
|
663
|
+
return {};
|
|
664
|
+
current = parent;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/** Merges package metadata with a plugin's identity and entries. Legacy manifests still load. */
|
|
668
|
+
function readPluginManifest(directory, onWarning, fallbackId = node_path_1.default.basename(directory)) {
|
|
669
|
+
for (const file of MANIFEST_FILES) {
|
|
670
|
+
const filePath = node_path_1.default.join(directory, file);
|
|
671
|
+
if (!node_fs_1.default.existsSync(filePath))
|
|
672
|
+
continue;
|
|
673
|
+
try {
|
|
674
|
+
const value = JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
|
|
675
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
676
|
+
return null;
|
|
677
|
+
const id = typeof value.id === "string" ? value.id : fallbackId;
|
|
678
|
+
if (!id.trim())
|
|
679
|
+
return null;
|
|
680
|
+
const manifest = {
|
|
681
|
+
...value,
|
|
682
|
+
...readPackageMetadata(directory, id.trim()),
|
|
683
|
+
id: id.trim()
|
|
684
|
+
};
|
|
685
|
+
// Identity and execution paths always belong to plugin.json. Package
|
|
686
|
+
// metadata wins for descriptions, priority, configuration and dependencies.
|
|
687
|
+
for (const field of ["version", "dual", ...ENTRY_FIELDS]) {
|
|
688
|
+
if (value[field] !== undefined)
|
|
689
|
+
manifest[field] = value[field];
|
|
690
|
+
}
|
|
691
|
+
return manifest;
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
onWarning?.(`Failed to read plugin manifest: ${filePath}`, error);
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Resolves one plugin's entry module. Returns null when the manifest names an
|
|
702
|
+
* entry that does not exist, or when none of the fallback candidates is present.
|
|
703
|
+
* Paths that would escape the plugin directory are rejected.
|
|
704
|
+
*/
|
|
705
|
+
function resolvePluginEntry(directory, manifest, options) {
|
|
706
|
+
const configured = options.entryFields
|
|
707
|
+
.map((field) => manifest[field])
|
|
708
|
+
.find((entry) => typeof entry === "string" && entry.length > 0);
|
|
709
|
+
const candidates = configured ? [configured] : options.entryCandidates ?? [];
|
|
710
|
+
const root = node_path_1.default.resolve(directory);
|
|
711
|
+
for (const candidate of candidates) {
|
|
712
|
+
const entry = node_path_1.default.resolve(directory, candidate);
|
|
713
|
+
if (entry.startsWith(`${root}${node_path_1.default.sep}`) && node_fs_1.default.existsSync(entry))
|
|
714
|
+
return entry;
|
|
715
|
+
}
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
function discoverPluginDirectory(directory, options, folder = node_path_1.default.basename(directory)) {
|
|
719
|
+
// Npm packages expose a side directory; its name is not the public plugin id.
|
|
720
|
+
const manifest = readPluginManifest(directory, options.onWarning, folder);
|
|
721
|
+
if (!manifest)
|
|
722
|
+
return null;
|
|
723
|
+
if (manifest.enabled === false && !options.includeDisabled)
|
|
724
|
+
return null;
|
|
725
|
+
const entry = resolvePluginEntry(directory, manifest, options);
|
|
726
|
+
if (!entry && options.entryFields.some((field) => typeof manifest[field] === "string")) {
|
|
727
|
+
options.onWarning?.(`Plugin "${manifest.id}" has no valid entry module.`);
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
return { manifest, directory, folder, entry: entry ?? undefined };
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Lists the usable plugins below `root`, in load order: ascending `priority`,
|
|
734
|
+
* then by id so the order is stable. Duplicate ids are dropped, and disabled
|
|
735
|
+
* ones too unless `includeDisabled` says otherwise. A plugin whose manifest names an entry that cannot be resolved is
|
|
736
|
+
* dropped too; one that names none keeps `entry` undefined, because a plugin may
|
|
737
|
+
* legitimately contribute to only one side of the panel.
|
|
738
|
+
*/
|
|
739
|
+
function discoverPlugins(root, options) {
|
|
740
|
+
if (!node_fs_1.default.existsSync(root))
|
|
741
|
+
return [];
|
|
742
|
+
const plugins = [];
|
|
743
|
+
const seenIds = new Set();
|
|
744
|
+
for (const item of node_fs_1.default.readdirSync(root, { withFileTypes: true })) {
|
|
745
|
+
if (!item.isDirectory())
|
|
746
|
+
continue;
|
|
747
|
+
const directory = node_path_1.default.join(root, item.name);
|
|
748
|
+
const plugin = discoverPluginDirectory(directory, options, item.name);
|
|
749
|
+
if (!plugin || seenIds.has(plugin.manifest.id)) {
|
|
750
|
+
if (plugin && seenIds.has(plugin.manifest.id)) {
|
|
751
|
+
options.onWarning?.(`Ignoring duplicate plugin id: ${plugin.manifest.id}`);
|
|
752
|
+
}
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
seenIds.add(plugin.manifest.id);
|
|
756
|
+
plugins.push(plugin);
|
|
757
|
+
}
|
|
758
|
+
return sortPlugins(plugins);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Discovers plugins from several roots while keeping the first root's plugin
|
|
762
|
+
* when ids collide. The built-in root is intentionally passed first by the
|
|
763
|
+
* panel and daemon loaders, so an installed market plugin cannot shadow a
|
|
764
|
+
* built-in npm dependency. A root can also force an installed project plugin
|
|
765
|
+
* into the disabled state; the user-owned override layer may enable it later.
|
|
766
|
+
*/
|
|
767
|
+
function discoverPluginsFromRoots(roots, options) {
|
|
768
|
+
const discovered = [];
|
|
769
|
+
const seenIds = new Set();
|
|
770
|
+
for (const root of roots) {
|
|
771
|
+
const isDirectPlugin = MANIFEST_FILES.some((file) => node_fs_1.default.existsSync(node_path_1.default.join(root.directory, file)));
|
|
772
|
+
const plugins = isDirectPlugin
|
|
773
|
+
? [discoverPluginDirectory(root.directory, options, root.folder)]
|
|
774
|
+
: discoverPlugins(root.directory, options);
|
|
775
|
+
for (const plugin of plugins) {
|
|
776
|
+
if (!plugin)
|
|
777
|
+
continue;
|
|
778
|
+
const manifest = root.defaultEnabled === false ? { ...plugin.manifest, enabled: false } : plugin.manifest;
|
|
779
|
+
if (manifest.enabled === false && !options.includeDisabled)
|
|
780
|
+
continue;
|
|
781
|
+
if (seenIds.has(plugin.manifest.id)) {
|
|
782
|
+
options.onWarning?.(`Ignoring duplicate plugin id: ${plugin.manifest.id}`);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
seenIds.add(plugin.manifest.id);
|
|
786
|
+
discovered.push({
|
|
787
|
+
...plugin,
|
|
788
|
+
manifest,
|
|
789
|
+
...(root.packageRoot
|
|
790
|
+
? {
|
|
791
|
+
packageRoot: root.packageRoot,
|
|
792
|
+
packageName: root.packageName,
|
|
793
|
+
npmSource: root.npmSource
|
|
794
|
+
}
|
|
795
|
+
: {}),
|
|
796
|
+
...(root.folder ? { folder: root.folder } : {})
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return sortPlugins(discovered);
|
|
801
|
+
}
|
|
802
|
+
/** Load order: ascending `priority`, then by id so it never depends on the filesystem. */
|
|
803
|
+
function sortPlugins(plugins) {
|
|
804
|
+
return plugins.sort((a, b) => (Number(a.manifest.priority) || 0) - (Number(b.manifest.priority) || 0) ||
|
|
805
|
+
a.manifest.id.localeCompare(b.manifest.id));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
/***/ },
|
|
810
|
+
|
|
811
|
+
/***/ "../common/src/plugin_modules.ts"
|
|
812
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
816
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
817
|
+
};
|
|
818
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
819
|
+
exports.pluginBackendRevision = pluginBackendRevision;
|
|
820
|
+
exports.forgetPluginModules = forgetPluginModules;
|
|
821
|
+
exports.loadPluginModule = loadPluginModule;
|
|
822
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
823
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
824
|
+
const node_url_1 = __webpack_require__("node:url");
|
|
825
|
+
const node_crypto_1 = __webpack_require__("node:crypto");
|
|
826
|
+
const node_module_1 = __webpack_require__("node:module");
|
|
827
|
+
const plugin_revision_1 = __webpack_require__("../common/src/plugin_revision.ts");
|
|
828
|
+
const REVISION = "epanelPluginRevision";
|
|
829
|
+
const graphs = new Map();
|
|
830
|
+
const previousRoots = new Map();
|
|
831
|
+
let hooksInstalled = false;
|
|
832
|
+
const sharedModules = new Set([
|
|
833
|
+
"cordis",
|
|
834
|
+
"@cordisjs/core",
|
|
835
|
+
"@cordisjs/logger",
|
|
836
|
+
"@cordisjs/timer",
|
|
837
|
+
"@cordisjs/schema",
|
|
838
|
+
"cosmokit",
|
|
839
|
+
"schemastery",
|
|
840
|
+
"reggol"
|
|
841
|
+
]);
|
|
842
|
+
function dependencyRoots(directory) {
|
|
843
|
+
const visited = new Set([directory]);
|
|
844
|
+
const walk = (root) => {
|
|
845
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
846
|
+
if (!node_fs_1.default.existsSync(filename))
|
|
847
|
+
return;
|
|
848
|
+
const metadata = JSON.parse(node_fs_1.default.readFileSync(filename, "utf8"));
|
|
849
|
+
const localRequire = (0, node_module_1.createRequire)(filename);
|
|
850
|
+
for (const name of Object.keys({
|
|
851
|
+
...metadata.dependencies,
|
|
852
|
+
...metadata.optionalDependencies
|
|
853
|
+
})) {
|
|
854
|
+
if (sharedModules.has(name))
|
|
855
|
+
continue;
|
|
856
|
+
const target = (localRequire.resolve.paths(name) || [])
|
|
857
|
+
.map((directory) => node_path_1.default.join(directory, name, "package.json"))
|
|
858
|
+
.find((candidate) => node_fs_1.default.existsSync(candidate));
|
|
859
|
+
if (!target)
|
|
860
|
+
continue;
|
|
861
|
+
const dependency = node_fs_1.default.realpathSync(node_path_1.default.dirname(target));
|
|
862
|
+
if (visited.has(dependency))
|
|
863
|
+
continue;
|
|
864
|
+
visited.add(dependency);
|
|
865
|
+
walk(dependency);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
walk(directory);
|
|
869
|
+
visited.delete(directory);
|
|
870
|
+
return [...visited];
|
|
871
|
+
}
|
|
872
|
+
/** Include transitive dependency versions so npm updates replace the affected graph. */
|
|
873
|
+
function pluginBackendRevision(entry, directory) {
|
|
874
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update((0, plugin_revision_1.pluginDirectoryRevision)(node_path_1.default.dirname(entry)));
|
|
875
|
+
for (const root of [directory, ...dependencyRoots(directory)]) {
|
|
876
|
+
const filename = node_path_1.default.join(root, "package.json");
|
|
877
|
+
if (node_fs_1.default.existsSync(filename))
|
|
878
|
+
hash.update(root).update(node_fs_1.default.readFileSync(filename));
|
|
879
|
+
}
|
|
880
|
+
return hash.digest("hex").slice(0, 24);
|
|
881
|
+
}
|
|
882
|
+
function within(root, filename) {
|
|
883
|
+
const relative = node_path_1.default.relative(root, filename);
|
|
884
|
+
return (relative === "" ||
|
|
885
|
+
(!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
|
|
886
|
+
}
|
|
887
|
+
/** Clear the plugin's complete CJS graph, keeping shared host modules intact. */
|
|
888
|
+
function forgetPluginModules(directory) {
|
|
889
|
+
const runtimeRequire = eval("require");
|
|
890
|
+
const owned = [directory, ...(previousRoots.get(directory) || []), ...dependencyRoots(directory)];
|
|
891
|
+
previousRoots.delete(directory);
|
|
892
|
+
const belongs = (filename) => {
|
|
893
|
+
const parts = filename.split(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`);
|
|
894
|
+
if (parts.slice(1).some((part) => {
|
|
895
|
+
const name = part.startsWith("@")
|
|
896
|
+
? part.split(node_path_1.default.sep).slice(0, 2).join("/")
|
|
897
|
+
: part.split(node_path_1.default.sep)[0];
|
|
898
|
+
return sharedModules.has(name);
|
|
899
|
+
}))
|
|
900
|
+
return false;
|
|
901
|
+
return owned.some((root) => within(root, filename));
|
|
902
|
+
};
|
|
903
|
+
for (const filename of Object.keys(runtimeRequire.cache)) {
|
|
904
|
+
if (belongs(filename))
|
|
905
|
+
delete runtimeRequire.cache[filename];
|
|
906
|
+
}
|
|
907
|
+
for (const mod of Object.values(runtimeRequire.cache)) {
|
|
908
|
+
if (mod)
|
|
909
|
+
mod.children = mod.children.filter((child) => !belongs(child.filename));
|
|
910
|
+
}
|
|
911
|
+
// npm can move a dependency or change its main entry. Node's CJS resolver
|
|
912
|
+
// retains those paths separately from require.cache.
|
|
913
|
+
const pathCache = runtimeRequire("node:module")._pathCache;
|
|
914
|
+
for (const [key, filename] of Object.entries(pathCache)) {
|
|
915
|
+
if (belongs(filename) || owned.some((root) => key.includes(root)))
|
|
916
|
+
delete pathCache[key];
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function isEsm(entry) {
|
|
920
|
+
if (entry.endsWith(".mjs"))
|
|
921
|
+
return true;
|
|
922
|
+
if (entry.endsWith(".cjs"))
|
|
923
|
+
return false;
|
|
924
|
+
let directory = node_path_1.default.dirname(entry);
|
|
925
|
+
while (true) {
|
|
926
|
+
const filename = node_path_1.default.join(directory, "package.json");
|
|
927
|
+
if (node_fs_1.default.existsSync(filename))
|
|
928
|
+
return JSON.parse(node_fs_1.default.readFileSync(filename, "utf8")).type === "module";
|
|
929
|
+
const parent = node_path_1.default.dirname(directory);
|
|
930
|
+
if (parent === directory)
|
|
931
|
+
return false;
|
|
932
|
+
directory = parent;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/** Give an ESM entry and its local imports the same immutable revision URL. */
|
|
936
|
+
async function loadPluginModule(entry, directory, revision) {
|
|
937
|
+
const runtimeRequire = eval("require");
|
|
938
|
+
previousRoots.set(directory, dependencyRoots(directory));
|
|
939
|
+
if (!isEsm(entry)) {
|
|
940
|
+
try {
|
|
941
|
+
return runtimeRequire(entry);
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
if (error?.code !== "ERR_REQUIRE_ESM")
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
graphs.set(revision, [directory, ...dependencyRoots(directory)]);
|
|
949
|
+
if (!hooksInstalled) {
|
|
950
|
+
const { registerHooks } = runtimeRequire("node:module");
|
|
951
|
+
if (typeof registerHooks !== "function")
|
|
952
|
+
throw new Error("ESM plugin updates require Node.js 22.15 or newer.");
|
|
953
|
+
registerHooks({
|
|
954
|
+
resolve(specifier, context, nextResolve) {
|
|
955
|
+
const resolved = nextResolve(specifier, context);
|
|
956
|
+
if (!context.parentURL?.startsWith("file:") || !resolved.url.startsWith("file:"))
|
|
957
|
+
return resolved;
|
|
958
|
+
const parent = new URL(context.parentURL);
|
|
959
|
+
const parentRevision = parent.searchParams.get(REVISION);
|
|
960
|
+
if (!parentRevision)
|
|
961
|
+
return resolved;
|
|
962
|
+
const roots = graphs.get(parentRevision);
|
|
963
|
+
if (!roots?.some((root) => within(root, (0, node_url_1.fileURLToPath)(parent))) ||
|
|
964
|
+
!roots.some((root) => within(root, (0, node_url_1.fileURLToPath)(resolved.url))))
|
|
965
|
+
return resolved;
|
|
966
|
+
const url = new URL(resolved.url);
|
|
967
|
+
url.searchParams.set(REVISION, parentRevision);
|
|
968
|
+
return { ...resolved, url: url.href };
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
hooksInstalled = true;
|
|
972
|
+
}
|
|
973
|
+
const url = (0, node_url_1.pathToFileURL)(entry);
|
|
974
|
+
url.searchParams.set(REVISION, revision);
|
|
975
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
976
|
+
return dynamicImport(url.href);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
/***/ },
|
|
981
|
+
|
|
982
|
+
/***/ "../common/src/plugin_npm.ts"
|
|
983
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
987
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
988
|
+
};
|
|
989
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
990
|
+
exports.currentPackageRoot = currentPackageRoot;
|
|
991
|
+
exports.currentProjectRoot = currentProjectRoot;
|
|
992
|
+
exports.npmPluginRoots = npmPluginRoots;
|
|
993
|
+
exports.listNpmPlugins = listNpmPlugins;
|
|
994
|
+
exports.npmPluginOperationPending = npmPluginOperationPending;
|
|
995
|
+
exports.isEssentialNpmPlugin = isEssentialNpmPlugin;
|
|
996
|
+
exports.installNpmPlugin = installNpmPlugin;
|
|
997
|
+
exports.uninstallNpmPlugin = uninstallNpmPlugin;
|
|
998
|
+
const child_process_1 = __webpack_require__("child_process");
|
|
999
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
1000
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
1001
|
+
const module_1 = __webpack_require__("module");
|
|
1002
|
+
const plugin_manifest_1 = __webpack_require__("../common/src/plugin_manifest.ts");
|
|
1003
|
+
const plugin_contract_1 = __webpack_require__("../common/src/plugin_contract.ts");
|
|
1004
|
+
const PLUGIN_ID = /^[a-z][a-z0-9_-]{1,63}$/;
|
|
1005
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9_.-]+\/)?epanel-plugin-[a-z0-9_.-]+$/;
|
|
1006
|
+
const VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,63}$/;
|
|
1007
|
+
function readJson(filename) {
|
|
1008
|
+
try {
|
|
1009
|
+
const parsed = JSON.parse(fs_extra_1.default.readFileSync(filename, "utf8"));
|
|
1010
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1011
|
+
? parsed
|
|
1012
|
+
: null;
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
if (error?.code === "ENOENT")
|
|
1016
|
+
return null;
|
|
1017
|
+
throw error;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function objectValue(value) {
|
|
1021
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
1022
|
+
? value
|
|
1023
|
+
: undefined;
|
|
1024
|
+
}
|
|
1025
|
+
function stringValue(value) {
|
|
1026
|
+
return typeof value === "string" ? value : "";
|
|
1027
|
+
}
|
|
1028
|
+
function currentPackageRoot() {
|
|
1029
|
+
return path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.cwd());
|
|
1030
|
+
}
|
|
1031
|
+
function currentProjectRoot(packageRoot = currentPackageRoot()) {
|
|
1032
|
+
// The npm worker sets EPANEL_PROJECT_ROOT to the installation directory. In a
|
|
1033
|
+
// development checkout the runtime package sits directly under the project.
|
|
1034
|
+
if (process.env.EPANEL_PROJECT_ROOT)
|
|
1035
|
+
return path_1.default.resolve(process.env.EPANEL_PROJECT_ROOT);
|
|
1036
|
+
const scope = path_1.default.resolve(packageRoot, "..");
|
|
1037
|
+
const nodeModules = path_1.default.dirname(scope);
|
|
1038
|
+
if (path_1.default.basename(nodeModules) === "node_modules" && path_1.default.basename(scope).startsWith("@"))
|
|
1039
|
+
return path_1.default.dirname(nodeModules);
|
|
1040
|
+
return scope;
|
|
1041
|
+
}
|
|
1042
|
+
function npmPluginRoots(options = {}) {
|
|
1043
|
+
const packageRoot = path_1.default.resolve(options.packageRoot ?? currentPackageRoot());
|
|
1044
|
+
return {
|
|
1045
|
+
packageRoot,
|
|
1046
|
+
projectRoot: path_1.default.resolve(options.projectRoot ?? currentProjectRoot(packageRoot))
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function projectManifestFile(projectRoot) {
|
|
1050
|
+
return path_1.default.join(projectRoot, "package.json");
|
|
1051
|
+
}
|
|
1052
|
+
function readProjectManifest(projectRoot) {
|
|
1053
|
+
return (readJson(projectManifestFile(projectRoot)) ?? {
|
|
1054
|
+
private: true,
|
|
1055
|
+
dependencies: {},
|
|
1056
|
+
elementspanel: { plugins: {} }
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
function writeProjectManifest(projectRoot, manifest) {
|
|
1060
|
+
fs_extra_1.default.writeFileSync(projectManifestFile(projectRoot), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
1061
|
+
}
|
|
1062
|
+
function dependenciesOf(manifest) {
|
|
1063
|
+
return objectValue(manifest?.dependencies) ?? {};
|
|
1064
|
+
}
|
|
1065
|
+
function mappingsOf(manifest) {
|
|
1066
|
+
return objectValue(objectValue(manifest?.elementspanel)?.plugins) ?? {};
|
|
1067
|
+
}
|
|
1068
|
+
function pluginRecord(pluginId, name, source, packageRoot, directory, folder, version) {
|
|
1069
|
+
return { pluginId, name, version, sides: [], directory, folder, packageRoot, source };
|
|
1070
|
+
}
|
|
1071
|
+
function readPackageEntry(packageRoot, name) {
|
|
1072
|
+
// Read package boundaries directly: Node caches require.resolve() paths even
|
|
1073
|
+
// after npm moves an installation between nested and hoisted node_modules.
|
|
1074
|
+
let manifestPath;
|
|
1075
|
+
for (const root of [packageRoot, currentProjectRoot(packageRoot)]) {
|
|
1076
|
+
const localRequire = (0, module_1.createRequire)(path_1.default.join(root, "package.json"));
|
|
1077
|
+
manifestPath = (localRequire.resolve.paths(name) || [])
|
|
1078
|
+
.map((directory) => path_1.default.join(directory, name, "package.json"))
|
|
1079
|
+
.find((filename) => fs_extra_1.default.existsSync(filename));
|
|
1080
|
+
if (manifestPath)
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
if (!manifestPath)
|
|
1084
|
+
throw new Error(`Missing npm plugin package: ${name}.`);
|
|
1085
|
+
manifestPath = fs_extra_1.default.realpathSync(manifestPath);
|
|
1086
|
+
const metadata = readJson(manifestPath);
|
|
1087
|
+
if (!metadata)
|
|
1088
|
+
throw new Error(`Missing package metadata for ${name}.`);
|
|
1089
|
+
return { root: path_1.default.dirname(manifestPath), metadata };
|
|
1090
|
+
}
|
|
1091
|
+
function sideDirectory(packageRoot, metadata, side) {
|
|
1092
|
+
const elements = objectValue(metadata.elementspanel);
|
|
1093
|
+
const definition = objectValue(elements?.[side]);
|
|
1094
|
+
const directory = stringValue(definition?.directory);
|
|
1095
|
+
if (!definition || !directory)
|
|
1096
|
+
return undefined;
|
|
1097
|
+
const target = path_1.default.resolve(packageRoot, directory);
|
|
1098
|
+
const relative = path_1.default.relative(packageRoot, target);
|
|
1099
|
+
if (relative === "" ||
|
|
1100
|
+
relative === ".." ||
|
|
1101
|
+
relative.startsWith(`..${path_1.default.sep}`) ||
|
|
1102
|
+
path_1.default.isAbsolute(relative))
|
|
1103
|
+
return undefined;
|
|
1104
|
+
return target;
|
|
1105
|
+
}
|
|
1106
|
+
function recordFromPackage(pluginId, name, source, packageRoot, metadata, side) {
|
|
1107
|
+
const elements = objectValue(metadata.elementspanel);
|
|
1108
|
+
if (!elements || elements.id !== pluginId)
|
|
1109
|
+
return undefined;
|
|
1110
|
+
const version = stringValue(metadata.version);
|
|
1111
|
+
const sides = ["panel", "daemon"].filter((item) => Boolean(sideDirectory(packageRoot, metadata, item)));
|
|
1112
|
+
if (!sides.length)
|
|
1113
|
+
return undefined;
|
|
1114
|
+
const target = side ?? sides[0];
|
|
1115
|
+
if (!sides.includes(target))
|
|
1116
|
+
return undefined;
|
|
1117
|
+
const directory = sideDirectory(packageRoot, metadata, target);
|
|
1118
|
+
const folder = stringValue(objectValue(elements[target])?.folder);
|
|
1119
|
+
if (!directory || !PLUGIN_ID.test(folder))
|
|
1120
|
+
return undefined;
|
|
1121
|
+
return {
|
|
1122
|
+
...pluginRecord(pluginId, name, source, packageRoot, directory, folder, version),
|
|
1123
|
+
sides
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Every npm plugin the runtime can load for a side, built-in and project-installed.
|
|
1128
|
+
*
|
|
1129
|
+
* Built-in mappings win over a project mapping for the same id: a third-party
|
|
1130
|
+
* package must not shadow a plugin that ships with the runtime. A project package
|
|
1131
|
+
* that does not describe the requested side is skipped instead of failing, which
|
|
1132
|
+
* is what lets a two-sided project share one dependency list between sides.
|
|
1133
|
+
*/
|
|
1134
|
+
function listNpmPlugins(side, options = {}) {
|
|
1135
|
+
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1136
|
+
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1137
|
+
if (!host)
|
|
1138
|
+
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
1139
|
+
const project = path_1.default.resolve(projectRoot) === path_1.default.resolve(packageRoot)
|
|
1140
|
+
? null
|
|
1141
|
+
: readProjectManifest(projectRoot);
|
|
1142
|
+
const hostMappings = mappingsOf(host);
|
|
1143
|
+
const projectMappings = { ...mappingsOf(project) };
|
|
1144
|
+
for (const name of Object.keys(dependenciesOf(project))) {
|
|
1145
|
+
if (!PACKAGE_NAME.test(name) || Object.values(projectMappings).includes(name))
|
|
1146
|
+
continue;
|
|
1147
|
+
try {
|
|
1148
|
+
const entry = readPackageEntry(projectRoot, name);
|
|
1149
|
+
const id = stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
1150
|
+
if (PLUGIN_ID.test(id) && projectMappings[id] === undefined)
|
|
1151
|
+
projectMappings[id] = name;
|
|
1152
|
+
}
|
|
1153
|
+
catch {
|
|
1154
|
+
/* npm may still be writing the package. */
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const mappings = new Map();
|
|
1158
|
+
for (const [pluginId, name] of Object.entries(projectMappings)) {
|
|
1159
|
+
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
1160
|
+
continue;
|
|
1161
|
+
mappings.set(pluginId, { name, source: "project" });
|
|
1162
|
+
}
|
|
1163
|
+
for (const [pluginId, name] of Object.entries(hostMappings)) {
|
|
1164
|
+
if (!PLUGIN_ID.test(pluginId) || typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
1165
|
+
continue;
|
|
1166
|
+
if (dependenciesOf(host)[name] === undefined)
|
|
1167
|
+
continue;
|
|
1168
|
+
mappings.set(pluginId, { name, source: "bundled" });
|
|
1169
|
+
}
|
|
1170
|
+
const hostDependencies = dependenciesOf(host);
|
|
1171
|
+
const projectDependencies = dependenciesOf(project);
|
|
1172
|
+
const folders = new Set();
|
|
1173
|
+
const records = [];
|
|
1174
|
+
for (const [pluginId, mapping] of mappings) {
|
|
1175
|
+
const declared = hostDependencies[mapping.name] ?? projectDependencies[mapping.name];
|
|
1176
|
+
if (declared === undefined) {
|
|
1177
|
+
if (options.skipInvalid)
|
|
1178
|
+
continue;
|
|
1179
|
+
throw new Error(`Missing npm plugin dependency: ${mapping.name}. Run npm install in the project directory.`);
|
|
1180
|
+
}
|
|
1181
|
+
let entry;
|
|
1182
|
+
try {
|
|
1183
|
+
entry = readPackageEntry(mapping.source === "project" ? projectRoot : packageRoot, mapping.name);
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
if (options.skipInvalid)
|
|
1187
|
+
continue;
|
|
1188
|
+
throw error;
|
|
1189
|
+
}
|
|
1190
|
+
const record = recordFromPackage(pluginId, mapping.name, mapping.source, entry.root, entry.metadata, side);
|
|
1191
|
+
if (!record) {
|
|
1192
|
+
if (options.skipInvalid || mapping.source === "project")
|
|
1193
|
+
continue;
|
|
1194
|
+
throw new Error(`Invalid ${side ?? "plugin"} entry in npm plugin ${mapping.name}.`);
|
|
1195
|
+
}
|
|
1196
|
+
const folder = record.folder;
|
|
1197
|
+
if (folders.has(folder)) {
|
|
1198
|
+
if (options.skipInvalid)
|
|
1199
|
+
continue;
|
|
1200
|
+
throw new Error(`Duplicate npm plugin asset directory: ${folder}.`);
|
|
1201
|
+
}
|
|
1202
|
+
folders.add(folder);
|
|
1203
|
+
records.push(record);
|
|
1204
|
+
}
|
|
1205
|
+
return records;
|
|
1206
|
+
}
|
|
1207
|
+
function runNpm(args, cwd) {
|
|
1208
|
+
return new Promise((resolve, reject) => {
|
|
1209
|
+
const npmCLI = process.env.npm_execpath;
|
|
1210
|
+
const invocation = npmCLI && /npm(?:-cli)?\.(?:c?js|mjs)$/.test(npmCLI)
|
|
1211
|
+
? { command: process.execPath, args: [npmCLI, ...args], shell: false }
|
|
1212
|
+
: process.platform === "win32"
|
|
1213
|
+
? {
|
|
1214
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
1215
|
+
args: [
|
|
1216
|
+
"/d",
|
|
1217
|
+
"/s",
|
|
1218
|
+
"/c",
|
|
1219
|
+
`npm ${args.map((item) => `"${item.replace(/"/g, '""')}"`).join(" ")}`
|
|
1220
|
+
],
|
|
1221
|
+
shell: false
|
|
1222
|
+
}
|
|
1223
|
+
: { command: "npm", args, shell: false };
|
|
1224
|
+
const child = (0, child_process_1.spawn)(invocation.command, invocation.args, {
|
|
1225
|
+
cwd,
|
|
1226
|
+
shell: invocation.shell,
|
|
1227
|
+
windowsHide: true,
|
|
1228
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1229
|
+
env: { ...process.env, npm_config_update_notifier: "false" }
|
|
1230
|
+
});
|
|
1231
|
+
let output = "";
|
|
1232
|
+
const append = (data) => {
|
|
1233
|
+
output += data.toString();
|
|
1234
|
+
if (output.length > 16000)
|
|
1235
|
+
output = output.slice(-16000);
|
|
1236
|
+
};
|
|
1237
|
+
child.stdout?.on("data", append);
|
|
1238
|
+
child.stderr?.on("data", append);
|
|
1239
|
+
const action = args.find((argument) => argument === "install" || argument === "uninstall") || "operation";
|
|
1240
|
+
const timer = setTimeout(() => {
|
|
1241
|
+
child.kill("SIGKILL");
|
|
1242
|
+
reject(new Error(`npm ${action} timed out.`));
|
|
1243
|
+
}, 10 * 60 * 1000);
|
|
1244
|
+
timer.unref?.();
|
|
1245
|
+
child.once("error", (error) => {
|
|
1246
|
+
clearTimeout(timer);
|
|
1247
|
+
reject(error);
|
|
1248
|
+
});
|
|
1249
|
+
child.once("close", (code) => {
|
|
1250
|
+
clearTimeout(timer);
|
|
1251
|
+
if (code === 0)
|
|
1252
|
+
return resolve(output);
|
|
1253
|
+
reject(new Error(`npm ${action} failed (${code}): ${output.trim()}`));
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
const operationsKey = Symbol.for("elementspanel.npm.operations");
|
|
1258
|
+
const shared = globalThis;
|
|
1259
|
+
const operations = (shared[operationsKey] ??= new Map());
|
|
1260
|
+
const LOCK_NAME = ".elementspanel-npm.lock";
|
|
1261
|
+
async function acquireProjectLock(projectRoot) {
|
|
1262
|
+
await fs_extra_1.default.ensureDir(projectRoot);
|
|
1263
|
+
const directory = path_1.default.join(projectRoot, LOCK_NAME);
|
|
1264
|
+
const deadline = Date.now() + 11 * 60 * 1000;
|
|
1265
|
+
while (true) {
|
|
1266
|
+
try {
|
|
1267
|
+
await fs_extra_1.default.mkdir(directory);
|
|
1268
|
+
await fs_extra_1.default.writeJson(path_1.default.join(directory, "owner.json"), { pid: process.pid });
|
|
1269
|
+
return () => fs_extra_1.default.remove(directory);
|
|
1270
|
+
}
|
|
1271
|
+
catch (error) {
|
|
1272
|
+
if (error.code !== "EEXIST")
|
|
1273
|
+
throw error;
|
|
1274
|
+
try {
|
|
1275
|
+
const owner = await fs_extra_1.default.readJson(path_1.default.join(directory, "owner.json"));
|
|
1276
|
+
try {
|
|
1277
|
+
process.kill(owner.pid, 0);
|
|
1278
|
+
}
|
|
1279
|
+
catch (failure) {
|
|
1280
|
+
if (failure.code === "ESRCH") {
|
|
1281
|
+
await fs_extra_1.default.remove(directory);
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
catch {
|
|
1287
|
+
if (Date.now() - ((await fs_extra_1.default.stat(directory).catch(() => null))?.mtimeMs ?? Date.now()) >
|
|
1288
|
+
60_000) {
|
|
1289
|
+
await fs_extra_1.default.remove(directory);
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (Date.now() > deadline)
|
|
1294
|
+
throw new Error("Another npm plugin operation is still running.");
|
|
1295
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function npmPluginOperationPending() {
|
|
1300
|
+
const { packageRoot, projectRoot } = npmPluginRoots();
|
|
1301
|
+
return (operations.size > 0 ||
|
|
1302
|
+
[packageRoot, projectRoot].some((root) => fs_extra_1.default.existsSync(path_1.default.join(root, LOCK_NAME))));
|
|
1303
|
+
}
|
|
1304
|
+
function withProjectLock(projectRoot, operation) {
|
|
1305
|
+
const key = process.platform === "win32" ? projectRoot.toLowerCase() : projectRoot;
|
|
1306
|
+
const previous = operations.get(key) ?? Promise.resolve();
|
|
1307
|
+
const result = previous.then(async () => {
|
|
1308
|
+
const release = await acquireProjectLock(projectRoot);
|
|
1309
|
+
try {
|
|
1310
|
+
return await operation();
|
|
1311
|
+
}
|
|
1312
|
+
finally {
|
|
1313
|
+
await release();
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
const settled = result.then(() => { }, () => { });
|
|
1317
|
+
operations.set(key, settled);
|
|
1318
|
+
return result.finally(() => {
|
|
1319
|
+
if (operations.get(key) === settled)
|
|
1320
|
+
operations.delete(key);
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
/** Core services keep the connection and management interface available. */
|
|
1324
|
+
function isEssentialNpmPlugin(pluginId) {
|
|
1325
|
+
return ["i18n", "storage", "runtime", "server", "console", "config"].includes(pluginId);
|
|
1326
|
+
}
|
|
1327
|
+
/** npm changes dependencies and lockfiles; only append our plugin mapping afterwards. */
|
|
1328
|
+
async function installNpmPlugin(options) {
|
|
1329
|
+
const { name, side } = options;
|
|
1330
|
+
const version = options.version || "latest";
|
|
1331
|
+
if (!PACKAGE_NAME.test(name))
|
|
1332
|
+
throw new Error(`Invalid npm plugin package: ${name}`);
|
|
1333
|
+
if (options.pluginId && !PLUGIN_ID.test(options.pluginId))
|
|
1334
|
+
throw new Error(`Invalid plugin id: ${options.pluginId}`);
|
|
1335
|
+
if (!VERSION.test(version))
|
|
1336
|
+
throw new Error(`Invalid plugin version: ${version}`);
|
|
1337
|
+
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1338
|
+
return withProjectLock(projectRoot, async () => {
|
|
1339
|
+
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1340
|
+
if (!host)
|
|
1341
|
+
throw new Error(`Missing runtime package metadata: ${packageRoot}`);
|
|
1342
|
+
const hostId = Object.entries(mappingsOf(host)).find(([, value]) => value === name && dependenciesOf(host)[name] !== undefined)?.[0];
|
|
1343
|
+
const targetRoot = hostId ? packageRoot : projectRoot;
|
|
1344
|
+
if (hostId && isEssentialNpmPlugin(hostId))
|
|
1345
|
+
throw new Error(`The essential plugin "${hostId}" cannot be replaced while running.`);
|
|
1346
|
+
if (options.pluginId &&
|
|
1347
|
+
mappingsOf(host)[options.pluginId] &&
|
|
1348
|
+
dependenciesOf(host)[String(mappingsOf(host)[options.pluginId])] !== undefined &&
|
|
1349
|
+
mappingsOf(host)[options.pluginId] !== name)
|
|
1350
|
+
throw new Error(`BUNDLED:${options.pluginId}`);
|
|
1351
|
+
const original = readProjectManifest(targetRoot);
|
|
1352
|
+
const previous = dependenciesOf(original)[name];
|
|
1353
|
+
let previousVersion = previous;
|
|
1354
|
+
if (previous) {
|
|
1355
|
+
try {
|
|
1356
|
+
previousVersion = readPackageEntry(targetRoot, name).metadata.version || previous;
|
|
1357
|
+
}
|
|
1358
|
+
catch {
|
|
1359
|
+
/* Keep the declared spec if the previous package is incomplete. */
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const backups = new Map();
|
|
1363
|
+
for (const filename of ["package.json", "package-lock.json", "npm-shrinkwrap.json"]) {
|
|
1364
|
+
const target = path_1.default.join(targetRoot, filename);
|
|
1365
|
+
backups.set(target, fs_extra_1.default.existsSync(target) ? fs_extra_1.default.readFileSync(target) : null);
|
|
1366
|
+
}
|
|
1367
|
+
try {
|
|
1368
|
+
await runNpm([
|
|
1369
|
+
"--prefix",
|
|
1370
|
+
targetRoot,
|
|
1371
|
+
"install",
|
|
1372
|
+
`${name}@${version}`,
|
|
1373
|
+
"--save",
|
|
1374
|
+
"--save-exact",
|
|
1375
|
+
"--no-fund",
|
|
1376
|
+
"--no-audit",
|
|
1377
|
+
"--ignore-scripts"
|
|
1378
|
+
], targetRoot);
|
|
1379
|
+
const entry = readPackageEntry(targetRoot, name);
|
|
1380
|
+
const pluginId = options.pluginId || stringValue(objectValue(entry.metadata.elementspanel)?.id);
|
|
1381
|
+
if (!PLUGIN_ID.test(pluginId))
|
|
1382
|
+
throw new Error(`Invalid plugin id in ${name}.`);
|
|
1383
|
+
if (mappingsOf(host)[pluginId] &&
|
|
1384
|
+
dependenciesOf(host)[String(mappingsOf(host)[pluginId])] !== undefined &&
|
|
1385
|
+
mappingsOf(host)[pluginId] !== name)
|
|
1386
|
+
throw new Error(`Plugin id ${pluginId} belongs to a bundled dependency.`);
|
|
1387
|
+
if (hostId && pluginId !== hostId)
|
|
1388
|
+
throw new Error(`The package ${name} changed its plugin id from ${hostId} to ${pluginId}.`);
|
|
1389
|
+
const mapped = mappingsOf(readProjectManifest(targetRoot))[pluginId];
|
|
1390
|
+
if (mapped !== undefined && mapped !== name)
|
|
1391
|
+
throw new Error(`Plugin id ${pluginId} is already installed as ${String(mapped)}.`);
|
|
1392
|
+
const record = recordFromPackage(pluginId, name, hostId ? "bundled" : "project", entry.root, entry.metadata, side);
|
|
1393
|
+
if (!record)
|
|
1394
|
+
throw new Error(`The package ${name} does not provide a ${side} plugin named ${pluginId}.`);
|
|
1395
|
+
if (/^[0-9]/.test(version) && record.version !== version)
|
|
1396
|
+
throw new Error(`The installed package ${name} is ${record.version}, expected ${version}.`);
|
|
1397
|
+
if (!process.env.EPANEL_PACKAGE_ROOT &&
|
|
1398
|
+
(0, plugin_manifest_1.discoverPlugins)(path_1.default.join(packageRoot, "plugins"), {
|
|
1399
|
+
entryFields: [],
|
|
1400
|
+
includeDisabled: true
|
|
1401
|
+
}).some((plugin) => plugin.manifest.id === pluginId))
|
|
1402
|
+
throw new Error(`Source plugin ${pluginId} cannot be replaced with an npm dependency.`);
|
|
1403
|
+
const manifest = (0, plugin_manifest_1.readPluginManifest)(record.directory);
|
|
1404
|
+
if (!manifest || manifest.id !== pluginId || manifest.version !== record.version)
|
|
1405
|
+
throw new Error(`The plugin manifest in ${name} does not match its package.`);
|
|
1406
|
+
(0, plugin_contract_1.validatePluginCompatibility)(manifest.elements);
|
|
1407
|
+
for (const field of ["backend", "frontend", "panel", "daemon", "main", "entry", "ui"]) {
|
|
1408
|
+
if (typeof manifest[field] === "string" &&
|
|
1409
|
+
!(0, plugin_manifest_1.resolvePluginEntry)(record.directory, manifest, { entryFields: [field] }))
|
|
1410
|
+
throw new Error(`Invalid ${field} entry in ${name}.`);
|
|
1411
|
+
}
|
|
1412
|
+
const others = listNpmPlugins(side, { packageRoot, projectRoot, skipInvalid: true });
|
|
1413
|
+
if (others.some((plugin) => plugin.pluginId === pluginId && plugin.name !== name))
|
|
1414
|
+
throw new Error(`Plugin id ${pluginId} is already provided by another dependency.`);
|
|
1415
|
+
const collision = others.find((plugin) => plugin.pluginId !== pluginId && plugin.folder === record.folder);
|
|
1416
|
+
if (collision)
|
|
1417
|
+
throw new Error(`Plugin asset directory ${record.folder} is already used by ${collision.name}.`);
|
|
1418
|
+
const updated = readProjectManifest(targetRoot);
|
|
1419
|
+
updated.dependencies = { ...dependenciesOf(updated), [name]: record.version };
|
|
1420
|
+
updated.elementspanel = {
|
|
1421
|
+
...objectValue(updated.elementspanel),
|
|
1422
|
+
plugins: { ...mappingsOf(updated), [pluginId]: name }
|
|
1423
|
+
};
|
|
1424
|
+
writeProjectManifest(targetRoot, updated);
|
|
1425
|
+
return record;
|
|
1426
|
+
}
|
|
1427
|
+
catch (error) {
|
|
1428
|
+
try {
|
|
1429
|
+
await runNpm(previous
|
|
1430
|
+
? [
|
|
1431
|
+
"--prefix",
|
|
1432
|
+
targetRoot,
|
|
1433
|
+
"install",
|
|
1434
|
+
`${name}@${String(previousVersion)}`,
|
|
1435
|
+
"--save",
|
|
1436
|
+
"--no-fund",
|
|
1437
|
+
"--no-audit",
|
|
1438
|
+
"--ignore-scripts"
|
|
1439
|
+
]
|
|
1440
|
+
: [
|
|
1441
|
+
"--prefix",
|
|
1442
|
+
targetRoot,
|
|
1443
|
+
"uninstall",
|
|
1444
|
+
name,
|
|
1445
|
+
"--no-fund",
|
|
1446
|
+
"--no-audit",
|
|
1447
|
+
"--ignore-scripts"
|
|
1448
|
+
], targetRoot);
|
|
1449
|
+
}
|
|
1450
|
+
catch (rollback) {
|
|
1451
|
+
throw new Error(`${String(error)}; npm rollback failed: ${String(rollback)}`);
|
|
1452
|
+
}
|
|
1453
|
+
finally {
|
|
1454
|
+
for (const [filename, content] of backups) {
|
|
1455
|
+
if (content === null)
|
|
1456
|
+
await fs_extra_1.default.remove(filename);
|
|
1457
|
+
else
|
|
1458
|
+
await fs_extra_1.default.writeFile(filename, content);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
throw error;
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
/** Remove the dependency from the manifest that owns it, preserving user configuration. */
|
|
1466
|
+
async function uninstallNpmPlugin(pluginId, options = {}) {
|
|
1467
|
+
if (!PLUGIN_ID.test(pluginId))
|
|
1468
|
+
throw new Error(`Invalid plugin id: ${pluginId}`);
|
|
1469
|
+
if (isEssentialNpmPlugin(pluginId))
|
|
1470
|
+
throw new Error(`The essential plugin "${pluginId}" cannot be uninstalled.`);
|
|
1471
|
+
const { packageRoot, projectRoot } = npmPluginRoots(options);
|
|
1472
|
+
return withProjectLock(projectRoot, async () => {
|
|
1473
|
+
const host = readJson(path_1.default.join(packageRoot, "package.json"));
|
|
1474
|
+
const bundledName = mappingsOf(host)[pluginId];
|
|
1475
|
+
const targetRoot = typeof bundledName === "string" && dependenciesOf(host)[bundledName] !== undefined
|
|
1476
|
+
? packageRoot
|
|
1477
|
+
: projectRoot;
|
|
1478
|
+
const manifest = readProjectManifest(targetRoot);
|
|
1479
|
+
const name = mappingsOf(manifest)[pluginId] ??
|
|
1480
|
+
listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId)?.name;
|
|
1481
|
+
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
1482
|
+
return null;
|
|
1483
|
+
const record = listNpmPlugins(undefined, { packageRoot, projectRoot, skipInvalid: true }).find((item) => item.pluginId === pluginId);
|
|
1484
|
+
await runNpm(["--prefix", targetRoot, "uninstall", name, "--no-fund", "--no-audit", "--ignore-scripts"], targetRoot);
|
|
1485
|
+
const updated = readProjectManifest(targetRoot);
|
|
1486
|
+
const plugins = { ...mappingsOf(updated) };
|
|
1487
|
+
delete plugins[pluginId];
|
|
1488
|
+
updated.elementspanel = { ...objectValue(updated.elementspanel), plugins };
|
|
1489
|
+
writeProjectManifest(targetRoot, updated);
|
|
1490
|
+
return record ?? null;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
/***/ },
|
|
1496
|
+
|
|
1497
|
+
/***/ "../common/src/plugin_overrides.ts"
|
|
1498
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1502
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1503
|
+
};
|
|
1504
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1505
|
+
exports.pluginOverridesPath = pluginOverridesPath;
|
|
1506
|
+
exports.readPluginOverrides = readPluginOverrides;
|
|
1507
|
+
exports.validatePluginOverride = validatePluginOverride;
|
|
1508
|
+
exports.applyPluginOverrides = applyPluginOverrides;
|
|
1509
|
+
exports.writePluginOverride = writePluginOverride;
|
|
1510
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
1511
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
1512
|
+
const node_crypto_1 = __webpack_require__("node:crypto");
|
|
1513
|
+
function pluginOverridesPath() {
|
|
1514
|
+
return node_path_1.default.resolve(process.cwd(), "data", "plugin-overrides.json");
|
|
1515
|
+
}
|
|
1516
|
+
/** Package defaults stay immutable; only the user's last layer lives in data. */
|
|
1517
|
+
function readPluginOverrides(filename = pluginOverridesPath()) {
|
|
1518
|
+
let raw;
|
|
1519
|
+
try {
|
|
1520
|
+
raw = node_fs_1.default.readFileSync(filename, "utf8");
|
|
1521
|
+
}
|
|
1522
|
+
catch (error) {
|
|
1523
|
+
if (error.code === "ENOENT")
|
|
1524
|
+
return { version: 1, plugins: {} };
|
|
1525
|
+
throw error;
|
|
1526
|
+
}
|
|
1527
|
+
const data = JSON.parse(raw);
|
|
1528
|
+
if (data?.version !== 1 ||
|
|
1529
|
+
!data.plugins ||
|
|
1530
|
+
typeof data.plugins !== "object" ||
|
|
1531
|
+
Array.isArray(data.plugins))
|
|
1532
|
+
throw new Error("Invalid plugin-overrides.json");
|
|
1533
|
+
for (const entry of Object.values(data.plugins))
|
|
1534
|
+
validatePluginOverride(entry);
|
|
1535
|
+
return data;
|
|
1536
|
+
}
|
|
1537
|
+
function validatePluginOverride(value) {
|
|
1538
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1539
|
+
throw new Error("Invalid plugin override.");
|
|
1540
|
+
const entry = value;
|
|
1541
|
+
if (entry.enabled !== undefined && typeof entry.enabled !== "boolean")
|
|
1542
|
+
throw new Error("Invalid plugin enablement.");
|
|
1543
|
+
if (entry.config !== undefined &&
|
|
1544
|
+
(!entry.config || typeof entry.config !== "object" || Array.isArray(entry.config)))
|
|
1545
|
+
throw new Error("Plugin config must be an object.");
|
|
1546
|
+
if (Object.keys(value).some((key) => !["enabled", "config"].includes(key)))
|
|
1547
|
+
throw new Error("Unknown plugin override field.");
|
|
1548
|
+
}
|
|
1549
|
+
function applyPluginOverrides(plugins, filename = pluginOverridesPath()) {
|
|
1550
|
+
const overrides = readPluginOverrides(filename).plugins;
|
|
1551
|
+
return plugins.map((plugin) => ({
|
|
1552
|
+
...plugin,
|
|
1553
|
+
manifest: {
|
|
1554
|
+
...plugin.manifest,
|
|
1555
|
+
...(Object.prototype.hasOwnProperty.call(overrides, plugin.manifest.id)
|
|
1556
|
+
? overrides[plugin.manifest.id]
|
|
1557
|
+
: {})
|
|
1558
|
+
}
|
|
1559
|
+
}));
|
|
1560
|
+
}
|
|
1561
|
+
/** Call inside the host's change queue; rename keeps readers off partial JSON. */
|
|
1562
|
+
function writePluginOverride(id, patch, filename = pluginOverridesPath()) {
|
|
1563
|
+
validatePluginOverride(patch);
|
|
1564
|
+
if (!id || ["__proto__", "constructor", "prototype"].includes(id))
|
|
1565
|
+
throw new Error("Invalid plugin id.");
|
|
1566
|
+
const data = readPluginOverrides(filename);
|
|
1567
|
+
data.plugins[id] = {
|
|
1568
|
+
...(Object.prototype.hasOwnProperty.call(data.plugins, id) ? data.plugins[id] : {}),
|
|
1569
|
+
...patch
|
|
1570
|
+
};
|
|
1571
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filename), { recursive: true });
|
|
1572
|
+
const temporary = `${filename}.${(0, node_crypto_1.randomBytes)(8).toString("hex")}.tmp`;
|
|
1573
|
+
try {
|
|
1574
|
+
node_fs_1.default.writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
1575
|
+
node_fs_1.default.renameSync(temporary, filename);
|
|
1576
|
+
}
|
|
1577
|
+
finally {
|
|
1578
|
+
node_fs_1.default.rmSync(temporary, { force: true });
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
|
|
1583
|
+
/***/ },
|
|
1584
|
+
|
|
1585
|
+
/***/ "../common/src/plugin_revision.ts"
|
|
1586
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1590
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1591
|
+
};
|
|
1592
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1593
|
+
exports.pluginDirectoryRevision = pluginDirectoryRevision;
|
|
1594
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
1595
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
1596
|
+
const node_crypto_1 = __webpack_require__("node:crypto");
|
|
1597
|
+
const cache = new Map();
|
|
1598
|
+
/** One revision addresses the complete module graph, including CSS and nested chunks. */
|
|
1599
|
+
function pluginDirectoryRevision(directory) {
|
|
1600
|
+
const files = [];
|
|
1601
|
+
const walk = (root) => {
|
|
1602
|
+
if (!node_fs_1.default.existsSync(root))
|
|
1603
|
+
return;
|
|
1604
|
+
for (const item of node_fs_1.default
|
|
1605
|
+
.readdirSync(root, { withFileTypes: true })
|
|
1606
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1607
|
+
const absolute = node_path_1.default.join(root, item.name);
|
|
1608
|
+
if (item.isDirectory())
|
|
1609
|
+
walk(absolute);
|
|
1610
|
+
else if (item.isFile())
|
|
1611
|
+
files.push({
|
|
1612
|
+
relative: node_path_1.default.relative(directory, absolute),
|
|
1613
|
+
absolute,
|
|
1614
|
+
stat: node_fs_1.default.statSync(absolute)
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
walk(directory);
|
|
1619
|
+
const signature = JSON.stringify(files.map(({ relative, stat }) => [relative, stat.size, stat.mtimeMs, stat.ctimeMs]));
|
|
1620
|
+
const previous = cache.get(directory);
|
|
1621
|
+
if (previous?.signature === signature)
|
|
1622
|
+
return previous.revision;
|
|
1623
|
+
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
1624
|
+
for (const file of files)
|
|
1625
|
+
hash
|
|
1626
|
+
.update(file.relative)
|
|
1627
|
+
.update("\0")
|
|
1628
|
+
.update(new Uint8Array(node_fs_1.default.readFileSync(file.absolute)))
|
|
1629
|
+
.update("\0");
|
|
1630
|
+
const revision = hash.digest("hex").slice(0, 24);
|
|
1631
|
+
if (cache.size > 1024)
|
|
1632
|
+
cache.clear();
|
|
1633
|
+
cache.set(directory, { signature, revision });
|
|
1634
|
+
return revision;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
|
|
1638
|
+
/***/ },
|
|
1639
|
+
|
|
1640
|
+
/***/ "../common/src/plugin_watch.ts"
|
|
1641
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1645
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1646
|
+
};
|
|
1647
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1648
|
+
exports.watchNpmPlugins = watchNpmPlugins;
|
|
1649
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
1650
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
1651
|
+
const plugin_npm_1 = __webpack_require__("../common/src/plugin_npm.ts");
|
|
1652
|
+
/** Poll package boundaries, allowing npm to finish replacing files before reconciliation. */
|
|
1653
|
+
function watchNpmPlugins(ctx, side, reload, changed) {
|
|
1654
|
+
const stamp = () => {
|
|
1655
|
+
const { packageRoot, projectRoot } = (0, plugin_npm_1.npmPluginRoots)();
|
|
1656
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(packageRoot, "package.json")))
|
|
1657
|
+
return "";
|
|
1658
|
+
const files = [];
|
|
1659
|
+
for (const root of [packageRoot, projectRoot])
|
|
1660
|
+
for (const name of [
|
|
1661
|
+
"package.json",
|
|
1662
|
+
"package-lock.json",
|
|
1663
|
+
"npm-shrinkwrap.json",
|
|
1664
|
+
"node_modules/.package-lock.json"
|
|
1665
|
+
])
|
|
1666
|
+
files.push(node_path_1.default.join(root, name));
|
|
1667
|
+
for (const plugin of (0, plugin_npm_1.listNpmPlugins)(side, { skipInvalid: true }))
|
|
1668
|
+
files.push(node_path_1.default.join(plugin.packageRoot, "package.json"), node_path_1.default.join(plugin.directory, "plugin.json"));
|
|
1669
|
+
return JSON.stringify(files.map((filename) => {
|
|
1670
|
+
try {
|
|
1671
|
+
const stat = node_fs_1.default.statSync(filename);
|
|
1672
|
+
return [filename, stat.size, stat.mtimeMs, stat.ctimeMs];
|
|
1673
|
+
}
|
|
1674
|
+
catch {
|
|
1675
|
+
return [filename, null];
|
|
1676
|
+
}
|
|
1677
|
+
}));
|
|
1678
|
+
};
|
|
1679
|
+
let applied = stamp();
|
|
1680
|
+
let candidate = applied;
|
|
1681
|
+
let busy = false;
|
|
1682
|
+
ctx.setInterval(() => {
|
|
1683
|
+
if (busy || (0, plugin_npm_1.npmPluginOperationPending)())
|
|
1684
|
+
return;
|
|
1685
|
+
try {
|
|
1686
|
+
const next = stamp();
|
|
1687
|
+
if (next === applied) {
|
|
1688
|
+
candidate = next;
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
if (next !== candidate) {
|
|
1692
|
+
candidate = next;
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
busy = true;
|
|
1696
|
+
void reload()
|
|
1697
|
+
.then(() => {
|
|
1698
|
+
applied = next;
|
|
1699
|
+
candidate = next;
|
|
1700
|
+
changed?.();
|
|
1701
|
+
})
|
|
1702
|
+
.catch((error) => ctx.logger.warn("Failed to synchronize npm plugins", error))
|
|
1703
|
+
.finally(() => {
|
|
1704
|
+
busy = false;
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
catch (error) {
|
|
1708
|
+
ctx.logger.warn("Failed to inspect npm plugins", error);
|
|
1709
|
+
}
|
|
1710
|
+
}, 2000);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
/***/ },
|
|
1715
|
+
|
|
1716
|
+
/***/ "../common/src/process_tools.ts"
|
|
1717
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1721
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1722
|
+
};
|
|
1723
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1724
|
+
exports.ProcessWrapper = exports.StartError = void 0;
|
|
1725
|
+
exports.killProcess = killProcess;
|
|
1726
|
+
const child_process_1 = __webpack_require__("child_process");
|
|
1727
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
1728
|
+
const child_process_2 = __importDefault(__webpack_require__("child_process"));
|
|
1729
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
1730
|
+
const events_1 = __importDefault(__webpack_require__("events"));
|
|
1731
|
+
const iconv_lite_1 = __importDefault(__webpack_require__("iconv-lite"));
|
|
1732
|
+
const process_1 = __importDefault(__webpack_require__("process"));
|
|
1733
|
+
class StartError extends Error {
|
|
1734
|
+
}
|
|
1735
|
+
exports.StartError = StartError;
|
|
1736
|
+
class ProcessWrapper extends events_1.default {
|
|
1737
|
+
file;
|
|
1738
|
+
args;
|
|
1739
|
+
cwd;
|
|
1740
|
+
timeout;
|
|
1741
|
+
code;
|
|
1742
|
+
option;
|
|
1743
|
+
process;
|
|
1744
|
+
pid;
|
|
1745
|
+
lastExitCode;
|
|
1746
|
+
completed = false;
|
|
1747
|
+
errMsg = {
|
|
1748
|
+
timeoutErr: "task timeout!",
|
|
1749
|
+
exitErr: "task error!",
|
|
1750
|
+
startErr: "task start error!"
|
|
1751
|
+
};
|
|
1752
|
+
constructor(file, args, cwd, timeout = 0, code = "utf-8", option = {}) {
|
|
1753
|
+
super();
|
|
1754
|
+
this.file = file;
|
|
1755
|
+
this.args = args;
|
|
1756
|
+
this.cwd = cwd;
|
|
1757
|
+
this.timeout = timeout;
|
|
1758
|
+
this.code = code;
|
|
1759
|
+
this.option = option;
|
|
1760
|
+
}
|
|
1761
|
+
setErrMsg(errMsg) {
|
|
1762
|
+
this.errMsg = errMsg;
|
|
1763
|
+
}
|
|
1764
|
+
start() {
|
|
1765
|
+
if (this.process)
|
|
1766
|
+
return Promise.reject(new Error("The process is already running"));
|
|
1767
|
+
return new Promise((resolve, reject) => {
|
|
1768
|
+
const stdoutDecoder = iconv_lite_1.default.decodeStream(this.code);
|
|
1769
|
+
const stderrDecoder = iconv_lite_1.default.decodeStream(this.code);
|
|
1770
|
+
let timeTask;
|
|
1771
|
+
let settled = false;
|
|
1772
|
+
let spawned = false;
|
|
1773
|
+
const settle = (error) => {
|
|
1774
|
+
if (settled)
|
|
1775
|
+
return;
|
|
1776
|
+
settled = true;
|
|
1777
|
+
if (timeTask)
|
|
1778
|
+
clearTimeout(timeTask);
|
|
1779
|
+
if (error)
|
|
1780
|
+
reject(error);
|
|
1781
|
+
else
|
|
1782
|
+
resolve(true);
|
|
1783
|
+
};
|
|
1784
|
+
const subProcess = child_process_2.default.spawn(this.file, this.args, {
|
|
1785
|
+
stdio: "pipe",
|
|
1786
|
+
windowsHide: true,
|
|
1787
|
+
cwd: path_1.default.normalize(this.cwd),
|
|
1788
|
+
...this.option
|
|
1789
|
+
});
|
|
1790
|
+
this.process = subProcess;
|
|
1791
|
+
this.pid = subProcess.pid;
|
|
1792
|
+
this.lastExitCode = undefined;
|
|
1793
|
+
this.completed = false;
|
|
1794
|
+
const fail = (error) => {
|
|
1795
|
+
if (settled)
|
|
1796
|
+
return;
|
|
1797
|
+
settle(error);
|
|
1798
|
+
if (subProcess.pid && subProcess.exitCode === null && subProcess.signalCode === null) {
|
|
1799
|
+
try {
|
|
1800
|
+
killProcess(subProcess.pid, subProcess);
|
|
1801
|
+
}
|
|
1802
|
+
catch (error) {
|
|
1803
|
+
console.error("[ProcessWrapper kill error]", error);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
subProcess.once("spawn", () => {
|
|
1808
|
+
spawned = true;
|
|
1809
|
+
this.emit("start", subProcess.pid);
|
|
1810
|
+
});
|
|
1811
|
+
for (const [source, decoder] of [
|
|
1812
|
+
[subProcess.stdout, stdoutDecoder],
|
|
1813
|
+
[subProcess.stderr, stderrDecoder]
|
|
1814
|
+
]) {
|
|
1815
|
+
decoder.on("data", (text) => this.emit("data", text));
|
|
1816
|
+
decoder.on("error", fail);
|
|
1817
|
+
source.on("error", fail);
|
|
1818
|
+
source.pipe(decoder);
|
|
1819
|
+
}
|
|
1820
|
+
subProcess.stdin.on("error", fail);
|
|
1821
|
+
subProcess.once("error", (error) => {
|
|
1822
|
+
fail(new Error(`${spawned ? this.errMsg.exitErr : this.errMsg.startErr} ${error.message}`));
|
|
1823
|
+
});
|
|
1824
|
+
subProcess.once("exit", (code) => {
|
|
1825
|
+
this.lastExitCode = code;
|
|
1826
|
+
this.emit("exit", code);
|
|
1827
|
+
});
|
|
1828
|
+
// `close` follows the final stdout/stderr data and also follows a failed spawn.
|
|
1829
|
+
subProcess.once("close", (code) => {
|
|
1830
|
+
this.lastExitCode = code;
|
|
1831
|
+
this.completed = true;
|
|
1832
|
+
this.process = undefined;
|
|
1833
|
+
settle(code === 0 ? undefined : new Error(this.errMsg.exitErr));
|
|
1834
|
+
});
|
|
1835
|
+
if (this.timeout > 0) {
|
|
1836
|
+
timeTask = setTimeout(() => {
|
|
1837
|
+
fail(new Error(this.errMsg.timeoutErr));
|
|
1838
|
+
}, 1000 * this.timeout);
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
getPid() {
|
|
1843
|
+
return this.process?.pid;
|
|
1844
|
+
}
|
|
1845
|
+
write(data) {
|
|
1846
|
+
return this.process?.stdin?.write(iconv_lite_1.default.encode(data, this.code));
|
|
1847
|
+
}
|
|
1848
|
+
kill() {
|
|
1849
|
+
if (this.process?.pid)
|
|
1850
|
+
killProcess(this.process?.pid, this.process);
|
|
1851
|
+
}
|
|
1852
|
+
status() {
|
|
1853
|
+
return this.completed;
|
|
1854
|
+
}
|
|
1855
|
+
exitCode() {
|
|
1856
|
+
return this.process?.exitCode ?? this.lastExitCode;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
exports.ProcessWrapper = ProcessWrapper;
|
|
1860
|
+
function killProcess(pid, process, signal) {
|
|
1861
|
+
const numericPid = Number(pid);
|
|
1862
|
+
if (!Number.isSafeInteger(numericPid) || numericPid <= 0) {
|
|
1863
|
+
throw new Error("Invalid process ID");
|
|
1864
|
+
}
|
|
1865
|
+
try {
|
|
1866
|
+
if (os_1.default.platform() === "win32") {
|
|
1867
|
+
(0, child_process_1.execFileSync)("taskkill", ["/PID", String(numericPid), "/T", "/F"], { windowsHide: true });
|
|
1868
|
+
return true;
|
|
1869
|
+
}
|
|
1870
|
+
return process_1.default.kill(numericPid, signal || "SIGKILL");
|
|
1871
|
+
}
|
|
1872
|
+
catch (err) {
|
|
1873
|
+
return signal ? process.kill(signal) : process.kill("SIGKILL");
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
/***/ },
|
|
1879
|
+
|
|
1880
|
+
/***/ "../common/src/query_wrapper.ts"
|
|
1881
|
+
(__unused_webpack_module, exports) {
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1885
|
+
exports.QueryWrapper = exports.LocalFileSource = exports.MySqlSource = exports.QueryMapWrapper = void 0;
|
|
1886
|
+
function paginate(data, page, pageSize) {
|
|
1887
|
+
if (!Number.isSafeInteger(page) || page < 1 || !Number.isSafeInteger(pageSize) || pageSize < 1) {
|
|
1888
|
+
throw new RangeError("page and pageSize must be positive integers");
|
|
1889
|
+
}
|
|
1890
|
+
const start = (page - 1) * pageSize;
|
|
1891
|
+
return {
|
|
1892
|
+
page,
|
|
1893
|
+
pageSize,
|
|
1894
|
+
maxPage: Math.ceil(data.length / pageSize),
|
|
1895
|
+
total: data.length,
|
|
1896
|
+
data: data.slice(start, start + pageSize)
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
// Provide the MAP query interface used by the routing layer
|
|
1900
|
+
class QueryMapWrapper {
|
|
1901
|
+
map;
|
|
1902
|
+
constructor(map) {
|
|
1903
|
+
this.map = map;
|
|
1904
|
+
}
|
|
1905
|
+
select(condition) {
|
|
1906
|
+
const result = [];
|
|
1907
|
+
this.map.forEach((v) => {
|
|
1908
|
+
if (condition(v))
|
|
1909
|
+
result.push(v);
|
|
1910
|
+
});
|
|
1911
|
+
return result;
|
|
1912
|
+
}
|
|
1913
|
+
page(data, page = 1, pageSize = 10) {
|
|
1914
|
+
return paginate(data, page, pageSize);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
exports.QueryMapWrapper = QueryMapWrapper;
|
|
1918
|
+
// MYSQL data source
|
|
1919
|
+
class MySqlSource {
|
|
1920
|
+
data;
|
|
1921
|
+
constructor(data) {
|
|
1922
|
+
this.data = data;
|
|
1923
|
+
}
|
|
1924
|
+
selectPage(condition, page, pageSize) {
|
|
1925
|
+
return {
|
|
1926
|
+
page,
|
|
1927
|
+
pageSize,
|
|
1928
|
+
maxPage: 0,
|
|
1929
|
+
total: 0,
|
|
1930
|
+
data: []
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
select(condition) {
|
|
1934
|
+
return [];
|
|
1935
|
+
}
|
|
1936
|
+
update(condition, data) { }
|
|
1937
|
+
delete(condition) { }
|
|
1938
|
+
insert(data) { }
|
|
1939
|
+
}
|
|
1940
|
+
exports.MySqlSource = MySqlSource;
|
|
1941
|
+
// local file data source (embedded microdatabase)
|
|
1942
|
+
class LocalFileSource {
|
|
1943
|
+
data;
|
|
1944
|
+
constructor(data) {
|
|
1945
|
+
this.data = data;
|
|
1946
|
+
}
|
|
1947
|
+
selectPage(condition, page = 1, pageSize = 10) {
|
|
1948
|
+
return this.page(this.select(condition), page, pageSize);
|
|
1949
|
+
}
|
|
1950
|
+
page(data, page = 1, pageSize = 10) {
|
|
1951
|
+
return paginate(data, page, pageSize);
|
|
1952
|
+
}
|
|
1953
|
+
select(condition = {}) {
|
|
1954
|
+
const result = [];
|
|
1955
|
+
const entries = Object.entries(condition);
|
|
1956
|
+
this.data.forEach((v) => {
|
|
1957
|
+
for (const [key, targetValue] of entries) {
|
|
1958
|
+
const dataValue = v[key];
|
|
1959
|
+
if (typeof targetValue === "string" &&
|
|
1960
|
+
targetValue.startsWith("%") &&
|
|
1961
|
+
targetValue.endsWith("%")) {
|
|
1962
|
+
if (typeof dataValue !== "string" || !dataValue.includes(targetValue.slice(1, -1)))
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
else if (targetValue !== dataValue) {
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
result.push(v);
|
|
1970
|
+
});
|
|
1971
|
+
return result;
|
|
1972
|
+
}
|
|
1973
|
+
update(condition, data) { }
|
|
1974
|
+
delete(condition) { }
|
|
1975
|
+
insert(data) { }
|
|
1976
|
+
}
|
|
1977
|
+
exports.LocalFileSource = LocalFileSource;
|
|
1978
|
+
// Provide the unified data query interface used by the routing layer
|
|
1979
|
+
class QueryWrapper {
|
|
1980
|
+
dataSource;
|
|
1981
|
+
constructor(dataSource) {
|
|
1982
|
+
this.dataSource = dataSource;
|
|
1983
|
+
}
|
|
1984
|
+
selectPage(condition, page = 1, pageSize = 10) {
|
|
1985
|
+
return this.dataSource.selectPage(condition, page, pageSize);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
exports.QueryWrapper = QueryWrapper;
|
|
1989
|
+
|
|
1990
|
+
|
|
1991
|
+
/***/ },
|
|
1992
|
+
|
|
1993
|
+
/***/ "../common/src/runtime_paths.ts"
|
|
1994
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
1998
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1999
|
+
};
|
|
2000
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2001
|
+
exports.isPackagedRuntime = isPackagedRuntime;
|
|
2002
|
+
exports.packagePath = packagePath;
|
|
2003
|
+
exports.packagePluginRoots = packagePluginRoots;
|
|
2004
|
+
exports.isPackagePlugin = isPackagePlugin;
|
|
2005
|
+
exports.bundledToolPath = bundledToolPath;
|
|
2006
|
+
exports.ensureExecutable = ensureExecutable;
|
|
2007
|
+
const node_fs_1 = __importDefault(__webpack_require__("node:fs"));
|
|
2008
|
+
const node_os_1 = __importDefault(__webpack_require__("node:os"));
|
|
2009
|
+
const node_path_1 = __importDefault(__webpack_require__("node:path"));
|
|
2010
|
+
/** Set by the npm worker before any application module is loaded. */
|
|
2011
|
+
function isPackagedRuntime() {
|
|
2012
|
+
return Boolean(process.env.EPANEL_PACKAGE_ROOT);
|
|
2013
|
+
}
|
|
2014
|
+
/** Code and bundled assets belong to the package; cwd always belongs to the user. */
|
|
2015
|
+
function packagePath(...parts) {
|
|
2016
|
+
return node_path_1.default.resolve(process.env.EPANEL_PACKAGE_ROOT || process.cwd(), ...parts);
|
|
2017
|
+
}
|
|
2018
|
+
function npmPluginRoots(side) {
|
|
2019
|
+
// The same resolver is used by the CLI before the bundled application starts.
|
|
2020
|
+
// A development checkout keeps it in the repository, beside the side packages,
|
|
2021
|
+
// so project-installed npm plugins work there as well.
|
|
2022
|
+
const runtimeRequire = eval("require");
|
|
2023
|
+
const resolver = isPackagedRuntime()
|
|
2024
|
+
? packagePath("runtime/npm-plugins.cjs")
|
|
2025
|
+
: node_path_1.default.resolve(packagePath(), "..", "scripts", "runtime", "npm-plugins.cjs");
|
|
2026
|
+
// A development process may run from a fixture directory without a runtime
|
|
2027
|
+
// package; there is nothing to resolve in that case.
|
|
2028
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(packagePath(), "package.json")))
|
|
2029
|
+
return [];
|
|
2030
|
+
if (!isPackagedRuntime() && !node_fs_1.default.existsSync(resolver))
|
|
2031
|
+
return [];
|
|
2032
|
+
const { resolveNpmPlugins } = runtimeRequire(resolver);
|
|
2033
|
+
return resolveNpmPlugins(packagePath(), side, { skipInvalid: true }).map((record) => ({
|
|
2034
|
+
directory: record.directory,
|
|
2035
|
+
packageRoot: record.packageRoot,
|
|
2036
|
+
packageName: record.name,
|
|
2037
|
+
npmSource: record.source,
|
|
2038
|
+
...(record.folder ? { folder: record.folder } : {}),
|
|
2039
|
+
// Built-in packages are part of the runtime's default configuration. A
|
|
2040
|
+
// project dependency is installed but remains dormant until the user
|
|
2041
|
+
// enables it through the plugin configuration service.
|
|
2042
|
+
defaultEnabled: record.defaultEnabled !== false
|
|
2043
|
+
}));
|
|
2044
|
+
}
|
|
2045
|
+
function packagePluginRoots(side) {
|
|
2046
|
+
const roots = isPackagedRuntime()
|
|
2047
|
+
? []
|
|
2048
|
+
: [{ directory: packagePath("plugins"), defaultEnabled: true }];
|
|
2049
|
+
// Project-installed npm plugins load in development too: they are dependencies
|
|
2050
|
+
// of the development project, not of the published runtime package.
|
|
2051
|
+
roots.push(...npmPluginRoots(side));
|
|
2052
|
+
return roots;
|
|
2053
|
+
}
|
|
2054
|
+
function isPackagePlugin(directory) {
|
|
2055
|
+
return npmPluginRoots().some((root) => {
|
|
2056
|
+
const relative = node_path_1.default.relative(root.directory, node_path_1.default.resolve(directory));
|
|
2057
|
+
return (relative === "" ||
|
|
2058
|
+
(!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative)));
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
function bundledToolPath(name) {
|
|
2062
|
+
const platform = node_os_1.default.platform();
|
|
2063
|
+
const arch = node_os_1.default.arch();
|
|
2064
|
+
const suffix = platform === "win32" ? ".exe" : "";
|
|
2065
|
+
const filename = `${name}_${platform}_${arch}${suffix}`;
|
|
2066
|
+
// Published daemon packages download native tools into the installation's
|
|
2067
|
+
// writable lib directory on first start. Development checkouts use the same
|
|
2068
|
+
// repository-local sibling of data and logs.
|
|
2069
|
+
const root = isPackagedRuntime() ? node_path_1.default.resolve(process.cwd(), "lib") : packagePath("lib");
|
|
2070
|
+
const primary = node_path_1.default.join(root, filename);
|
|
2071
|
+
// Zip-Tools names its Intel macOS binary "amd64", while Node reports "x64".
|
|
2072
|
+
if (!node_fs_1.default.existsSync(primary) && name === "file_zip" && platform === "darwin" && arch === "x64") {
|
|
2073
|
+
const alternate = node_path_1.default.join(root, "file_zip_darwin_amd64");
|
|
2074
|
+
if (node_fs_1.default.existsSync(alternate))
|
|
2075
|
+
return alternate;
|
|
2076
|
+
}
|
|
2077
|
+
return primary;
|
|
2078
|
+
}
|
|
2079
|
+
/** Published binaries already carry executable permissions, including in read-only installs. */
|
|
2080
|
+
function ensureExecutable(filename) {
|
|
2081
|
+
if (node_os_1.default.platform() === "win32")
|
|
2082
|
+
return;
|
|
2083
|
+
try {
|
|
2084
|
+
node_fs_1.default.accessSync(filename, node_fs_1.default.constants.X_OK);
|
|
2085
|
+
}
|
|
2086
|
+
catch {
|
|
2087
|
+
node_fs_1.default.chmodSync(filename, 0o755);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
/***/ },
|
|
2093
|
+
|
|
2094
|
+
/***/ "../common/src/string_utils.ts"
|
|
2095
|
+
(__unused_webpack_module, exports) {
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2099
|
+
exports.removeTrail = removeTrail;
|
|
2100
|
+
function removeTrail(origin, trail) {
|
|
2101
|
+
if (origin.endsWith(trail)) {
|
|
2102
|
+
return origin.slice(0, origin.length - trail.length);
|
|
2103
|
+
}
|
|
2104
|
+
else {
|
|
2105
|
+
return origin;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
|
|
2110
|
+
/***/ },
|
|
2111
|
+
|
|
2112
|
+
/***/ "../common/src/system_info.ts"
|
|
2113
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2117
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2118
|
+
};
|
|
2119
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2120
|
+
exports.systemInfo = systemInfo;
|
|
2121
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
2122
|
+
const os_utils_1 = __importDefault(__webpack_require__("os-utils"));
|
|
2123
|
+
const fs_1 = __importDefault(__webpack_require__("fs"));
|
|
2124
|
+
// System details are updated every time
|
|
2125
|
+
const info = {
|
|
2126
|
+
type: os_1.default.type(),
|
|
2127
|
+
hostname: os_1.default.hostname(),
|
|
2128
|
+
platform: os_1.default.platform(),
|
|
2129
|
+
release: os_1.default.release(),
|
|
2130
|
+
uptime: os_1.default.uptime(),
|
|
2131
|
+
cwd: process.cwd(),
|
|
2132
|
+
loadavg: os_1.default.loadavg(),
|
|
2133
|
+
freemem: 0,
|
|
2134
|
+
cpuUsage: 0,
|
|
2135
|
+
memUsage: 0,
|
|
2136
|
+
totalmem: 0,
|
|
2137
|
+
processCpu: 0,
|
|
2138
|
+
processMem: 0
|
|
2139
|
+
};
|
|
2140
|
+
let previousCpu = process.cpuUsage();
|
|
2141
|
+
let previousTime = process.hrtime.bigint();
|
|
2142
|
+
// periodically refresh the cache
|
|
2143
|
+
function refresh() {
|
|
2144
|
+
const now = process.hrtime.bigint();
|
|
2145
|
+
const cpu = process.cpuUsage();
|
|
2146
|
+
const elapsed = Number(now - previousTime) / 1000;
|
|
2147
|
+
info.processCpu =
|
|
2148
|
+
elapsed > 0 ? (cpu.user - previousCpu.user + cpu.system - previousCpu.system) / elapsed : 0;
|
|
2149
|
+
previousCpu = cpu;
|
|
2150
|
+
previousTime = now;
|
|
2151
|
+
info.uptime = os_1.default.uptime();
|
|
2152
|
+
info.loadavg = os_1.default.loadavg();
|
|
2153
|
+
info.processMem = process.memoryUsage().rss;
|
|
2154
|
+
if (os_1.default.platform() === "linux") {
|
|
2155
|
+
return setLinuxSystemInfo();
|
|
2156
|
+
}
|
|
2157
|
+
return otherSystemInfo();
|
|
2158
|
+
}
|
|
2159
|
+
refresh();
|
|
2160
|
+
setInterval(refresh, 3000).unref();
|
|
2161
|
+
function otherSystemInfo() {
|
|
2162
|
+
info.freemem = os_1.default.freemem();
|
|
2163
|
+
info.totalmem = os_1.default.totalmem();
|
|
2164
|
+
info.memUsage = (os_1.default.totalmem() - os_1.default.freemem()) / os_1.default.totalmem();
|
|
2165
|
+
os_utils_1.default.cpuUsage((p) => (info.cpuUsage = p));
|
|
2166
|
+
}
|
|
2167
|
+
function setLinuxSystemInfo() {
|
|
2168
|
+
try {
|
|
2169
|
+
// read memory data based on /proc/meminfo
|
|
2170
|
+
const data = fs_1.default.readFileSync("/proc/meminfo", { encoding: "utf-8" });
|
|
2171
|
+
const list = data.split("\n");
|
|
2172
|
+
const infoTable = {};
|
|
2173
|
+
list.forEach((line) => {
|
|
2174
|
+
const kv = line.split(":");
|
|
2175
|
+
if (kv.length === 2) {
|
|
2176
|
+
const k = kv[0].replace(/ /gim, "").replace(/\t/gim, "").trim().toLowerCase();
|
|
2177
|
+
let v = kv[1].replace(/ /gim, "").replace(/\t/gim, "").trim().toLowerCase();
|
|
2178
|
+
v = v.replace(/kb/gim, "").replace(/mb/gim, "").replace(/gb/gim, "");
|
|
2179
|
+
let vNumber = parseInt(v);
|
|
2180
|
+
if (isNaN(vNumber))
|
|
2181
|
+
vNumber = 0;
|
|
2182
|
+
infoTable[k] = vNumber;
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
const memAvailable = infoTable["memavailable"] ?? infoTable["memfree"];
|
|
2186
|
+
const memTotal = infoTable["memtotal"];
|
|
2187
|
+
info.freemem = memAvailable * 1024;
|
|
2188
|
+
info.totalmem = memTotal * 1024;
|
|
2189
|
+
info.memUsage = (info.totalmem - info.freemem) / info.totalmem;
|
|
2190
|
+
os_utils_1.default.cpuUsage((p) => (info.cpuUsage = p));
|
|
2191
|
+
}
|
|
2192
|
+
catch (error) {
|
|
2193
|
+
// If the reading is wrong, the default general reading method is automatically used
|
|
2194
|
+
otherSystemInfo();
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
function systemInfo() {
|
|
2198
|
+
return info;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
|
|
2202
|
+
/***/ },
|
|
2203
|
+
|
|
2204
|
+
/***/ "../common/src/system_storage.ts"
|
|
2205
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2209
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2210
|
+
};
|
|
2211
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2212
|
+
const crypto_1 = __webpack_require__("crypto");
|
|
2213
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
2214
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
2215
|
+
class StorageSubsystem {
|
|
2216
|
+
static DATA_PATH = path_1.default.resolve(process.cwd(), "data");
|
|
2217
|
+
static INDEX_PATH = path_1.default.join(StorageSubsystem.DATA_PATH, "Index");
|
|
2218
|
+
resolvePath(name) {
|
|
2219
|
+
if (typeof name !== "string" ||
|
|
2220
|
+
!name ||
|
|
2221
|
+
/[\0:]/.test(name) ||
|
|
2222
|
+
path_1.default.isAbsolute(name) ||
|
|
2223
|
+
path_1.default.win32.isAbsolute(name)) {
|
|
2224
|
+
throw new Error(`Invalid storage path: ${name}`);
|
|
2225
|
+
}
|
|
2226
|
+
const target = path_1.default.resolve(StorageSubsystem.DATA_PATH, name);
|
|
2227
|
+
const relative = path_1.default.relative(StorageSubsystem.DATA_PATH, target);
|
|
2228
|
+
if (!relative ||
|
|
2229
|
+
relative === ".." ||
|
|
2230
|
+
relative.startsWith(`..${path_1.default.sep}`) ||
|
|
2231
|
+
path_1.default.isAbsolute(relative)) {
|
|
2232
|
+
throw new Error(`Invalid storage path: ${name}`);
|
|
2233
|
+
}
|
|
2234
|
+
const realRelative = path_1.default.relative(this.realPath(StorageSubsystem.DATA_PATH), this.realPath(target));
|
|
2235
|
+
if (realRelative === ".." ||
|
|
2236
|
+
realRelative.startsWith(`..${path_1.default.sep}`) ||
|
|
2237
|
+
path_1.default.isAbsolute(realRelative)) {
|
|
2238
|
+
throw new Error(`Invalid storage path: ${name}`);
|
|
2239
|
+
}
|
|
2240
|
+
return target;
|
|
2241
|
+
}
|
|
2242
|
+
realPath(target) {
|
|
2243
|
+
if (fs_extra_1.default.existsSync(target))
|
|
2244
|
+
return fs_extra_1.default.realpathSync(target);
|
|
2245
|
+
const parent = path_1.default.dirname(target);
|
|
2246
|
+
return parent === target ? target : path_1.default.join(this.realPath(parent), path_1.default.basename(target));
|
|
2247
|
+
}
|
|
2248
|
+
entityPath(category, uuid) {
|
|
2249
|
+
if (typeof uuid !== "string" || !uuid || /[\\/\0:]/.test(uuid) || uuid.includes("..")) {
|
|
2250
|
+
throw new Error(`UUID ${uuid} does not conform to specification`);
|
|
2251
|
+
}
|
|
2252
|
+
this.resolvePath(category);
|
|
2253
|
+
return path_1.default.join(category, `${uuid}.json`);
|
|
2254
|
+
}
|
|
2255
|
+
writeFile(name, data) {
|
|
2256
|
+
const targetPath = this.resolvePath(name);
|
|
2257
|
+
fs_extra_1.default.ensureDirSync(path_1.default.dirname(targetPath));
|
|
2258
|
+
const temporaryPath = `${targetPath}.${(0, crypto_1.randomUUID)()}.tmp`;
|
|
2259
|
+
const mode = fs_extra_1.default.existsSync(targetPath) ? fs_extra_1.default.statSync(targetPath).mode : 0o600;
|
|
2260
|
+
try {
|
|
2261
|
+
fs_extra_1.default.writeFileSync(temporaryPath, data, { encoding: "utf8", flag: "wx", mode });
|
|
2262
|
+
fs_extra_1.default.renameSync(temporaryPath, targetPath);
|
|
2263
|
+
}
|
|
2264
|
+
finally {
|
|
2265
|
+
if (fs_extra_1.default.existsSync(temporaryPath))
|
|
2266
|
+
fs_extra_1.default.unlinkSync(temporaryPath);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
readFile(name) {
|
|
2270
|
+
return fs_extra_1.default.readFileSync(this.resolvePath(name), "utf8");
|
|
2271
|
+
}
|
|
2272
|
+
readDir(dirName) {
|
|
2273
|
+
const targetPath = this.resolvePath(dirName);
|
|
2274
|
+
if (!fs_extra_1.default.existsSync(targetPath))
|
|
2275
|
+
return [];
|
|
2276
|
+
return fs_extra_1.default.readdirSync(targetPath).map((name) => path_1.default.join(dirName, name));
|
|
2277
|
+
}
|
|
2278
|
+
deleteFile(name) {
|
|
2279
|
+
fs_extra_1.default.removeSync(this.resolvePath(name));
|
|
2280
|
+
}
|
|
2281
|
+
fileExists(name) {
|
|
2282
|
+
return fs_extra_1.default.existsSync(this.resolvePath(name));
|
|
2283
|
+
}
|
|
2284
|
+
store(category, uuid, object) {
|
|
2285
|
+
this.writeFile(this.entityPath(category, uuid), JSON.stringify(object, null, 4));
|
|
2286
|
+
}
|
|
2287
|
+
// Restore declared fields while keeping defaults for fields absent in older data.
|
|
2288
|
+
defineAttr(target, object) {
|
|
2289
|
+
for (const key of Object.keys(target)) {
|
|
2290
|
+
if (!Object.prototype.hasOwnProperty.call(object, key) || object[key] === undefined)
|
|
2291
|
+
continue;
|
|
2292
|
+
const value = object[key];
|
|
2293
|
+
if (value !== null &&
|
|
2294
|
+
typeof value === "object" &&
|
|
2295
|
+
!Array.isArray(value) &&
|
|
2296
|
+
target[key] !== null &&
|
|
2297
|
+
typeof target[key] === "object" &&
|
|
2298
|
+
!Array.isArray(target[key])) {
|
|
2299
|
+
this.defineAttr(target[key], value);
|
|
2300
|
+
}
|
|
2301
|
+
else {
|
|
2302
|
+
target[key] = value;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
return target;
|
|
2306
|
+
}
|
|
2307
|
+
load(category, classz, uuid) {
|
|
2308
|
+
const file = this.entityPath(category, uuid);
|
|
2309
|
+
if (!this.fileExists(file))
|
|
2310
|
+
return null;
|
|
2311
|
+
const object = JSON.parse(this.readFile(file));
|
|
2312
|
+
if (!object || typeof object !== "object" || Array.isArray(object)) {
|
|
2313
|
+
throw new Error(`Invalid stored entity: ${file}`);
|
|
2314
|
+
}
|
|
2315
|
+
return this.defineAttr(new classz(), object);
|
|
2316
|
+
}
|
|
2317
|
+
list(category) {
|
|
2318
|
+
const directory = this.resolvePath(category);
|
|
2319
|
+
if (!fs_extra_1.default.existsSync(directory))
|
|
2320
|
+
return [];
|
|
2321
|
+
return fs_extra_1.default
|
|
2322
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
2323
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
2324
|
+
.map((entry) => entry.name.slice(0, -5));
|
|
2325
|
+
}
|
|
2326
|
+
delete(category, uuid) {
|
|
2327
|
+
this.deleteFile(this.entityPath(category, uuid));
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
exports["default"] = StorageSubsystem;
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
/***/ },
|
|
2334
|
+
|
|
2335
|
+
/***/ "../common/src/typecheck.ts"
|
|
2336
|
+
(__unused_webpack_module, exports) {
|
|
2337
|
+
|
|
2338
|
+
|
|
2339
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2340
|
+
exports.configureEntityParams = configureEntityParams;
|
|
2341
|
+
exports.toText = toText;
|
|
2342
|
+
exports.toNumber = toNumber;
|
|
2343
|
+
exports.toBoolean = toBoolean;
|
|
2344
|
+
exports.isEmpty = isEmpty;
|
|
2345
|
+
exports.supposeValue = supposeValue;
|
|
2346
|
+
function configureEntityParams(self, args, key, typeFn) {
|
|
2347
|
+
const selfDefaultValue = self[key] ?? null;
|
|
2348
|
+
const v = args[key] != null ? args[key] : selfDefaultValue;
|
|
2349
|
+
if (typeFn === Number) {
|
|
2350
|
+
if (v === "" || v == null) {
|
|
2351
|
+
self[key] = null;
|
|
2352
|
+
}
|
|
2353
|
+
else {
|
|
2354
|
+
if (isNaN(Number(v)))
|
|
2355
|
+
throw new Error(`ConfigureEntityParams Error: Expected type to be Number, but got ${typeof v}`);
|
|
2356
|
+
self[key] = Number(v);
|
|
2357
|
+
}
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
if (typeFn === String) {
|
|
2361
|
+
if (v == null) {
|
|
2362
|
+
self[key] = null;
|
|
2363
|
+
}
|
|
2364
|
+
else {
|
|
2365
|
+
self[key] = String(v);
|
|
2366
|
+
}
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
if (typeFn === Boolean) {
|
|
2370
|
+
if (v == null) {
|
|
2371
|
+
self[key] = false;
|
|
2372
|
+
}
|
|
2373
|
+
else {
|
|
2374
|
+
self[key] = Boolean(v);
|
|
2375
|
+
}
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
if (typeFn) {
|
|
2379
|
+
self[key] = typeFn(v);
|
|
2380
|
+
}
|
|
2381
|
+
else {
|
|
2382
|
+
self[key] = v;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
function toText(v) {
|
|
2386
|
+
if (isEmpty(v))
|
|
2387
|
+
return null;
|
|
2388
|
+
return String(v);
|
|
2389
|
+
}
|
|
2390
|
+
function toNumber(v) {
|
|
2391
|
+
if (isEmpty(v))
|
|
2392
|
+
return null;
|
|
2393
|
+
if (isNaN(Number(v)))
|
|
2394
|
+
return null;
|
|
2395
|
+
return Number(v);
|
|
2396
|
+
}
|
|
2397
|
+
function toBoolean(v) {
|
|
2398
|
+
if (isEmpty(v))
|
|
2399
|
+
return null;
|
|
2400
|
+
return Boolean(v);
|
|
2401
|
+
}
|
|
2402
|
+
function isEmpty(v) {
|
|
2403
|
+
return v === null || v === undefined;
|
|
2404
|
+
}
|
|
2405
|
+
function supposeValue(v, def) {
|
|
2406
|
+
if (isEmpty(v))
|
|
2407
|
+
return def;
|
|
2408
|
+
return v;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
|
|
2412
|
+
/***/ },
|
|
2413
|
+
|
|
2414
|
+
/***/ "./plugins/file/src/backend/file_router.ts"
|
|
2415
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
2416
|
+
|
|
2417
|
+
|
|
2418
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2419
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2420
|
+
};
|
|
2421
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2422
|
+
exports.registerFileEvents = registerFileEvents;
|
|
2423
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
2424
|
+
const file_service_1 = __webpack_require__("./plugins/file/src/backend/file_service.ts");
|
|
2425
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
2426
|
+
const upload_manager_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/upload_manager.ts"));
|
|
2427
|
+
const url_1 = __webpack_require__("./plugins/file/src/backend/url.ts");
|
|
2428
|
+
/**
|
|
2429
|
+
* Every `file/*` protocol event, plus the middleware that gates them.
|
|
2430
|
+
*
|
|
2431
|
+
* Registered from `apply()`, so all of it — the middleware included — leaves with
|
|
2432
|
+
* the plugin. The middleware runs after `plugins/auth`'s, which loads first.
|
|
2433
|
+
*/
|
|
2434
|
+
function registerFileEvents() {
|
|
2435
|
+
// Some routers operate router authentication middleware
|
|
2436
|
+
(0, runtime_1.protocol)().use((event, ctx, data, next) => {
|
|
2437
|
+
if (event.startsWith("file/")) {
|
|
2438
|
+
const instanceUuid = data.instanceUuid;
|
|
2439
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(instanceUuid);
|
|
2440
|
+
if (!instance) {
|
|
2441
|
+
return (0, runtime_1.protocol)().error(ctx, event, {
|
|
2442
|
+
instanceUuid: instanceUuid,
|
|
2443
|
+
err: (0, runtime_1.$t)("TXT_CODE_file_router.instanceNotExist", { instanceUuid: instanceUuid })
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
if ([(0, runtime_1.instances)().Instance.STATUS_BUSY, (0, runtime_1.instances)().Instance.STATUS_STARTING].includes(instance.status()) &&
|
|
2447
|
+
!["file/list", "file/status"].includes(event)) {
|
|
2448
|
+
return (0, runtime_1.protocol)().error(ctx, event, {
|
|
2449
|
+
instanceUuid: instanceUuid,
|
|
2450
|
+
err: (0, runtime_1.$t)("TXT_CODE_bbedcf29")
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
next();
|
|
2455
|
+
});
|
|
2456
|
+
// List the files in the specified instance working directory
|
|
2457
|
+
(0, runtime_1.protocol)().on("file/list", async (ctx, data) => {
|
|
2458
|
+
try {
|
|
2459
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2460
|
+
const { page, pageSize, target, fileName } = data;
|
|
2461
|
+
fileManager.cd(target);
|
|
2462
|
+
const overview = await fileManager.list(page, pageSize, fileName);
|
|
2463
|
+
(0, runtime_1.protocol)().response(ctx, overview);
|
|
2464
|
+
}
|
|
2465
|
+
catch (error) {
|
|
2466
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
// Preview archive contents without extracting the archive
|
|
2470
|
+
(0, runtime_1.protocol)().on("file/preview", async (ctx, data) => {
|
|
2471
|
+
const maxFileTask = (0, runtime_1.settings)().config.maxFileTask;
|
|
2472
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(data.instanceUuid);
|
|
2473
|
+
let taskStarted = false;
|
|
2474
|
+
try {
|
|
2475
|
+
if (!instance)
|
|
2476
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_3bfb9e04"));
|
|
2477
|
+
if (instance.info.fileLock >= maxFileTask) {
|
|
2478
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_file_router.unzipLimit", {
|
|
2479
|
+
maxFileTask,
|
|
2480
|
+
fileLock: instance.info.fileLock
|
|
2481
|
+
}));
|
|
2482
|
+
}
|
|
2483
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2484
|
+
instance.info.fileLock++;
|
|
2485
|
+
runtime_1.fileTasks.count++;
|
|
2486
|
+
taskStarted = true;
|
|
2487
|
+
const entries = await fileManager.previewArchive(data.target, data.code);
|
|
2488
|
+
(0, runtime_1.protocol)().response(ctx, { items: entries, total: entries.length });
|
|
2489
|
+
}
|
|
2490
|
+
catch (error) {
|
|
2491
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2492
|
+
}
|
|
2493
|
+
finally {
|
|
2494
|
+
if (instance && taskStarted) {
|
|
2495
|
+
instance.info.fileLock = Math.max(0, instance.info.fileLock - 1);
|
|
2496
|
+
runtime_1.fileTasks.count = Math.max(0, runtime_1.fileTasks.count - 1);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
// File chmod (only Linux)
|
|
2501
|
+
(0, runtime_1.protocol)().on("file/chmod", async (ctx, data) => {
|
|
2502
|
+
try {
|
|
2503
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2504
|
+
const { chmod, target, deep } = data;
|
|
2505
|
+
await fileManager.chmod(target, chmod, deep);
|
|
2506
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2507
|
+
}
|
|
2508
|
+
catch (error) {
|
|
2509
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
(0, runtime_1.protocol)().on("file/chmod_batch", async (ctx, data) => {
|
|
2513
|
+
try {
|
|
2514
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2515
|
+
const { chmod, targets, deep: rawDeep } = data;
|
|
2516
|
+
const deep = Boolean(rawDeep);
|
|
2517
|
+
const results = [];
|
|
2518
|
+
let success = 0;
|
|
2519
|
+
let failed = 0;
|
|
2520
|
+
for (const target of targets || []) {
|
|
2521
|
+
const currentTarget = String(target);
|
|
2522
|
+
try {
|
|
2523
|
+
await fileManager.chmod(currentTarget, chmod, deep);
|
|
2524
|
+
success += 1;
|
|
2525
|
+
results.push({
|
|
2526
|
+
target: currentTarget,
|
|
2527
|
+
success: true
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
catch (error) {
|
|
2531
|
+
failed += 1;
|
|
2532
|
+
results.push({
|
|
2533
|
+
target: currentTarget,
|
|
2534
|
+
success: false,
|
|
2535
|
+
error: error?.message || String(error)
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
(0, runtime_1.protocol)().response(ctx, {
|
|
2540
|
+
success,
|
|
2541
|
+
failed,
|
|
2542
|
+
total: results.length,
|
|
2543
|
+
results
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
catch (error) {
|
|
2547
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
// Query the status of the file management system
|
|
2551
|
+
(0, runtime_1.protocol)().on("file/status", async (ctx, data) => {
|
|
2552
|
+
try {
|
|
2553
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(data.instanceUuid);
|
|
2554
|
+
if (!instance)
|
|
2555
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_3bfb9e04"));
|
|
2556
|
+
const downloads = (0, runtime_1.transfer)().downloads;
|
|
2557
|
+
const downloadTasks = [];
|
|
2558
|
+
if (downloads.task) {
|
|
2559
|
+
downloadTasks.push({
|
|
2560
|
+
path: downloads.task.path,
|
|
2561
|
+
total: downloads.task.total,
|
|
2562
|
+
current: downloads.task.current,
|
|
2563
|
+
status: downloads.task.status,
|
|
2564
|
+
error: downloads.task.error
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
(0, runtime_1.protocol)().response(ctx, {
|
|
2568
|
+
instanceFileTask: instance.info.fileLock ?? 0,
|
|
2569
|
+
globalFileTask: runtime_1.fileTasks.count ?? 0,
|
|
2570
|
+
downloadFileFromURLTask: downloads.downloadingCount,
|
|
2571
|
+
downloadTasks,
|
|
2572
|
+
platform: os_1.default.platform(),
|
|
2573
|
+
isGlobalInstance: data.instanceUuid === (0, runtime_1.instances)().subsystem.GLOBAL_INSTANCE_UUID,
|
|
2574
|
+
disks: (0, file_service_1.getWindowsDisks)()
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
catch (error) {
|
|
2578
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
// Create a new file
|
|
2582
|
+
(0, runtime_1.protocol)().on("file/create-text", async (ctx, data) => {
|
|
2583
|
+
try {
|
|
2584
|
+
if (typeof data.target !== "string" ||
|
|
2585
|
+
!data.target ||
|
|
2586
|
+
data.target.length > 1024 ||
|
|
2587
|
+
typeof data.text !== "string" ||
|
|
2588
|
+
Buffer.byteLength(data.text, "utf8") > 64 * 1024 ||
|
|
2589
|
+
/[\x00-\x08\x0b\x0c\x0e-\x1f\ufffd]/.test(data.text))
|
|
2590
|
+
throw new Error("Invalid text file request");
|
|
2591
|
+
const result = await (0, file_service_1.getFileManager)(data.instanceUuid).createTextFile(data.target, data.text);
|
|
2592
|
+
(0, runtime_1.protocol)().response(ctx, result);
|
|
2593
|
+
}
|
|
2594
|
+
catch (error) {
|
|
2595
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
(0, runtime_1.protocol)().on("file/remove-file", async (ctx, data) => {
|
|
2599
|
+
try {
|
|
2600
|
+
if (typeof data.target !== "string" || !data.target || data.target.length > 1024)
|
|
2601
|
+
throw new Error("Invalid file request");
|
|
2602
|
+
const result = await (0, file_service_1.getFileManager)(data.instanceUuid).removeFile(data.target);
|
|
2603
|
+
(0, runtime_1.protocol)().response(ctx, result);
|
|
2604
|
+
}
|
|
2605
|
+
catch (error) {
|
|
2606
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
(0, runtime_1.protocol)().on("file/touch", (ctx, data) => {
|
|
2610
|
+
try {
|
|
2611
|
+
const target = data.target;
|
|
2612
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2613
|
+
fileManager.newFile(target);
|
|
2614
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2615
|
+
}
|
|
2616
|
+
catch (error) {
|
|
2617
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
// Create a directory
|
|
2621
|
+
(0, runtime_1.protocol)().on("file/mkdir", (ctx, data) => {
|
|
2622
|
+
try {
|
|
2623
|
+
const target = data.target;
|
|
2624
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2625
|
+
fileManager.mkdir(target);
|
|
2626
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2627
|
+
}
|
|
2628
|
+
catch (error) {
|
|
2629
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
// download a file from url
|
|
2633
|
+
(0, runtime_1.protocol)().on("file/download_from_url", async (ctx, data) => {
|
|
2634
|
+
try {
|
|
2635
|
+
const url = data.url;
|
|
2636
|
+
const fileName = data.fileName;
|
|
2637
|
+
if (!(0, url_1.checkSafeUrl)(url) || (data.fallbackUrl && !(0, url_1.checkSafeUrl)(data.fallbackUrl))) {
|
|
2638
|
+
(0, runtime_1.protocol)().responseError(ctx, (0, runtime_1.$t)("TXT_CODE_3fe1b194"), {
|
|
2639
|
+
disablePrint: true
|
|
2640
|
+
});
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2644
|
+
if (!fileManager.checkPath(fileName))
|
|
2645
|
+
throw new Error("Access denied: Invalid destination");
|
|
2646
|
+
const targetPath = fileManager.toAbsolutePath(fileName);
|
|
2647
|
+
// Start download in background
|
|
2648
|
+
const fallbackUrl = data.fallbackUrl;
|
|
2649
|
+
const maxDownloadFromUrlFileCount = (0, runtime_1.settings)().config.maxDownloadFromUrlFileCount;
|
|
2650
|
+
if ((data.ifIdle === true && (0, runtime_1.transfer)().downloads.downloadingCount > 0) ||
|
|
2651
|
+
(maxDownloadFromUrlFileCount > 0 &&
|
|
2652
|
+
(0, runtime_1.transfer)().downloads.downloadingCount >= maxDownloadFromUrlFileCount)) {
|
|
2653
|
+
(0, runtime_1.protocol)().responseError(ctx, (0, runtime_1.$t)("TXT_CODE_821a742e", {
|
|
2654
|
+
count: data.ifIdle === true ? 1 : maxDownloadFromUrlFileCount
|
|
2655
|
+
}), {
|
|
2656
|
+
disablePrint: true
|
|
2657
|
+
});
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
(0, runtime_1.transfer)()
|
|
2661
|
+
.downloads.downloadFromUrl(url, targetPath, fallbackUrl)
|
|
2662
|
+
.catch((err) => {
|
|
2663
|
+
(0, runtime_1.logger)().error(`Download failed: ${url} -> ${targetPath}`, err);
|
|
2664
|
+
});
|
|
2665
|
+
(0, runtime_1.protocol)().response(ctx, {});
|
|
2666
|
+
}
|
|
2667
|
+
catch (error) {
|
|
2668
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
// stop download from url
|
|
2672
|
+
(0, runtime_1.protocol)().on("file/download_stop", (ctx, data) => {
|
|
2673
|
+
try {
|
|
2674
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2675
|
+
if (!fileManager.checkPath(data.fileName))
|
|
2676
|
+
throw new Error("Access denied: Invalid destination");
|
|
2677
|
+
const targetPath = fileManager.toAbsolutePath(data.fileName);
|
|
2678
|
+
const result = (0, runtime_1.transfer)().downloads.stop(targetPath);
|
|
2679
|
+
(0, runtime_1.protocol)().response(ctx, result);
|
|
2680
|
+
}
|
|
2681
|
+
catch (error) {
|
|
2682
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
// copy the file
|
|
2686
|
+
(0, runtime_1.protocol)().on("file/copy", async (ctx, data) => {
|
|
2687
|
+
try {
|
|
2688
|
+
// [["a.txt","b.txt"],["cxz","zzz"]]
|
|
2689
|
+
const targets = data.targets;
|
|
2690
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2691
|
+
for (const target of targets) {
|
|
2692
|
+
await fileManager.copy(target[0], target[1]);
|
|
2693
|
+
}
|
|
2694
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2695
|
+
}
|
|
2696
|
+
catch (error) {
|
|
2697
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
// move the file
|
|
2701
|
+
(0, runtime_1.protocol)().on("file/move", async (ctx, data) => {
|
|
2702
|
+
try {
|
|
2703
|
+
// [["a.txt","b.txt"],["cxz","zzz"]]
|
|
2704
|
+
const targets = data.targets;
|
|
2705
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2706
|
+
for (const target of targets) {
|
|
2707
|
+
await fileManager.move(target[0], target[1]);
|
|
2708
|
+
}
|
|
2709
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2710
|
+
}
|
|
2711
|
+
catch (error) {
|
|
2712
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
// Delete Files
|
|
2716
|
+
(0, runtime_1.protocol)().on("file/delete", async (ctx, data) => {
|
|
2717
|
+
try {
|
|
2718
|
+
const targets = data.targets;
|
|
2719
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2720
|
+
for (const target of targets) {
|
|
2721
|
+
const path = fileManager.toAbsolutePath(target);
|
|
2722
|
+
const uploadTask = upload_manager_1.default.getByPath(path);
|
|
2723
|
+
if (uploadTask != undefined) {
|
|
2724
|
+
upload_manager_1.default.delete(uploadTask.id);
|
|
2725
|
+
await uploadTask.writer.stop();
|
|
2726
|
+
}
|
|
2727
|
+
else {
|
|
2728
|
+
// async delete
|
|
2729
|
+
await fileManager.delete(target);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2733
|
+
}
|
|
2734
|
+
catch (error) {
|
|
2735
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2736
|
+
}
|
|
2737
|
+
});
|
|
2738
|
+
// edit file
|
|
2739
|
+
(0, runtime_1.protocol)().on("file/edit", async (ctx, data) => {
|
|
2740
|
+
try {
|
|
2741
|
+
const target = data.target;
|
|
2742
|
+
const text = data.text;
|
|
2743
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2744
|
+
const result = await fileManager.edit(target, text);
|
|
2745
|
+
(0, runtime_1.protocol)().response(ctx, result ? result : true);
|
|
2746
|
+
}
|
|
2747
|
+
catch (error) {
|
|
2748
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
// compress/decompress the file
|
|
2752
|
+
(0, runtime_1.protocol)().on("file/compress", async (ctx, data) => {
|
|
2753
|
+
const maxFileTask = (0, runtime_1.settings)().config.maxFileTask;
|
|
2754
|
+
try {
|
|
2755
|
+
const source = data.source;
|
|
2756
|
+
const targets = data.targets;
|
|
2757
|
+
const type = data.type;
|
|
2758
|
+
const code = data.code;
|
|
2759
|
+
const fileManager = (0, file_service_1.getFileManager)(data.instanceUuid);
|
|
2760
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(data.instanceUuid);
|
|
2761
|
+
if (!instance)
|
|
2762
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_3bfb9e04"));
|
|
2763
|
+
if (instance.info.fileLock >= maxFileTask) {
|
|
2764
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_file_router.unzipLimit", {
|
|
2765
|
+
maxFileTask: maxFileTask,
|
|
2766
|
+
fileLock: instance.info.fileLock
|
|
2767
|
+
}));
|
|
2768
|
+
}
|
|
2769
|
+
// Statistics of the number of tasks in a single instance file and the number of tasks in the entire daemon process
|
|
2770
|
+
function fileTaskStart() {
|
|
2771
|
+
if (instance) {
|
|
2772
|
+
instance.info.fileLock++;
|
|
2773
|
+
runtime_1.fileTasks.count++;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
function fileTaskEnd() {
|
|
2777
|
+
if (instance) {
|
|
2778
|
+
instance.info.fileLock--;
|
|
2779
|
+
runtime_1.fileTasks.count--;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
// start decompressing or compressing the file
|
|
2783
|
+
fileTaskStart();
|
|
2784
|
+
try {
|
|
2785
|
+
if (type === 1) {
|
|
2786
|
+
await fileManager.zip(source, targets, code);
|
|
2787
|
+
}
|
|
2788
|
+
else {
|
|
2789
|
+
await fileManager.unzip(source, targets, code);
|
|
2790
|
+
}
|
|
2791
|
+
(0, runtime_1.protocol)().response(ctx, true);
|
|
2792
|
+
}
|
|
2793
|
+
catch (error) {
|
|
2794
|
+
throw error;
|
|
2795
|
+
}
|
|
2796
|
+
finally {
|
|
2797
|
+
fileTaskEnd();
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
catch (error) {
|
|
2801
|
+
(0, runtime_1.protocol)().responseError(ctx, error);
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
|
|
2807
|
+
/***/ },
|
|
2808
|
+
|
|
2809
|
+
/***/ "./plugins/file/src/backend/file_service.ts"
|
|
2810
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
2811
|
+
|
|
2812
|
+
|
|
2813
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2814
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2815
|
+
};
|
|
2816
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2817
|
+
exports.getFileManager = getFileManager;
|
|
2818
|
+
exports.getWindowsDisks = getWindowsDisks;
|
|
2819
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
2820
|
+
const system_file_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/system_file.ts"));
|
|
2821
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
2822
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
2823
|
+
function getFileManager(instanceUuid) {
|
|
2824
|
+
// Initialize a file manager for the instance, and assign codes, restrictions, etc.
|
|
2825
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(instanceUuid);
|
|
2826
|
+
if (!instance)
|
|
2827
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_file_router_service.instanceNotExit", { uuid: instanceUuid }));
|
|
2828
|
+
const fileCode = instance.config?.fileCode;
|
|
2829
|
+
return new system_file_1.default(instance.absoluteCwdPath(), fileCode);
|
|
2830
|
+
}
|
|
2831
|
+
let cacheDisks = [];
|
|
2832
|
+
function getWindowsDisks() {
|
|
2833
|
+
if (os_1.default.platform() !== "win32")
|
|
2834
|
+
return [];
|
|
2835
|
+
if (cacheDisks.length > 0)
|
|
2836
|
+
return cacheDisks;
|
|
2837
|
+
const res = [];
|
|
2838
|
+
// A - Z
|
|
2839
|
+
for (let i = 65; i <= 90; i++) {
|
|
2840
|
+
const letter = String.fromCharCode(i);
|
|
2841
|
+
try {
|
|
2842
|
+
if (fs_extra_1.default.existsSync(`${letter}:\\`))
|
|
2843
|
+
res.push(letter);
|
|
2844
|
+
}
|
|
2845
|
+
catch (error) {
|
|
2846
|
+
// ignore error
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
cacheDisks = res;
|
|
2850
|
+
return res;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
|
|
2854
|
+
/***/ },
|
|
2855
|
+
|
|
2856
|
+
/***/ "./plugins/file/src/backend/file_writer.ts"
|
|
2857
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
2858
|
+
|
|
2859
|
+
|
|
2860
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
2861
|
+
if (k2 === undefined) k2 = k;
|
|
2862
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
2863
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
2864
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
2865
|
+
}
|
|
2866
|
+
Object.defineProperty(o, k2, desc);
|
|
2867
|
+
}) : (function(o, m, k, k2) {
|
|
2868
|
+
if (k2 === undefined) k2 = k;
|
|
2869
|
+
o[k2] = m[k];
|
|
2870
|
+
}));
|
|
2871
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
2872
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
2873
|
+
}) : function(o, v) {
|
|
2874
|
+
o["default"] = v;
|
|
2875
|
+
});
|
|
2876
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
2877
|
+
var ownKeys = function(o) {
|
|
2878
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
2879
|
+
var ar = [];
|
|
2880
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
2881
|
+
return ar;
|
|
2882
|
+
};
|
|
2883
|
+
return ownKeys(o);
|
|
2884
|
+
};
|
|
2885
|
+
return function (mod) {
|
|
2886
|
+
if (mod && mod.__esModule) return mod;
|
|
2887
|
+
var result = {};
|
|
2888
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
2889
|
+
__setModuleDefault(result, mod);
|
|
2890
|
+
return result;
|
|
2891
|
+
};
|
|
2892
|
+
})();
|
|
2893
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2894
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2895
|
+
};
|
|
2896
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2897
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
2898
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
2899
|
+
const lockfile = __importStar(__webpack_require__("proper-lockfile"));
|
|
2900
|
+
const system_file_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/system_file.ts"));
|
|
2901
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
2902
|
+
const upload_manager_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/upload_manager.ts"));
|
|
2903
|
+
class FileWriter {
|
|
2904
|
+
cwd;
|
|
2905
|
+
filename;
|
|
2906
|
+
size;
|
|
2907
|
+
unzip;
|
|
2908
|
+
zipCode;
|
|
2909
|
+
path;
|
|
2910
|
+
id;
|
|
2911
|
+
releaseLock;
|
|
2912
|
+
fd = null;
|
|
2913
|
+
operations = Promise.resolve();
|
|
2914
|
+
completed = false;
|
|
2915
|
+
received = [];
|
|
2916
|
+
lastUpdate = Date.now();
|
|
2917
|
+
onFinish;
|
|
2918
|
+
get cancellable() {
|
|
2919
|
+
return !this.completed;
|
|
2920
|
+
}
|
|
2921
|
+
constructor(cwd, filename, size, unzip, zipCode, filePath) {
|
|
2922
|
+
this.cwd = cwd;
|
|
2923
|
+
this.filename = filename;
|
|
2924
|
+
this.size = size;
|
|
2925
|
+
this.unzip = unzip;
|
|
2926
|
+
this.zipCode = zipCode;
|
|
2927
|
+
if (!system_file_1.default.checkFileName(path_1.default.basename(this.filename)))
|
|
2928
|
+
throw new Error("Access denied: Malformed file name");
|
|
2929
|
+
if (!Number.isSafeInteger(size) || size < 0)
|
|
2930
|
+
throw new Error("Invalid file size");
|
|
2931
|
+
this.path = filePath;
|
|
2932
|
+
}
|
|
2933
|
+
static async getPath(cwd, dir, filename, overwrite) {
|
|
2934
|
+
const fileManager = new system_file_1.default(cwd);
|
|
2935
|
+
const ext = path_1.default.extname(filename);
|
|
2936
|
+
const basename = path_1.default.basename(filename, ext);
|
|
2937
|
+
let tempFileSaveName = basename + ext;
|
|
2938
|
+
let counter = 1;
|
|
2939
|
+
const checkFile = async (name) => {
|
|
2940
|
+
const absolutePath = fileManager.toAbsolutePath(path_1.default.normalize(path_1.default.join(dir, name)));
|
|
2941
|
+
const isLock = await lockfile
|
|
2942
|
+
.check(absolutePath)
|
|
2943
|
+
.then((isLock) => isLock)
|
|
2944
|
+
.catch(() => false);
|
|
2945
|
+
const isAccess = await fs_extra_1.default
|
|
2946
|
+
.access(absolutePath)
|
|
2947
|
+
.then(() => true)
|
|
2948
|
+
.catch(() => false);
|
|
2949
|
+
return isAccess && !isLock && !overwrite;
|
|
2950
|
+
};
|
|
2951
|
+
while (await checkFile(tempFileSaveName)) {
|
|
2952
|
+
if (counter == 1) {
|
|
2953
|
+
tempFileSaveName = `${basename}-copy${ext}`;
|
|
2954
|
+
}
|
|
2955
|
+
else {
|
|
2956
|
+
tempFileSaveName = `${basename}-copy-${counter}${ext}`;
|
|
2957
|
+
}
|
|
2958
|
+
counter++;
|
|
2959
|
+
if (counter > 100) {
|
|
2960
|
+
throw new Error("Access denied: File name already exists!");
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
const fileSaveRelativePath = path_1.default.normalize(path_1.default.join(dir, tempFileSaveName));
|
|
2964
|
+
if (!fileManager.checkPath(fileSaveRelativePath))
|
|
2965
|
+
throw new Error("Access denied: Invalid destination");
|
|
2966
|
+
return fileManager.toAbsolutePath(fileSaveRelativePath);
|
|
2967
|
+
}
|
|
2968
|
+
async init() {
|
|
2969
|
+
if (this.fd != null)
|
|
2970
|
+
return;
|
|
2971
|
+
try {
|
|
2972
|
+
// Lock before opening with w+: a competing upload must not truncate the file.
|
|
2973
|
+
this.releaseLock = await lockfile.lock(this.path, { realpath: false });
|
|
2974
|
+
this.fd = await fs_extra_1.default.open(this.path, "w+");
|
|
2975
|
+
await fs_extra_1.default.ftruncate(this.fd, this.size);
|
|
2976
|
+
}
|
|
2977
|
+
catch (e) {
|
|
2978
|
+
try {
|
|
2979
|
+
await this.close();
|
|
2980
|
+
}
|
|
2981
|
+
finally {
|
|
2982
|
+
await this.unlock();
|
|
2983
|
+
}
|
|
2984
|
+
throw e;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
enqueue(operation) {
|
|
2988
|
+
const pending = this.operations.then(operation);
|
|
2989
|
+
this.operations = pending.catch(() => { });
|
|
2990
|
+
return pending;
|
|
2991
|
+
}
|
|
2992
|
+
write(offset, chunk) {
|
|
2993
|
+
return this.enqueue(async () => {
|
|
2994
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset + chunk.length > this.size) {
|
|
2995
|
+
throw new Error("Write exceeds file size limit");
|
|
2996
|
+
}
|
|
2997
|
+
if (this.fd === null || this.completed)
|
|
2998
|
+
throw new Error("File is not opened");
|
|
2999
|
+
this.lastUpdate = Date.now();
|
|
3000
|
+
let written = 0;
|
|
3001
|
+
while (written < chunk.length) {
|
|
3002
|
+
const { bytesWritten } = await fs_extra_1.default.write(this.fd, chunk, written, chunk.length - written, offset + written);
|
|
3003
|
+
if (bytesWritten === 0)
|
|
3004
|
+
throw new Error("Unable to write upload chunk");
|
|
3005
|
+
written += bytesWritten;
|
|
3006
|
+
}
|
|
3007
|
+
this.addWrittenRange(offset, offset + chunk.length);
|
|
3008
|
+
if (this.isFullyCovered())
|
|
3009
|
+
await this.finish(false);
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
done() {
|
|
3013
|
+
return this.enqueue(() => this.finish(false));
|
|
3014
|
+
}
|
|
3015
|
+
stop() {
|
|
3016
|
+
return this.enqueue(() => this.finish(true));
|
|
3017
|
+
}
|
|
3018
|
+
async close() {
|
|
3019
|
+
const fd = this.fd;
|
|
3020
|
+
this.fd = null;
|
|
3021
|
+
if (fd != null)
|
|
3022
|
+
await fs_extra_1.default.close(fd);
|
|
3023
|
+
}
|
|
3024
|
+
async unlock() {
|
|
3025
|
+
const releaseLock = this.releaseLock;
|
|
3026
|
+
this.releaseLock = undefined;
|
|
3027
|
+
await releaseLock?.();
|
|
3028
|
+
}
|
|
3029
|
+
async finish(cancelled) {
|
|
3030
|
+
if (this.completed)
|
|
3031
|
+
return;
|
|
3032
|
+
this.completed = true;
|
|
3033
|
+
let failure;
|
|
3034
|
+
try {
|
|
3035
|
+
await this.close();
|
|
3036
|
+
if (cancelled) {
|
|
3037
|
+
await fs_extra_1.default.remove(this.path);
|
|
3038
|
+
(0, runtime_1.logger)().info("Browser Upload Task Stopped:", this.path);
|
|
3039
|
+
}
|
|
3040
|
+
else {
|
|
3041
|
+
(0, runtime_1.logger)().info("Browser Uploaded File:", this.path);
|
|
3042
|
+
if (this.unzip) {
|
|
3043
|
+
const instanceFiles = new system_file_1.default(this.cwd);
|
|
3044
|
+
await instanceFiles.unzip(this.path, ".", this.zipCode);
|
|
3045
|
+
(0, runtime_1.logger)().info("File unzipped:", this.path);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
catch (error) {
|
|
3050
|
+
failure = error;
|
|
3051
|
+
throw error;
|
|
3052
|
+
}
|
|
3053
|
+
finally {
|
|
3054
|
+
try {
|
|
3055
|
+
// Keep ownership until removal/extraction completes, so a newer upload
|
|
3056
|
+
// cannot start using the same path while this task is still changing it.
|
|
3057
|
+
await this.unlock();
|
|
3058
|
+
}
|
|
3059
|
+
catch (error) {
|
|
3060
|
+
failure ||= error;
|
|
3061
|
+
throw error;
|
|
3062
|
+
}
|
|
3063
|
+
finally {
|
|
3064
|
+
if (this.id != null)
|
|
3065
|
+
upload_manager_1.default.delete(this.id);
|
|
3066
|
+
this.onFinish?.(failure, cancelled);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
addWrittenRange(start, end) {
|
|
3071
|
+
if (start >= end)
|
|
3072
|
+
return;
|
|
3073
|
+
let i = 0;
|
|
3074
|
+
let ranges = this.received;
|
|
3075
|
+
while (i < ranges.length && ranges[i].end < start)
|
|
3076
|
+
i++;
|
|
3077
|
+
let mergeStart = start, mergeEnd = end;
|
|
3078
|
+
let removeCount = 0;
|
|
3079
|
+
while (i + removeCount < ranges.length && ranges[i + removeCount].start <= mergeEnd) {
|
|
3080
|
+
mergeStart = Math.min(mergeStart, ranges[i + removeCount].start);
|
|
3081
|
+
mergeEnd = Math.max(mergeEnd, ranges[i + removeCount].end);
|
|
3082
|
+
removeCount++;
|
|
3083
|
+
}
|
|
3084
|
+
ranges.splice(i, removeCount, { start: mergeStart, end: mergeEnd });
|
|
3085
|
+
}
|
|
3086
|
+
isFullyCovered() {
|
|
3087
|
+
return (this.received.length === 1 &&
|
|
3088
|
+
this.received[0].start === 0 &&
|
|
3089
|
+
this.received[0].end === this.size);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
exports["default"] = FileWriter;
|
|
3093
|
+
|
|
3094
|
+
|
|
3095
|
+
/***/ },
|
|
3096
|
+
|
|
3097
|
+
/***/ "./plugins/file/src/backend/filepath.ts"
|
|
3098
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
3099
|
+
|
|
3100
|
+
|
|
3101
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3102
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3103
|
+
};
|
|
3104
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3105
|
+
exports.checkFileName = checkFileName;
|
|
3106
|
+
exports.clearUploadFiles = clearUploadFiles;
|
|
3107
|
+
exports.normalizedJoin = normalizedJoin;
|
|
3108
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
3109
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
3110
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
3111
|
+
function checkFileName(fileName) {
|
|
3112
|
+
return (typeof fileName === "string" &&
|
|
3113
|
+
fileName.length > 0 &&
|
|
3114
|
+
fileName !== "." &&
|
|
3115
|
+
fileName !== ".." &&
|
|
3116
|
+
!/[\\/|?*><;":\0]/.test(fileName));
|
|
3117
|
+
}
|
|
3118
|
+
function clearUploadFiles(file) {
|
|
3119
|
+
if (!file)
|
|
3120
|
+
return;
|
|
3121
|
+
if (file instanceof Array) {
|
|
3122
|
+
file.forEach((v) => {
|
|
3123
|
+
fs_extra_1.default.remove(v.filepath, () => { });
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
else {
|
|
3127
|
+
fs_extra_1.default.remove(file.filepath, () => { });
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
function normalizedJoin(...paths) {
|
|
3131
|
+
if (os_1.default.platform() === "win32") {
|
|
3132
|
+
return joinWin32(...paths);
|
|
3133
|
+
}
|
|
3134
|
+
return path_1.default.normalize(path_1.default.join(...paths));
|
|
3135
|
+
}
|
|
3136
|
+
function joinWin32(...paths) {
|
|
3137
|
+
if (!paths.length)
|
|
3138
|
+
return ".";
|
|
3139
|
+
if (paths.length == 1)
|
|
3140
|
+
return paths[0];
|
|
3141
|
+
const parts = path_1.default.win32.parse(paths[1]);
|
|
3142
|
+
let p;
|
|
3143
|
+
if (parts.root) {
|
|
3144
|
+
p = paths[1];
|
|
3145
|
+
}
|
|
3146
|
+
else {
|
|
3147
|
+
p = path_1.default.normalize(path_1.default.join(paths[0], paths[1]));
|
|
3148
|
+
}
|
|
3149
|
+
if (paths.length <= 2)
|
|
3150
|
+
return p;
|
|
3151
|
+
return joinWin32(p, ...paths.slice(2));
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
|
|
3155
|
+
/***/ },
|
|
3156
|
+
|
|
3157
|
+
/***/ "./plugins/file/src/backend/http_router.ts"
|
|
3158
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
3159
|
+
|
|
3160
|
+
|
|
3161
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3162
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3163
|
+
};
|
|
3164
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3165
|
+
exports.registerHttpRoutes = registerHttpRoutes;
|
|
3166
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
3167
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
3168
|
+
const file_writer_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/file_writer.ts"));
|
|
3169
|
+
const filepath_1 = __webpack_require__("./plugins/file/src/backend/filepath.ts");
|
|
3170
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
3171
|
+
const system_file_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/system_file.ts"));
|
|
3172
|
+
const upload_manager_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/upload_manager.ts"));
|
|
3173
|
+
/**
|
|
3174
|
+
* The upload and download HTTP routes.
|
|
3175
|
+
*
|
|
3176
|
+
* Both are authorized by a passport the panel registered over the protocol first
|
|
3177
|
+
* — the server plugin issues those (`passport/register`) — and the upload routes
|
|
3178
|
+
* sit behind the runtime plugin's upload middleware, which the web server mounts ahead of
|
|
3179
|
+
* the body parser.
|
|
3180
|
+
*/
|
|
3181
|
+
function registerHttpRoutes() {
|
|
3182
|
+
const router = (0, runtime_1.core)().koa.router();
|
|
3183
|
+
// File download route
|
|
3184
|
+
router.get("/download/:key/:fileName", async (ctx) => {
|
|
3185
|
+
const key = ctx.params.key;
|
|
3186
|
+
const paramsFileName = ctx.params.fileName;
|
|
3187
|
+
try {
|
|
3188
|
+
// Get the task from the task center
|
|
3189
|
+
const mission = (0, runtime_1.transfer)().passports.getMission(key, "download");
|
|
3190
|
+
if (!mission)
|
|
3191
|
+
throw new Error((ctx.body = "Access denied: No task found"));
|
|
3192
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(mission.parameter.instanceUuid);
|
|
3193
|
+
if (!instance)
|
|
3194
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_http_router.instanceNotExist"));
|
|
3195
|
+
if (!system_file_1.default.checkFileName(paramsFileName))
|
|
3196
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_http_router.fileNameNotSpec"));
|
|
3197
|
+
const cwd = instance.absoluteCwdPath();
|
|
3198
|
+
const fileRelativePath = mission.parameter.fileName;
|
|
3199
|
+
// Check for file cross-directory security risks
|
|
3200
|
+
const fileManager = new system_file_1.default(cwd);
|
|
3201
|
+
if (!fileManager.check(fileRelativePath))
|
|
3202
|
+
throw new Error((ctx.body = "Access denied: Invalid destination"));
|
|
3203
|
+
const fileAbsPath = fileManager.toAbsolutePath(fileRelativePath);
|
|
3204
|
+
await (0, runtime_1.transfer)().sendFile(ctx, fileAbsPath);
|
|
3205
|
+
}
|
|
3206
|
+
catch (error) {
|
|
3207
|
+
if (!ctx.res.headersSent) {
|
|
3208
|
+
ctx.body = (0, runtime_1.$t)("TXT_CODE_http_router.downloadErr", { error: error.message });
|
|
3209
|
+
ctx.status = 500;
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
finally {
|
|
3213
|
+
(0, runtime_1.transfer)().passports.deleteMission(key);
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
// Old version upload route
|
|
3217
|
+
router.post("/upload/:key", async (ctx) => {
|
|
3218
|
+
const key = String(ctx.params.key);
|
|
3219
|
+
const unzip = ctx.query.unzip === "true";
|
|
3220
|
+
const zipCode = String(ctx.query.code);
|
|
3221
|
+
const tmpFiles = ctx.request.files?.file;
|
|
3222
|
+
try {
|
|
3223
|
+
const mission = (0, runtime_1.transfer)().passports.getMission(key, "upload");
|
|
3224
|
+
if (!mission)
|
|
3225
|
+
throw new Error("Access denied: No task found");
|
|
3226
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(mission.parameter.instanceUuid);
|
|
3227
|
+
if (!instance)
|
|
3228
|
+
throw new Error("Access denied: No instance found");
|
|
3229
|
+
const uploadDir = mission.parameter.uploadDir;
|
|
3230
|
+
const cwd = instance.absoluteCwdPath();
|
|
3231
|
+
if (tmpFiles) {
|
|
3232
|
+
let uploadedFile;
|
|
3233
|
+
if (tmpFiles instanceof Array) {
|
|
3234
|
+
uploadedFile = tmpFiles[0];
|
|
3235
|
+
}
|
|
3236
|
+
else if (tmpFiles) {
|
|
3237
|
+
uploadedFile = tmpFiles;
|
|
3238
|
+
}
|
|
3239
|
+
else {
|
|
3240
|
+
throw new Error("Empty File!");
|
|
3241
|
+
}
|
|
3242
|
+
const originFileName = uploadedFile.originalFilename || "";
|
|
3243
|
+
if (!system_file_1.default.checkFileName(path_1.default.basename(originFileName)))
|
|
3244
|
+
throw new Error("Access denied: Malformed file name");
|
|
3245
|
+
const fileManager = new system_file_1.default(cwd);
|
|
3246
|
+
const ext = path_1.default.extname(originFileName);
|
|
3247
|
+
const basename = path_1.default.basename(originFileName, ext);
|
|
3248
|
+
let tempFileSaveName = basename + ext;
|
|
3249
|
+
let counter = 1;
|
|
3250
|
+
while (fs_extra_1.default.existsSync(fileManager.toAbsolutePath(path_1.default.normalize(path_1.default.join(uploadDir, tempFileSaveName)))) &&
|
|
3251
|
+
ctx.query.overwrite === "false") {
|
|
3252
|
+
if (counter == 1) {
|
|
3253
|
+
tempFileSaveName = `${basename}-copy${ext}`;
|
|
3254
|
+
}
|
|
3255
|
+
else {
|
|
3256
|
+
tempFileSaveName = `${basename}-copy-${counter}${ext}`;
|
|
3257
|
+
}
|
|
3258
|
+
counter++;
|
|
3259
|
+
}
|
|
3260
|
+
let fileSaveRelativePath = path_1.default.normalize(path_1.default.join(uploadDir, tempFileSaveName));
|
|
3261
|
+
if (!fileManager.checkPath(fileSaveRelativePath))
|
|
3262
|
+
throw new Error("Access denied: Invalid destination");
|
|
3263
|
+
const fileSaveAbsolutePath = fileManager.toAbsolutePath(fileSaveRelativePath);
|
|
3264
|
+
await fs_extra_1.default.move(uploadedFile.filepath, fileSaveAbsolutePath, {
|
|
3265
|
+
overwrite: true
|
|
3266
|
+
});
|
|
3267
|
+
if (unzip) {
|
|
3268
|
+
const instanceFiles = new system_file_1.default(instance.absoluteCwdPath());
|
|
3269
|
+
await instanceFiles.unzip(fileSaveAbsolutePath, ".", zipCode);
|
|
3270
|
+
}
|
|
3271
|
+
ctx.body = "OK";
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
ctx.body = "Access denied: No file found";
|
|
3275
|
+
ctx.status = 500;
|
|
3276
|
+
}
|
|
3277
|
+
catch (error) {
|
|
3278
|
+
ctx.body = error.message;
|
|
3279
|
+
ctx.status = 500;
|
|
3280
|
+
}
|
|
3281
|
+
finally {
|
|
3282
|
+
(0, runtime_1.transfer)().passports.deleteMission(key);
|
|
3283
|
+
if (tmpFiles)
|
|
3284
|
+
(0, filepath_1.clearUploadFiles)(tmpFiles);
|
|
3285
|
+
}
|
|
3286
|
+
});
|
|
3287
|
+
router.post("/upload-new/:key", async (ctx) => {
|
|
3288
|
+
const key = String(ctx.params.key);
|
|
3289
|
+
const unzip = ctx.query.unzip === "true";
|
|
3290
|
+
const zipCode = String(ctx.query.code);
|
|
3291
|
+
const filename = String(ctx.query.filename);
|
|
3292
|
+
const size = Number(ctx.query.size);
|
|
3293
|
+
if (ctx.query.stop) {
|
|
3294
|
+
const writer = upload_manager_1.default.get(key);
|
|
3295
|
+
if (writer) {
|
|
3296
|
+
await writer.stop();
|
|
3297
|
+
upload_manager_1.default.delete(key);
|
|
3298
|
+
ctx.body = "OK";
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
else {
|
|
3302
|
+
ctx.body = "Access denied: No task found";
|
|
3303
|
+
ctx.status = 500;
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
try {
|
|
3308
|
+
const mission = (0, runtime_1.transfer)().passports.getMission(key, "upload");
|
|
3309
|
+
if (!mission)
|
|
3310
|
+
throw new Error("Access denied: No task found");
|
|
3311
|
+
const instance = (0, runtime_1.instances)().subsystem.getInstance(mission.parameter.instanceUuid);
|
|
3312
|
+
if (!instance)
|
|
3313
|
+
throw new Error("Access denied: No instance found");
|
|
3314
|
+
const uploadDir = mission.parameter.uploadDir;
|
|
3315
|
+
const cwd = instance.absoluteCwdPath();
|
|
3316
|
+
const overwrite = ctx.query.overwrite !== "false";
|
|
3317
|
+
if (!Number.isSafeInteger(size) || size < 0)
|
|
3318
|
+
throw new Error("Invalid file size");
|
|
3319
|
+
const filePath = await file_writer_1.default.getPath(cwd, uploadDir, filename, overwrite);
|
|
3320
|
+
let fr = upload_manager_1.default.getByPath(filePath);
|
|
3321
|
+
if (fr && size != fr.writer.size) {
|
|
3322
|
+
upload_manager_1.default.delete(fr.id);
|
|
3323
|
+
await fr.writer.stop();
|
|
3324
|
+
fr = undefined;
|
|
3325
|
+
}
|
|
3326
|
+
if (!fr) {
|
|
3327
|
+
const fileWriter = new file_writer_1.default(cwd, filename, size, unzip, zipCode, filePath);
|
|
3328
|
+
await fileWriter.init();
|
|
3329
|
+
const id = upload_manager_1.default.add(fileWriter);
|
|
3330
|
+
fr = { id, writer: fileWriter };
|
|
3331
|
+
const tasks = (0, runtime_1.core)().get("backgroundTasks");
|
|
3332
|
+
tasks?.runRequest(unzip ? "file/upload_extract" : "file/upload", {
|
|
3333
|
+
_taskOwner: mission.parameter._taskOwner,
|
|
3334
|
+
instanceUuid: mission.parameter.instanceUuid,
|
|
3335
|
+
fileName: filename
|
|
3336
|
+
}, () => {
|
|
3337
|
+
tasks.progress(() => ({
|
|
3338
|
+
current: fileWriter.received.reduce((sum, range) => sum + range.end - range.start, 0),
|
|
3339
|
+
total: size,
|
|
3340
|
+
cancellable: fileWriter.cancellable
|
|
3341
|
+
}), () => { void fileWriter.stop().catch((err) => (0, runtime_1.core)().logger.warn(err)); });
|
|
3342
|
+
tasks.follow(new Promise((resolve, reject) => {
|
|
3343
|
+
fileWriter.onFinish = (error, cancelled) => {
|
|
3344
|
+
if (error)
|
|
3345
|
+
reject(error);
|
|
3346
|
+
else if (cancelled)
|
|
3347
|
+
reject(Object.assign(new Error("Upload cancelled"), { code: "ABORT_ERR" }));
|
|
3348
|
+
else
|
|
3349
|
+
resolve();
|
|
3350
|
+
};
|
|
3351
|
+
}));
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
if (size === 0)
|
|
3355
|
+
await fr.writer.done();
|
|
3356
|
+
ctx.body = {
|
|
3357
|
+
data: {
|
|
3358
|
+
id: fr.id,
|
|
3359
|
+
received: fr.writer.received
|
|
3360
|
+
},
|
|
3361
|
+
status: 200
|
|
3362
|
+
};
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
catch (error) {
|
|
3366
|
+
ctx.body = error.message;
|
|
3367
|
+
ctx.status = 500;
|
|
3368
|
+
}
|
|
3369
|
+
finally {
|
|
3370
|
+
(0, runtime_1.transfer)().passports.deleteMission(key);
|
|
3371
|
+
}
|
|
3372
|
+
});
|
|
3373
|
+
router.post("/upload-piece/:id", async (ctx) => {
|
|
3374
|
+
const id = String(ctx.params.id);
|
|
3375
|
+
const offset = Number(ctx.query.offset);
|
|
3376
|
+
const tmpFiles = ctx.request.files?.file;
|
|
3377
|
+
try {
|
|
3378
|
+
if (!tmpFiles) {
|
|
3379
|
+
ctx.body = "Access denied: No file found";
|
|
3380
|
+
ctx.status = 500;
|
|
3381
|
+
}
|
|
3382
|
+
let uploadedFile;
|
|
3383
|
+
if (tmpFiles instanceof Array) {
|
|
3384
|
+
uploadedFile = tmpFiles[0];
|
|
3385
|
+
}
|
|
3386
|
+
else if (tmpFiles) {
|
|
3387
|
+
uploadedFile = tmpFiles;
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
throw new Error("Empty File!");
|
|
3391
|
+
}
|
|
3392
|
+
const writer = upload_manager_1.default.get(id);
|
|
3393
|
+
if (!writer)
|
|
3394
|
+
throw new Error("Access denied: No task found");
|
|
3395
|
+
const chunk = await fs_extra_1.default.readFile(uploadedFile.filepath);
|
|
3396
|
+
await writer.write(offset, chunk);
|
|
3397
|
+
ctx.body = "OK";
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
catch (error) {
|
|
3401
|
+
ctx.body = error.message;
|
|
3402
|
+
ctx.status = 500;
|
|
3403
|
+
}
|
|
3404
|
+
finally {
|
|
3405
|
+
(0, filepath_1.clearUploadFiles)(tmpFiles);
|
|
3406
|
+
}
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
|
|
3411
|
+
/***/ },
|
|
3412
|
+
|
|
3413
|
+
/***/ "./plugins/file/src/backend/index.ts"
|
|
3414
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
3415
|
+
|
|
3416
|
+
|
|
3417
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3418
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3419
|
+
};
|
|
3420
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3421
|
+
exports.inject = void 0;
|
|
3422
|
+
exports.apply = apply;
|
|
3423
|
+
const i18n_1 = __webpack_require__("./plugins/file/src/i18n/index.ts");
|
|
3424
|
+
const file_router_1 = __webpack_require__("./plugins/file/src/backend/file_router.ts");
|
|
3425
|
+
const file_service_1 = __webpack_require__("./plugins/file/src/backend/file_service.ts");
|
|
3426
|
+
const http_router_1 = __webpack_require__("./plugins/file/src/backend/http_router.ts");
|
|
3427
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
3428
|
+
const system_file_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/system_file.ts"));
|
|
3429
|
+
const upload_manager_1 = __importDefault(__webpack_require__("./plugins/file/src/backend/upload_manager.ts"));
|
|
3430
|
+
const settings_1 = __webpack_require__("./plugins/file/src/backend/settings.ts");
|
|
3431
|
+
// The daemon's file subsystem.
|
|
3432
|
+
//
|
|
3433
|
+
// Everything about reading and writing instance files lives here: the
|
|
3434
|
+
// `FileManager` that resolves and sandboxes a path, the chunked upload manager,
|
|
3435
|
+
// the fourteen `file/*` protocol events and the upload/download HTTP routes.
|
|
3436
|
+
//
|
|
3437
|
+
// The primitives are handed to the rest of the daemon as `ctx.files`, because
|
|
3438
|
+
// instance creation, the Java manager, SteamCMD and the mod service all need to
|
|
3439
|
+
// touch files too. Consumers resolve the service through their plugin context,
|
|
3440
|
+
// so removing this plugin removes the daemon's
|
|
3441
|
+
// ability to touch instance files rather than breaking the build.
|
|
3442
|
+
// File routes resolve the instance service when a request arrives. Keeping the
|
|
3443
|
+
// dependency lazy avoids a startup cycle: the instance plugin depends on the
|
|
3444
|
+
// file primitives, while the file routes only need instances at request time.
|
|
3445
|
+
exports.inject = {
|
|
3446
|
+
i18n: { required: true },
|
|
3447
|
+
settings: { required: true },
|
|
3448
|
+
protocol: { required: true },
|
|
3449
|
+
archive: { required: true },
|
|
3450
|
+
transfer: { required: true },
|
|
3451
|
+
koa: { required: true },
|
|
3452
|
+
// File routes resolve the instance subsystem lazily to avoid a startup
|
|
3453
|
+
// cycle: the instance plugin requires the file service in return.
|
|
3454
|
+
instances: { required: false }
|
|
3455
|
+
};
|
|
3456
|
+
function apply(ctx) {
|
|
3457
|
+
// Before anything else: the modules below read the logger, the configuration,
|
|
3458
|
+
// the instances and `$t` through this handle.
|
|
3459
|
+
(0, runtime_1.setPluginContext)(ctx);
|
|
3460
|
+
ctx.i18n.define(i18n_1.localeMessages);
|
|
3461
|
+
(0, settings_1.registerSettings)(ctx);
|
|
3462
|
+
// `ctx.set()` from inside a plugin belongs to that plugin: the primitives — and
|
|
3463
|
+
// with them the daemon's access to instance files — leave when it unloads.
|
|
3464
|
+
ctx.set("files", {
|
|
3465
|
+
FileManager: system_file_1.default,
|
|
3466
|
+
uploads: upload_manager_1.default,
|
|
3467
|
+
getFileManager: file_service_1.getFileManager,
|
|
3468
|
+
getWindowsDisks: file_service_1.getWindowsDisks
|
|
3469
|
+
});
|
|
3470
|
+
(0, file_router_1.registerFileEvents)();
|
|
3471
|
+
(0, http_router_1.registerHttpRoutes)();
|
|
3472
|
+
// Chunked uploads hold open file handles and lock files, so they are stopped
|
|
3473
|
+
// with the plugin. The daemon used to do this from its own shutdown path.
|
|
3474
|
+
ctx.on("dispose", () => upload_manager_1.default.exit());
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
|
|
3478
|
+
/***/ },
|
|
3479
|
+
|
|
3480
|
+
/***/ "./plugins/file/src/backend/runtime.ts"
|
|
3481
|
+
(__unused_webpack_module, exports) {
|
|
3482
|
+
|
|
3483
|
+
|
|
3484
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3485
|
+
exports.fileTasks = exports.instances = exports.$t = exports.transfer = exports.archive = exports.protocol = exports.settings = exports.logger = void 0;
|
|
3486
|
+
exports.setPluginContext = setPluginContext;
|
|
3487
|
+
exports.core = core;
|
|
3488
|
+
/**
|
|
3489
|
+
* This plugin's private handle on its cordis context.
|
|
3490
|
+
*
|
|
3491
|
+
* The daemon core is bundled into `app.js`, so a plugin cannot import its
|
|
3492
|
+
* singletons. The only channel is the context `apply()` receives, and this
|
|
3493
|
+
* plugin is six modules deep — a `FileManager` throws translated errors, a
|
|
3494
|
+
* `FileWriter` logs from inside a stream callback — so threading a parameter
|
|
3495
|
+
* through all of them would say nothing the accessors below do not.
|
|
3496
|
+
*
|
|
3497
|
+
* A plugin with only a handful of such call sites should pass `ctx` as an
|
|
3498
|
+
* argument instead.
|
|
3499
|
+
*/
|
|
3500
|
+
let context;
|
|
3501
|
+
function setPluginContext(value) {
|
|
3502
|
+
context = value;
|
|
3503
|
+
}
|
|
3504
|
+
function core() {
|
|
3505
|
+
if (!context)
|
|
3506
|
+
throw new Error("The file plugin has not been initialized yet.");
|
|
3507
|
+
return context;
|
|
3508
|
+
}
|
|
3509
|
+
/**
|
|
3510
|
+
* `ctx.logger` is bound to this plugin's own cordis scope, so it is gone once
|
|
3511
|
+
* that scope is disposed — and the disposer stops every upload, a path that
|
|
3512
|
+
* logs. Shutting down must not fail on a log line.
|
|
3513
|
+
*/
|
|
3514
|
+
const SILENT = {
|
|
3515
|
+
info() { },
|
|
3516
|
+
warn() { },
|
|
3517
|
+
error() { },
|
|
3518
|
+
debug() { },
|
|
3519
|
+
success() { },
|
|
3520
|
+
extend: () => SILENT
|
|
3521
|
+
};
|
|
3522
|
+
const logger = () => core().logger ?? SILENT;
|
|
3523
|
+
exports.logger = logger;
|
|
3524
|
+
const settings = () => core().settings;
|
|
3525
|
+
exports.settings = settings;
|
|
3526
|
+
const protocol = () => core().protocol;
|
|
3527
|
+
exports.protocol = protocol;
|
|
3528
|
+
const archive = () => core().archive;
|
|
3529
|
+
exports.archive = archive;
|
|
3530
|
+
const transfer = () => core().transfer;
|
|
3531
|
+
exports.transfer = transfer;
|
|
3532
|
+
const $t = (key, options) => (core().get("i18n")?.$t(key, options) ?? key);
|
|
3533
|
+
exports.$t = $t;
|
|
3534
|
+
/**
|
|
3535
|
+
* The file plugin can load before the instance plugin. `get()` avoids Cordis'
|
|
3536
|
+
* undeclared-property warning while keeping the existing lazy dependency.
|
|
3537
|
+
*/
|
|
3538
|
+
const instances = () => {
|
|
3539
|
+
const service = core().get("instances");
|
|
3540
|
+
if (!service)
|
|
3541
|
+
throw new Error("Instance access requires the daemon instance plugin.");
|
|
3542
|
+
return service;
|
|
3543
|
+
};
|
|
3544
|
+
exports.instances = instances;
|
|
3545
|
+
/**
|
|
3546
|
+
* How many file tasks are running across the whole daemon. It used to be
|
|
3547
|
+
* `globalEnv` in the core's configuration module, read by nothing but the file
|
|
3548
|
+
* routes, so it lives with them now.
|
|
3549
|
+
*/
|
|
3550
|
+
exports.fileTasks = { count: 0 };
|
|
3551
|
+
|
|
3552
|
+
|
|
3553
|
+
/***/ },
|
|
3554
|
+
|
|
3555
|
+
/***/ "./plugins/file/src/backend/settings.ts"
|
|
3556
|
+
(__unused_webpack_module, exports) {
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3560
|
+
exports.registerSettings = registerSettings;
|
|
3561
|
+
function registerSettings(ctx) {
|
|
3562
|
+
const config = ctx.settings.config;
|
|
3563
|
+
const $t = ctx.i18n.$t;
|
|
3564
|
+
const speedOptions = (current) => [...new Set([0, 5, 10, 16, 32, 64, 96, 128, 160, 240, 320, 480, current])].map((value) => ({
|
|
3565
|
+
value,
|
|
3566
|
+
label: value === 0
|
|
3567
|
+
? $t("TXT_CODE_e3a77a77")
|
|
3568
|
+
: value < 16
|
|
3569
|
+
? `${value * 64}KB/s`
|
|
3570
|
+
: `${value / 16}MB/s`
|
|
3571
|
+
}));
|
|
3572
|
+
ctx.inject(["settingsForm"], (settingsCtx) => settingsCtx.settingsForm.declare({
|
|
3573
|
+
fields: () => [
|
|
3574
|
+
{
|
|
3575
|
+
key: "uploadSpeedRate",
|
|
3576
|
+
type: "select",
|
|
3577
|
+
title: $t("TXT_CODE_fde31068"),
|
|
3578
|
+
description: $t("TXT_CODE_d8d19932"),
|
|
3579
|
+
options: speedOptions(config.uploadSpeedRate)
|
|
3580
|
+
},
|
|
3581
|
+
{
|
|
3582
|
+
key: "downloadSpeedRate",
|
|
3583
|
+
type: "select",
|
|
3584
|
+
title: $t("TXT_CODE_785a0fcf"),
|
|
3585
|
+
description: $t("TXT_CODE_b9fc604c"),
|
|
3586
|
+
options: speedOptions(config.downloadSpeedRate)
|
|
3587
|
+
},
|
|
3588
|
+
{
|
|
3589
|
+
key: "maxDownloadFromUrlFileCount",
|
|
3590
|
+
type: "number",
|
|
3591
|
+
title: $t("TXT_CODE_a15fca22"),
|
|
3592
|
+
description: $t("TXT_CODE_ecaf78a2"),
|
|
3593
|
+
min: 0
|
|
3594
|
+
}
|
|
3595
|
+
],
|
|
3596
|
+
read: () => ({
|
|
3597
|
+
uploadSpeedRate: config.uploadSpeedRate,
|
|
3598
|
+
downloadSpeedRate: config.downloadSpeedRate,
|
|
3599
|
+
maxDownloadFromUrlFileCount: config.maxDownloadFromUrlFileCount
|
|
3600
|
+
}),
|
|
3601
|
+
write: (values) => {
|
|
3602
|
+
const count = values.maxDownloadFromUrlFileCount;
|
|
3603
|
+
if (count != null && (!Number.isInteger(count) || Number(count) < 0)) {
|
|
3604
|
+
throw new Error($t("TXT_CODE_ecaf78a2"));
|
|
3605
|
+
}
|
|
3606
|
+
if (values.uploadSpeedRate != null)
|
|
3607
|
+
config.uploadSpeedRate = Number(values.uploadSpeedRate);
|
|
3608
|
+
if (values.downloadSpeedRate != null)
|
|
3609
|
+
config.downloadSpeedRate = Number(values.downloadSpeedRate);
|
|
3610
|
+
if (count != null)
|
|
3611
|
+
config.maxDownloadFromUrlFileCount = Number(count);
|
|
3612
|
+
ctx.settings.save();
|
|
3613
|
+
}
|
|
3614
|
+
}));
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
|
|
3618
|
+
/***/ },
|
|
3619
|
+
|
|
3620
|
+
/***/ "./plugins/file/src/backend/system_file.ts"
|
|
3621
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
3622
|
+
|
|
3623
|
+
|
|
3624
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3625
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3626
|
+
};
|
|
3627
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3628
|
+
const fs_extra_1 = __importDefault(__webpack_require__("fs-extra"));
|
|
3629
|
+
const iconv_lite_1 = __importDefault(__webpack_require__("iconv-lite"));
|
|
3630
|
+
const mcsmanager_common_1 = __webpack_require__("../common/src/index.ts");
|
|
3631
|
+
const os_1 = __importDefault(__webpack_require__("os"));
|
|
3632
|
+
const path_1 = __importDefault(__webpack_require__("path"));
|
|
3633
|
+
const filepath_1 = __webpack_require__("./plugins/file/src/backend/filepath.ts");
|
|
3634
|
+
const runtime_1 = __webpack_require__("./plugins/file/src/backend/runtime.ts");
|
|
3635
|
+
/** Resolved per use: the module is required before `apply()` runs. */
|
|
3636
|
+
const ERROR_MSG_01 = () => (0, runtime_1.$t)("TXT_CODE_system_file.illegalAccess");
|
|
3637
|
+
const MAX_EDIT_SIZE = 1024 * 1024 * 5;
|
|
3638
|
+
class FileManager {
|
|
3639
|
+
topPath;
|
|
3640
|
+
fileCode;
|
|
3641
|
+
cwd = ".";
|
|
3642
|
+
constructor(topPath = "", fileCode) {
|
|
3643
|
+
this.topPath = topPath;
|
|
3644
|
+
this.fileCode = fileCode;
|
|
3645
|
+
if (!path_1.default.isAbsolute(topPath)) {
|
|
3646
|
+
this.topPath = path_1.default.normalize(path_1.default.join(process.cwd(), topPath));
|
|
3647
|
+
}
|
|
3648
|
+
else {
|
|
3649
|
+
this.topPath = path_1.default.normalize(topPath);
|
|
3650
|
+
}
|
|
3651
|
+
if (!fileCode) {
|
|
3652
|
+
this.fileCode = "utf-8";
|
|
3653
|
+
if ((0, runtime_1.settings)().config.language === "zh_cn")
|
|
3654
|
+
this.fileCode = "gbk";
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
isRootTopRath() {
|
|
3658
|
+
return this.topPath === "/" || this.topPath === "\\";
|
|
3659
|
+
}
|
|
3660
|
+
toAbsolutePath(fileName = "") {
|
|
3661
|
+
const topAbsolutePath = this.topPath;
|
|
3662
|
+
if (path_1.default.normalize(fileName).indexOf(topAbsolutePath) === 0) {
|
|
3663
|
+
const resolved = path_1.default.resolve(fileName);
|
|
3664
|
+
this.assertInsideRoot(resolved);
|
|
3665
|
+
return resolved;
|
|
3666
|
+
}
|
|
3667
|
+
let finalPath = "";
|
|
3668
|
+
if (os_1.default.platform() === "win32") {
|
|
3669
|
+
const reg = new RegExp("^[A-Za-z]{1}:[\\\\/]{1}");
|
|
3670
|
+
if (reg.test(this.cwd)) {
|
|
3671
|
+
finalPath = path_1.default.normalize(path_1.default.join(this.cwd, fileName));
|
|
3672
|
+
}
|
|
3673
|
+
else if (reg.test(fileName)) {
|
|
3674
|
+
finalPath = path_1.default.normalize(fileName);
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
if (!finalPath) {
|
|
3678
|
+
finalPath = path_1.default.normalize(path_1.default.join(this.topPath, this.cwd, fileName));
|
|
3679
|
+
}
|
|
3680
|
+
this.assertInsideRoot(finalPath);
|
|
3681
|
+
return finalPath;
|
|
3682
|
+
}
|
|
3683
|
+
assertInsideRoot(target) {
|
|
3684
|
+
if (this.isRootTopRath())
|
|
3685
|
+
return;
|
|
3686
|
+
const isInside = (root, candidate) => {
|
|
3687
|
+
const relative = path_1.default.relative(root, candidate);
|
|
3688
|
+
return (relative !== ".." && !relative.startsWith(`..${path_1.default.sep}`) && !path_1.default.isAbsolute(relative));
|
|
3689
|
+
};
|
|
3690
|
+
if (!isInside(this.topPath, target)) {
|
|
3691
|
+
throw new Error(ERROR_MSG_01());
|
|
3692
|
+
}
|
|
3693
|
+
// New files need the same protection as existing files: resolve the nearest
|
|
3694
|
+
// existing parent so a symlink/junction cannot escape an instance directory.
|
|
3695
|
+
if (!fs_extra_1.default.existsSync(this.topPath))
|
|
3696
|
+
return;
|
|
3697
|
+
const realRoot = fs_extra_1.default.realpathSync(this.topPath);
|
|
3698
|
+
let ancestor = target;
|
|
3699
|
+
while (true) {
|
|
3700
|
+
try {
|
|
3701
|
+
fs_extra_1.default.lstatSync(ancestor);
|
|
3702
|
+
break;
|
|
3703
|
+
}
|
|
3704
|
+
catch (error) {
|
|
3705
|
+
if (error.code !== "ENOENT")
|
|
3706
|
+
throw error;
|
|
3707
|
+
}
|
|
3708
|
+
const parent = path_1.default.dirname(ancestor);
|
|
3709
|
+
if (parent === ancestor)
|
|
3710
|
+
throw new Error(ERROR_MSG_01());
|
|
3711
|
+
ancestor = parent;
|
|
3712
|
+
}
|
|
3713
|
+
if (!isInside(realRoot, fs_extra_1.default.realpathSync(ancestor)))
|
|
3714
|
+
throw new Error(ERROR_MSG_01());
|
|
3715
|
+
}
|
|
3716
|
+
checkPath(fileNameOrPath) {
|
|
3717
|
+
if (this.isRootTopRath())
|
|
3718
|
+
return true;
|
|
3719
|
+
const destAbsolutePath = path_1.default.normalize(this.toAbsolutePath(fileNameOrPath));
|
|
3720
|
+
const topAbsolutePath = path_1.default.normalize(this.topPath);
|
|
3721
|
+
const destPath = destAbsolutePath.endsWith(path_1.default.sep)
|
|
3722
|
+
? destAbsolutePath.slice(0, -1)
|
|
3723
|
+
: destAbsolutePath;
|
|
3724
|
+
const topPath = topAbsolutePath.endsWith(path_1.default.sep)
|
|
3725
|
+
? topAbsolutePath.slice(0, -1)
|
|
3726
|
+
: topAbsolutePath;
|
|
3727
|
+
if (destPath.startsWith(topPath)) {
|
|
3728
|
+
const parts = destPath.split(path_1.default.sep);
|
|
3729
|
+
return topPath.split(path_1.default.sep).every((part, index) => {
|
|
3730
|
+
return part === parts[index];
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
return false;
|
|
3734
|
+
}
|
|
3735
|
+
check(destPath) {
|
|
3736
|
+
if (this.isRootTopRath())
|
|
3737
|
+
return true;
|
|
3738
|
+
return this.checkPath(destPath) && fs_extra_1.default.existsSync(this.toAbsolutePath(destPath));
|
|
3739
|
+
}
|
|
3740
|
+
cd(dirName) {
|
|
3741
|
+
if (!this.check(dirName))
|
|
3742
|
+
throw new Error(ERROR_MSG_01());
|
|
3743
|
+
this.cwd = (0, filepath_1.normalizedJoin)(this.cwd, dirName);
|
|
3744
|
+
}
|
|
3745
|
+
async list(page, pageSize = 40, searchFileName) {
|
|
3746
|
+
if (pageSize > 100 || pageSize <= 0 || page < 0)
|
|
3747
|
+
throw new Error("Beyond the value limit");
|
|
3748
|
+
// Use withFileTypes option to get file type directly, reducing stat calls
|
|
3749
|
+
const dirents = await fs_extra_1.default.readdir(this.toAbsolutePath(), { withFileTypes: true });
|
|
3750
|
+
// Filter search results and create basic file info with type
|
|
3751
|
+
let filteredItems = dirents
|
|
3752
|
+
.filter((dirent) => !searchFileName || dirent.name.toLowerCase().includes(searchFileName.toLowerCase()))
|
|
3753
|
+
.map((dirent) => ({
|
|
3754
|
+
name: dirent.name,
|
|
3755
|
+
type: dirent.isFile() ? 1 : 0
|
|
3756
|
+
}));
|
|
3757
|
+
const total = filteredItems.length;
|
|
3758
|
+
// Sort: directories first (type 0), then files (type 1), both alphabetically
|
|
3759
|
+
filteredItems.sort((a, b) => {
|
|
3760
|
+
if (a.type !== b.type)
|
|
3761
|
+
return a.type - b.type;
|
|
3762
|
+
return a.name.localeCompare(b.name);
|
|
3763
|
+
});
|
|
3764
|
+
const sliceStart = page * pageSize;
|
|
3765
|
+
const sliceEnd = sliceStart + pageSize;
|
|
3766
|
+
const targetItems = filteredItems.slice(sliceStart, sliceEnd);
|
|
3767
|
+
const statPromises = targetItems.map(async (item) => {
|
|
3768
|
+
try {
|
|
3769
|
+
const info = await fs_extra_1.default.stat(this.toAbsolutePath(item.name));
|
|
3770
|
+
const mode = parseInt(String(parseInt(info.mode?.toString(8), 10)).slice(-3));
|
|
3771
|
+
return {
|
|
3772
|
+
name: item.name,
|
|
3773
|
+
size: info.isFile() ? info.size : 0,
|
|
3774
|
+
time: info.atime.toString(),
|
|
3775
|
+
mode,
|
|
3776
|
+
type: item.type
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
catch (error) {
|
|
3780
|
+
return {
|
|
3781
|
+
name: item.name,
|
|
3782
|
+
size: 0,
|
|
3783
|
+
time: new Date().toString(),
|
|
3784
|
+
mode: 0,
|
|
3785
|
+
type: item.type
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
});
|
|
3789
|
+
// Execute all stat operations concurrently
|
|
3790
|
+
const resultList = await Promise.all(statPromises);
|
|
3791
|
+
return {
|
|
3792
|
+
items: resultList,
|
|
3793
|
+
page,
|
|
3794
|
+
pageSize,
|
|
3795
|
+
total,
|
|
3796
|
+
absolutePath: this.toAbsolutePath()
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
async chmod(fileName, chmodValue, deep) {
|
|
3800
|
+
if (!this.check(fileName) || isNaN(parseInt(chmodValue)))
|
|
3801
|
+
throw new Error(ERROR_MSG_01());
|
|
3802
|
+
const absPath = this.toAbsolutePath(fileName);
|
|
3803
|
+
const defaultPath = "/bin/chmod";
|
|
3804
|
+
let file = "chmod";
|
|
3805
|
+
if (fs_extra_1.default.existsSync(defaultPath))
|
|
3806
|
+
file = defaultPath;
|
|
3807
|
+
const params = [];
|
|
3808
|
+
if (deep)
|
|
3809
|
+
params.push("-R");
|
|
3810
|
+
params.push(String(chmodValue));
|
|
3811
|
+
params.push(absPath);
|
|
3812
|
+
return await new mcsmanager_common_1.ProcessWrapper(file, params, ".", 60 * 10).start();
|
|
3813
|
+
}
|
|
3814
|
+
async readFile(fileName) {
|
|
3815
|
+
if (!this.check(fileName))
|
|
3816
|
+
throw new Error(ERROR_MSG_01());
|
|
3817
|
+
const absPath = this.toAbsolutePath(fileName);
|
|
3818
|
+
const buf = await fs_extra_1.default.readFile(absPath);
|
|
3819
|
+
const text = iconv_lite_1.default.decode(buf, this.fileCode || "utf-8");
|
|
3820
|
+
return text;
|
|
3821
|
+
}
|
|
3822
|
+
async writeFile(fileName, data) {
|
|
3823
|
+
if (!this.check(fileName))
|
|
3824
|
+
throw new Error(ERROR_MSG_01());
|
|
3825
|
+
const absPath = this.toAbsolutePath(fileName);
|
|
3826
|
+
const buf = iconv_lite_1.default.encode(data, this.fileCode || "utf-8");
|
|
3827
|
+
return await fs_extra_1.default.writeFile(absPath, buf);
|
|
3828
|
+
}
|
|
3829
|
+
async newFile(fileName) {
|
|
3830
|
+
// if (!FileManager.checkFileName(fileName)) throw new Error(ERROR_MSG_01());
|
|
3831
|
+
if (!this.checkPath(fileName))
|
|
3832
|
+
throw new Error(ERROR_MSG_01());
|
|
3833
|
+
const target = this.toAbsolutePath(fileName);
|
|
3834
|
+
await fs_extra_1.default.createFile(target);
|
|
3835
|
+
}
|
|
3836
|
+
/** Exclusive creation: never truncate an existing file or follow its symlink. */
|
|
3837
|
+
async createTextFile(target, text) {
|
|
3838
|
+
if (!this.checkPath(target))
|
|
3839
|
+
throw new Error(ERROR_MSG_01());
|
|
3840
|
+
const content = iconv_lite_1.default.encode(text, this.fileCode || "utf-8");
|
|
3841
|
+
if (content.length > 64 * 1024)
|
|
3842
|
+
throw new Error("File content exceeds 64 KiB");
|
|
3843
|
+
await fs_extra_1.default.writeFile(this.toAbsolutePath(target), content, { flag: "wx" });
|
|
3844
|
+
return true;
|
|
3845
|
+
}
|
|
3846
|
+
/** Unlink one regular file; never recursively remove directories. */
|
|
3847
|
+
async removeFile(target) {
|
|
3848
|
+
if (!this.check(target))
|
|
3849
|
+
throw new Error(ERROR_MSG_01());
|
|
3850
|
+
const absolute = this.toAbsolutePath(target);
|
|
3851
|
+
if (!(await fs_extra_1.default.lstat(absolute)).isFile())
|
|
3852
|
+
throw new Error("Expected a regular file");
|
|
3853
|
+
await fs_extra_1.default.unlink(absolute);
|
|
3854
|
+
return true;
|
|
3855
|
+
}
|
|
3856
|
+
async copy(target1, target2) {
|
|
3857
|
+
if (!this.checkPath(target2) || !this.check(target1))
|
|
3858
|
+
throw new Error(ERROR_MSG_01());
|
|
3859
|
+
const targetPath = this.toAbsolutePath(target1);
|
|
3860
|
+
target2 = this.toAbsolutePath(target2);
|
|
3861
|
+
return await fs_extra_1.default.copy(targetPath, target2);
|
|
3862
|
+
}
|
|
3863
|
+
mkdir(target) {
|
|
3864
|
+
if (!this.checkPath(target))
|
|
3865
|
+
throw new Error(ERROR_MSG_01());
|
|
3866
|
+
const targetPath = this.toAbsolutePath(target);
|
|
3867
|
+
return fs_extra_1.default.mkdirSync(targetPath);
|
|
3868
|
+
}
|
|
3869
|
+
async delete(target) {
|
|
3870
|
+
if (!this.check(target))
|
|
3871
|
+
throw new Error(ERROR_MSG_01());
|
|
3872
|
+
const targetPath = this.toAbsolutePath(target);
|
|
3873
|
+
return new Promise((r, j) => {
|
|
3874
|
+
fs_extra_1.default.remove(targetPath, (err) => {
|
|
3875
|
+
if (!err)
|
|
3876
|
+
r(true);
|
|
3877
|
+
else
|
|
3878
|
+
j(err);
|
|
3879
|
+
});
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
async move(target, destPath) {
|
|
3883
|
+
if (!this.check(target))
|
|
3884
|
+
throw new Error(ERROR_MSG_01());
|
|
3885
|
+
if (!this.checkPath(destPath))
|
|
3886
|
+
throw new Error(ERROR_MSG_01());
|
|
3887
|
+
const targetPath = this.toAbsolutePath(target);
|
|
3888
|
+
destPath = this.toAbsolutePath(destPath);
|
|
3889
|
+
await fs_extra_1.default.move(targetPath, destPath);
|
|
3890
|
+
}
|
|
3891
|
+
zipFileCheck(path) {
|
|
3892
|
+
const fileInfo = fs_extra_1.default.statSync(path);
|
|
3893
|
+
const MAX_ZIP_GB = (0, runtime_1.settings)().config.maxZipFileSize;
|
|
3894
|
+
if (fileInfo.size > 1024 * 1024 * 1024 * MAX_ZIP_GB)
|
|
3895
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_system_file.unzipLimit", { max: MAX_ZIP_GB }));
|
|
3896
|
+
}
|
|
3897
|
+
async unzip(sourceZip, destDir, code) {
|
|
3898
|
+
if (!code)
|
|
3899
|
+
code = this.fileCode;
|
|
3900
|
+
if (!this.check(sourceZip) || !this.checkPath(destDir))
|
|
3901
|
+
throw new Error(ERROR_MSG_01());
|
|
3902
|
+
this.zipFileCheck(this.toAbsolutePath(sourceZip));
|
|
3903
|
+
return await (0, runtime_1.archive)().decompress(this.toAbsolutePath(sourceZip), this.toAbsolutePath(destDir), code);
|
|
3904
|
+
}
|
|
3905
|
+
async previewArchive(sourceZip, code) {
|
|
3906
|
+
if (!this.check(sourceZip))
|
|
3907
|
+
throw new Error(ERROR_MSG_01());
|
|
3908
|
+
this.zipFileCheck(this.toAbsolutePath(sourceZip));
|
|
3909
|
+
return await (0, runtime_1.archive)().listArchiveEntries(this.toAbsolutePath(sourceZip), code || this.fileCode);
|
|
3910
|
+
}
|
|
3911
|
+
async zip(sourceZip, files, code) {
|
|
3912
|
+
if (!code)
|
|
3913
|
+
code = this.fileCode;
|
|
3914
|
+
if (!this.checkPath(sourceZip))
|
|
3915
|
+
throw new Error(ERROR_MSG_01());
|
|
3916
|
+
const MAX_ZIP_GB = (0, runtime_1.settings)().config.maxZipFileSize;
|
|
3917
|
+
const MAX_TOTAL_FIELS_SIZE = 1024 * 1024 * 1024 * MAX_ZIP_GB;
|
|
3918
|
+
const sourceZipPath = this.toAbsolutePath(sourceZip);
|
|
3919
|
+
const filesPath = [];
|
|
3920
|
+
let totalSize = 0;
|
|
3921
|
+
for (const iterator of files) {
|
|
3922
|
+
if (this.check(iterator)) {
|
|
3923
|
+
filesPath.push(this.toAbsolutePath(iterator));
|
|
3924
|
+
try {
|
|
3925
|
+
totalSize += fs_extra_1.default.statSync(this.toAbsolutePath(iterator))?.size;
|
|
3926
|
+
}
|
|
3927
|
+
catch (error) { }
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (totalSize > MAX_TOTAL_FIELS_SIZE)
|
|
3931
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_system_file.unzipLimit", { max: MAX_ZIP_GB }));
|
|
3932
|
+
return await (0, runtime_1.archive)().compress(sourceZipPath, filesPath, code);
|
|
3933
|
+
}
|
|
3934
|
+
async edit(target, data) {
|
|
3935
|
+
if (!this.check(target))
|
|
3936
|
+
throw new Error(ERROR_MSG_01());
|
|
3937
|
+
if (data || typeof data === "string") {
|
|
3938
|
+
return await this.writeFile(target, data);
|
|
3939
|
+
}
|
|
3940
|
+
else {
|
|
3941
|
+
const absPath = this.toAbsolutePath(target);
|
|
3942
|
+
const info = fs_extra_1.default.statSync(absPath);
|
|
3943
|
+
if (info.size > MAX_EDIT_SIZE) {
|
|
3944
|
+
throw new Error((0, runtime_1.$t)("TXT_CODE_system_file.execLimit"));
|
|
3945
|
+
}
|
|
3946
|
+
return await this.readFile(target);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
rename(target, newName) {
|
|
3950
|
+
if (!this.check(target))
|
|
3951
|
+
throw new Error(ERROR_MSG_01());
|
|
3952
|
+
if (!this.checkPath(newName))
|
|
3953
|
+
throw new Error(ERROR_MSG_01());
|
|
3954
|
+
const targetPath = this.toAbsolutePath(target);
|
|
3955
|
+
const newPath = this.toAbsolutePath(newName);
|
|
3956
|
+
fs_extra_1.default.renameSync(targetPath, newPath);
|
|
3957
|
+
}
|
|
3958
|
+
static checkFileName(fileName) {
|
|
3959
|
+
return (0, filepath_1.checkFileName)(fileName);
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
exports["default"] = FileManager;
|
|
3963
|
+
|
|
3964
|
+
|
|
3965
|
+
/***/ },
|
|
3966
|
+
|
|
3967
|
+
/***/ "./plugins/file/src/backend/upload_manager.ts"
|
|
3968
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
3969
|
+
|
|
3970
|
+
|
|
3971
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3972
|
+
const uuid_1 = __webpack_require__("uuid");
|
|
3973
|
+
class UploadManager {
|
|
3974
|
+
uploads = new Map();
|
|
3975
|
+
static timeout = 60 * 60 * 1000; // 1 hour
|
|
3976
|
+
cleanupTimer;
|
|
3977
|
+
startCleanup() {
|
|
3978
|
+
if (this.cleanupTimer)
|
|
3979
|
+
return;
|
|
3980
|
+
this.cleanupTimer = setInterval(async () => {
|
|
3981
|
+
await this.clearExpired();
|
|
3982
|
+
}, 5 * 60 * 1000 // 5 min
|
|
3983
|
+
);
|
|
3984
|
+
this.cleanupTimer.unref();
|
|
3985
|
+
}
|
|
3986
|
+
add(file) {
|
|
3987
|
+
this.startCleanup();
|
|
3988
|
+
const key = (0, uuid_1.v4)();
|
|
3989
|
+
file.id = key;
|
|
3990
|
+
this.uploads.set(key, file);
|
|
3991
|
+
return key;
|
|
3992
|
+
}
|
|
3993
|
+
getUploads() {
|
|
3994
|
+
return this.uploads.entries();
|
|
3995
|
+
}
|
|
3996
|
+
get(key) {
|
|
3997
|
+
return this.uploads.get(key);
|
|
3998
|
+
}
|
|
3999
|
+
delete(key) {
|
|
4000
|
+
return this.uploads.delete(key);
|
|
4001
|
+
}
|
|
4002
|
+
getByPath(path) {
|
|
4003
|
+
for (const [id, writer] of this.uploads.entries()) {
|
|
4004
|
+
if (writer.path == path) {
|
|
4005
|
+
return { id, writer };
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
return undefined;
|
|
4009
|
+
}
|
|
4010
|
+
async clearExpired() {
|
|
4011
|
+
const now = Date.now();
|
|
4012
|
+
for (const [key, writer] of this.uploads.entries()) {
|
|
4013
|
+
if (now - writer.lastUpdate > UploadManager.timeout) {
|
|
4014
|
+
try {
|
|
4015
|
+
await writer.stop();
|
|
4016
|
+
this.uploads.delete(key);
|
|
4017
|
+
}
|
|
4018
|
+
catch (e) {
|
|
4019
|
+
console.error(`Failed to stop upload writer for key ${key}:`, e);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
async exit() {
|
|
4025
|
+
clearInterval(this.cleanupTimer);
|
|
4026
|
+
this.cleanupTimer = undefined;
|
|
4027
|
+
for (const [key, writer] of this.uploads.entries()) {
|
|
4028
|
+
try {
|
|
4029
|
+
await writer.stop();
|
|
4030
|
+
this.uploads.delete(key);
|
|
4031
|
+
}
|
|
4032
|
+
catch (e) {
|
|
4033
|
+
console.error(`Failed to stop upload writer for key ${key}:`, e);
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
const uploadManager = new UploadManager();
|
|
4039
|
+
exports["default"] = uploadManager;
|
|
4040
|
+
|
|
4041
|
+
|
|
4042
|
+
/***/ },
|
|
4043
|
+
|
|
4044
|
+
/***/ "./plugins/file/src/backend/url.ts"
|
|
4045
|
+
(__unused_webpack_module, exports) {
|
|
4046
|
+
|
|
4047
|
+
|
|
4048
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4049
|
+
exports.checkSafeUrl = checkSafeUrl;
|
|
4050
|
+
function checkSafeUrl(url) {
|
|
4051
|
+
try {
|
|
4052
|
+
const urlObj = new URL(url);
|
|
4053
|
+
if (!["http:", "https:"].includes(urlObj.protocol) || urlObj.username || urlObj.password) {
|
|
4054
|
+
return false;
|
|
4055
|
+
}
|
|
4056
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
4057
|
+
// Reject IPv6 addresses (IPv6 addresses are wrapped in brackets by URL object)
|
|
4058
|
+
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
4059
|
+
return false;
|
|
4060
|
+
}
|
|
4061
|
+
// Reject IPv4 address format
|
|
4062
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
4063
|
+
if (ipv4Regex.test(hostname)) {
|
|
4064
|
+
return false;
|
|
4065
|
+
}
|
|
4066
|
+
// Reject local domains and loopback addresses
|
|
4067
|
+
const localDomains = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
|
|
4068
|
+
if (localDomains.includes(hostname)) {
|
|
4069
|
+
return false;
|
|
4070
|
+
}
|
|
4071
|
+
// Reject .local domains
|
|
4072
|
+
if (hostname.endsWith(".local")) {
|
|
4073
|
+
return false;
|
|
4074
|
+
}
|
|
4075
|
+
// Must contain at least one dot (ensure it's a valid domain, not a single word)
|
|
4076
|
+
if (!hostname.includes(".")) {
|
|
4077
|
+
return false;
|
|
4078
|
+
}
|
|
4079
|
+
// Domain must have at least a top-level domain (exclude single dot cases)
|
|
4080
|
+
const parts = hostname.split(".");
|
|
4081
|
+
if (parts.length < 2 || parts.some((part) => part.length === 0)) {
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
return true;
|
|
4085
|
+
}
|
|
4086
|
+
catch (error) {
|
|
4087
|
+
return false;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
|
|
4092
|
+
/***/ },
|
|
4093
|
+
|
|
4094
|
+
/***/ "./plugins/file/src/i18n/index.ts"
|
|
4095
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
4096
|
+
|
|
4097
|
+
|
|
4098
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4099
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4100
|
+
};
|
|
4101
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4102
|
+
exports.localeMessages = void 0;
|
|
4103
|
+
const de_DE_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/de_DE.json"));
|
|
4104
|
+
const en_US_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/en_US.json"));
|
|
4105
|
+
const es_ES_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/es_ES.json"));
|
|
4106
|
+
const fr_FR_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/fr_FR.json"));
|
|
4107
|
+
const ja_JP_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/ja_JP.json"));
|
|
4108
|
+
const ko_KR_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/ko_KR.json"));
|
|
4109
|
+
const pt_BR_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/pt_BR.json"));
|
|
4110
|
+
const ru_RU_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/ru_RU.json"));
|
|
4111
|
+
const th_TH_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/th_TH.json"));
|
|
4112
|
+
const tr_TR_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/tr_TR.json"));
|
|
4113
|
+
const zh_CN_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/zh_CN.json"));
|
|
4114
|
+
const zh_TW_json_1 = __importDefault(__webpack_require__("./plugins/file/src/i18n/zh_TW.json"));
|
|
4115
|
+
// The translated errors the file subsystem throws: path sandbox violations, the
|
|
4116
|
+
// per-instance task limit and the archive size limit. Registered at setup, so the
|
|
4117
|
+
// foundational `plugins/i18n` catalogue carries nothing file-specific.
|
|
4118
|
+
exports.localeMessages = {
|
|
4119
|
+
de_de: de_DE_json_1.default,
|
|
4120
|
+
en_us: en_US_json_1.default,
|
|
4121
|
+
es_es: es_ES_json_1.default,
|
|
4122
|
+
fr_fr: fr_FR_json_1.default,
|
|
4123
|
+
ja_jp: ja_JP_json_1.default,
|
|
4124
|
+
ko_kr: ko_KR_json_1.default,
|
|
4125
|
+
pt_br: pt_BR_json_1.default,
|
|
4126
|
+
ru_ru: ru_RU_json_1.default,
|
|
4127
|
+
th_th: th_TH_json_1.default,
|
|
4128
|
+
tr_tr: tr_TR_json_1.default,
|
|
4129
|
+
zh_cn: zh_CN_json_1.default,
|
|
4130
|
+
zh_tw: zh_TW_json_1.default
|
|
4131
|
+
};
|
|
4132
|
+
|
|
4133
|
+
|
|
4134
|
+
/***/ },
|
|
4135
|
+
|
|
4136
|
+
/***/ "events"
|
|
4137
|
+
(module) {
|
|
4138
|
+
|
|
4139
|
+
module.exports = require("events");
|
|
4140
|
+
|
|
4141
|
+
/***/ },
|
|
4142
|
+
|
|
4143
|
+
/***/ "fs-extra"
|
|
4144
|
+
(module) {
|
|
4145
|
+
|
|
4146
|
+
module.exports = require("fs-extra");
|
|
4147
|
+
|
|
4148
|
+
/***/ },
|
|
4149
|
+
|
|
4150
|
+
/***/ "iconv-lite"
|
|
4151
|
+
(module) {
|
|
4152
|
+
|
|
4153
|
+
module.exports = require("iconv-lite");
|
|
4154
|
+
|
|
4155
|
+
/***/ },
|
|
4156
|
+
|
|
4157
|
+
/***/ "os-utils"
|
|
4158
|
+
(module) {
|
|
4159
|
+
|
|
4160
|
+
module.exports = require("os-utils");
|
|
4161
|
+
|
|
4162
|
+
/***/ },
|
|
4163
|
+
|
|
4164
|
+
/***/ "proper-lockfile"
|
|
4165
|
+
(module) {
|
|
4166
|
+
|
|
4167
|
+
module.exports = require("proper-lockfile");
|
|
4168
|
+
|
|
4169
|
+
/***/ },
|
|
4170
|
+
|
|
4171
|
+
/***/ "uuid"
|
|
4172
|
+
(module) {
|
|
4173
|
+
|
|
4174
|
+
module.exports = require("uuid");
|
|
4175
|
+
|
|
4176
|
+
/***/ },
|
|
4177
|
+
|
|
4178
|
+
/***/ "child_process"
|
|
4179
|
+
(module) {
|
|
4180
|
+
|
|
4181
|
+
module.exports = require("child_process");
|
|
4182
|
+
|
|
4183
|
+
/***/ },
|
|
4184
|
+
|
|
4185
|
+
/***/ "crypto"
|
|
4186
|
+
(module) {
|
|
4187
|
+
|
|
4188
|
+
module.exports = require("crypto");
|
|
4189
|
+
|
|
4190
|
+
/***/ },
|
|
4191
|
+
|
|
4192
|
+
/***/ "fs"
|
|
4193
|
+
(module) {
|
|
4194
|
+
|
|
4195
|
+
module.exports = require("fs");
|
|
4196
|
+
|
|
4197
|
+
/***/ },
|
|
4198
|
+
|
|
4199
|
+
/***/ "module"
|
|
4200
|
+
(module) {
|
|
4201
|
+
|
|
4202
|
+
module.exports = require("module");
|
|
4203
|
+
|
|
4204
|
+
/***/ },
|
|
4205
|
+
|
|
4206
|
+
/***/ "net"
|
|
4207
|
+
(module) {
|
|
4208
|
+
|
|
4209
|
+
module.exports = require("net");
|
|
4210
|
+
|
|
4211
|
+
/***/ },
|
|
4212
|
+
|
|
4213
|
+
/***/ "node:crypto"
|
|
4214
|
+
(module) {
|
|
4215
|
+
|
|
4216
|
+
module.exports = require("node:crypto");
|
|
4217
|
+
|
|
4218
|
+
/***/ },
|
|
4219
|
+
|
|
4220
|
+
/***/ "node:fs"
|
|
4221
|
+
(module) {
|
|
4222
|
+
|
|
4223
|
+
module.exports = require("node:fs");
|
|
4224
|
+
|
|
4225
|
+
/***/ },
|
|
4226
|
+
|
|
4227
|
+
/***/ "node:module"
|
|
4228
|
+
(module) {
|
|
4229
|
+
|
|
4230
|
+
module.exports = require("node:module");
|
|
4231
|
+
|
|
4232
|
+
/***/ },
|
|
4233
|
+
|
|
4234
|
+
/***/ "node:os"
|
|
4235
|
+
(module) {
|
|
4236
|
+
|
|
4237
|
+
module.exports = require("node:os");
|
|
4238
|
+
|
|
4239
|
+
/***/ },
|
|
4240
|
+
|
|
4241
|
+
/***/ "node:path"
|
|
4242
|
+
(module) {
|
|
4243
|
+
|
|
4244
|
+
module.exports = require("node:path");
|
|
4245
|
+
|
|
4246
|
+
/***/ },
|
|
4247
|
+
|
|
4248
|
+
/***/ "node:url"
|
|
4249
|
+
(module) {
|
|
4250
|
+
|
|
4251
|
+
module.exports = require("node:url");
|
|
4252
|
+
|
|
4253
|
+
/***/ },
|
|
4254
|
+
|
|
4255
|
+
/***/ "os"
|
|
4256
|
+
(module) {
|
|
4257
|
+
|
|
4258
|
+
module.exports = require("os");
|
|
4259
|
+
|
|
4260
|
+
/***/ },
|
|
4261
|
+
|
|
4262
|
+
/***/ "path"
|
|
4263
|
+
(module) {
|
|
4264
|
+
|
|
4265
|
+
module.exports = require("path");
|
|
4266
|
+
|
|
4267
|
+
/***/ },
|
|
4268
|
+
|
|
4269
|
+
/***/ "process"
|
|
4270
|
+
(module) {
|
|
4271
|
+
|
|
4272
|
+
module.exports = require("process");
|
|
4273
|
+
|
|
4274
|
+
/***/ },
|
|
4275
|
+
|
|
4276
|
+
/***/ "./plugins/file/src/i18n/de_DE.json"
|
|
4277
|
+
(module) {
|
|
4278
|
+
|
|
4279
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"Instanz {{instanceUuid}} existiert nicht","TXT_CODE_file_router.unzipLimit":"Die maximale Anzahl gleichzeitiger Dekomprimierungsaufgaben wurde überschritten. Maximal sind {{maxFileTask}} zulässig; derzeit werden {{fileLock}} Aufgaben ausgeführt.","TXT_CODE_file_router_service.instanceNotExit":"Instanz {{uuid}} existiert nicht","TXT_CODE_system_file.execLimit":"Maximale Dateibearbeitungsgrenze überschritten","TXT_CODE_system_file.illegalAccess":"Falscher Zugriffspfad","TXT_CODE_system_file.unzipLimit":"Die Dateidekomprimierung unterstützt nur die Dekomprimierung von Dateien bis zu {{max}} GB. Wenn Sie die Obergrenze ändern müssen, gehen Sie bitte zur Datei data/Config/global.json.","TXT_CODE_e3a77a77":"Unbegrenzt","TXT_CODE_fde31068":"Upload -Rate für Benutzerdatei","TXT_CODE_d8d19932":"Begrenzung pro HTTP","TXT_CODE_785a0fcf":"Benutzerdatei -Download -Rate","TXT_CODE_b9fc604c":"Limit pro HTTP -Anforderung, Maximale Datei -Download -Geschwindigkeit","TXT_CODE_a15fca22":"Maximale Downloads von URL","TXT_CODE_ecaf78a2":"0 für unbegrenzt"}');
|
|
4280
|
+
|
|
4281
|
+
/***/ },
|
|
4282
|
+
|
|
4283
|
+
/***/ "./plugins/file/src/i18n/en_US.json"
|
|
4284
|
+
(module) {
|
|
4285
|
+
|
|
4286
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"Instance {{instanceUuid}} does not exist","TXT_CODE_file_router.unzipLimit":"Exceeded the maximum number of concurrent decompression tasks. The maximum allowed is {{maxFileTask}}, and currently, {{fileLock}} tasks are in progress. Please be patient.","TXT_CODE_file_router_service.instanceNotExit":"Instance {{uuid}} does not exist","TXT_CODE_system_file.execLimit":"Exceeded maximum file editing limit","TXT_CODE_system_file.illegalAccess":"Illegal access path","TXT_CODE_system_file.unzipLimit":"File decompression only supports file up to {{max}}GB. To change this limit, please go to the data/Config/global.json file.","TXT_CODE_e3a77a77":"Unlimited","TXT_CODE_fde31068":"User file upload rate","TXT_CODE_d8d19932":"Limit per HTTP request, maximum file upload speed","TXT_CODE_785a0fcf":"User file download rate","TXT_CODE_b9fc604c":"Limit per HTTP request, maximum file download speed","TXT_CODE_a15fca22":"Maximum of download from URL","TXT_CODE_ecaf78a2":"Set to 0 to unlimited"}');
|
|
4287
|
+
|
|
4288
|
+
/***/ },
|
|
4289
|
+
|
|
4290
|
+
/***/ "./plugins/file/src/i18n/es_ES.json"
|
|
4291
|
+
(module) {
|
|
4292
|
+
|
|
4293
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"La instancia {{instanceUuid}} no existe","TXT_CODE_file_router.unzipLimit":"Se ha excedido el número máximo de tareas de descompresión simultáneas. El máximo permitido es {{maxFileTask}}. Actualmente hay {{fileLock}} tareas en progreso.","TXT_CODE_file_router_service.instanceNotExit":"La instancia {{uuid}} no existe","TXT_CODE_system_file.execLimit":"Se superó el límite máximo de edición de archivos","TXT_CODE_system_file.illegalAccess":"Ruta de acceso incorrecta","TXT_CODE_system_file.unzipLimit":"La descompresión de archivos solo admite la descompresión de archivos de hasta {{max}} GB. Si necesita cambiar el límite superior, vaya al archivo data/Config/global.json.","TXT_CODE_e3a77a77":"Ilimitado","TXT_CODE_fde31068":"Tasa de carga de archivo de usuario","TXT_CODE_d8d19932":"Límite por solicitud HTTP, velocidad máxima de carga de archivo","TXT_CODE_785a0fcf":"Tasa de descarga de archivos de usuario","TXT_CODE_b9fc604c":"Límite por solicitud HTTP, velocidad máxima de descarga de archivo","TXT_CODE_a15fca22":"Máximo de descargas desde URL","TXT_CODE_ecaf78a2":"Establecer en 0 para ilimitado"}');
|
|
4294
|
+
|
|
4295
|
+
/***/ },
|
|
4296
|
+
|
|
4297
|
+
/***/ "./plugins/file/src/i18n/fr_FR.json"
|
|
4298
|
+
(module) {
|
|
4299
|
+
|
|
4300
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"L\'instance {{instanceUuid}} n\'existe pas","TXT_CODE_file_router.unzipLimit":"Le nombre maximum de tâches de décompression simultanées a été dépassé. Le maximum autorisé est de {{maxFileTask}}. Il y a actuellement {{fileLock}} tâches en cours.","TXT_CODE_file_router_service.instanceNotExit":"L\'instance {{uuid}} n\'existe pas","TXT_CODE_system_file.execLimit":"Limite maximale de modification de fichier dépassée","TXT_CODE_system_file.illegalAccess":"Mauvais chemin d\'accès","TXT_CODE_system_file.unzipLimit":"La décompression de fichiers ne prend en charge que la décompression de fichiers jusqu\'à {{max}} Go. Si vous devez modifier la limite supérieure, veuillez accéder au fichier data/Config/global.json.","TXT_CODE_e3a77a77":"Illimité","TXT_CODE_fde31068":"Taux de téléchargement de fichiers utilisateur","TXT_CODE_d8d19932":"Limite par demande HTTP, vitesse maximale de téléchargement de fichiers","TXT_CODE_785a0fcf":"Taux de téléchargement des fichiers utilisateur","TXT_CODE_b9fc604c":"Limite par demande HTTP, vitesse de téléchargement de fichiers maximum","TXT_CODE_a15fca22":"Maximum de téléchargements depuis l\'URL","TXT_CODE_ecaf78a2":"Mettre 0 pour illimité"}');
|
|
4301
|
+
|
|
4302
|
+
/***/ },
|
|
4303
|
+
|
|
4304
|
+
/***/ "./plugins/file/src/i18n/ja_JP.json"
|
|
4305
|
+
(module) {
|
|
4306
|
+
|
|
4307
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"インスタンス {{instanceUuid}} は存在しません","TXT_CODE_file_router.unzipLimit":"同時解凍タスクの最大数は {{maxFileTask}} を超えています。現在進行中のタスクは {{fileLock}} です。しばらくお待ちください。","TXT_CODE_file_router_service.instanceNotExit":"インスタンス {{uuid}} は存在しません","TXT_CODE_system_file.execLimit":"ファイル編集の最大制限を超えました","TXT_CODE_system_file.illegalAccess":"間違ったアクセスパス","TXT_CODE_system_file.unzipLimit":"ファイルの解凍では、最大 {{max}} GB のファイルの解凍のみがサポートされます。上限を変更する必要がある場合は、data/Config/global.json ファイルに移動してください。","TXT_CODE_e3a77a77":"無制限","TXT_CODE_fde31068":"ユーザーファイルのアップロードレート","TXT_CODE_d8d19932":"HTTPリクエストごとに制限、最大ファイルアップロード速度","TXT_CODE_785a0fcf":"ユーザーファイルのダウンロードレート","TXT_CODE_b9fc604c":"HTTP要求ごとに制限、最大ファイルのダウンロード速度","TXT_CODE_a15fca22":"URLからの最大ダウンロード数","TXT_CODE_ecaf78a2":"0で無制限に設定"}');
|
|
4308
|
+
|
|
4309
|
+
/***/ },
|
|
4310
|
+
|
|
4311
|
+
/***/ "./plugins/file/src/i18n/ko_KR.json"
|
|
4312
|
+
(module) {
|
|
4313
|
+
|
|
4314
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"{{instanceUuid}} 인스턴스가 존재하지 않습니다.","TXT_CODE_file_router.unzipLimit":"동시 압축 해제 작업의 최대 개수는 {{maxFileTask}}개를 초과했습니다. 현재 진행 중인 작업은 {{fileLock}}개입니다.","TXT_CODE_file_router_service.instanceNotExit":"{{uuid}} 인스턴스가 존재하지 않습니다.","TXT_CODE_system_file.execLimit":"최대 파일 편집 제한을 초과했습니다.","TXT_CODE_system_file.illegalAccess":"잘못된 액세스 경로","TXT_CODE_system_file.unzipLimit":"파일 압축 해제는 최대 {{max}}GB까지의 파일 압축 해제만 지원합니다. 상한을 변경해야 하는 경우 data/Config/global.json 파일로 이동하세요.","TXT_CODE_e3a77a77":"제한 없는","TXT_CODE_fde31068":"사용자 파일 업로드 속도","TXT_CODE_d8d19932":"HTTP 요청 당 제한, 최대 파일 업로드 속도","TXT_CODE_785a0fcf":"사용자 파일 다운로드 속도","TXT_CODE_b9fc604c":"HTTP 요청 당 제한, 최대 파일 다운로드 속도","TXT_CODE_a15fca22":"URL 최대 다운로드 수","TXT_CODE_ecaf78a2":"0으로 설정 시 무제한"}');
|
|
4315
|
+
|
|
4316
|
+
/***/ },
|
|
4317
|
+
|
|
4318
|
+
/***/ "./plugins/file/src/i18n/pt_BR.json"
|
|
4319
|
+
(module) {
|
|
4320
|
+
|
|
4321
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"A instância {{instanceUuid}} não existe","TXT_CODE_file_router.unzipLimit":"Excedido o número máximo de tarefas de descompressão simultâneas. O máximo permitido é {{maxFileTask}}, e atualmente, {{fileLock}} tarefas estão em andamento. Por favor, seja paciente.","TXT_CODE_file_router_service.instanceNotExit":"A instância {{uuid}} não existe","TXT_CODE_system_file.execLimit":"Limite máximo de edição de arquivos excedido","TXT_CODE_system_file.illegalAccess":"Caminho de acesso ilegal","TXT_CODE_system_file.unzipLimit":"A descompactação de arquivos suporta apenas arquivos de até {{max}}GB. Para alterar esse limite, vá até o arquivo data/Config/global.json.","TXT_CODE_e3a77a77":"Ilimitado","TXT_CODE_fde31068":"Taxa de upload de arquivos do usuário","TXT_CODE_d8d19932":"Limite por solicitação HTTP, velocidade máxima de upload do arquivo","TXT_CODE_785a0fcf":"Taxa de download de arquivos do usuário","TXT_CODE_b9fc604c":"Limite por solicitação HTTP, velocidade máxima de download de arquivo","TXT_CODE_a15fca22":"Máximo de downloads por URL","TXT_CODE_ecaf78a2":"Defina 0 para ilimitado"}');
|
|
4322
|
+
|
|
4323
|
+
/***/ },
|
|
4324
|
+
|
|
4325
|
+
/***/ "./plugins/file/src/i18n/ru_RU.json"
|
|
4326
|
+
(module) {
|
|
4327
|
+
|
|
4328
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"Экземпляр {{instanceUuid}} не существует","TXT_CODE_file_router.unzipLimit":"Превышено максимальное количество одновременных задач разархивирования. Максимально допустимое количество — {{maxFileTask}}, в настоящее время выполняется {{fileLock}} задач. Пожалуйста, будьте терпеливы.","TXT_CODE_file_router_service.instanceNotExit":"Экземпляр {{uuid}} не существует","TXT_CODE_system_file.execLimit":"Превышен лимит редактирования файлов","TXT_CODE_system_file.illegalAccess":"Незаконный путь доступа","TXT_CODE_system_file.unzipLimit":"Распаковка файлов поддерживается только для файлов до {{max}}GB. Чтобы изменить этот лимит, пожалуйста, перейдите в файл data/Config/global.json.","TXT_CODE_e3a77a77":"Неограничено","TXT_CODE_fde31068":"Пользовательская скорость загрузки файлов","TXT_CODE_d8d19932":"Ограничение на HTTP -запрос, максимальная скорость загрузки файлов","TXT_CODE_785a0fcf":"Пользовательская скорость загрузки файлов","TXT_CODE_b9fc604c":"Ограничение на HTTP -запрос, максимальная скорость загрузки файлов","TXT_CODE_a15fca22":"Максимум загрузок по URL","TXT_CODE_ecaf78a2":"0 - без ограничений"}');
|
|
4329
|
+
|
|
4330
|
+
/***/ },
|
|
4331
|
+
|
|
4332
|
+
/***/ "./plugins/file/src/i18n/th_TH.json"
|
|
4333
|
+
(module) {
|
|
4334
|
+
|
|
4335
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"ไม่พบอินสแตนซ์ {{instanceUuid}}","TXT_CODE_file_router.unzipLimit":"เกินจำนวนงานคลายการบีบอัดพร้อมกันสูงสุด อนุญาตสูงสุด {{maxFileTask}} งาน และขณะนี้มี {{fileLock}} งานกำลังดำเนินการ กรุณารอสักครู่","TXT_CODE_file_router_service.instanceNotExit":"ไม่พบอินสแตนซ์ {{uuid}}","TXT_CODE_system_file.execLimit":"เกินขีดจำกัดการแก้ไขไฟล์สูงสุด","TXT_CODE_system_file.illegalAccess":"เส้นทางการเข้าถึงไม่ถูกต้อง","TXT_CODE_system_file.unzipLimit":"การแตกไฟล์รองรับไฟล์ขนาดไม่เกิน {{max}}GB หากต้องการเปลี่ยนขีดจำกัดนี้ กรุณาไปที่ไฟล์ data/Config/global.json","TXT_CODE_e3a77a77":"ไม่จำกัด","TXT_CODE_fde31068":"อัตราการอัปโหลดไฟล์ผู้ใช้","TXT_CODE_d8d19932":"ขีด จำกัด ต่อคำขอ HTTP ความเร็วในการอัปโหลดไฟล์สูงสุด","TXT_CODE_785a0fcf":"อัตราการดาวน์โหลดไฟล์ผู้ใช้","TXT_CODE_b9fc604c":"ขีด จำกัด ต่อคำขอ HTTP ความเร็วในการดาวน์โหลดไฟล์สูงสุด","TXT_CODE_a15fca22":"จำนวนสูงสุดของการดาวน์โหลดจาก URL","TXT_CODE_ecaf78a2":"ตั้งเป็น 0 สำหรับไม่จำกัด"}');
|
|
4336
|
+
|
|
4337
|
+
/***/ },
|
|
4338
|
+
|
|
4339
|
+
/***/ "./plugins/file/src/i18n/tr_TR.json"
|
|
4340
|
+
(module) {
|
|
4341
|
+
|
|
4342
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"Örnek {{instanceUuid}} mevcut değil","TXT_CODE_file_router.unzipLimit":"Eşzamanlı dekompresyon görevlerinin maksimum sayısı aşıldı. İzin verilen maksimum sayı {{maxFileTask}} ve şu anda {{fileLock}} görevleri devam ediyor. Lütfen sabırlı olun.","TXT_CODE_file_router_service.instanceNotExit":"Örnek {{uuid}} mevcut değil","TXT_CODE_system_file.execLimit":"Maksimum dosya düzenleme sınırı aşıldı","TXT_CODE_system_file.illegalAccess":"Yasadışı erişim yolu","TXT_CODE_system_file.unzipLimit":"Dosya açma yalnızca {{max}}GB\'a kadar olan dosyaları destekler. Bu sınırı değiştirmek için lütfen data/Config/global.json dosyasına gidin.","TXT_CODE_e3a77a77":"Sınırsız","TXT_CODE_fde31068":"Kullanıcı Dosyası Yükleme Oranı","TXT_CODE_d8d19932":"HTTP isteği başına sınır, maksimum dosya yükleme hızı","TXT_CODE_785a0fcf":"Kullanıcı Dosyası İndirme Oranı","TXT_CODE_b9fc604c":"HTTP İsteği Başına Limit, Maksimum Dosya İndirme Hızı","TXT_CODE_a15fca22":"URL\'den maksimum indirme","TXT_CODE_ecaf78a2":"Sınırsız için 0\'a ayarla"}');
|
|
4343
|
+
|
|
4344
|
+
/***/ },
|
|
4345
|
+
|
|
4346
|
+
/***/ "./plugins/file/src/i18n/zh_CN.json"
|
|
4347
|
+
(module) {
|
|
4348
|
+
|
|
4349
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"实例 {{instanceUuid}} 不存在","TXT_CODE_file_router.unzipLimit":"超出最大同时解压缩任务量,最大准许{{maxFileTask}}个,目前有{{fileLock}}个任务正在进行,请耐心等待","TXT_CODE_file_router_service.instanceNotExit":"实例 {{uuid}} 不存在","TXT_CODE_system_file.execLimit":"超出最大文件编辑限制","TXT_CODE_system_file.illegalAccess":"非法访问路径","TXT_CODE_system_file.unzipLimit":"文件解压缩只支持最大 {{max}}GB 文件的解压缩,如需改变上限请前往 data/Config/global.json 文件","TXT_CODE_e3a77a77":"无限制","TXT_CODE_fde31068":"用户上传文件速率","TXT_CODE_d8d19932":"限制每个 HTTP 请求,上传文件最大速度","TXT_CODE_785a0fcf":"用户下载文件速率","TXT_CODE_b9fc604c":"限制每个 HTTP 请求,下载文件最大速度","TXT_CODE_a15fca22":"从URL下载文件最大任务数","TXT_CODE_ecaf78a2":"设置为 0 为无限制"}');
|
|
4350
|
+
|
|
4351
|
+
/***/ },
|
|
4352
|
+
|
|
4353
|
+
/***/ "./plugins/file/src/i18n/zh_TW.json"
|
|
4354
|
+
(module) {
|
|
4355
|
+
|
|
4356
|
+
module.exports = /*#__PURE__*/JSON.parse('{"TXT_CODE_file_router.instanceNotExist":"實例 {{instanceUuid}} 不存在","TXT_CODE_file_router.unzipLimit":"超出最大同時解壓縮任務量,最大允許{{maxFileTask}}個,目前有{{fileLock}}個任務正在進行,請耐心等待","TXT_CODE_file_router_service.instanceNotExit":"實例 {{uuid}} 不存在","TXT_CODE_system_file.execLimit":"超出最大檔案編輯限制","TXT_CODE_system_file.illegalAccess":"錯誤存取路徑","TXT_CODE_system_file.unzipLimit":"檔案解壓縮只支援最大 {{max}}GB 檔案的解壓縮,如需改變上限請前往 data/Config/global.json 檔案","TXT_CODE_e3a77a77":"無限制","TXT_CODE_fde31068":"使用者上傳檔案速率","TXT_CODE_d8d19932":"限制每個 HTTP 請求,上傳檔案最大速度","TXT_CODE_785a0fcf":"使用者下載檔案速率","TXT_CODE_b9fc604c":"限制每個 HTTP 請求,下載檔案最大速度","TXT_CODE_a15fca22":"網址下載上限","TXT_CODE_ecaf78a2":"設為 0 表示無限制"}');
|
|
4357
|
+
|
|
4358
|
+
/***/ }
|
|
4359
|
+
|
|
4360
|
+
/******/ });
|
|
4361
|
+
/************************************************************************/
|
|
4362
|
+
/******/ // The module cache
|
|
4363
|
+
/******/ var __webpack_module_cache__ = {};
|
|
4364
|
+
/******/
|
|
4365
|
+
/******/ // The require function
|
|
4366
|
+
/******/ function __webpack_require__(moduleId) {
|
|
4367
|
+
/******/ // Check if module is in cache
|
|
4368
|
+
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
4369
|
+
/******/ if (cachedModule !== undefined) {
|
|
4370
|
+
/******/ return cachedModule.exports;
|
|
4371
|
+
/******/ }
|
|
4372
|
+
/******/ // Create a new module (and put it into the cache)
|
|
4373
|
+
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
4374
|
+
/******/ // no module.id needed
|
|
4375
|
+
/******/ // no module.loaded needed
|
|
4376
|
+
/******/ exports: {}
|
|
4377
|
+
/******/ };
|
|
4378
|
+
/******/
|
|
4379
|
+
/******/ // Execute the module function
|
|
4380
|
+
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
4381
|
+
/******/
|
|
4382
|
+
/******/ // Return the exports of the module
|
|
4383
|
+
/******/ return module.exports;
|
|
4384
|
+
/******/ }
|
|
4385
|
+
/******/
|
|
4386
|
+
/************************************************************************/
|
|
4387
|
+
/******/
|
|
4388
|
+
/******/ // startup
|
|
4389
|
+
/******/ // Load entry module and return exports
|
|
4390
|
+
/******/ // This entry module is referenced by other modules so it can't be inlined
|
|
4391
|
+
/******/ var __webpack_exports__ = __webpack_require__("./plugins/file/src/backend/index.ts");
|
|
4392
|
+
/******/ module.exports = __webpack_exports__;
|
|
4393
|
+
/******/
|
|
4394
|
+
/******/ })()
|
|
4395
|
+
;
|