hfs 0.55.2 → 0.56.0-alpha1

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.
Files changed (47) hide show
  1. package/admin/assets/index-D3kXFfFt.css +1 -0
  2. package/admin/assets/index-nlAmDXAU.js +819 -0
  3. package/admin/assets/sha512-BP2pyBBC.js +8 -0
  4. package/admin/index.html +2 -2
  5. package/frontend/assets/index-legacy-BEI2K_mw.js +50 -0
  6. package/frontend/assets/polyfills-legacy-Ca1TOBqp.js +1 -0
  7. package/frontend/assets/sha512-legacy-Z6a6T8lE.js +9 -0
  8. package/frontend/index.html +2 -2
  9. package/package.json +1 -1
  10. package/src/SendList.js +1 -1
  11. package/src/adminApis.js +1 -1
  12. package/src/api.auth.js +2 -0
  13. package/src/api.get_file_list.js +0 -2
  14. package/src/api.vfs.js +7 -12
  15. package/src/auth.js +1 -0
  16. package/src/basicWeb.js +1 -1
  17. package/src/commands.js +3 -6
  18. package/src/config.js +31 -10
  19. package/src/const.js +2 -2
  20. package/src/cross-const.js +2 -1
  21. package/src/cross.js +1 -1
  22. package/src/debounceAsync.js +3 -2
  23. package/src/frontEndApis.js +12 -0
  24. package/src/icons.js +35 -0
  25. package/src/middlewares.js +11 -2
  26. package/src/misc.js +7 -1
  27. package/src/nat.js +1 -1
  28. package/src/outboundProxy.js +43 -0
  29. package/src/plugins.js +25 -14
  30. package/src/serveGuiAndSharedFiles.js +15 -0
  31. package/src/serveGuiFiles.js +5 -0
  32. package/src/update.js +1 -1
  33. package/src/upload.js +12 -1
  34. package/src/util-files.js +3 -1
  35. package/src/util-http.js +14 -7
  36. package/src/util-os.js +5 -1
  37. package/src/vfs.js +29 -10
  38. package/src/watchLoad.js +3 -2
  39. package/src/zip.js +3 -1
  40. package/admin/assets/index-B4HJwRLS.js +0 -809
  41. package/admin/assets/index-CrbMquLL.css +0 -1
  42. package/admin/assets/sha512-iUmyklA_.js +0 -8
  43. package/admin/prism.css +0 -4
  44. package/admin/prism.js +0 -10
  45. package/frontend/assets/index-legacy-BFUS5Ol_.js +0 -60
  46. package/frontend/assets/polyfills-legacy-DMrMt_pQ.js +0 -4
  47. package/frontend/assets/sha512-legacy-HnxdKRIz.js +0 -9
package/src/plugins.js CHANGED
@@ -52,6 +52,7 @@ const lang_1 = require("./lang");
52
52
  const i18n_1 = require("./i18n");
53
53
  const perm_1 = require("./perm");
54
54
  const auth_1 = require("./auth");
55
+ const icons_1 = require("./icons");
55
56
  exports.PATH = 'plugins';
56
57
  exports.DISABLING_SUFFIX = '-disabled';
57
58
  exports.STORAGE_FOLDER = 'storage';
@@ -110,9 +111,8 @@ function setPluginConfig(id, changes) {
110
111
  }
111
112
  exports.setPluginConfig = setPluginConfig;
112
113
  function getPluginInfo(id) {
113
- var _a;
114
- const running = (_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.getData();
115
- return running && Object.assign(running, { id }) || availablePlugins[id];
114
+ const running = plugins.get(id);
115
+ return running && { ...running.getData(), ...running } || availablePlugins[id];
116
116
  }
117
117
  exports.getPluginInfo = getPluginInfo;
118
118
  function findPluginByRepo(repo) {
@@ -139,6 +139,7 @@ async function initPlugin(pl, morePassedToInit) {
139
139
  getConnections: connections_1.getConnections,
140
140
  events: events_1.default,
141
141
  log: console.log,
142
+ setError(msg) { setError((morePassedToInit === null || morePassedToInit === void 0 ? void 0 : morePassedToInit.id) || 'server_code', msg); },
142
143
  getHfsConfig: config_1.getConfig,
143
144
  customApiCall,
144
145
  notifyClient: frontEndApis_1.notifyClient,
@@ -148,7 +149,9 @@ async function initPlugin(pl, morePassedToInit) {
148
149
  getCurrentUsername: auth_1.getCurrentUsername,
149
150
  ...morePassedToInit
150
151
  }));
151
- return Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
152
+ Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
153
+ events_1.default.emit('pluginInitialized', pl);
154
+ return pl;
152
155
  }
153
156
  const already = new Set();
154
157
  function warnOnce(msg) {
@@ -208,12 +211,12 @@ const pluginsMiddleware = async (ctx, next) => {
208
211
  catch (e) {
209
212
  printError(id, e);
210
213
  }
214
+ function printError(id, e) {
215
+ console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
216
+ console.debug(e);
217
+ }
211
218
  };
212
219
  exports.pluginsMiddleware = pluginsMiddleware;
213
- function printError(id, e) {
214
- console.log(`error middleware plugin ${id}: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
215
- console.debug(e);
216
- }
217
220
  events_1.default.once('app', () => Object.assign(index_1.app.context, {
218
221
  isStopped: false,
219
222
  stop() { return this.isStopped = true; }
@@ -280,7 +283,7 @@ class Plugin {
280
283
  return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
281
284
  }
282
285
  getData() {
283
- return { ...this.data };
286
+ return this.data;
284
287
  }
285
288
  async unload(reloading = false) {
286
289
  var _a, _b;
@@ -425,9 +428,8 @@ function watchPlugin(id, path) {
425
428
  };
426
429
  async function onUninstalled() {
427
430
  await stop();
428
- const was = getPluginInfo(id);
429
- if (!was)
430
- return;
431
+ if (!getPluginInfo(id))
432
+ return; // already missing
431
433
  delete availablePlugins[id];
432
434
  events_1.default.emit('pluginUninstalled', id);
433
435
  }
@@ -474,6 +476,7 @@ function watchPlugin(id, path) {
474
476
  await (0, promises_1.mkdir)(storageDir, { recursive: true });
475
477
  const dbs = [];
476
478
  await initPlugin(pluginData, {
479
+ id,
477
480
  srcDir: __dirname,
478
481
  storageDir,
479
482
  async openDb(filename, options) {
@@ -516,7 +519,9 @@ function watchPlugin(id, path) {
516
519
  const folder = (0, path_1.dirname)(module);
517
520
  const { sections, unwatch } = (0, customHtml_1.watchLoadCustomHtml)(folder);
518
521
  pluginData.getCustomHtml = () => Object.assign(Object.fromEntries(sections), (0, misc_1.callable)(pluginData.customHtml) || {});
522
+ const unwatchIcons = (0, icons_1.watchIconsFolder)(folder, v => plugin.icons = v);
519
523
  const plugin = new Plugin(id, folder, pluginData, async () => {
524
+ unwatchIcons();
520
525
  unwatch();
521
526
  await Promise.allSettled(dbs.map(x => x.close()));
522
527
  dbs.length = 0;
@@ -536,7 +541,6 @@ function watchPlugin(id, path) {
536
541
  const parsed = (_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n\n'); // this form is used by syntax-errors inside the plugin, which is useful to show
537
542
  const where = (parsed === null || parsed === void 0 ? void 0 : parsed.length) > 1 ? `\n${parsed[0]}` : '';
538
543
  e = e.message + where || String(e);
539
- console.log(`plugin error: ${id}:`, e);
540
544
  setError(id, e);
541
545
  }
542
546
  finally {
@@ -552,12 +556,19 @@ function getError(id) {
552
556
  var _a;
553
557
  return (_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.error;
554
558
  }
559
+ // returns true if there's an error, and it has changed
555
560
  function setError(id, error) {
556
561
  const info = getPluginInfo(id);
557
562
  if (!info)
558
563
  return;
564
+ if (info.error === error)
565
+ return;
559
566
  info.error = error;
560
- events_1.default.emit('pluginUpdated', { id, error });
567
+ events_1.default.emit('pluginUpdated', info);
568
+ if (!error)
569
+ return;
570
+ console.log(`plugin error: ${id}:`, error);
571
+ return true;
561
572
  }
562
573
  function deleteModule(id) {
563
574
  var _a;
@@ -21,11 +21,14 @@ const koa_mount_1 = __importDefault(require("koa-mount"));
21
21
  const listen_1 = require("./listen");
22
22
  const misc_1 = require("./misc");
23
23
  const basicWeb_1 = require("./basicWeb");
24
+ const icons_1 = require("./icons");
25
+ const plugins_1 = require("./plugins");
24
26
  const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
25
27
  const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
26
28
  const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
27
29
  const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
28
30
  const serveGuiAndSharedFiles = async (ctx, next) => {
31
+ var _a;
29
32
  const { path } = ctx;
30
33
  // dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
31
34
  if (const_1.DEV && path.startsWith('/node_modules/')) {
@@ -40,6 +43,18 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
40
43
  if (path.startsWith(cross_const_1.ADMIN_URI))
41
44
  return (0, adminApis_1.allowAdmin)(ctx) ? serveAdminPrefixed(ctx, next)
42
45
  : (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_FORBIDDEN);
46
+ if (path.startsWith(cross_const_1.ICONS_URI)) {
47
+ const a = path.substring(cross_const_1.ICONS_URI.length).split('/');
48
+ const iconName = a.at(-1);
49
+ if (!iconName)
50
+ return;
51
+ const plugin = a.length > 1 && (0, plugins_1.getPluginInfo)(a[0]); // an extra level in the path indicates a plugin
52
+ const file = plugin ? (_a = plugin.icons) === null || _a === void 0 ? void 0 : _a[iconName] : icons_1.customizedIcons === null || icons_1.customizedIcons === void 0 ? void 0 : icons_1.customizedIcons[iconName];
53
+ if (!file)
54
+ return;
55
+ ctx.state.considerAsGui = true;
56
+ return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)((plugin === null || plugin === void 0 ? void 0 : plugin.folder) || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
57
+ }
43
58
  if (ctx.method === 'PUT') { // curl -T file url/
44
59
  const decPath = decodeURIComponent(path);
45
60
  let rest = (0, path_1.basename)(decPath);
@@ -19,6 +19,7 @@ const lodash_1 = __importDefault(require("lodash"));
19
19
  const config_1 = require("./config");
20
20
  const lang_1 = require("./lang");
21
21
  const upload_1 = require("./upload");
22
+ const icons_1 = require("./icons");
22
23
  const size1024 = (0, config_1.defineConfig)(misc_1.CFG.size_1024, false, x => misc_1.formatBytes.k = x ? 1024 : 1000); // we both configure formatBytes, and also provide a compiled version (number instead of boolean)
23
24
  const splitUploads = (0, config_1.defineConfig)(misc_1.CFG.split_uploads, 0);
24
25
  exports.logGui = (0, config_1.defineConfig)(misc_1.CFG.log_gui, false);
@@ -107,6 +108,7 @@ async function treatIndex(ctx, filesUri, body) {
107
108
  forceTheme: (0, plugins_1.mapPlugins)(p => lodash_1.default.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
108
109
  customHtml: lodash_1.default.omit((0, customHtml_1.getAllSections)(), ['top', 'bottom', 'htmlHead', 'style']),
109
110
  ...(0, misc_1.newObj)(misc_1.FRONTEND_OPTIONS, (v, k) => (0, config_1.getConfig)(k)),
111
+ icons: Object.assign({}, ...(0, plugins_1.mapPlugins)(p => iconsToObj(p.icons, p.id + '/')), iconsToObj(icons_1.customizedIcons)),
110
112
  lang
111
113
  }, null, 4).replace(/<(\/script)/g, '<"+"$1') /*avoid breaking our script container*/}
112
114
  document.documentElement.setAttribute('ver', HFS.VERSION.split('-')[0])
@@ -116,6 +118,9 @@ async function treatIndex(ctx, filesUri, body) {
116
118
  <link rel="shortcut icon" href="/favicon.ico?${timestamp}" />
117
119
  ${(0, customHtml_1.getSection)('htmlHead')}`}
118
120
  `;
121
+ function iconsToObj(icons, pre = '') {
122
+ return icons && (0, misc_1.objSameKeys)(icons, (v, k) => const_1.ICONS_URI + pre + k);
123
+ }
119
124
  if (isBody && isOpen)
120
125
  return `${all}
121
126
  ${isFrontend && (0, customHtml_1.getSection)('top')}
package/src/update.js CHANGED
@@ -93,7 +93,7 @@ function localUpdateAvailable() {
93
93
  }
94
94
  exports.localUpdateAvailable = localUpdateAvailable;
95
95
  async function updateSupported() {
96
- return argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE;
96
+ return process.env.DISABLE_UPDATE ? false : (argv_1.argv.forceupdate || const_1.IS_BINARY && !await util_os_1.RUNNING_AS_SERVICE);
97
97
  }
98
98
  exports.updateSupported = updateSupported;
99
99
  async function update(tagOrUrl = '') {
package/src/upload.js CHANGED
@@ -20,10 +20,21 @@ const lodash_1 = __importDefault(require("lodash"));
20
20
  const events_1 = __importDefault(require("./events"));
21
21
  const promises_1 = require("fs/promises");
22
22
  const expiringCache_1 = require("./expiringCache");
23
+ const first_1 = require("./first");
23
24
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
24
25
  exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
25
26
  exports.dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', true);
26
27
  const waitingToBeDeleted = {};
28
+ (0, first_1.onProcessExit)(() => {
29
+ if (!Object.keys(waitingToBeDeleted).length)
30
+ return;
31
+ console.log("removing unfinished uploads");
32
+ for (const path in waitingToBeDeleted)
33
+ try {
34
+ fs_1.default.rmSync(path, { force: true });
35
+ }
36
+ catch (_a) { }
37
+ });
27
38
  const ATTR_UPLOADER = 'uploader';
28
39
  function getUploadMeta(path) {
29
40
  return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
@@ -148,7 +159,7 @@ function uploadWriter(base, baseUri, path, ctx) {
148
159
  writeStream.once('close', async () => {
149
160
  try {
150
161
  await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
151
- if (ctx.req.aborted) {
162
+ if (ctx.req.aborted) { // doc says .aborted is deprecated, replaced by .destroyed, but in my tests the latter is true even for completed uploads, while .aborted is only for interrupted ones
152
163
  if (resumableTempName && !resumableLost && !resuming) // we don't want to be left with 2 temp files
153
164
  return (0, promises_1.rm)(tempName);
154
165
  const sec = exports.deleteUnfinishedUploadsAfter.get();
package/src/util-files.js CHANGED
@@ -31,7 +31,7 @@ async function readFileBusy(path) {
31
31
  });
32
32
  }
33
33
  exports.readFileBusy = readFileBusy;
34
- function watchDir(dir, cb) {
34
+ function watchDir(dir, cb, atStart = false) {
35
35
  let watcher;
36
36
  let paused = false;
37
37
  try {
@@ -56,6 +56,8 @@ function watchDir(dir, cb) {
56
56
  console.debug(String(e));
57
57
  }
58
58
  }
59
+ if (atStart)
60
+ controlledCb();
59
61
  return {
60
62
  working() { return Boolean(watcher); },
61
63
  stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); },
package/src/util-http.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.httpStream = exports.httpWithBody = exports.httpString = exports.stream2string = void 0;
8
+ const node_url_1 = require("node:url");
8
9
  const node_https_1 = __importDefault(require("node:https"));
9
10
  const node_http_1 = __importDefault(require("node:http"));
10
11
  const node_stream_1 = require("node:stream");
@@ -23,25 +24,31 @@ async function httpWithBody(url, options) {
23
24
  });
24
25
  }
25
26
  exports.httpWithBody = httpWithBody;
26
- function httpStream(url, { body, jar, noRedirect, httpThrow, ...options } = {}) {
27
+ function httpStream(url, { body, jar, noRedirect, httpThrow, proxy, ...options } = {}) {
27
28
  return new Promise((resolve, reject) => {
28
- var _a, _b, _c;
29
- var _d, _e;
29
+ var _a, _b, _c, _d;
30
+ var _e, _f, _g;
31
+ proxy !== null && proxy !== void 0 ? proxy : (proxy = httpStream.defaultProxy);
30
32
  (_a = options.headers) !== null && _a !== void 0 ? _a : (options.headers = {});
31
33
  if (body) {
32
34
  options.method || (options.method = 'POST');
33
35
  if (lodash_1.default.isPlainObject(body)) {
34
- (_b = (_d = options.headers)['content-type']) !== null && _b !== void 0 ? _b : (_d['content-type'] = 'application/json');
36
+ (_b = (_e = options.headers)['content-type']) !== null && _b !== void 0 ? _b : (_e['content-type'] = 'application/json');
35
37
  body = JSON.stringify(body);
36
38
  }
37
39
  if (!(body instanceof node_stream_1.Readable))
38
- (_c = (_e = options.headers)['content-length']) !== null && _c !== void 0 ? _c : (_e['content-length'] = Buffer.byteLength(body));
40
+ (_c = (_f = options.headers)['content-length']) !== null && _c !== void 0 ? _c : (_f['content-length'] = Buffer.byteLength(body));
39
41
  }
40
42
  if (jar)
41
43
  options.headers.cookie = lodash_1.default.map(jar, (v, k) => `${k}=${v}; `).join('')
42
44
  + (options.headers.cookie || ''); // preserve parameter
43
- const proto = url.startsWith('https:') ? node_https_1.default : node_http_1.default;
44
- const req = proto.request(url, options, res => {
45
+ Object.assign(options, lodash_1.default.pick((0, node_url_1.parse)(proxy || url), ['hostname', 'port', 'path', 'protocol']));
46
+ if (proxy) {
47
+ options.path = url;
48
+ (_d = (_g = options.headers).host) !== null && _d !== void 0 ? _d : (_g.host = (0, node_url_1.parse)(url).host || undefined);
49
+ }
50
+ const proto = options.protocol === 'https:' ? node_https_1.default : node_http_1.default;
51
+ const req = proto.request(options, res => {
45
52
  var _a;
46
53
  var _b;
47
54
  console.debug("http responded", res.statusCode, "to", url);
package/src/util-os.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
6
+ exports.reg = exports.RUNNING_AS_SERVICE = exports.runCmd = exports.getDrives = exports.getDiskSpaces = exports.getDiskSpace = exports.cmdEscape = exports.bashEscape = exports.getDiskSpaceSync = void 0;
7
7
  const path_1 = require("path");
8
8
  const fs_1 = require("fs");
9
9
  const child_process_1 = require("child_process");
@@ -93,3 +93,7 @@ exports.RUNNING_AS_SERVICE = const_1.IS_WINDOWS && getWindowsServicePids().then(
93
93
  console.log("couldn't determine if we are running as a service");
94
94
  console.debug(e);
95
95
  });
96
+ function reg(...pars) {
97
+ return (0, util_1.promisify)(child_process_1.execFile)('reg', pars).then(x => x.stdout);
98
+ }
99
+ exports.reg = reg;
package/src/vfs.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
7
+ exports.parentMaskApplier = exports.masksCouldGivePermission = exports.walkNode = exports.statusCodeForMissingPerm = exports.hasPermission = exports.nodeIsLink = exports.hasDefaultFile = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.getNodeByName = exports.nodeStats = exports.urlToNode = exports.applyParentToChild = exports.isSameFilenameAs = exports.permsFromParent = void 0;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = require("path");
10
10
  const misc_1 = require("./misc");
@@ -112,6 +112,7 @@ async function nodeStats(ret) {
112
112
  (0, misc_1.setHidden)(ret, { stats });
113
113
  return stats;
114
114
  }
115
+ exports.nodeStats = nodeStats;
115
116
  async function isHiddenFile(path) {
116
117
  return const_1.IS_WINDOWS ? new Promise(res => fswin_1.default.getAttributes(path, x => res(x === null || x === void 0 ? void 0 : x.IS_HIDDEN)))
117
118
  : path[0] === '.';
@@ -144,6 +145,14 @@ async function getNodeByName(name, parent) {
144
145
  exports.getNodeByName = getNodeByName;
145
146
  exports.vfs = {};
146
147
  (0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = (function recur(node) {
148
+ const { masks } = node;
149
+ lodash_1.default.each(masks, (v, mask) => {
150
+ if (v.maskOnly) {
151
+ masks[`${mask}|${v.maskOnly}|`] = v = lodash_1.default.omit(v, 'maskOnly');
152
+ delete masks[mask];
153
+ }
154
+ recur(v);
155
+ });
147
156
  if (node.children)
148
157
  for (const c of node.children)
149
158
  recur(c);
@@ -175,10 +184,10 @@ async function nodeIsDirectory(node) {
175
184
  var _a;
176
185
  if (node.isFolder !== undefined)
177
186
  return node.isFolder;
178
- if (((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) || !node.source)
179
- return true;
180
187
  if (nodeIsLink(node))
181
188
  return false;
189
+ if (((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) || !node.source)
190
+ return true;
182
191
  const isFolder = await nodeStats(node).then(x => x.isDirectory(), () => false);
183
192
  (0, misc_1.setHidden)(node, { isFolder }); // don't make it to the storage (a node.isTemp doesn't need it to be hidden)
184
193
  return isFolder;
@@ -273,9 +282,9 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
273
282
  try {
274
283
  let lastDir = prefixPath.slice(0, -1) || '.';
275
284
  parentsCache.set(lastDir, parent);
276
- for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders, onlyFiles, hidden: showHiddenFiles.get() })) {
285
+ for await (const entry of (0, misc_1.dirStream)(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
277
286
  if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
278
- return;
287
+ break; // investigate: "aborted" is deprecated, but "destroyed" will cause failure of some tests
279
288
  const { path } = entry;
280
289
  const isFolder = entry.isDirectory();
281
290
  const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
@@ -294,7 +303,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
294
303
  };
295
304
  if (isFolder) // store it even if we can't see it (masks), as its children can be produced by dirStream
296
305
  parentsCache.set(name, item);
297
- if (await canSee(item))
306
+ if (!(onlyFiles && isFolder) && await canSee(item))
298
307
  yield item;
299
308
  (_b = entry.closingBranch) === null || _b === void 0 ? void 0 : _b.then(p => parentsCache.delete(p || '.'));
300
309
  }
@@ -320,11 +329,21 @@ function masksCouldGivePermission(masks, perm) {
320
329
  }
321
330
  exports.masksCouldGivePermission = masksCouldGivePermission;
322
331
  function parentMaskApplier(parent) {
323
- const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (v, k) => {
332
+ const matchers = (0, misc_1.onlyTruthy)(lodash_1.default.map(parent.masks, (mods, k) => {
333
+ if (!mods)
334
+ return;
335
+ const mustBeFolder = (() => {
336
+ if (k.at(-1) !== '|')
337
+ return; // parse special flag syntax as suffix |FLAG| inside the key. This allows specifying different flags with the same mask using separate keys. To avoid syntax conflicts with the rest of the file-mask, we look for an ending pipe, as it has no practical use. Ending-pipe was preferred over starting-pipe to leave the rest of the logic (inheritMasks) untouched.
338
+ const i = k.lastIndexOf('|', k.length - 2);
339
+ if (i < 0)
340
+ return;
341
+ const type = k.slice(i + 1, -1);
342
+ k = k.slice(0, i); // remove
343
+ return type === 'folders';
344
+ })();
324
345
  k = k.startsWith('**/') ? k.slice(3) : !k.includes('/') ? k : ''; // ** globstar matches also zero subfolders, so this mask must be applied here too
325
- const { maskOnly, ...mods } = v || {};
326
- // k is stored into the object for debugging purposes
327
- return k && { k, mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder: maskOnly && (maskOnly === 'folders') };
346
+ return k && { mods, matcher: (0, misc_1.makeMatcher)(k), mustBeFolder };
328
347
  }));
329
348
  return async (item, virtualBasename = getNodeName(item)) => {
330
349
  let isFolder = undefined;
package/src/watchLoad.js CHANGED
@@ -8,16 +8,17 @@ exports.watchLoad = void 0;
8
8
  const fs_1 = require("fs");
9
9
  const promises_1 = __importDefault(require("fs/promises"));
10
10
  const misc_1 = require("./misc");
11
+ const debounceAsync_1 = require("./debounceAsync");
11
12
  const events_1 = require("./events");
12
13
  function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {}) {
13
14
  let doing = false;
14
15
  let watcher;
15
- const debounced = (0, misc_1.debounceAsync)(load, { wait: 500, maxWait: 1000 });
16
+ const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000 });
16
17
  let retry;
17
18
  let last;
18
19
  const emitter = new events_1.BetterEventEmitter();
19
20
  install(true);
20
- const save = (0, misc_1.debounceAsync)(async (data, { reparse = false } = {}) => {
21
+ const save = (0, debounceAsync_1.debounceAsync)(async (data, { reparse = false } = {}) => {
21
22
  await promises_1.default.writeFile(path, data, 'utf8');
22
23
  last = data;
23
24
  if (reparse)
package/src/zip.js CHANGED
@@ -29,6 +29,8 @@ async function zipStreamFromFolder(node, ctx) {
29
29
  const walker = !list ? (0, vfs_1.walkNode)(node, { ctx, requiredPerm: 'can_archive' })
30
30
  : (async function* () {
31
31
  for await (const uri of list) {
32
+ if (ctx.req.destroyed)
33
+ return;
32
34
  const subNode = await (0, vfs_1.urlToNode)(uri, ctx, node);
33
35
  if (!subNode)
34
36
  continue;
@@ -51,7 +53,7 @@ async function zipStreamFromFolder(node, ctx) {
51
53
  return; // the fact you see it doesn't mean you can get it
52
54
  const { source } = el;
53
55
  const name = (0, vfs_1.getNodeName)(el);
54
- if (ctx.req.aborted || !filter(name))
56
+ if (!filter(name))
55
57
  return;
56
58
  try {
57
59
  if (el.isFolder)