hfs 0.26.2 → 0.26.5

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.
@@ -0,0 +1 @@
1
+ :root{--bg: #fff;--text: #555;--faint-contrast: #0002;--mild-contrast: #0005;--good-contrast: #000a;--button-bg: #68a;--button-text: #fff;--focus-color: #468}:root .theme-dark{--bg: #000;--text: #999;--faint-contrast: #fff2;--mild-contrast: #fff5;--good-contrast: #fffa;--button-bg: #345;--button-text: #999}:root .theme-dark body{color-scheme:dark}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}body{background:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input,select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box}input[type=checkbox]{margin:0 1.3em 0 .8em;transform:scale(1.7);accent-color:var(--button-bg)}.hidden{display:none!important}.icon{font-size:1.2em}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button.toggled{opacity:.6}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.error-msg{background-color:#faa;color:#833}header{position:sticky;top:0;background:var(--bg);padding:.2em;z-index:1}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats{font-size:90%;margin:.4em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}header input{width:100%;margin:.2em auto;box-sizing:border-box}#filter-bar{display:flex;gap:.3em;margin:.5em 0}#filter-bar input{flex:1}#filter-bar button{padding:0 .5em}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir li{display:block;list-style-type:none;margin-bottom:.3em;padding:.3em;border-top:1px solid var(--button-bg)}ul.dir li a{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a.container-folder:hover{text-decoration:underline}ul.dir li .entry-props{float:right;font-size:90%;margin-left:12px;margin-top:.2em}ul.dir li .entry-props .icon{margin:0 .3em}ul.dir li .entry-props .entry-size{display:inline-block}#menu-panel{margin-bottom:.2em}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:auto;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#menu-bar>a>button{width:100%}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-confirm,.dialog-prompt{--color: var(--button-bg)}#paging{display:flex;position:sticky;bottom:0;background:var(--bg);gap:.5em;overflow-x:auto}#paging>button{flex:1;background:var(--button-bg);text-align:center}*{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--faint-contrast)}*::-webkit-scrollbar{width:12px}*::-webkit-scrollbar-track{background:var(--faint-contrast)}*::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--faint-contrast)}@media (max-width: 42em){body,button,select{font-size:14pt}#menu-bar button label{display:none}#filter-bar{margin:.2em 0}#filter-bar label{display:none}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#888a;display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em)}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:1.8em;height:1.7em;text-align:center;border-radius:.8em 0}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c99}.dialog-icon~.dialog-content{margin-top:1.3em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.7em}.dialog-content{overflow:auto;max-height:calc(100vh - 4em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 50em){.dialog-closer{font-size:120%}.dialog-icon~.dialog-content{margin-top:2em}}
@@ -1,4 +1,4 @@
1
- import{c as SF}from"./index.f056db34.js";function OF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var yF={exports:{}};/*
1
+ import{c as SF}from"./index.1151988f.js";function OF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var yF={exports:{}};/*
2
2
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
3
3
  *
4
4
  * @version 0.8.0
@@ -6,8 +6,8 @@
6
6
  <link href="fontello.css" rel="stylesheet" />
7
7
  <script>SESSION = _HFS_SESSION_</script>
8
8
  <title>File Server</title>
9
- <script type="module" crossorigin src="/assets/index.55c710c2.js"></script>
10
- <link rel="stylesheet" href="/assets/index.ee805a6c.css">
9
+ <script type="module" crossorigin src="/assets/index.1151988f.js"></script>
10
+ <link rel="stylesheet" href="/assets/index.93366732.css">
11
11
  </head>
12
12
  <body>
13
13
  <div hidden>_HFS_PLUGINS_</div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.26.2",
3
+ "version": "0.26.5",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -30,7 +30,6 @@
30
30
  "dist-bin": "npm run dist-modules && cd dist && pkg . -C gzip && mv -f hfs-win-x64.exe hfs.exe && zip hfs-windows.zip hfs.exe -r plugins && cp -f hfs-linux-x64 hfs && zip hfs-linux.zip hfs -r plugins && cp -f hfs-macos-x64 hfs && zip hfs-mac.zip hfs -r plugins && cp -f hfs-macos-arm64 hfs && zip hfs-mac-arm.zip hfs -r plugins && rm hfs",
31
31
  "dist-modules": "cp package*.json dist && cd dist && npm ci --omit=dev && npm i -f --no-save --omit=dev @node-rs/crc32-win32-x64-msvc && rm package-lock.json && cd .. && node prune_modules",
32
32
  "dist-win": "npm run dist-modules && cd dist && pkg . -C gzip -t node16-win-x64 && zip hfs-windows.zip hfs.exe -r plugins",
33
- "dist-mac": "cd dist && pkg . -C gzip -t node16-mac-arm64",
34
33
  "dist-node": "npm run dist-modules && cd dist && zip hfs-node.zip -r * -x *.zip *.exe hfs-* *.log logs"
35
34
  },
36
35
  "engines": {
@@ -9,9 +9,7 @@ exports.config = {
9
9
  fields: {
10
10
  host: { label: "Domain" },
11
11
  root: { helperText: "Root path in VFS" },
12
- },
13
- defaultValue: [],
14
- height: 300,
12
+ }
15
13
  },
16
14
  mandatory: {
17
15
  label: "Block requests that are not using any of the domains above",
@@ -23,7 +23,9 @@ const apis = {
23
23
  pluginUninstalled: id => list.remove({ id }),
24
24
  });
25
25
  function serialize(p) {
26
- return lodash_1.default.defaults('getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData()) : p, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
26
+ const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
27
+ : { ...p }; // _.defaults mutates object, and we don't want that
28
+ return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
27
29
  }
28
30
  },
29
31
  async get_plugin_updates() {
package/src/commands.js CHANGED
@@ -7,10 +7,23 @@ const perm_1 = require("./perm");
7
7
  const config_1 = require("./config");
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
9
  const update_1 = require("./update");
10
+ const listen_1 = require("./listen");
10
11
  const yaml_1 = __importDefault(require("yaml"));
11
12
  const const_1 = require("./const");
12
- console.log(`HINT: type "help" for help`);
13
- require('readline').createInterface({ input: process.stdin }).on('line', (line) => {
13
+ const readline_1 = require("readline");
14
+ try {
15
+ /*
16
+ is this try-block useful in case the stdin is unavailable?
17
+ Not sure, but someone reported a problem using nohup https://github.com/rejetto/hfs/issues/74
18
+ and I've found this example try-catching https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dda83a906914489e09ca28afea12948529015d4a/types/node/readline.d.ts#L489
19
+ */
20
+ (0, readline_1.createInterface)({ input: process.stdin }).on('line', parseCommandLine);
21
+ console.log(`HINT: type "help" for help`);
22
+ }
23
+ catch (_a) {
24
+ console.log("console commands not available");
25
+ }
26
+ function parseCommandLine(line) {
14
27
  if (!line)
15
28
  return;
16
29
  const [name, ...params] = line.trim().split(/ +/);
@@ -25,7 +38,7 @@ require('readline').createInterface({ input: process.stdin }).on('line', (line)
25
38
  else
26
39
  throw err;
27
40
  });
28
- });
41
+ }
29
42
  const commands = {
30
43
  help: {
31
44
  params: '',
@@ -33,6 +46,12 @@ const commands = {
33
46
  console.log("supported commands:", ...lodash_1.default.map(commands, ({ params }, name) => '\n - ' + name + ' ' + params));
34
47
  }
35
48
  },
49
+ 'show-admin': {
50
+ params: '',
51
+ cb() {
52
+ (0, listen_1.openAdmin)();
53
+ }
54
+ },
36
55
  'create-admin': {
37
56
  params: '<password> [<username>=admin]',
38
57
  async cb(password, username = 'admin') {
package/src/config.js CHANGED
@@ -12,7 +12,6 @@ const yaml_1 = __importDefault(require("yaml"));
12
12
  const lodash_1 = __importDefault(require("lodash"));
13
13
  const misc_1 = require("./misc");
14
14
  const fs_1 = require("fs");
15
- const util_1 = require("util");
16
15
  const path_1 = require("path");
17
16
  const events_2 = __importDefault(require("./events"));
18
17
  const FILE = 'config.yaml';
@@ -163,10 +162,7 @@ exports.saveConfigAsap = (0, misc_1.debounceAsync)(async () => {
163
162
  await (0, misc_1.wait)(100);
164
163
  let txt = yaml_1.default.stringify(state, { lineWidth: 1000 });
165
164
  if (txt.trim() === '{}') // most users wouldn't understand
166
- if (await (0, util_1.promisify)(fs_1.exists)(path)) // if a file exists then empty it, else don't bother creating it
167
- txt = '';
168
- else
169
- return;
165
+ txt = '';
170
166
  save(path, txt)
171
167
  .catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
172
168
  });
package/src/const.js CHANGED
@@ -43,7 +43,7 @@ const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
43
  exports.VERSION = pkg.version;
44
44
  exports.DAY = 86400000;
45
45
  exports.SESSION_DURATION = exports.DAY;
46
- exports.API_VERSION = 4; // introduced type:real_path and api.subscribeConfig/setConfig/getHfsConfig
46
+ exports.API_VERSION = 4.1; // array.$width
47
47
  exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
48
48
  exports.SPECIAL_URI = '/~/';
49
49
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
package/src/listen.js CHANGED
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.getUrls = exports.getStatus = exports.httpsPortCfg = exports.portCfg = void 0;
30
+ exports.getUrls = exports.getStatus = exports.httpsPortCfg = exports.openAdmin = exports.portCfg = void 0;
31
31
  const http = __importStar(require("http"));
32
32
  const config_1 = require("./config");
33
33
  const index_1 = require("./index");
@@ -40,6 +40,7 @@ const misc_1 = require("./misc");
40
40
  const const_1 = require("./const");
41
41
  const find_process_1 = __importDefault(require("find-process"));
42
42
  const perm_1 = require("./perm");
43
+ const lodash_1 = __importDefault(require("lodash"));
43
44
  let httpSrv;
44
45
  let httpsSrv;
45
46
  const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
@@ -55,13 +56,25 @@ exports.portCfg.sub(async (port) => {
55
56
  httpSrv.on('connection', connections_1.newConnection);
56
57
  printUrls(port, 'http');
57
58
  if (openBrowserAtStart.get())
58
- (0, open_1.default)('http://localhost' + (port === 80 ? '' : ':' + port) + const_1.ADMIN_URI, { wait: true }).catch(e => {
59
+ openAdmin();
60
+ });
61
+ function openAdmin() {
62
+ for (const srv of [httpSrv, httpsSrv]) {
63
+ const a = srv.address();
64
+ if (!a || typeof a === 'string')
65
+ continue;
66
+ const baseUrl = srv.name + '://localhost:' + a.port;
67
+ (0, open_1.default)(baseUrl + const_1.ADMIN_URI, { wait: true }).catch(e => {
59
68
  console.debug(String(e));
60
69
  console.warn("cannot launch browser on this machine >PLEASE< open your browser and reach one of these (you may need a different address)", ...Object.values(getUrls()).flat().map(x => '\n - ' + x + const_1.ADMIN_URI));
61
70
  if (!(0, perm_1.anyAccountCanLoginAdmin)())
62
71
  console.log(`HINT: you can enter command: create-admin YOUR_PASSWORD`);
63
72
  });
64
- });
73
+ return true;
74
+ }
75
+ console.log("openAdmin failed");
76
+ }
77
+ exports.openAdmin = openAdmin;
65
78
  const considerHttps = (0, misc_1.debounceAsync)(async () => {
66
79
  var _a, _b;
67
80
  stopServer(httpsSrv).then();
@@ -207,20 +220,16 @@ function printUrls(port, proto) {
207
220
  if (!port)
208
221
  return;
209
222
  for (const [name, nets] of Object.entries((0, os_1.networkInterfaces)())) {
210
- if (!nets)
223
+ if (!nets || ignore.test(name))
211
224
  continue;
212
- const filteredNets = nets.filter(n => !n.internal);
213
- if (!filteredNets.length || ignore.test(name))
225
+ lodash_1.default.remove(nets, 'internal');
226
+ if (!nets.length)
214
227
  continue;
215
- console.log('network', name);
216
- for (const net of nets) {
217
- if (net.internal)
218
- continue;
219
- const appendPort = port === (proto === 'https' ? 443 : 80) ? '' : ':' + port;
220
- let { address } = net;
221
- if (address.includes(':'))
222
- address = '[' + address + ']';
223
- console.log('-', proto + '://' + address + appendPort);
224
- }
228
+ const best = lodash_1.default.find(nets, { family: 'IPv4' }) || nets[0];
229
+ const appendPort = port === (proto === 'https' ? 443 : 80) ? '' : ':' + port;
230
+ let { address } = best;
231
+ if (address.includes(':'))
232
+ address = '[' + address + ']';
233
+ console.log('network', name, proto + '://' + address + appendPort);
225
234
  }
226
235
  }
package/src/plugins.js CHANGED
@@ -31,7 +31,7 @@ exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.ena
31
31
  const fast_glob_1 = __importDefault(require("fast-glob"));
32
32
  const watchLoad_1 = require("./watchLoad");
33
33
  const lodash_1 = __importDefault(require("lodash"));
34
- const path_1 = __importStar(require("path"));
34
+ const path_1 = __importDefault(require("path"));
35
35
  const const_1 = require("./const");
36
36
  const Const = __importStar(require("./const"));
37
37
  const misc_1 = require("./misc");
@@ -66,8 +66,9 @@ function setPluginConfig(id, changes) {
66
66
  }
67
67
  exports.setPluginConfig = setPluginConfig;
68
68
  function getPluginInfo(id) {
69
- var _a, _b;
70
- return (_b = (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData()) !== null && _b !== void 0 ? _b : availablePlugins[id];
69
+ var _a;
70
+ const running = (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData();
71
+ return running && Object.assign(running, { id }) || availablePlugins[id];
71
72
  }
72
73
  exports.getPluginInfo = getPluginInfo;
73
74
  function mapPlugins(cb) {
@@ -123,25 +124,14 @@ function pluginsMiddleware() {
123
124
  exports.pluginsMiddleware = pluginsMiddleware;
124
125
  class Plugin {
125
126
  constructor(id, data, unwatch) {
126
- var _a, _b;
127
127
  this.id = id;
128
128
  this.data = data;
129
129
  this.unwatch = unwatch;
130
130
  this.started = new Date();
131
131
  if (!data)
132
132
  throw 'invalid data';
133
- // if a previous instance is present, we are going to overwrite it, but first call its unload callback
134
- const old = plugins[id];
135
- try {
136
- (_b = (_a = old === null || old === void 0 ? void 0 : old.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a);
137
- } // we don't want all the effects of the Plugin.unload
138
- catch (e) {
139
- console.debug('error unloading plugin', id, String(e));
140
- }
141
- // track this
142
- const wasStopped = availablePlugins[id];
143
- if (wasStopped)
144
- delete availablePlugins[id];
133
+ if (plugins[id])
134
+ throw "unload first: " + id;
145
135
  plugins[id] = this;
146
136
  this.data = data = { ...data }; // clone to make object modifiable. Objects coming from import are not.
147
137
  // some validation
@@ -154,7 +144,6 @@ class Plugin {
154
144
  console.warn('invalid', k);
155
145
  }
156
146
  }
157
- events_1.default.emit(old || wasStopped ? 'pluginStarted' : 'pluginInstalled', this);
158
147
  }
159
148
  get middleware() {
160
149
  var _a;
@@ -175,17 +164,19 @@ class Plugin {
175
164
  getData() {
176
165
  return { ...this.data };
177
166
  }
178
- async unload() {
167
+ async unload(reloading = false) {
179
168
  var _a, _b;
180
169
  const { id } = this;
181
- console.log('unloading plugin', id);
182
170
  try {
183
171
  await ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
172
+ console.log('unloaded plugin', id);
184
173
  }
185
174
  catch (e) {
186
- console.debug('error unloading plugin', id, String(e));
175
+ console.log('error unloading plugin', id, String(e));
187
176
  }
188
177
  delete plugins[id];
178
+ if (reloading)
179
+ return;
189
180
  this.unwatch();
190
181
  if (availablePlugins[id])
191
182
  events_1.default.emit('pluginStopped', availablePlugins[id]);
@@ -213,8 +204,8 @@ async function rescan() {
213
204
  console.debug('scanning plugins');
214
205
  const found = [];
215
206
  const foundDisabled = {};
216
- const MASK = (0, path_1.join)(exports.PATH, '*', 'plugin.js');
217
- for (const f of await (0, fast_glob_1.default)([(0, path_1.join)(const_1.APP_PATH, MASK), MASK])) {
207
+ const MASK = exports.PATH + '/*/plugin.js'; // be sure to not use path.join as fast-glob doesn't work with \
208
+ for (const f of await (0, fast_glob_1.default)([(0, misc_1.adjustStaticPathForGlob)(const_1.APP_PATH) + '/' + MASK, MASK])) {
218
209
  const id = f.split('/').slice(-2)[0];
219
210
  if (id.endsWith(exports.DISABLING_POSTFIX))
220
211
  continue;
@@ -232,14 +223,16 @@ async function rescan() {
232
223
  const module = path_1.default.resolve(f);
233
224
  const { unwatch } = (0, watchLoad_1.watchLoad)(f, async () => {
234
225
  try {
235
- const reloading = plugins[id];
236
- console.log(reloading ? "reloading plugin" : "loading plugin", id);
226
+ const alreadyRunning = plugins[id];
227
+ console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id);
237
228
  const { init, ...data } = await Promise.resolve().then(() => __importStar(require(module)));
238
229
  delete data.default;
239
230
  deleteModule(require.resolve(module)); // avoid caching at next import
240
231
  calculateBadApi(data);
241
232
  if (data.badApi)
242
233
  console.log("plugin", id, data.badApi);
234
+ await (alreadyRunning === null || alreadyRunning === void 0 ? void 0 : alreadyRunning.unload(true));
235
+ console.debug("starting plugin", id);
243
236
  const res = await (init === null || init === void 0 ? void 0 : init.call(null, {
244
237
  srcDir: __dirname,
245
238
  const: Const,
@@ -256,16 +249,28 @@ async function rescan() {
256
249
  cb(last);
257
250
  return exports.pluginsConfig.sub(() => {
258
251
  const now = this.getConfig(cfgKey);
259
- if (!(0, misc_1.same)(now, last))
252
+ if ((0, misc_1.same)(now, last))
253
+ return;
254
+ try {
260
255
  cb(last = now);
256
+ }
257
+ catch (e) {
258
+ console.log('plugin', id, String(e));
259
+ }
261
260
  });
262
261
  },
263
262
  getHfsConfig: config_1.getConfig,
264
263
  }));
265
264
  Object.assign(data, res);
266
- new Plugin(id, data, unwatch);
267
- if (reloading)
268
- events_1.default.emit('pluginUpdated', getPluginInfo(id));
265
+ const plugin = new Plugin(id, data, unwatch);
266
+ if (alreadyRunning)
267
+ events_1.default.emit('pluginUpdated', Object.assign(lodash_1.default.pick(plugin, 'started'), getPluginInfo(id)));
268
+ else {
269
+ const wasInstalled = availablePlugins[id];
270
+ if (wasInstalled)
271
+ delete availablePlugins[id];
272
+ events_1.default.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin);
273
+ }
269
274
  }
270
275
  catch (e) {
271
276
  console.log("plugin error:", e);
package/src/util-files.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.unzip = exports.run = exports.dirStream = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
6
+ exports.unzip = exports.run = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  const misc_1 = require("./misc");
9
9
  const fs_1 = require("fs");
@@ -86,23 +86,30 @@ function isWindowsDrive(s) {
86
86
  return s && /^[a-zA-Z]:$/.test(s);
87
87
  }
88
88
  exports.isWindowsDrive = isWindowsDrive;
89
- async function* dirStream(path) {
90
- const stats = await promises_1.default.stat(path);
91
- if (!stats.isDirectory())
89
+ // apply this to paths that may contain \ as separator (not supported by fast-glob) and other special chars to be escaped (parenthesis)
90
+ function adjustStaticPathForGlob(path) {
91
+ return fast_glob_1.default.escapePath(path.replace(/\\/g, '/'));
92
+ }
93
+ exports.adjustStaticPathForGlob = adjustStaticPathForGlob;
94
+ async function* dirStream(path, deep) {
95
+ if (!await isDirectory(path))
92
96
  throw Error('ENOTDIR');
93
- const dirStream = fast_glob_1.default.stream('*', {
97
+ const dirStream = fast_glob_1.default.stream(deep ? '**/*' : '*', {
94
98
  cwd: path,
95
99
  dot: true,
100
+ deep,
96
101
  onlyFiles: false,
97
102
  suppressErrors: true,
103
+ objectMode: true,
98
104
  });
99
105
  const skip = await getItemsToSkip(path);
100
- for await (let path of dirStream) {
101
- if (path instanceof Buffer)
102
- path = path.toString('utf8');
103
- if (skip === null || skip === void 0 ? void 0 : skip.includes(path))
106
+ for await (const entry of dirStream) {
107
+ let { path, dirent } = entry;
108
+ if (!dirent.isDirectory() && !dirent.isFile())
104
109
  continue;
105
- yield path;
110
+ path = String(path);
111
+ if (!(skip === null || skip === void 0 ? void 0 : skip.includes(path)))
112
+ yield path;
106
113
  }
107
114
  async function getItemsToSkip(path) {
108
115
  if (!const_1.IS_WINDOWS)
package/src/vfs.js CHANGED
@@ -114,7 +114,19 @@ function hasPermission(node, perm, ctx) {
114
114
  && (perm !== 'can_see' || hasPermission(node, 'can_read', ctx)); // for can_see you must also can_read
115
115
  }
116
116
  exports.hasPermission = hasPermission;
117
+ /* redev: 1322k716 8.6s
118
+ -temp 6.43s
119
+ */
120
+ setTimeout(async () => {
121
+ let n = 0;
122
+ console.time('asd');
123
+ for await (const x of walkNode({ source: '/Users/rejetto/redev' }, undefined, Infinity))
124
+ ++n;
125
+ console.timeEnd('asd');
126
+ console.log({ n });
127
+ }, 3000);
117
128
  async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
129
+ var _a;
118
130
  const { children, source } = parent;
119
131
  if (children)
120
132
  for (let idx = 0; idx < children.length; idx++) {
@@ -122,60 +134,58 @@ async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
122
134
  yield* workItem({
123
135
  ...child,
124
136
  name: prefixPath ? (prefixPath + getNodeName(child)) : child.name
125
- });
137
+ }, depth > 0 && await nodeIsDirectory(child).catch(() => false));
126
138
  }
127
139
  if (!source)
128
140
  return;
129
141
  try {
130
- for await (const path of (0, misc_1.dirStream)(source)) {
131
- if (ctx.req.aborted)
142
+ for await (const path of (0, misc_1.dirStream)(source, depth)) {
143
+ if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
132
144
  return;
133
- let { rename } = parent;
134
- const renamed = rename === null || rename === void 0 ? void 0 : rename[path];
145
+ const renamed = (_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path];
135
146
  yield* workItem({
136
- name: (prefixPath || renamed) && prefixPath + (renamed || path),
147
+ name: prefixPath + (renamed || path),
137
148
  source: (0, path_1.join)(source, path),
138
- rename: renameUnderPath(rename, path),
149
+ rename: renameUnderPath(parent.rename, path),
139
150
  });
140
151
  }
141
152
  }
142
153
  catch (e) {
143
154
  console.debug('glob', source, e); // ENOTDIR, or lacking permissions
144
155
  }
145
- async function* workItem(item) {
156
+ // item will be changed, so be sure to pass a temp node
157
+ async function* workItem(item, recur = false) {
158
+ const name = getNodeName(item);
146
159
  // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
147
- const name = (0, path_1.basename)(getNodeName(item));
148
- const url = (0, misc_1.enforceFinal)('/', parent.url || '') + name;
149
- const temp = inheritFromParent(parent, {
150
- ...item,
160
+ const virtualBasename = (0, path_1.basename)(name);
161
+ const url = (0, misc_1.enforceFinal)('/', parent.url || '') + virtualBasename;
162
+ Object.assign(item, {
151
163
  isTemp: true,
152
164
  url,
153
165
  parents: [...parent.parents || [], parent],
154
166
  });
155
- applyMasks(temp, parent, name);
156
- if (!hasPermission(temp, 'can_see', ctx))
167
+ inheritFromParent(parent, item);
168
+ applyMasks(item, parent, virtualBasename);
169
+ if (ctx && !hasPermission(item, 'can_see', ctx))
157
170
  return;
158
- yield temp;
159
- try {
160
- if (!depth || !await nodeIsDirectory(temp))
161
- return;
162
- inheritMasks(temp, parent, name);
163
- yield* walkNode(temp, ctx, depth - 1, getNodeName(temp) + '/');
164
- }
165
- catch (_a) { } // stat failed in nodeIsDirectory, ignore
171
+ yield item;
172
+ if (!recur)
173
+ return;
174
+ inheritMasks(item, parent, virtualBasename);
175
+ yield* walkNode(item, ctx, depth - 1, name + '/');
166
176
  }
167
177
  }
168
178
  exports.walkNode = walkNode;
169
- function applyMasks(item, parent, name) {
179
+ function applyMasks(item, parent, virtualBasename) {
170
180
  const { masks } = parent;
171
181
  if (!masks)
172
182
  return;
173
183
  for (const k in masks)
174
- if (k.startsWith('**/') && (0, micromatch_1.isMatch)(name, k.slice(3))
175
- || !k.includes('/') && (0, micromatch_1.isMatch)(name, k))
184
+ if (k.startsWith('**/') && (0, micromatch_1.isMatch)(virtualBasename, k.slice(3))
185
+ || !k.includes('/') && (0, micromatch_1.isMatch)(virtualBasename, k))
176
186
  Object.assign(item, masks[k]);
177
187
  }
178
- function inheritMasks(item, parent, name) {
188
+ function inheritMasks(item, parent, virtualBasename) {
179
189
  const { masks } = parent;
180
190
  if (!masks)
181
191
  return;
@@ -183,8 +193,8 @@ function inheritMasks(item, parent, name) {
183
193
  for (const k in masks)
184
194
  if (k.startsWith('**/'))
185
195
  o[k.slice(3)] = masks[k];
186
- else if (k.startsWith(name + '/'))
187
- o[k.slice(name.length + 1)] = masks[k];
196
+ else if (k.startsWith(virtualBasename + '/'))
197
+ o[k.slice(virtualBasename.length + 1)] = masks[k];
188
198
  if (Object.keys(o).length)
189
199
  item.masks = o;
190
200
  }
@@ -1 +0,0 @@
1
- :root{--bg: #fff;--text: #555;--faint-contrast: #0002;--mild-contrast: #0005;--good-contrast: #000a;--button-bg: #68a;--button-text: #fff;--focus-color: #468}:root .theme-dark{--bg: #000;--text: #999;--faint-contrast: #fff2;--mild-contrast: #fff5;--good-contrast: #fffa;--button-bg: #345;--button-text: #999}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}body{background:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input,select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box}input[type=checkbox]{margin:0 1.3em 0 .8em;transform:scale(1.7);accent-color:var(--button-bg)}.hidden{display:none!important}.icon{font-size:1.2em}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button.toggled{opacity:.6}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.error-msg{background-color:#faa;color:#833}header{position:sticky;top:0;background:var(--bg);padding:.2em;z-index:1}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats{font-size:90%;margin:.4em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}header input{width:100%;margin:.2em auto;box-sizing:border-box}#filter-bar{display:flex;gap:.3em;margin:.5em 0}#filter-bar input{flex:1}#filter-bar button{padding:0 .5em}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir li{display:block;list-style-type:none;margin-bottom:.3em;padding:.3em;border-top:1px solid var(--button-bg)}ul.dir li a{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a.container-folder:hover{text-decoration:underline}ul.dir li .entry-props{float:right;font-size:90%;margin-left:12px;margin-top:.2em}ul.dir li .entry-props .icon{margin:0 .3em}ul.dir li .entry-props .entry-size{display:inline-block}#menu-panel{margin-bottom:.2em}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:auto;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#menu-bar>a>button{width:100%}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-confirm,.dialog-prompt{--color: var(--button-bg)}#paging{display:flex;position:sticky;bottom:0;background:var(--bg);gap:.5em;overflow-x:auto}#paging>button{flex:1;background:var(--button-bg);text-align:center}*{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--faint-contrast)}*::-webkit-scrollbar{width:12px}*::-webkit-scrollbar-track{background:var(--faint-contrast)}*::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--faint-contrast)}@media (max-width: 42em){body,button,select{font-size:14pt}#menu-bar button label{display:none}#filter-bar{margin:.2em 0}#filter-bar label{display:none}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#888a;display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em)}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:1.8em;height:1.7em;text-align:center;border-radius:.8em 0}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c99}.dialog-icon~.dialog-content{margin-top:1.3em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.7em}.dialog-content{overflow:auto;max-height:calc(100vh - 4em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 50em){.dialog-closer{font-size:120%}.dialog-icon~.dialog-content{margin-top:2em}}