hfs 3.1.5 → 3.2.0-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.
Files changed (224) hide show
  1. package/admin/assets/af-DWEYq388.js +1 -0
  2. package/admin/assets/am-DgLbAgj6.js +1 -0
  3. package/admin/assets/ar-DgEWkO74.js +1 -0
  4. package/admin/assets/ar-dz-DHq--Sr8.js +1 -0
  5. package/admin/assets/ar-iq-D1r3nsb9.js +1 -0
  6. package/admin/assets/ar-kw-C85fGHwp.js +1 -0
  7. package/admin/assets/ar-ly-Bq6pjGjs.js +1 -0
  8. package/admin/assets/ar-ma-SGvmh6Mj.js +1 -0
  9. package/admin/assets/ar-sa-Bv8hiFi6.js +1 -0
  10. package/admin/assets/ar-tn-Bdvo77v3.js +1 -0
  11. package/admin/assets/az-7fPndoov.js +1 -0
  12. package/admin/assets/be-wjXeIeAK.js +1 -0
  13. package/admin/assets/bg-DZwBvjzH.js +1 -0
  14. package/admin/assets/bi-qCtxvMhO.js +1 -0
  15. package/admin/assets/bm-BVPWvreb.js +1 -0
  16. package/admin/assets/bn-CdWvye7d.js +1 -0
  17. package/admin/assets/bn-bd-B5-3blmz.js +1 -0
  18. package/admin/assets/bo-BEVcgyN2.js +1 -0
  19. package/admin/assets/br-DGQD6fFs.js +1 -0
  20. package/admin/assets/bs-So4MoRua.js +1 -0
  21. package/admin/assets/ca-Bk5ytG4g.js +1 -0
  22. package/admin/assets/cs-C4NU8eW-.js +1 -0
  23. package/admin/assets/cv-Ct-s-zrW.js +1 -0
  24. package/admin/assets/cy-ojtDPj8_.js +1 -0
  25. package/admin/assets/da-CYGin6vm.js +1 -0
  26. package/admin/assets/de-BEbJ01zH.js +1 -0
  27. package/admin/assets/de-at-C7UwE1rJ.js +1 -0
  28. package/admin/assets/de-ch-CsZ7YQc_.js +1 -0
  29. package/admin/assets/dv-DaVliwLd.js +1 -0
  30. package/admin/assets/el-DM_KqKEP.js +1 -0
  31. package/admin/assets/en-DedtOfaf.js +1 -0
  32. package/admin/assets/en-au-52Bzk5D9.js +1 -0
  33. package/admin/assets/en-ca-3pzEPK2N.js +1 -0
  34. package/admin/assets/en-gb-BrwDQS2G.js +1 -0
  35. package/admin/assets/en-ie-BUXSHrkL.js +1 -0
  36. package/admin/assets/en-il-a22drDCn.js +1 -0
  37. package/admin/assets/en-in-BUjecjkp.js +1 -0
  38. package/admin/assets/en-nz-Bbo7tnB_.js +1 -0
  39. package/admin/assets/en-sg-CZVDddmd.js +1 -0
  40. package/admin/assets/en-tt-DmSGwRia.js +1 -0
  41. package/admin/assets/eo-B71nkHZU.js +1 -0
  42. package/admin/assets/es-Dk6VCuuk.js +1 -0
  43. package/admin/assets/es-do-DMErY8ol.js +1 -0
  44. package/admin/assets/es-mx-BMRmqa3u.js +1 -0
  45. package/admin/assets/es-pr-CtBQz48p.js +1 -0
  46. package/admin/assets/es-us-CrDl5pnO.js +1 -0
  47. package/admin/assets/et-CO9OHqio.js +1 -0
  48. package/admin/assets/eu-Bip44atW.js +1 -0
  49. package/admin/assets/fa-CHbJ_dTM.js +1 -0
  50. package/admin/assets/fi-DKfoLmaQ.js +1 -0
  51. package/admin/assets/fo-DG1kOEfw.js +1 -0
  52. package/admin/assets/fr-DV73GZR4.js +1 -0
  53. package/admin/assets/fr-ca-BK-RoZiC.js +1 -0
  54. package/admin/assets/fr-ch-DjqEC5E_.js +1 -0
  55. package/admin/assets/fy-znrRQdeC.js +1 -0
  56. package/admin/assets/ga-BlZeKu0N.js +1 -0
  57. package/admin/assets/gd-BmrycMnC.js +1 -0
  58. package/admin/assets/gl-CuT8e5mi.js +1 -0
  59. package/admin/assets/gom-latn-BSWVd0A6.js +1 -0
  60. package/admin/assets/gu-BHK6LfvD.js +1 -0
  61. package/admin/assets/he-DPoTUevR.js +1 -0
  62. package/admin/assets/hi-BRuLafoW.js +1 -0
  63. package/admin/assets/hr-Bzge-10P.js +1 -0
  64. package/admin/assets/ht-Ck9BCna1.js +1 -0
  65. package/admin/assets/hu-CzqqbYmU.js +1 -0
  66. package/admin/assets/hy-am-C-eV4E8v.js +1 -0
  67. package/admin/assets/id-Dv8GZQvB.js +1 -0
  68. package/admin/assets/{index-DTxjaflW.js → index-BPIX0qPj.js} +1 -1
  69. package/admin/assets/index-CFWd-FDo.css +1 -0
  70. package/admin/assets/index-D3HviM6x.js +889 -0
  71. package/admin/assets/is-CK6VY3M_.js +1 -0
  72. package/admin/assets/it-1gtki4a5.js +1 -0
  73. package/admin/assets/it-ch-C0Mj3-pC.js +1 -0
  74. package/admin/assets/ja-Dl3AfnM1.js +1 -0
  75. package/admin/assets/jv-CznX-tGV.js +1 -0
  76. package/admin/assets/ka-BNjZxCug.js +1 -0
  77. package/admin/assets/kk-80J_xldf.js +1 -0
  78. package/admin/assets/km-CuWChDRB.js +1 -0
  79. package/admin/assets/kn-BZp_PBdl.js +1 -0
  80. package/admin/assets/ko-RirpyUl_.js +1 -0
  81. package/admin/assets/ku-Dz8ACD5w.js +1 -0
  82. package/admin/assets/ky-BN3ylOhj.js +1 -0
  83. package/admin/assets/lb-D7h_YoEn.js +1 -0
  84. package/admin/assets/lo-BrlbTUPD.js +1 -0
  85. package/admin/assets/lt-cXuHFdTa.js +1 -0
  86. package/admin/assets/lv-CjIpv13Q.js +1 -0
  87. package/admin/assets/me-B-jTh39Z.js +1 -0
  88. package/admin/assets/mi-D06xdVmt.js +1 -0
  89. package/admin/assets/mk-iLbuxyOf.js +1 -0
  90. package/admin/assets/ml-2W0y6Zb2.js +1 -0
  91. package/admin/assets/mn-6zbvKjeb.js +1 -0
  92. package/admin/assets/mr-7-jrgLyw.js +1 -0
  93. package/admin/assets/ms-CRH6rEXt.js +1 -0
  94. package/admin/assets/ms-my-BIJmu2S-.js +1 -0
  95. package/admin/assets/mt-CbXmxK-D.js +1 -0
  96. package/admin/assets/my-BvMUsxU8.js +1 -0
  97. package/admin/assets/nb-DvKHgF7L.js +1 -0
  98. package/admin/assets/ne-DiZZ3Lm6.js +1 -0
  99. package/admin/assets/nl-be-Dy7PYRbC.js +1 -0
  100. package/admin/assets/nl-t_2A_VAT.js +1 -0
  101. package/admin/assets/nn-Cw7EwosO.js +1 -0
  102. package/admin/assets/oc-lnc-CuFfB75K.js +1 -0
  103. package/admin/assets/pa-in-DOFyZ-Ft.js +1 -0
  104. package/admin/assets/pl-BD7FyCJj.js +1 -0
  105. package/admin/assets/pt-DSKLLE_u.js +1 -0
  106. package/admin/assets/pt-br-BhL4gb5Z.js +1 -0
  107. package/admin/assets/rn-DoHoZZPd.js +1 -0
  108. package/admin/assets/ro-B0v-_lH0.js +1 -0
  109. package/admin/assets/ru-BMVOk5eA.js +1 -0
  110. package/admin/assets/rw-CWF0w6eL.js +1 -0
  111. package/admin/assets/sd-DO2rrjch.js +1 -0
  112. package/admin/assets/se-CAolO9WQ.js +1 -0
  113. package/admin/assets/{sha512-D936QW8l.js → sha512-ZlUYj4Hr.js} +1 -1
  114. package/admin/assets/si-D03dHfb5.js +1 -0
  115. package/admin/assets/sk-_GcZGaN3.js +1 -0
  116. package/admin/assets/sl-Cb1lUGab.js +1 -0
  117. package/admin/assets/sq-Czzt23Tr.js +1 -0
  118. package/admin/assets/sr-D76dVqKJ.js +1 -0
  119. package/admin/assets/sr-cyrl-CuoFbJjW.js +1 -0
  120. package/admin/assets/ss-g-fGaM29.js +1 -0
  121. package/admin/assets/sv-CZxc8I45.js +1 -0
  122. package/admin/assets/sv-fi-D8REJeLz.js +1 -0
  123. package/admin/assets/sw-B1n3PjWG.js +1 -0
  124. package/admin/assets/ta-K5mexJNT.js +1 -0
  125. package/admin/assets/te-DT6dj5B6.js +1 -0
  126. package/admin/assets/tet-DXwYNm_H.js +1 -0
  127. package/admin/assets/tg-BCcZKcE2.js +1 -0
  128. package/admin/assets/th-CeeeseFX.js +1 -0
  129. package/admin/assets/tk-CJ6KW44d.js +1 -0
  130. package/admin/assets/tl-ph-DzE8lDmm.js +1 -0
  131. package/admin/assets/tlh-ER6KiMxG.js +1 -0
  132. package/admin/assets/tr-D48NGNpr.js +1 -0
  133. package/admin/assets/tzl-5AsmDTYM.js +1 -0
  134. package/admin/assets/tzm-2AzZ1YPf.js +1 -0
  135. package/admin/assets/tzm-latn-Cd5hPQuT.js +1 -0
  136. package/admin/assets/ug-cn-DU-MZ9Vx.js +1 -0
  137. package/admin/assets/uk-hn6vcOkn.js +1 -0
  138. package/admin/assets/ur-jOObtqB6.js +1 -0
  139. package/admin/assets/uz-BlftYfHF.js +1 -0
  140. package/admin/assets/uz-latn-BpuI0ccM.js +1 -0
  141. package/admin/assets/vi-DVo3LusT.js +1 -0
  142. package/admin/assets/x-pseudo-BuVzNhqi.js +1 -0
  143. package/admin/assets/yo-BvWGwb4m.js +1 -0
  144. package/admin/assets/zh-D9-tfba1.js +1 -0
  145. package/admin/assets/zh-cn-CFL5sbIW.js +1 -0
  146. package/admin/assets/zh-hk-DRP8u65r.js +1 -0
  147. package/admin/assets/zh-tw-CtVs1ihI.js +1 -0
  148. package/admin/index.html +2 -2
  149. package/frontend/assets/index-legacy-D3BTBYs5.js +9 -0
  150. package/frontend/assets/{index-legacy-vmpqwZZf.js → index-legacy-DcrWtKxQ.js} +1 -1
  151. package/frontend/assets/{sha512-legacy-wI89-UHR.js → sha512-legacy-DJvEwScE.js} +1 -1
  152. package/frontend/index.html +1 -1
  153. package/npm-shrinkwrap.json +144 -71
  154. package/package.json +9 -9
  155. package/plugins/antibrute/plugin.js +150 -19
  156. package/plugins/list-uploader/public/main.js +1 -1
  157. package/src/acme.js +11 -7
  158. package/src/api.accounts.js +3 -3
  159. package/src/api.auth.js +3 -1
  160. package/src/api.get_file_list.js +17 -13
  161. package/src/api.monitor.js +47 -43
  162. package/src/api.net.js +4 -3
  163. package/src/api.vfs.js +1 -1
  164. package/src/basicWeb.js +1 -1
  165. package/src/commands.js +54 -1
  166. package/src/comments.js +7 -4
  167. package/src/config.js +9 -5
  168. package/src/consoleLog.js +39 -1
  169. package/src/const.js +4 -0
  170. package/src/cross.js +33 -6
  171. package/src/errorPages.js +20 -10
  172. package/src/events.js +3 -3
  173. package/src/expiringCache.js +8 -7
  174. package/src/fileAttr.js +73 -15
  175. package/src/frontEndApis.js +20 -15
  176. package/src/index.js +2 -1
  177. package/src/langs/hfs-lang-ar.json +2 -1
  178. package/src/langs/hfs-lang-bg.json +2 -1
  179. package/src/langs/hfs-lang-de.json +2 -1
  180. package/src/langs/hfs-lang-el.json +2 -1
  181. package/src/langs/hfs-lang-es.json +2 -1
  182. package/src/langs/hfs-lang-fi.json +2 -1
  183. package/src/langs/hfs-lang-fr.json +2 -1
  184. package/src/langs/hfs-lang-hu.json +2 -1
  185. package/src/langs/hfs-lang-it.json +2 -1
  186. package/src/langs/hfs-lang-ja.json +2 -1
  187. package/src/langs/hfs-lang-ko.json +2 -1
  188. package/src/langs/hfs-lang-lt.json +2 -1
  189. package/src/langs/hfs-lang-ms.json +2 -0
  190. package/src/langs/hfs-lang-nl.json +2 -1
  191. package/src/langs/hfs-lang-pt-br.json +2 -1
  192. package/src/langs/hfs-lang-ro.json +2 -1
  193. package/src/langs/hfs-lang-ru.json +2 -1
  194. package/src/langs/hfs-lang-sr-latn.json +2 -1
  195. package/src/langs/hfs-lang-sr.json +2 -1
  196. package/src/langs/hfs-lang-th.json +2 -1
  197. package/src/langs/hfs-lang-tr.json +2 -1
  198. package/src/langs/hfs-lang-uk.json +2 -1
  199. package/src/langs/hfs-lang-vi.json +2 -1
  200. package/src/langs/hfs-lang-zh-tw.json +2 -1
  201. package/src/langs/hfs-lang-zh.json +2 -1
  202. package/src/listen.js +2 -1
  203. package/src/log.js +27 -2
  204. package/src/middlewares.js +8 -2
  205. package/src/misc.js +14 -0
  206. package/src/nat.js +61 -21
  207. package/src/outboundProxy.js +1 -1
  208. package/src/perm.js +5 -1
  209. package/src/plugins.js +34 -5
  210. package/src/roots.js +1 -1
  211. package/src/selfCheck.js +2 -1
  212. package/src/serveGuiAndSharedFiles.js +18 -7
  213. package/src/serveGuiFiles.js +2 -1
  214. package/src/update.js +10 -18
  215. package/src/urlList.js +32 -0
  216. package/src/util-files.js +24 -9
  217. package/src/util-http.js +4 -0
  218. package/src/vfs.js +21 -9
  219. package/src/walkDir.js +7 -1
  220. package/src/webdav.js +4 -1
  221. package/src/zip.js +3 -2
  222. package/admin/assets/index-B66w-a0v.css +0 -1
  223. package/admin/assets/index-Df6vYR7s.js +0 -822
  224. package/frontend/assets/index-legacy-emBsvICj.js +0 -9
package/src/acme.js CHANGED
@@ -32,17 +32,21 @@ const acmeMiddleware = (ctx, next) => {
32
32
  return next();
33
33
  };
34
34
  exports.acmeMiddleware = acmeMiddleware;
35
- const TEMP_MAP = { private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 5000 }; // from my tests (zyxel VMG8825), lower values won't make a working mapping
35
+ const TEMP_MAP = (0, nat_1.upnpMappingParam)(80, 80, 'hfs temporary', 5000); // from my tests (zyxel VMG8825), lower values won't make a working mapping
36
+ // remove temporary port mapping, if any is left from previous execution
36
37
  (0, misc_1.repeat)(misc_1.MINUTE, async (stop) => {
37
- await nat_1.upnpClient.getGateway(); // without this, the next call will break upnp support
38
- const res = await nat_1.upnpClient.getMappings();
38
+ if (!await nat_1.upnpEnabled.getWhenReady())
39
+ return stop();
40
+ const client = (0, nat_1.getUpnpClient)();
41
+ await client.getGateway(); // without this, the next call will break upnp support
42
+ const res = await client.getMappings();
39
43
  const leftover = res.find(x => x.description === TEMP_MAP.description); // in case the process is interrupted
40
44
  if (!leftover)
41
45
  return void stop(); // we are good
42
46
  if (acmeOngoing)
43
47
  return; // it doesn't count, as we are in the middle of something. Retry later
44
48
  stop();
45
- return nat_1.upnpClient.removeMapping(TEMP_MAP);
49
+ return client.removeMapping(TEMP_MAP);
46
50
  });
47
51
  async function generateSSLCert(domain, email, altNames) {
48
52
  // will answer the challenge through our koa app (if on port 80) or must we spawn a dedicated server?
@@ -63,7 +67,7 @@ async function generateSSLCert(domain, email, altNames) {
63
67
  let check = await (0, selfCheck_1.selfCheck)(checkUrl); // some check services may not consider the domain, but we already verified that
64
68
  if (check?.success === false && nat.upnp && !nat.mapped80) {
65
69
  console.debug("Setting temporary port forward");
66
- tempMap = await (0, misc_1.haveTimeout)(10_000, nat_1.upnpClient.createMapping(TEMP_MAP).catch(() => { })).catch(() => { });
70
+ tempMap = await (0, misc_1.haveTimeout)(10_000, (0, nat_1.getUpnpClient)().createMapping(TEMP_MAP)).catch(() => { });
67
71
  check = await (0, selfCheck_1.selfCheck)(checkUrl); // repeat test
68
72
  }
69
73
  //if (!check) throw new ApiError(HTTP_FAILED_DEPENDENCY, "couldn't test port 80")
@@ -88,9 +92,9 @@ async function generateSSLCert(domain, email, altNames) {
88
92
  return { key, cert };
89
93
  }
90
94
  finally {
91
- if (tempMap) {
95
+ if (tempMap && nat_1.upnpEnabled.get()) {
92
96
  console.debug("Removing temporary port forward");
93
- nat_1.upnpClient.removeMapping(TEMP_MAP).catch(() => { }); // clean after ourselves
97
+ (0, nat_1.getUpnpClient)().removeMapping(TEMP_MAP).catch(() => { }); // clean after ourselves
94
98
  }
95
99
  acmeOngoing = false;
96
100
  if (tempSrv)
@@ -16,9 +16,9 @@ function prepareAccount(ac) {
16
16
  ...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
17
17
  username: ac.username, // omit won't copy it because it's a hidden prop
18
18
  hasPassword: (0, perm_1.accountHasPassword)(ac),
19
- isGroup: !ac.plugin?.auth && !(0, perm_1.accountHasPassword)(ac),
19
+ isGroup: !(0, perm_1.accountHasLoginMethod)(ac),
20
20
  adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
21
- canLogin: (0, perm_1.accountHasPassword)(ac) ? (0, perm_1.accountCanLogin)(ac) : undefined,
21
+ canLogin: (0, perm_1.accountHasLoginMethod)(ac) ? (0, perm_1.accountCanLogin)(ac) : undefined,
22
22
  canChangePassword: (0, perm_1.accountCanChangePassword)(ac),
23
23
  invalidated: auth_1.invalidateSessionBefore.get(ac.username),
24
24
  directMembers: Object.values(perm_1.accounts.get()).filter(a => a.belongs?.includes(ac.username)).map(x => x.username),
@@ -33,7 +33,7 @@ function prepareAccount(ac) {
33
33
  })
34
34
  };
35
35
  }
36
- const ALLOWED_KEYS = ['admin', 'allow_net', 'belongs', 'days_to_live', 'disable_password_change',
36
+ const ALLOWED_KEYS = ['admin', 'allow_net', 'auto_login_net', 'belongs', 'days_to_live', 'disable_password_change',
37
37
  'disabled', 'expire', 'ignore_limits', 'notes', 'password', 'redirect', 'require_password_change', 'username'];
38
38
  exports.default = {
39
39
  get_usernames() {
package/src/api.auth.js CHANGED
@@ -18,10 +18,12 @@ const ongoingLogins = {}; // store data that doesn't fit session object
18
18
  const keepSessionAlive = (0, config_1.defineConfig)('keep_session_alive', true);
19
19
  const refresh_session = async ({}, ctx) => {
20
20
  const username = (0, auth_1.getCurrentUsername)(ctx);
21
+ const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx) || undefined;
21
22
  return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
22
23
  username,
23
24
  expandedUsername: Array.from((0, perm_1.expandUsername)(username)),
24
- adminUrl: (0, adminApis_1.ctxAdminAccess)(ctx) ? ctx.state.revProxyPath + const_1.ADMIN_URI : undefined,
25
+ isAdmin,
26
+ adminUrl: isAdmin && ctx.state.revProxyPath + const_1.ADMIN_URI,
25
27
  canChangePassword: (0, perm_1.accountCanChangePassword)(ctx.state.account),
26
28
  requireChangePassword: ctx.state.account?.require_password_change,
27
29
  exp: username && keepSessionAlive.get() ? new Date(Date.now() + middlewares_1.sessionDuration.compiled()) : undefined,
@@ -35,7 +35,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
35
35
  if (!node)
36
36
  return fail(const_1.HTTP_NOT_FOUND);
37
37
  admin &&= (0, adminApis_1.ctxAdminAccess)(ctx); // validate 'admin' flag
38
- if (await (0, vfs_1.hasDefaultFile)(node, ctx) || !(0, vfs_1.nodeIsFolder)(node)) // for files without permission, the frontend is sent, and the location is the file itself
38
+ if (await (0, vfs_1.getDefaultFile)(node, ctx) || !(0, vfs_1.nodeIsFolder)(node)) // for files without permission, the frontend is sent, and the location is the file itself
39
39
  // so, we first check if you have a permission problem, to tell frontend to show login, otherwise we fall back to method_not_allowed, as it's proper for files.
40
40
  return fail(!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? undefined : const_1.HTTP_METHOD_NOT_ALLOWED);
41
41
  if (!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
@@ -44,7 +44,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
44
44
  limit = Number(limit);
45
45
  const { filterName, filterComment, fileMask, depth } = paramsToFilter(rest);
46
46
  const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth });
47
- const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
47
+ const onDirEntryHandlers = (0, plugins_1.mapPlugins)((plug, id) => plug.onDirEntry && { id, cb: plug.onDirEntry });
48
48
  const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
49
49
  const can_delete = admin || (0, vfs_1.hasPermission)(node, 'can_delete', ctx);
50
50
  const fakeChild = await (0, vfs_1.applyParentToChild)({ source: 'dummy-file', original: undefined }, node); // used to check permission; simple but can produce false results; 'original' to simulate a non-vfs node
@@ -74,7 +74,7 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
74
74
  async function* produceEntries() {
75
75
  for await (const sub of walker) {
76
76
  let name = (0, vfs_1.getNodeName)(sub);
77
- name = (0, path_1.basename)(name) || name; // on windows, basename('C:') === ''
77
+ name = (0, path_1.basename)(name) || name; // on Windows, basename('C:') === ''
78
78
  if (filterName && !filterName(name) || fileMask && !(0, vfs_1.nodeIsFolder)(sub) && !fileMask(name)
79
79
  || filterComment && !filterComment(await (0, comments_1.getCommentFor)(sub.source) || ''))
80
80
  continue;
@@ -83,15 +83,15 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
83
83
  continue;
84
84
  const cbParams = { entry, ctx, listUri: uri, node: sub };
85
85
  try {
86
- const res = await Promise.all(onDirEntryHandlers.map(cb => cb(cbParams)));
86
+ const res = await Promise.all(onDirEntryHandlers.map(({ id, cb }) => Promise.resolve().then(() => cb(cbParams)).catch(error => { throw { id, error }; })));
87
87
  if (res.some(x => x === false))
88
88
  continue;
89
- if ((await events_1.default.emitAsync('dirEntry', cbParams))?.isDefaultPrevented())
90
- continue;
91
89
  }
92
90
  catch (e) {
93
- console.warn("A plugin is causing problems on dirEntry:", e);
91
+ console.warn(`Plugin ${e?.id || '?'} is causing problems on onDirEntry:`, e?.error ?? e);
94
92
  }
93
+ if ((await events_1.default.emitAsync('dirEntry', cbParams))?.isDefaultPrevented())
94
+ continue;
95
95
  if (offset) {
96
96
  --offset;
97
97
  continue;
@@ -111,10 +111,14 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
111
111
  const name = (0, vfs_1.getNodeName)(node);
112
112
  const isFolder = (0, vfs_1.nodeIsFolder)(node);
113
113
  try {
114
- const st = source ? await (node.stats || (0, misc_1.statWithTimeout)(source).catch(e => {
115
- if (!isFolder || !node.children?.length) // folders with virtual children, keep them
116
- throw e;
117
- })) : undefined;
114
+ const [web, comment, st] = await Promise.all([
115
+ (0, vfs_1.getDefaultFile)(node, ctx).then(x => x ? true : undefined),
116
+ node.comment ?? (0, comments_1.getCommentFor)(source),
117
+ (0, vfs_1.nodeStats)(node).catch(e => {
118
+ if (!isFolder || !node.children?.length) // folders with virtual children, keep them
119
+ throw e;
120
+ })
121
+ ]);
118
122
  // permissions of entries are sent as a difference with permissions of parent
119
123
  const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
120
124
  : !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
@@ -136,9 +140,9 @@ const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFil
136
140
  url,
137
141
  target: node.target,
138
142
  order: node.order,
139
- comment: node.comment ?? await (0, comments_1.getCommentFor)(source),
143
+ comment,
140
144
  icon: getNodeIcon(node),
141
- web: await (0, vfs_1.hasDefaultFile)(node, ctx) ? true : undefined,
145
+ web,
142
146
  };
143
147
  }
144
148
  catch {
@@ -4,6 +4,8 @@ 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.serializeConnection = serializeConnection;
8
+ exports.inferOperation = inferOperation;
7
9
  const lodash_1 = __importDefault(require("lodash"));
8
10
  const connections_1 = require("./connections");
9
11
  const misc_1 = require("./misc");
@@ -11,6 +13,7 @@ const throttler_1 = require("./throttler");
11
13
  const auth_1 = require("./auth");
12
14
  const SendList_1 = require("./SendList");
13
15
  const persistence_1 = require("./persistence");
16
+ const cross_1 = require("./cross");
14
17
  exports.default = {
15
18
  async disconnect({ ip, port, allButLocalhost }) {
16
19
  (0, misc_1.apiAssertTypes)({
@@ -28,25 +31,21 @@ exports.default = {
28
31
  get_connections({}, ctx) {
29
32
  const list = new SendList_1.SendListReadable({
30
33
  diff: true,
31
- addAtStart: (0, connections_1.getConnections)().map(c => !ignore(c) && serializeConnection(c)).filter(Boolean),
34
+ addAtStart: (0, connections_1.getConnections)().map(serializeConnection),
32
35
  });
33
36
  list.props({ you: ctx.ip });
34
37
  return list.events(ctx, {
35
38
  connection(conn) {
36
- if (ignore(conn))
37
- return;
38
39
  list.add(serializeConnection(conn));
39
40
  },
40
41
  connectionClosed(conn) {
41
- if (ignore(conn))
42
- return;
43
42
  list.remove(getConnAddress(conn));
44
43
  },
45
44
  connectionNewIp(conn, oldIp, newIp) {
46
45
  list.update(getConnAddress(conn, oldIp), { ip: newIp });
47
46
  },
48
47
  connectionUpdated(conn, change) {
49
- if (conn.socket.closed || ignore(conn) || ignore(change) || lodash_1.default.isEmpty(change))
48
+ if (conn.socket.closed || lodash_1.default.isEmpty(change))
50
49
  return;
51
50
  if (change.ctx) {
52
51
  Object.assign(change, fromCtx(change.ctx));
@@ -55,48 +54,16 @@ exports.default = {
55
54
  list.update(getConnAddress(conn), change);
56
55
  },
57
56
  });
58
- function serializeConnection(conn) {
59
- const { socket, started, secure } = conn;
60
- return {
61
- ...getConnAddress(conn),
62
- v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
63
- got: socket.bytesRead,
64
- sent: socket.bytesWritten,
65
- country: conn.country,
66
- started,
67
- secure: (secure || undefined), // undefined will save some space once json-ed
68
- ...fromCtx(conn.ctx),
69
- };
70
- }
71
- function fromCtx(ctx) {
72
- if (!ctx)
73
- return;
74
- const s = ctx.state; // short alias
75
- return {
76
- user: (0, auth_1.getCurrentUsername)(ctx),
77
- agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
78
- archive: s.archive,
79
- ...s.browsing ? { op: 'browsing', path: (0, misc_1.safeDecodeURIComponent)(s.browsing) }
80
- : s.uploadPath ? { op: 'upload', path: (0, misc_1.safeDecodeURIComponent)(s.uploadPath) }
81
- : {
82
- op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
83
- path: (0, misc_1.safeDecodeURIComponent)(ctx.originalUrl),
84
- },
85
- opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
86
- opTotal: s.opTotal,
87
- opOffset: s.opOffset,
88
- };
89
- }
90
57
  },
91
58
  async *get_connection_stats() {
92
59
  while (1) {
93
- const filtered = (0, connections_1.getConnections)().filter(x => !ignore(x));
60
+ const connections = (0, connections_1.getConnections)();
94
61
  yield {
95
62
  outSpeedKb: throttler_1.totalOutSpeedKb,
96
63
  inSpeedKb: throttler_1.totalInSpeedKb,
97
64
  sent_got: [throttler_1.totalSent.get(), throttler_1.totalGot.get(), totalGotSentResetTime.get()],
98
- connections: filtered.length,
99
- ips: lodash_1.default.uniqBy(filtered, x => x.ip).length,
65
+ connections: connections.length,
66
+ ips: (0, cross_1.countUniqueBy)(connections, conn => conn.ip),
100
67
  };
101
68
  await (0, misc_1.wait)(1000);
102
69
  }
@@ -108,8 +75,45 @@ exports.default = {
108
75
  void persistence_1.storedMap.del(x);
109
76
  },
110
77
  };
111
- function ignore(conn) {
112
- return false; //conn.socket && isLocalHost(conn)
78
+ function serializeConnection(conn) {
79
+ const { socket, started, secure } = conn;
80
+ return {
81
+ ...getConnAddress(conn),
82
+ v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
83
+ // connection fields are request-scoped once transfer tracking starts; socket counters cover earlier snapshots
84
+ got: conn.got || socket.bytesRead,
85
+ sent: conn.sent || socket.bytesWritten,
86
+ outSpeedKb: conn.outSpeedKb,
87
+ inSpeedKb: conn.inSpeedKb,
88
+ country: conn.country,
89
+ started,
90
+ secure: (secure || undefined), // undefined will save some space once json-ed
91
+ ...fromCtx(conn.ctx),
92
+ };
93
+ }
94
+ function fromCtx(ctx) {
95
+ if (!ctx)
96
+ return;
97
+ return {
98
+ user: (0, auth_1.getCurrentUsername)(ctx),
99
+ agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
100
+ ...inferOperation(ctx)
101
+ };
102
+ }
103
+ function inferOperation(ctx) {
104
+ const s = ctx.state; // short alias
105
+ return {
106
+ archive: s.archive,
107
+ ...s.browsing ? { op: 'browsing', path: (0, misc_1.safeDecodeURIComponent)(s.browsing) }
108
+ : s.uploadPath ? { op: 'upload', path: (0, misc_1.safeDecodeURIComponent)(s.uploadPath) }
109
+ : {
110
+ op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
111
+ path: (0, misc_1.safeDecodeURIComponent)(ctx.originalUrl),
112
+ },
113
+ opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
114
+ opTotal: s.opTotal,
115
+ opOffset: s.opOffset,
116
+ };
113
117
  }
114
118
  function getConnAddress(conn, overrideIp) {
115
119
  return {
package/src/api.net.js CHANGED
@@ -52,16 +52,17 @@ exports.default = {
52
52
  return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "no internal port");
53
53
  if (externalPort)
54
54
  try {
55
- await nat_1.upnpClient.removeMapping({ public: { host: '', port: externalPort } });
55
+ await (0, nat_1.getUpnpClient)().removeMapping({ public: { host: '', port: externalPort } });
56
56
  }
57
57
  catch (e) {
58
58
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "removeMapping failed: " + String(e));
59
59
  }
60
- if (external) // must use the object form of 'public' to work around a bug of the library
61
- await nat_1.upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 })
60
+ if (external)
61
+ await (0, nat_1.getUpnpClient)().createMapping((0, nat_1.upnpMappingParam)(internal || internalPort, external))
62
62
  .catch(res => {
63
63
  throw new apiMiddleware_1.ApiError(res.errorCode || const_1.HTTP_SERVER_ERROR, res.errorCode === 718 ? "Port not available" : res.errorDescription || res.message || "unknown error");
64
64
  });
65
+ nat_1.mappedPort.set(external || 0); // remember only successful HFS mappings
65
66
  return {};
66
67
  },
67
68
  async self_check({ url }) {
package/src/api.vfs.js CHANGED
@@ -225,7 +225,7 @@ exports.default = {
225
225
  for (const k of ['*', 'Directory']) {
226
226
  await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k), '/ve', '/f', '/d', 'Add to HFS (new)');
227
227
  await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k), '/v', 'icon', '/f', '/d', const_1.IS_BINARY ? process.execPath : const_1.APP_PATH + '\\hfs.ico');
228
- await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -WindowStyle Hidden -Command "
228
+ await (0, util_os_1.reg)('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -Command "
229
229
  [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12;
230
230
  $wsh = New-Object -ComObject Wscript.Shell;
231
231
  $j = @{parent=@'\n${parent}\n'@; source=@'\n%1\n'@} | ConvertTo-Json -Compress
package/src/basicWeb.js CHANGED
@@ -41,7 +41,7 @@ function basicWeb(ctx, node) {
41
41
  const stream = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
42
42
  const isFolder = (0, vfs_1.nodeIsFolder)(el);
43
43
  const name = (0, vfs_1.getNodeName)(el) + (isFolder ? '/' : '');
44
- return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.hasDefaultFile)(el, ctx) ? force : ''), name)}\n`;
44
+ return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.getDefaultFile)(el, ctx) ? force : ''), name)}\n`;
45
45
  }));
46
46
  ctx.body = stream;
47
47
  stream.push(`<meta name="viewport" content="width=device-width" />`);
package/src/commands.js CHANGED
@@ -1,5 +1,38 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
38
  };
@@ -17,7 +50,8 @@ const plugins_1 = require("./plugins");
17
50
  const fileAttr_1 = require("./fileAttr");
18
51
  const github_1 = require("./github");
19
52
  const cross_1 = require("./cross");
20
- const api_monitor_1 = __importDefault(require("./api.monitor"));
53
+ const api_monitor_1 = __importStar(require("./api.monitor"));
54
+ const connections_1 = require("./connections");
21
55
  const argv_1 = require("./argv");
22
56
  const listen_2 = require("./listen");
23
57
  let debugEnabled = argv_1.argv.debug || process.env.HFS_DEBUG || const_1.DEV;
@@ -191,11 +225,30 @@ const commands = {
191
225
  params: '',
192
226
  cb: fileAttr_1.purgeFileAttr,
193
227
  },
228
+ transfers: {
229
+ params: '',
230
+ cb() {
231
+ const transfers = (0, connections_1.getConnections)().map(api_monitor_1.serializeConnection).filter(x => x.op === 'upload' || x.op === 'download');
232
+ if (!transfers.length)
233
+ return console.log("No ongoing uploads/downloads");
234
+ console.table(transfers.map(x => ({
235
+ type: x.op,
236
+ progress: (0, cross_1.with_)(x.opProgress ?? x.opOffset, v => v == null ? '' : (0, cross_1.formatPerc)(v)),
237
+ transferred: (0, cross_1.formatBytes)(Math.max(x.sent || 0, x.got || 0)),
238
+ total: x.opTotal == null ? '' : (0, cross_1.formatBytes)(x.opTotal),
239
+ speed: (0, cross_1.formatSpeed)(Math.max(x.outSpeedKb || 0, x.inSpeedKb || 0) * 1000),
240
+ user: x.user,
241
+ path: x.path,
242
+ })));
243
+ }
244
+ },
194
245
  status: {
195
246
  params: '',
196
247
  async cb() {
197
248
  const ports = await (0, listen_2.getServerStatus)(false);
198
249
  console.log(lodash_1.default.map(ports, (x, k) => `${k.toUpperCase()} ${x.configuredPort < 0 ? "disabled" : x.listening ? `on port ${x.port}` : (x.error || "not working")}`).join(" – "));
250
+ const operations = lodash_1.default.countBy((0, connections_1.getConnections)(), x => x.ctx && (0, api_monitor_1.inferOperation)(x.ctx).op);
251
+ console.log(`Active downloads ↑ ${operations.download || 0} – uploads ↓ ${operations.upload || 0}`);
199
252
  const conn = (await api_monitor_1.default.get_connection_stats().next()).value;
200
253
  if (conn) {
201
254
  const { sent_got: sg } = conn;
package/src/comments.js CHANGED
@@ -16,6 +16,7 @@ const misc_1 = require("./misc");
16
16
  const lodash_1 = __importDefault(require("lodash"));
17
17
  const iconv_lite_1 = __importDefault(require("iconv-lite"));
18
18
  const promises_1 = require("node:fs/promises");
19
+ const expiringCache_1 = require("./expiringCache");
19
20
  exports.DESCRIPT_ION = 'descript.ion';
20
21
  exports.DESCRIPT_ION_ALT = 'DESCRIPT.ION';
21
22
  const commentsStorage = (0, config_1.defineConfig)(cross_1.CFG.comments_storage, '', v => ['', 'attr+ion'].includes(v)); // compiled tell us if we are using descript.ion
@@ -81,13 +82,15 @@ async function filePathHelper(folder) {
81
82
  return await (0, util_files_1.exists)(alt) && !await (0, util_files_1.exists)(main) ? alt : main;
82
83
  }
83
84
  const MULTILINE_SUFFIX = Buffer.from([4, 0xC2]);
85
+ const pathCache = (0, expiringCache_1.expiringCache)(2_000);
86
+ // this can be called many times when listing a folder, and we want to also not check too often as it can be expensive, especially on a networked drive
84
87
  async function readDescriptIon(path) {
85
- // decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
86
- return (0, util_files_1.parseFile)(await filePathHelper(path), raw => {
87
- // for simplicity we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
88
+ return (0, util_files_1.parseFile)(await pathCache.try(path, filePathHelper), raw => {
89
+ // for simplicity, we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
88
90
  for (let i = 0; i < raw.length; i++)
89
91
  if (raw[i] === MULTILINE_SUFFIX[0] && raw[i + 1] === MULTILINE_SUFFIX[1] && [undefined, 13, 10].includes(raw[i + 2]))
90
92
  raw[i] = raw[i + 1] = 10;
93
+ // decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
91
94
  const decoded = iconv_lite_1.default.decode(raw, descriptIonEncoding.get());
92
95
  const ret = new Map(decoded.split('\n').map(line => {
93
96
  const quoted = line[0] === '"' ? 1 : 0;
@@ -98,7 +101,7 @@ async function readDescriptIon(path) {
98
101
  }));
99
102
  ret.delete('');
100
103
  return ret;
101
- });
104
+ }, 2000);
102
105
  }
103
106
  descriptIonEncoding.sub(() => {
104
107
  for (const k of util_files_1.parseFileCache.keys())
package/src/config.js CHANGED
@@ -21,10 +21,8 @@ const fs_1 = require("fs");
21
21
  const path_1 = require("path");
22
22
  const events_1 = __importDefault(require("./events"));
23
23
  const promises_1 = require("fs/promises");
24
- const immer_1 = require("immer");
25
24
  const argv_1 = require("./argv");
26
25
  const util_files_1 = require("./util-files");
27
- (0, immer_1.setAutoFreeze)(false); // we still want to mess with objects later (eg: account.belongs)
28
26
  // keep definition of config properties
29
27
  const configProps = {};
30
28
  let started = false; // this will tell the difference for subscribeConfig()s that are called before or after config is loaded
@@ -74,6 +72,10 @@ function defineConfig(k, defaultValue, compiler) {
74
72
  get() {
75
73
  return getConfig(k);
76
74
  },
75
+ async getWhenReady() {
76
+ await exports.configReady;
77
+ return this.get();
78
+ },
77
79
  sub(cb) {
78
80
  if (started) // initial event already passed, we'll make the first call
79
81
  cb(getConfig(k), { k, was: defaultValue, defaultValue, version: configVersion.compiled(), object });
@@ -90,8 +92,10 @@ function defineConfig(k, defaultValue, compiler) {
90
92
  }, { warnAfter: 1000 }); // e.g. each plugin watch enable_plugins
91
93
  },
92
94
  set(v) {
93
- if (typeof v === 'function')
94
- this.set((0, immer_1.produce)(this.get(), v));
95
+ if (typeof v === 'function') {
96
+ const draft = structuredClone(this.get());
97
+ this.set(v(draft) ?? draft); // use return value if provided
98
+ }
95
99
  else
96
100
  setConfig1(k, v);
97
101
  },
@@ -220,7 +224,7 @@ function subMultipleConfigs(cb, configs) {
220
224
  };
221
225
  }
222
226
  exports.showHelp = argv_1.argv.help;
223
- exports.configReady = events_1.default.once('configReady'); // the boolean value means startedWithoutConfig
227
+ exports.configReady = events_1.default.once('configReady').then(x => x[0]); // the value is startedWithoutConfig. The .then also avoids exposing the cancel-subscription function.
224
228
  exports.configReady.then(() => {
225
229
  if (!exports.showHelp)
226
230
  return;
package/src/consoleLog.js CHANGED
@@ -10,7 +10,16 @@ const cross_1 = require("./cross");
10
10
  const fs_1 = require("fs");
11
11
  const argv_1 = require("./argv");
12
12
  exports.consoleLog = [];
13
+ const originalConsoleLog = console.log;
13
14
  const f = argv_1.argv.consoleFile ? (0, fs_1.createWriteStream)(argv_1.argv.consoleFile, { flags: 'a', encoding: 'utf8' }) : null;
15
+ let terminalOutputBroken = false;
16
+ for (const stream of [process.stdout, process.stderr])
17
+ stream.on('error', err => {
18
+ if (!isBrokenTerminalOutput(err))
19
+ throw err;
20
+ // after the terminal/pipe is gone, further console writes would just re-emit the same process-level error
21
+ terminalOutputBroken = true;
22
+ });
14
23
  for (const k of ['log', 'warn', 'error', 'debug']) {
15
24
  const original = console[k];
16
25
  console[k] = (...args) => {
@@ -28,7 +37,31 @@ for (const k of ['log', 'warn', 'error', 'debug']) {
28
37
  if (k !== 'log')
29
38
  args.unshift('!');
30
39
  }
31
- return original((0, cross_1.formatTime)(ts), ...args); // bundled nodejs doesn't have locales (and apparently uses en-US)
40
+ if (!terminalOutputBroken) {
41
+ try {
42
+ return original((0, cross_1.formatTime)(ts), ...args);
43
+ } // bundled nodejs doesn't have locales (and apparently uses en-US)
44
+ catch (err) {
45
+ if (!isBrokenTerminalOutput(err))
46
+ throw err;
47
+ terminalOutputBroken = true;
48
+ }
49
+ }
50
+ };
51
+ Object.assign(console[k], { original });
52
+ }
53
+ const over = console.log;
54
+ for (const k of ['table']) {
55
+ const original = console[k];
56
+ console[k] = (...args) => {
57
+ console.log = originalConsoleLog;
58
+ // @ts-ignore
59
+ try {
60
+ return original(...args);
61
+ }
62
+ finally {
63
+ console.log = over;
64
+ }
32
65
  };
33
66
  }
34
67
  function safeJoin(a) {
@@ -55,6 +88,11 @@ function safeJoin(a) {
55
88
  }).join(' ');
56
89
  }
57
90
  }
91
+ function isBrokenTerminalOutput(err) {
92
+ const code = err?.code;
93
+ return code === 'EPIPE' || code === 'EIO'
94
+ || code === 'ERR_STREAM_DESTROYED' || code === 'ERR_STREAM_WRITE_AFTER_END';
95
+ }
58
96
  function consoleHint(msg) {
59
97
  console.log("HINT: " + msg);
60
98
  }
package/src/const.js CHANGED
@@ -78,6 +78,10 @@ exports.IS_BINARY = !/node|bun/.test((0, path_1.basename)(process.execPath)); //
78
78
  exports.APP_PATH = (0, path_1.dirname)(exports.IS_BINARY ? process.execPath : __dirname); // __dirname's parent can be compared with cwd
79
79
  exports.MIME_AUTO = 'auto';
80
80
  exports.CONFIG_FILE = 'config.yaml';
81
+ if (argv_1.argv.version) {
82
+ process.stdout.write(exports.VERSION + '\n');
83
+ process.exit(0);
84
+ }
81
85
  // we want this to be the first stuff to be printed, then we print it in this module, that is executed at the beginning
82
86
  if (exports.DEV) {
83
87
  console.clear();