hfs 0.57.26 → 0.57.27-beta2
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/admin/assets/{index-CHMhmNYS.js → index-DSkhXlWX.js} +103 -103
- package/admin/assets/{sha512-Dsv49IAs.js → sha512-CoddZKeD.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{index-legacy-BI8icwFp.js → index-legacy-C4Gclsbz.js} +4 -4
- package/frontend/assets/{sha512-legacy-D82alHBq.js → sha512-legacy-C-FMk44u.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +4 -3
- package/src/QuickZipStream.js +11 -8
- package/src/SendList.js +10 -8
- package/src/ThrottledStream.js +8 -6
- package/src/acme.js +5 -7
- package/src/adminApis.js +6 -7
- package/src/api.accounts.js +4 -6
- package/src/api.auth.js +7 -11
- package/src/api.get_file_list.js +11 -16
- package/src/api.lang.js +1 -1
- package/src/api.monitor.js +2 -3
- package/src/api.net.js +1 -2
- package/src/api.plugins.js +8 -10
- package/src/api.vfs.js +14 -19
- package/src/apiMiddleware.js +4 -4
- package/src/auth.js +6 -9
- package/src/basicWeb.js +1 -2
- package/src/block.js +1 -1
- package/src/commands.js +9 -9
- package/src/comments.js +4 -5
- package/src/config.js +8 -9
- package/src/connections.js +11 -6
- package/src/consoleLog.js +6 -6
- package/src/const.js +4 -4
- package/src/cross.js +19 -21
- package/src/customHtml.js +3 -4
- package/src/ddns.js +4 -4
- package/src/debounceAsync.js +3 -3
- package/src/errorPages.js +2 -3
- package/src/events.js +7 -10
- package/src/fileAttr.js +6 -6
- package/src/first.js +13 -8
- package/src/frontEndApis.js +5 -5
- package/src/geo.js +3 -5
- package/src/github.js +14 -17
- package/src/i18n.js +3 -4
- package/src/icons.js +1 -1
- package/src/index.js +2 -3
- package/src/lang.js +5 -6
- package/src/listen.js +25 -28
- package/src/log.js +18 -17
- package/src/middlewares.js +12 -15
- package/src/misc.js +9 -11
- package/src/nat.js +14 -16
- package/src/outboundProxy.js +7 -7
- package/src/perm.js +7 -12
- package/src/persistence.js +1 -1
- package/src/plugins.js +62 -73
- package/src/roots.js +1 -2
- package/src/selfCheck.js +5 -4
- package/src/serveFile.js +6 -7
- package/src/serveGuiAndSharedFiles.js +7 -9
- package/src/serveGuiFiles.js +11 -20
- package/src/stat.js +40 -0
- package/src/statWorker.js +11 -0
- package/src/throttler.js +5 -7
- package/src/update.js +7 -7
- package/src/upload.js +11 -13
- package/src/util-files.js +42 -32
- package/src/util-http.js +80 -29
- package/src/util-os.js +2 -4
- package/src/vfs.js +77 -68
- package/src/walkDir.js +9 -9
- package/src/watchLoad.js +4 -4
- package/src/zip.js +5 -6
package/src/plugins.js
CHANGED
|
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
exports.PLUGIN_MAIN_FILE = exports.pluginsConfig = exports.suspendPlugins = exports.enablePlugins = exports.pluginsWatcher = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.STORAGE_FOLDER = exports.DELETE_ME_SUFFIX = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
40
|
+
exports.PLUGIN_MAIN_FILE = exports.pluginsConfig = exports.suspendPlugins = exports.enablePlugins = exports.pluginsWatcher = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.pluginsScanned = exports.STORAGE_FOLDER = exports.DELETE_ME_SUFFIX = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
41
41
|
exports.isPluginRunning = isPluginRunning;
|
|
42
42
|
exports.isPluginEnabled = isPluginEnabled;
|
|
43
43
|
exports.enablePlugin = enablePlugin;
|
|
@@ -83,6 +83,7 @@ exports.PATH = 'plugins';
|
|
|
83
83
|
exports.DISABLING_SUFFIX = '-disabled';
|
|
84
84
|
exports.DELETE_ME_SUFFIX = '-delete_me' + exports.DISABLING_SUFFIX;
|
|
85
85
|
exports.STORAGE_FOLDER = 'storage';
|
|
86
|
+
exports.pluginsScanned = (0, misc_1.pendingPromise)();
|
|
86
87
|
setTimeout(async () => {
|
|
87
88
|
for (const x of await (0, promises_1.readdir)(exports.PATH))
|
|
88
89
|
if (x.endsWith(exports.DELETE_ME_SUFFIX))
|
|
@@ -90,8 +91,7 @@ setTimeout(async () => {
|
|
|
90
91
|
}, 1000);
|
|
91
92
|
const plugins = new Map(); // now that we care about the order, a simple object wouldn't do, because numbers are always at the beginning
|
|
92
93
|
function isPluginRunning(id) {
|
|
93
|
-
|
|
94
|
-
return Boolean((_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.started);
|
|
94
|
+
return Boolean(plugins.get(id)?.started);
|
|
95
95
|
}
|
|
96
96
|
function isPluginEnabled(id, considerSuspension = false) {
|
|
97
97
|
return (!considerSuspension || !exports.suspendPlugins.get()) && exports.enablePlugins.get().includes(id);
|
|
@@ -113,8 +113,7 @@ async function stopPlugin(id) {
|
|
|
113
113
|
await waitRunning(id, false);
|
|
114
114
|
}
|
|
115
115
|
async function startPlugin(id) {
|
|
116
|
-
|
|
117
|
-
if ((_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.isTheme)
|
|
116
|
+
if (getPluginInfo(id)?.isTheme)
|
|
118
117
|
await Promise.all(mapPlugins((pl, id) => pl.isTheme && stopPlugin(id)));
|
|
119
118
|
enablePlugin(id);
|
|
120
119
|
await waitRunning(id);
|
|
@@ -132,7 +131,7 @@ function setPluginConfig(id, changes) {
|
|
|
132
131
|
exports.pluginsConfig.set(allConfigs => {
|
|
133
132
|
const fields = getPluginConfigFields(id);
|
|
134
133
|
const oldConfig = allConfigs[id];
|
|
135
|
-
const newConfig = changes && lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) =>
|
|
134
|
+
const newConfig = changes && lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) => v != null && !(0, misc_1.same)(v, fields?.[k]?.defaultValue));
|
|
136
135
|
return { ...allConfigs, [id]: lodash_1.default.isEmpty(newConfig) ? undefined : newConfig };
|
|
137
136
|
});
|
|
138
137
|
}
|
|
@@ -146,16 +145,13 @@ function findPluginByRepo(repo) {
|
|
|
146
145
|
return pl;
|
|
147
146
|
return lodash_1.default.find(inactivePlugins, match);
|
|
148
147
|
function match(rec) {
|
|
149
|
-
|
|
150
|
-
return repo === ((_b = (_a = rec === null || rec === void 0 ? void 0 : rec.repo) === null || _a === void 0 ? void 0 : _a.main) !== null && _b !== void 0 ? _b : rec === null || rec === void 0 ? void 0 : rec.repo);
|
|
148
|
+
return repo === (rec?.repo?.main ?? rec?.repo);
|
|
151
149
|
}
|
|
152
150
|
}
|
|
153
151
|
function getPluginConfigFields(id) {
|
|
154
|
-
|
|
155
|
-
return (_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.getData().config;
|
|
152
|
+
return plugins.get(id)?.getData().config;
|
|
156
153
|
}
|
|
157
154
|
async function initPlugin(pl, morePassedToInit) {
|
|
158
|
-
var _a;
|
|
159
155
|
const undoEvents = [];
|
|
160
156
|
const timeouts = [];
|
|
161
157
|
const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
|
|
@@ -165,12 +161,12 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
165
161
|
return ret;
|
|
166
162
|
}
|
|
167
163
|
})));
|
|
168
|
-
const res = await
|
|
164
|
+
const res = await pl.init?.({
|
|
169
165
|
Const, require,
|
|
170
166
|
// intercept all subscriptions, so to be able to undo them on unload
|
|
171
167
|
events: controlledEvents,
|
|
172
168
|
log: console.log,
|
|
173
|
-
setError(msg) { setError(
|
|
169
|
+
setError(msg) { setError(morePassedToInit?.id || 'server_code', msg); },
|
|
174
170
|
getHfsConfig: config_1.getConfig,
|
|
175
171
|
setInterval() {
|
|
176
172
|
const ret = setInterval(...arguments);
|
|
@@ -194,7 +190,7 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
194
190
|
customApiCall, notifyClient: frontEndApis_1.notifyClient, addBlock: block_1.addBlock, ctxBelongsTo: perm_1.ctxBelongsTo, getConnections: connections_1.getConnections,
|
|
195
191
|
getCurrentUsername: auth_1.getCurrentUsername, getAccount: perm_1.getAccount, getUsernames: perm_1.getUsernames, addAccount: perm_1.addAccount, delAccount: perm_1.delAccount, updateAccount: perm_1.updateAccount, renameAccount: perm_1.renameAccount,
|
|
196
192
|
...morePassedToInit
|
|
197
|
-
})
|
|
193
|
+
});
|
|
198
194
|
Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
|
|
199
195
|
(0, misc_1.patchKey)(pl, 'unload', was => () => {
|
|
200
196
|
for (const x of timeouts)
|
|
@@ -215,15 +211,13 @@ function warnOnce(msg) {
|
|
|
215
211
|
console.log('Warning: ' + msg);
|
|
216
212
|
}
|
|
217
213
|
const pluginsMiddleware = async (ctx, next) => {
|
|
218
|
-
var _a;
|
|
219
214
|
const after = {};
|
|
220
215
|
// run middleware plugins
|
|
221
216
|
let lastStatus = ctx.status;
|
|
222
217
|
let lastBody = ctx.body;
|
|
223
218
|
await Promise.all(mapPlugins(async (pl, id) => {
|
|
224
|
-
var _a;
|
|
225
219
|
try {
|
|
226
|
-
const res = await
|
|
220
|
+
const res = await pl.middleware?.(ctx);
|
|
227
221
|
if (lastStatus !== ctx.status || lastBody !== ctx.body) {
|
|
228
222
|
console.debug("plugin changed response", id);
|
|
229
223
|
lastStatus = ctx.status;
|
|
@@ -248,7 +242,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
248
242
|
const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
|
|
249
243
|
const name = a.shift();
|
|
250
244
|
if (plugins.has(name)) { // do it only if the plugin is loaded
|
|
251
|
-
if (
|
|
245
|
+
if (ctx.get('referer')?.endsWith('/'))
|
|
252
246
|
ctx.state.considerAsGui = true;
|
|
253
247
|
await (0, serveFile_1.serveFile)(ctx, plugins.get(name).folder + '/public/' + a.join('/'), const_1.MIME_AUTO);
|
|
254
248
|
}
|
|
@@ -265,7 +259,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
265
259
|
printError(id, e);
|
|
266
260
|
}
|
|
267
261
|
function printError(id, e) {
|
|
268
|
-
console.log(`error middleware plugin ${id}: ${
|
|
262
|
+
console.log(`error middleware plugin ${id}: ${e?.message || e}`);
|
|
269
263
|
console.debug(e);
|
|
270
264
|
}
|
|
271
265
|
};
|
|
@@ -275,12 +269,18 @@ events_1.default.once('app', () => Object.assign(index_1.app.context, {
|
|
|
275
269
|
stop() { return this.isStopped = true; }
|
|
276
270
|
}));
|
|
277
271
|
class Plugin {
|
|
272
|
+
id;
|
|
273
|
+
folder;
|
|
274
|
+
data;
|
|
275
|
+
onUnload;
|
|
276
|
+
started = new Date();
|
|
277
|
+
icons;
|
|
278
|
+
log;
|
|
278
279
|
constructor(id, folder, data, onUnload) {
|
|
279
280
|
this.id = id;
|
|
280
281
|
this.folder = folder;
|
|
281
282
|
this.data = data;
|
|
282
283
|
this.onUnload = onUnload;
|
|
283
|
-
this.started = new Date();
|
|
284
284
|
if (!data)
|
|
285
285
|
throw 'invalid data';
|
|
286
286
|
this.log = [];
|
|
@@ -313,41 +313,36 @@ class Plugin {
|
|
|
313
313
|
plugins.set(k, temp);
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
|
-
get version() {
|
|
317
|
-
get description() {
|
|
318
|
-
get apiRequired() {
|
|
319
|
-
get isTheme() {
|
|
320
|
-
get repo() {
|
|
321
|
-
get depend() {
|
|
322
|
-
get afterPlugin() {
|
|
323
|
-
get beforePlugin() {
|
|
316
|
+
get version() { return this.data?.version; }
|
|
317
|
+
get description() { return this.data?.description; }
|
|
318
|
+
get apiRequired() { return this.data?.apiRequired; }
|
|
319
|
+
get isTheme() { return this.data?.isTheme; }
|
|
320
|
+
get repo() { return this.data?.repo; }
|
|
321
|
+
get depend() { return this.data?.depend; }
|
|
322
|
+
get afterPlugin() { return this.data?.afterPlugin; }
|
|
323
|
+
get beforePlugin() { return this.data?.beforePlugin; }
|
|
324
324
|
get middleware() {
|
|
325
|
-
|
|
326
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
|
|
325
|
+
return this.data?.middleware;
|
|
327
326
|
}
|
|
328
327
|
get frontend_css() {
|
|
329
|
-
|
|
330
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_css;
|
|
328
|
+
return this.data?.frontend_css;
|
|
331
329
|
}
|
|
332
330
|
get frontend_js() {
|
|
333
|
-
|
|
334
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_js;
|
|
331
|
+
return this.data?.frontend_js;
|
|
335
332
|
}
|
|
336
333
|
get onDirEntry() {
|
|
337
|
-
|
|
338
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
|
|
334
|
+
return this.data?.onDirEntry;
|
|
339
335
|
}
|
|
340
336
|
getData() {
|
|
341
337
|
return this.data;
|
|
342
338
|
}
|
|
343
339
|
async unload(reloading = false) {
|
|
344
|
-
var _a, _b;
|
|
345
340
|
if (!this.started)
|
|
346
341
|
return;
|
|
347
342
|
this.started = null;
|
|
348
343
|
const { id } = this;
|
|
349
344
|
try {
|
|
350
|
-
await
|
|
345
|
+
await this.data?.unload?.();
|
|
351
346
|
}
|
|
352
347
|
catch (e) {
|
|
353
348
|
console.log('error unloading plugin', id, String(e));
|
|
@@ -362,11 +357,10 @@ class Plugin {
|
|
|
362
357
|
exports.Plugin = Plugin;
|
|
363
358
|
exports.SERVER_CODE_ID = '.'; // a name that will surely be not found among plugin folders
|
|
364
359
|
const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
|
|
365
|
-
var _a;
|
|
366
360
|
try {
|
|
367
|
-
(
|
|
361
|
+
(await serverCode.compiled())?.unload();
|
|
368
362
|
}
|
|
369
|
-
catch
|
|
363
|
+
catch { }
|
|
370
364
|
const res = {};
|
|
371
365
|
try {
|
|
372
366
|
new Function('exports', script)(res); // parse
|
|
@@ -414,7 +408,7 @@ if (!(0, fs_1.existsSync)(exports.PATH))
|
|
|
414
408
|
try {
|
|
415
409
|
(0, fs_1.mkdirSync)(exports.PATH);
|
|
416
410
|
}
|
|
417
|
-
catch
|
|
411
|
+
catch { }
|
|
418
412
|
exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
|
|
419
413
|
exports.enablePlugins = (0, config_1.defineConfig)('enable_plugins', ['antibrute']);
|
|
420
414
|
exports.enablePlugins.sub(rescanAsap);
|
|
@@ -426,22 +420,23 @@ async function rescan() {
|
|
|
426
420
|
console.debug('scanning plugins');
|
|
427
421
|
const patterns = [exports.PATH + '/*'];
|
|
428
422
|
if (const_1.APP_PATH !== process.cwd())
|
|
429
|
-
patterns.unshift((0, misc_1.
|
|
430
|
-
const
|
|
423
|
+
patterns.unshift((0, misc_1.escapeGlobPath)(const_1.APP_PATH) + '/' + patterns[0]); // first search bundled plugins, because otherwise they won't be loaded because of the folders with same name in .hfs/plugins (used for storage)
|
|
424
|
+
const existing = [];
|
|
431
425
|
for (const { path, dirent } of await (0, fast_glob_1.default)(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
|
|
432
426
|
if (!dirent.isDirectory() || path.endsWith(exports.DISABLING_SUFFIX))
|
|
433
427
|
continue;
|
|
434
428
|
const id = path.split('/').slice(-1)[0];
|
|
435
|
-
|
|
429
|
+
existing.push(id);
|
|
436
430
|
if (!pluginWatchers.has(id))
|
|
437
431
|
pluginWatchers.set(id, watchPlugin(id, (0, path_1.join)(path, exports.PLUGIN_MAIN_FILE)));
|
|
438
432
|
}
|
|
439
433
|
for (const [id, cancelWatcher] of pluginWatchers.entries())
|
|
440
|
-
if (!
|
|
434
|
+
if (!existing.includes(id)) {
|
|
441
435
|
enablePlugin(id, false);
|
|
442
436
|
cancelWatcher();
|
|
443
437
|
pluginWatchers.delete(id);
|
|
444
438
|
}
|
|
439
|
+
exports.pluginsScanned.resolve();
|
|
445
440
|
}
|
|
446
441
|
function watchPlugin(id, path) {
|
|
447
442
|
console.debug('plugin watch', id);
|
|
@@ -499,14 +494,13 @@ function watchPlugin(id, path) {
|
|
|
499
494
|
events_1.default.emit('pluginStopped', p);
|
|
500
495
|
}
|
|
501
496
|
async function start() {
|
|
502
|
-
var _a;
|
|
503
497
|
if (starting)
|
|
504
498
|
return;
|
|
505
499
|
try {
|
|
506
500
|
starting = (0, misc_1.pendingPromise)();
|
|
507
501
|
// if dependencies are not ready right now, we give some time. Not super-solid but good enough for now.
|
|
508
502
|
const info = await parsePlugin();
|
|
509
|
-
if (!await (0, misc_1.waitFor)(() => lodash_1.default.isEmpty(getMissingDependencies(info)), { timeout:
|
|
503
|
+
if (!await (0, misc_1.waitFor)(() => lodash_1.default.isEmpty(getMissingDependencies(info)), { timeout: 5_000 }))
|
|
510
504
|
throw Error("plugin missing dependencies: " + lodash_1.default.map(getMissingDependencies(info), x => x.repo).join(', '));
|
|
511
505
|
if (getPluginInfo(id))
|
|
512
506
|
setError(id, '');
|
|
@@ -517,7 +511,7 @@ function watchPlugin(id, path) {
|
|
|
517
511
|
calculateBadApi(pluginData);
|
|
518
512
|
if (pluginData.badApi)
|
|
519
513
|
throw Error(pluginData.badApi);
|
|
520
|
-
await
|
|
514
|
+
await alreadyRunning?.unload(true);
|
|
521
515
|
console.debug("starting plugin", id);
|
|
522
516
|
const storageDir = (0, path_1.resolve)(exports.PATH, id, exports.STORAGE_FOLDER) + (const_1.IS_WINDOWS ? '\\' : '/');
|
|
523
517
|
await (0, promises_1.mkdir)(storageDir, { recursive: true });
|
|
@@ -551,9 +545,8 @@ function watchPlugin(id, path) {
|
|
|
551
545
|
});
|
|
552
546
|
},
|
|
553
547
|
getConfig(cfgKey) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return cfgKey ? (_b = cur === null || cur === void 0 ? void 0 : cur[cfgKey]) !== null && _b !== void 0 ? _b : (_d = (_c = pluginData.config) === null || _c === void 0 ? void 0 : _c[cfgKey]) === null || _d === void 0 ? void 0 : _d.defaultValue
|
|
548
|
+
const cur = exports.pluginsConfig.get()?.[id];
|
|
549
|
+
return cfgKey ? cur?.[cfgKey] ?? pluginData.config?.[cfgKey]?.defaultValue
|
|
557
550
|
: lodash_1.default.defaults(cur, (0, misc_1.objSameKeys)(pluginData.config, x => x.defaultValue));
|
|
558
551
|
},
|
|
559
552
|
setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
|
|
@@ -605,23 +598,22 @@ function watchPlugin(id, path) {
|
|
|
605
598
|
}
|
|
606
599
|
catch (e) {
|
|
607
600
|
await markItInactive();
|
|
608
|
-
const parsed =
|
|
609
|
-
const where =
|
|
601
|
+
const parsed = e.stack?.split('\n\n'); // this form is used by syntax-errors inside the plugin, which is useful to show
|
|
602
|
+
const where = parsed?.length > 1 ? `\n${parsed[0]}` : '';
|
|
610
603
|
e = (0, misc_1.prefix)('', e.message, where) || String(e);
|
|
611
604
|
setError(id, e);
|
|
612
605
|
}
|
|
613
606
|
finally {
|
|
614
|
-
starting
|
|
607
|
+
starting?.resolve();
|
|
615
608
|
starting = undefined;
|
|
616
609
|
}
|
|
617
610
|
}
|
|
618
611
|
}
|
|
619
612
|
function customApiCall(method, ...params) {
|
|
620
|
-
return mapPlugins(pl =>
|
|
613
|
+
return mapPlugins(pl => pl.getData().customApi?.[method]?.(...params));
|
|
621
614
|
}
|
|
622
615
|
function getError(id) {
|
|
623
|
-
|
|
624
|
-
return (_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.error;
|
|
616
|
+
return getPluginInfo(id)?.error;
|
|
625
617
|
}
|
|
626
618
|
// returns true if there's an error, and it has changed
|
|
627
619
|
function setError(id, error) {
|
|
@@ -638,15 +630,13 @@ function setError(id, error) {
|
|
|
638
630
|
return true;
|
|
639
631
|
}
|
|
640
632
|
function deleteModule(id) {
|
|
641
|
-
var _a;
|
|
642
|
-
var _b;
|
|
643
633
|
const { cache } = require;
|
|
644
634
|
// build reversed map of dependencies
|
|
645
635
|
const requiredBy = { '.': ['.'] }; // don't touch main entry
|
|
646
636
|
for (const k in cache)
|
|
647
637
|
if (k !== id)
|
|
648
|
-
for (const child of (0, misc_1.wantArray)(
|
|
649
|
-
(requiredBy[
|
|
638
|
+
for (const child of (0, misc_1.wantArray)(cache[k]?.children))
|
|
639
|
+
(requiredBy[child.id] ||= []).push(k);
|
|
650
640
|
const deleted = [];
|
|
651
641
|
(function deleteCache(id) {
|
|
652
642
|
const mod = cache[id];
|
|
@@ -661,17 +651,16 @@ function deleteModule(id) {
|
|
|
661
651
|
}
|
|
662
652
|
(0, first_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
663
653
|
function parsePluginSource(id, source) {
|
|
664
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
665
654
|
const pl = { id };
|
|
666
|
-
pl.description = (0, misc_1.tryJson)(
|
|
667
|
-
pl.repo = (0, misc_1.tryJson)(
|
|
668
|
-
pl.version =
|
|
669
|
-
pl.apiRequired = (
|
|
670
|
-
pl.isTheme = (
|
|
671
|
-
pl.preview = (
|
|
672
|
-
pl.depend = (
|
|
655
|
+
pl.description = (0, misc_1.tryJson)(/exports.description\s*=\s*(".*")/.exec(source)?.[1]);
|
|
656
|
+
pl.repo = (0, misc_1.tryJson)(/exports.repo\s*=\s*(\S*)/.exec(source)?.[1]);
|
|
657
|
+
pl.version = Number(/exports.version\s*=\s*(\d*\.?\d+)/.exec(source)?.[1]) ?? undefined;
|
|
658
|
+
pl.apiRequired = (0, misc_1.tryJson)(/exports.apiRequired\s*=\s*([ \d.,[\]]+)/.exec(source)?.[1]) ?? undefined;
|
|
659
|
+
pl.isTheme = (0, misc_1.tryJson)(/exports.isTheme\s*=\s*(true|false|"light"|"dark")/.exec(source)?.[1]) ?? (id.endsWith('-theme') || undefined);
|
|
660
|
+
pl.preview = (0, misc_1.tryJson)(/exports.preview\s*=\s*(.+)/.exec(source)?.[1]) ?? undefined;
|
|
661
|
+
pl.depend = (0, misc_1.tryJson)(/exports.depend\s*=\s*(\[[\s\S]*?])/m.exec(source)?.[1])?.filter((x) => typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
|
|
673
662
|
|| console.warn("plugin dependency discarded", x));
|
|
674
|
-
pl.changelog = (0, misc_1.tryJson)(
|
|
663
|
+
pl.changelog = (0, misc_1.tryJson)(/exports.changelog\s*=\s*(\[[\s\S]*?])/m.exec(source)?.[1]);
|
|
675
664
|
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
|
|
676
665
|
pl.apiRequired = undefined;
|
|
677
666
|
calculateBadApi(pl);
|
|
@@ -685,13 +674,13 @@ function calculateBadApi(data) {
|
|
|
685
674
|
: undefined;
|
|
686
675
|
}
|
|
687
676
|
function getMissingDependencies(plugin) {
|
|
688
|
-
return (0, misc_1.onlyTruthy)((
|
|
677
|
+
return (0, misc_1.onlyTruthy)((plugin?.depend || []).map((dep) => {
|
|
689
678
|
const res = findPluginByRepo(dep.repo);
|
|
690
679
|
const error = !res ? 'missing'
|
|
691
680
|
: (res.version || 0) < dep.version ? 'version'
|
|
692
681
|
: !isPluginEnabled(res.id) ? 'disabled'
|
|
693
682
|
: !isPluginRunning(res.id) ? 'stopped'
|
|
694
683
|
: '';
|
|
695
|
-
return error && { repo: dep.repo, error, id: res
|
|
684
|
+
return error && { repo: dep.repo, error, id: res?.id };
|
|
696
685
|
}));
|
|
697
686
|
}
|
package/src/roots.js
CHANGED
|
@@ -17,7 +17,6 @@ exports.roots = (0, config_1.defineConfig)(misc_1.CFG.roots, {}, map => {
|
|
|
17
17
|
});
|
|
18
18
|
const forceAddress = (0, config_1.defineConfig)(misc_1.CFG.force_address, false);
|
|
19
19
|
const rootsMiddleware = (ctx, next) => (() => {
|
|
20
|
-
var _a;
|
|
21
20
|
if (!ctx.path) // it was once reported "Cannot read properties of null (reading 'startsWith')" but I can't reproduce it, and it shouldn't happen anyway
|
|
22
21
|
return (0, connections_1.disconnect)(ctx, 'no path'); // let's assume that such requests can't be served anyway. This will leave a trace with an ip, anyway
|
|
23
22
|
ctx.state.originalPath = ctx.path;
|
|
@@ -33,7 +32,7 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
33
32
|
}
|
|
34
33
|
if (lodash_1.default.isEmpty(exports.roots.get()))
|
|
35
34
|
return;
|
|
36
|
-
const root = ctx.state.root =
|
|
35
|
+
const root = ctx.state.root = exports.roots.compiled()?.(ctx.host);
|
|
37
36
|
if (!ctx.state.skipFilters && forceAddress.get()
|
|
38
37
|
&& root === undefined && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled())
|
|
39
38
|
return (0, connections_1.disconnect)(ctx, forceAddress.key()); // returning truthy will not call next
|
package/src/selfCheck.js
CHANGED
|
@@ -21,7 +21,6 @@ const selfCheckMiddleware = (ctx, next) => {
|
|
|
21
21
|
};
|
|
22
22
|
exports.selfCheckMiddleware = selfCheckMiddleware;
|
|
23
23
|
async function selfCheck(url) {
|
|
24
|
-
var _a;
|
|
25
24
|
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
26
25
|
console.log(`checking server ${url}`);
|
|
27
26
|
const parsed = new URL(url);
|
|
@@ -39,7 +38,7 @@ async function selfCheck(url) {
|
|
|
39
38
|
console.debug(svc);
|
|
40
39
|
body = applySymbols(body);
|
|
41
40
|
serviceUrl = applySymbols(serviceUrl);
|
|
42
|
-
const res = await (0, cross_1.haveTimeout)(
|
|
41
|
+
const res = await (0, cross_1.haveTimeout)(6_000, (0, util_http_1.httpString)(serviceUrl, { family, ...rest, body }));
|
|
43
42
|
const success = new RegExp(regexpSuccess).test(res);
|
|
44
43
|
const failure = new RegExp(regexpFailure).test(res);
|
|
45
44
|
if (success === failure)
|
|
@@ -49,7 +48,7 @@ async function selfCheck(url) {
|
|
|
49
48
|
}));
|
|
50
49
|
}
|
|
51
50
|
catch (e) {
|
|
52
|
-
console.debug(
|
|
51
|
+
console.debug(e?.errors?.map(String) || e?.cause || String(e));
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -57,6 +56,8 @@ async function selfCheck(url) {
|
|
|
57
56
|
selfChecking = false;
|
|
58
57
|
}
|
|
59
58
|
function applySymbols(s) {
|
|
60
|
-
return s
|
|
59
|
+
return s?.replace('$IP', parsed.hostname)
|
|
60
|
+
.replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80'))
|
|
61
|
+
.replace('$URL', url.replace(/\/$/, '') + CHECK_URL);
|
|
61
62
|
}
|
|
62
63
|
}
|
package/src/serveFile.js
CHANGED
|
@@ -41,7 +41,6 @@ function disposition(ctx, name, forceDownload = false) {
|
|
|
41
41
|
+ `filename="${toAsciiEquivalent(name)}"; filename*=UTF-8''${encodeURIComponent(name)}`);
|
|
42
42
|
}
|
|
43
43
|
async function serveFileNode(ctx, node) {
|
|
44
|
-
var _a;
|
|
45
44
|
const { source, mime } = node;
|
|
46
45
|
const name = (0, vfs_1.getNodeName)(node);
|
|
47
46
|
const mimeString = typeof mime === 'string' ? mime
|
|
@@ -55,7 +54,7 @@ async function serveFileNode(ctx, node) {
|
|
|
55
54
|
ctx.state.vfsNode = node; // useful to tell service files from files shared by the user
|
|
56
55
|
const download = 'dl' in ctx.query;
|
|
57
56
|
disposition(ctx, name, download);
|
|
58
|
-
if (!download &&
|
|
57
|
+
if (!download && ctx.get('referer')?.endsWith('/') && (0, misc_1.with_)(ctx.get('accept'), x => x && !x.includes('text')))
|
|
59
58
|
ctx.state.considerAsGui = true;
|
|
60
59
|
await serveFile(ctx, source || '', mimeString);
|
|
61
60
|
if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits
|
|
@@ -71,7 +70,7 @@ const cacheControlDiskFiles = (0, config_1.defineConfig)('cache_control_disk_fil
|
|
|
71
70
|
async function serveFile(ctx, source, mime, content) {
|
|
72
71
|
if (!source)
|
|
73
72
|
return;
|
|
74
|
-
mime
|
|
73
|
+
mime ??= mimeCfg.compiled()((0, path_1.basename)(source));
|
|
75
74
|
if (mime === undefined || mime === const_1.MIME_AUTO)
|
|
76
75
|
mime = mime_types_1.default.lookup(source) || '';
|
|
77
76
|
if (mime)
|
|
@@ -104,7 +103,7 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
104
103
|
const range = applyRange(ctx, size);
|
|
105
104
|
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
106
105
|
if (ctx.state.vfsNode)
|
|
107
|
-
monitorAsDownload(ctx, size, range
|
|
106
|
+
monitorAsDownload(ctx, size, range?.start);
|
|
108
107
|
}
|
|
109
108
|
catch (e) {
|
|
110
109
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
@@ -133,10 +132,10 @@ function applyRange(ctx, totalSize = ctx.response.length) {
|
|
|
133
132
|
const [unit, ranges] = range.split('=');
|
|
134
133
|
if (unit !== 'bytes')
|
|
135
134
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range unit');
|
|
136
|
-
if (ranges
|
|
135
|
+
if (ranges?.includes(','))
|
|
137
136
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'multi-range not supported');
|
|
138
|
-
let bytes = ranges
|
|
139
|
-
if (!
|
|
137
|
+
let bytes = ranges?.split('-');
|
|
138
|
+
if (!bytes?.length)
|
|
140
139
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range');
|
|
141
140
|
const max = totalSize - 1;
|
|
142
141
|
const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
|
|
@@ -33,12 +33,11 @@ const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_UR
|
|
|
33
33
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
34
34
|
const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
35
35
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
36
|
-
var _a, _b;
|
|
37
36
|
const { path } = ctx;
|
|
38
37
|
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
39
38
|
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
40
39
|
const { referer: r } = ctx.headers;
|
|
41
|
-
return (0, misc_1.try_)(() =>
|
|
40
|
+
return (0, misc_1.try_)(() => r && new URL(r).pathname?.startsWith(cross_const_1.ADMIN_URI)) ? serveAdminFiles(ctx, next)
|
|
42
41
|
: serveFrontendFiles(ctx, next);
|
|
43
42
|
}
|
|
44
43
|
if (path.startsWith(cross_const_1.FRONTEND_URI))
|
|
@@ -53,11 +52,11 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
53
52
|
if (!iconName)
|
|
54
53
|
return;
|
|
55
54
|
const plugin = a.length > 1 && (0, plugins_1.getPluginInfo)(a[0]); // an extra level in the path indicates a plugin
|
|
56
|
-
const file = plugin ?
|
|
55
|
+
const file = plugin ? plugin.icons?.[iconName] : icons_1.customizedIcons?.[iconName];
|
|
57
56
|
if (!file)
|
|
58
57
|
return;
|
|
59
58
|
ctx.state.considerAsGui = true;
|
|
60
|
-
return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)(
|
|
59
|
+
return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)(plugin?.folder || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
|
|
61
60
|
}
|
|
62
61
|
const { get } = ctx.query;
|
|
63
62
|
const getUploadTempHash = get === cross_const_1.UPLOAD_TEMP_HASH;
|
|
@@ -71,7 +70,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
71
70
|
ctx.state.uploadPath = decPath;
|
|
72
71
|
if (getUploadTempHash)
|
|
73
72
|
return !folder.source ? (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_NOT_FOUND)
|
|
74
|
-
: ctx.body = await (0, misc_1.
|
|
73
|
+
: ctx.body = await (0, misc_1.loadFileCached)((0, upload_1.getUploadTempFor)((0, path_1.join)(folder.source, rest)), calcHash); // negligible memory leak
|
|
75
74
|
const dest = (0, upload_1.uploadWriter)(folder, folderUri, rest, ctx);
|
|
76
75
|
if (dest) {
|
|
77
76
|
ctx.req.pipe(dest).on('error', err => {
|
|
@@ -126,7 +125,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
126
125
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
|
|
127
126
|
return;
|
|
128
127
|
try {
|
|
129
|
-
if ((
|
|
128
|
+
if ((await events_1.default.emitAsync('deleting', { node, ctx }))?.isDefaultPrevented())
|
|
130
129
|
return ctx.status = cross_const_1.HTTP_FAILED_DEPENDENCY;
|
|
131
130
|
await (0, promises_1.rm)(source, { recursive: true });
|
|
132
131
|
void (0, comments_1.setCommentFor)(source, ''); // necessary only to clean a possible descript.ion or kvstorage
|
|
@@ -141,7 +140,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
141
140
|
const found = await (0, vfs_1.urlToNode)(node.default, ctx, node);
|
|
142
141
|
if (found && /\.html?/i.test(node.default))
|
|
143
142
|
ctx.state.considerAsGui = true;
|
|
144
|
-
node = found
|
|
143
|
+
node = found ?? node;
|
|
145
144
|
}
|
|
146
145
|
if (get === 'icon')
|
|
147
146
|
return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
|
|
@@ -172,8 +171,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
172
171
|
};
|
|
173
172
|
exports.serveGuiAndSharedFiles = serveGuiAndSharedFiles;
|
|
174
173
|
async function sendFolderList(node, ctx) {
|
|
175
|
-
|
|
176
|
-
if ((_a = (await events_1.default.emitAsync('getList', { node, ctx }))) === null || _a === void 0 ? void 0 : _a.isDefaultPrevented())
|
|
174
|
+
if ((await events_1.default.emitAsync('getList', { node, ctx }))?.isDefaultPrevented())
|
|
177
175
|
return;
|
|
178
176
|
let { depth = 0, folders, prepend } = ctx.query;
|
|
179
177
|
ctx.type = 'text';
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -40,7 +40,7 @@ function serveStatic(uri) {
|
|
|
40
40
|
return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
|
|
41
41
|
const serveApp = shouldServeApp(ctx);
|
|
42
42
|
const fullPath = (0, path_1.join)(__dirname, '..', folder, serveApp ? '/index.html' : ctx.path);
|
|
43
|
-
const content = await (0, misc_1.
|
|
43
|
+
const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw))
|
|
44
44
|
.catch(() => null);
|
|
45
45
|
if (content === null)
|
|
46
46
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
@@ -51,8 +51,7 @@ function serveStatic(uri) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
function shouldServeApp(ctx) {
|
|
54
|
-
|
|
55
|
-
return (_a = ctx.state).serveApp || (_a.serveApp = ctx.path.endsWith('/') && !ctx.headers.upgrade); // skip websockets
|
|
54
|
+
return ctx.state.serveApp ||= ctx.path.endsWith('/') && !ctx.headers.upgrade; // skip websockets
|
|
56
55
|
}
|
|
57
56
|
function adjustBundlerLinks(ctx, uri, data) {
|
|
58
57
|
const ext = (0, path_1.extname)(ctx.path);
|
|
@@ -61,8 +60,8 @@ function adjustBundlerLinks(ctx, uri, data) {
|
|
|
61
60
|
}
|
|
62
61
|
const getFaviconTimestamp = (0, misc_1.debounceAsync)(async () => {
|
|
63
62
|
const f = adminApis_1.favicon.get();
|
|
64
|
-
return !f ? 0 : (0, misc_1.statWithTimeout)(f).then(x =>
|
|
65
|
-
}, { retain:
|
|
63
|
+
return !f ? 0 : (0, misc_1.statWithTimeout)(f).then(x => x?.mtimeMs || 0, () => 0);
|
|
64
|
+
}, { retain: 5_000 });
|
|
66
65
|
async function treatIndex(ctx, filesUri, body) {
|
|
67
66
|
const session = await (0, api_auth_1.refresh_session)({}, ctx);
|
|
68
67
|
ctx.set('etag', '');
|
|
@@ -72,13 +71,9 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
72
71
|
const pub = ctx.state.revProxyPath + const_1.PLUGINS_PUB_URI;
|
|
73
72
|
// expose plugins' configs that are declared with 'frontend' attribute
|
|
74
73
|
const plugins = Object.fromEntries((0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((pl, name) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return !v.frontend ? skip() :
|
|
79
|
-
adjustValueByConfig((_b = (_a = plugins_1.pluginsConfig.get()) === null || _a === void 0 ? void 0 : _a[name]) === null || _b === void 0 ? void 0 : _b[k], (_c = pl.getData().config) === null || _c === void 0 ? void 0 : _c[k]);
|
|
80
|
-
});
|
|
81
|
-
configs = ((_b = (_a = (0, plugins_1.getPluginInfo)(name)).onFrontendConfig) === null || _b === void 0 ? void 0 : _b.call(_a, configs)) || configs;
|
|
74
|
+
let configs = (0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(name), (v, k, skip) => !v.frontend ? skip() :
|
|
75
|
+
adjustValueByConfig(plugins_1.pluginsConfig.get()?.[name]?.[k], pl.getData().config?.[k]));
|
|
76
|
+
configs = (0, plugins_1.getPluginInfo)(name).onFrontendConfig?.(configs) || configs;
|
|
82
77
|
return !lodash_1.default.isEmpty(configs) && [name, configs];
|
|
83
78
|
})));
|
|
84
79
|
const timestamp = await getFaviconTimestamp();
|
|
@@ -98,7 +93,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
98
93
|
SPECIAL_URI: const_1.SPECIAL_URI, PLUGINS_PUB_URI: const_1.PLUGINS_PUB_URI, FRONTEND_URI: const_1.FRONTEND_URI,
|
|
99
94
|
session: session instanceof apiMiddleware_1.ApiError ? null : session,
|
|
100
95
|
plugins,
|
|
101
|
-
loadScripts: Object.fromEntries((0, plugins_1.mapPlugins)((p, id) =>
|
|
96
|
+
loadScripts: Object.fromEntries((0, plugins_1.mapPlugins)((p, id) => [id, p.frontend_js?.map(f => f.includes('//') ? f : pub + id + '/' + f)])),
|
|
102
97
|
prefixUrl: ctx.state.revProxyPath,
|
|
103
98
|
dontOverwriteUploading: upload_1.dontOverwriteUploading.get(),
|
|
104
99
|
splitUploads: splitUploads.get(),
|
|
@@ -132,10 +127,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
132
127
|
}
|
|
133
128
|
${isFrontend && (0, customHtml_1.getSection)('style')}
|
|
134
129
|
</style>
|
|
135
|
-
${isFrontend && (0, plugins_1.mapPlugins)((plug, id) => {
|
|
136
|
-
var _a;
|
|
137
|
-
return (_a = plug.frontend_css) === null || _a === void 0 ? void 0 : _a.map(f => `<link rel='stylesheet' type='text/css' href='${f.includes('//') ? f : pub + id + '/' + f}' plugin=${JSON.stringify(id)}/>`);
|
|
138
|
-
})
|
|
130
|
+
${isFrontend && (0, plugins_1.mapPlugins)((plug, id) => plug.frontend_css?.map(f => `<link rel='stylesheet' type='text/css' href='${f.includes('//') ? f : pub + id + '/' + f}' plugin=${JSON.stringify(id)}/>`))
|
|
139
131
|
.flat().filter(Boolean).join('\n')}
|
|
140
132
|
`;
|
|
141
133
|
if (isBody && isClose)
|
|
@@ -143,7 +135,7 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
143
135
|
return all; // unchanged
|
|
144
136
|
});
|
|
145
137
|
function adjustValueByConfig(v, cfg) {
|
|
146
|
-
v
|
|
138
|
+
v ??= cfg.defaultValue;
|
|
147
139
|
const { type } = cfg;
|
|
148
140
|
if (v && type === 'vfs_path') {
|
|
149
141
|
v = (0, misc_1.enforceStarting)('/', v);
|
|
@@ -162,9 +154,8 @@ async function treatIndex(ctx, filesUri, body) {
|
|
|
162
154
|
}
|
|
163
155
|
}
|
|
164
156
|
function serializeCss(v) {
|
|
165
|
-
var _a;
|
|
166
157
|
return typeof v === 'string' && /^#[0-9a-fA-F]{3,8}|rgba?\(.+\)$/.test(v) ? v // colors
|
|
167
|
-
: (0, misc_1.isPrimitive)(v) ?
|
|
158
|
+
: (0, misc_1.isPrimitive)(v) ? JSON.stringify(v)?.replace(/</g, '<') : undefined;
|
|
168
159
|
}
|
|
169
160
|
function serveProxied(port, uri) {
|
|
170
161
|
if (!port)
|