smart-home-engine 0.23.3 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -27,12 +27,12 @@ if (process.argv.includes('--install')) {
27
27
 
28
28
  // Resolve --data-dir early so that storage.js and config.js both see the correct
29
29
  // data root when they are first require()'d below.
30
- ;(function () {
30
+ (function () {
31
31
  const idx = process.argv.indexOf('--data-dir');
32
32
  if (idx !== -1 && process.argv[idx + 1] && !process.argv[idx + 1].startsWith('-')) {
33
33
  process.env.SHE_DATA_DIR = process.argv[idx + 1];
34
34
  }
35
- }());
35
+ })();
36
36
 
37
37
  // Ensure the data directory exists before anything else runs
38
38
  require('./lib/storage').ensureRoot();
@@ -73,6 +73,21 @@ const log = {
73
73
  const config = require('./config.js');
74
74
  const pkg = require('../package.json');
75
75
 
76
+ /**
77
+ * Build a short log label for a script file.
78
+ * Uses the path relative to the configured script dir(s) when possible,
79
+ * falling back to the bare filename for files outside the script dir.
80
+ */
81
+ function makeLabel(filePath) {
82
+ const normalized = filePath.replace(/\\/g, '/');
83
+ const dirs = config.dir ? (Array.isArray(config.dir) ? config.dir : [config.dir]) : [];
84
+ for (const d of dirs) {
85
+ const rel = path.relative(d, normalized).replace(/\\/g, '/');
86
+ if (!rel.startsWith('..')) return rel + ':';
87
+ }
88
+ return path.basename(normalized) + ':';
89
+ }
90
+
76
91
  log.setLevel(['debug', 'info', 'warn', 'error'].indexOf(config.verbosity) === -1 ? 'info' : config.verbosity);
77
92
  log.info('she ' + pkg.version + ' starting');
78
93
  log.debug('loaded config: ', config);
@@ -296,9 +311,9 @@ if (config.url) {
296
311
  const _mqttOpts = { will: { topic: config.name + '/connected', payload: '0', retain: true } };
297
312
  if (config.mqttUsername) _mqttOpts.username = config.mqttUsername;
298
313
  if (config.mqttPassword) _mqttOpts.password = config.mqttPassword;
299
- if (config.mqttCa) _mqttOpts.ca = config.mqttCa;
314
+ if (config.mqttCa) _mqttOpts.ca = config.mqttCa;
300
315
  if (config.mqttCert) _mqttOpts.cert = config.mqttCert;
301
- if (config.mqttKey) _mqttOpts.key = config.mqttKey;
316
+ if (config.mqttKey) _mqttOpts.key = config.mqttKey;
302
317
  mqtt = modules.mqtt.connect(config.url, _mqttOpts);
303
318
  mqtt.publish(config.name + '/connected', '2', { retain: true });
304
319
 
@@ -497,18 +512,19 @@ function setVariable(name, val) {
497
512
  }
498
513
 
499
514
  function createScript(source, name) {
500
- log.debug(name, 'compiling');
515
+ const compileLabel = makeLabel(name);
516
+ log.debug(compileLabel, 'compiling');
501
517
  try {
502
518
  return new vm.Script(source, { filename: name });
503
519
  } catch (err) {
504
- log.error(name, err.name + ':', err.message);
520
+ log.error(compileLabel, err.name + ':', err.message);
505
521
  return false;
506
522
  }
507
523
  }
508
524
 
509
525
  function runScript(script, name, origin) {
510
526
  const scriptDir = path.dirname(path.resolve(name));
511
- const logLabel = (origin || 'user') + '::' + path.basename(name) + ':';
527
+ const logLabel = makeLabel(name);
512
528
 
513
529
  // Initialise per-script resource tracking
514
530
  if (!scriptJobs.has(name)) scriptJobs.set(name, []);
@@ -521,6 +537,10 @@ function runScript(script, name, origin) {
521
537
 
522
538
  log.debug(logLabel, 'creating sandbox');
523
539
 
540
+ function serializeArg(a) {
541
+ return a !== null && typeof a === 'object' ? JSON.stringify(a) : a;
542
+ }
543
+
524
544
  const she = {
525
545
  global: _global,
526
546
 
@@ -530,7 +550,7 @@ function runScript(script, name, origin) {
530
550
  * @param {...*}
531
551
  */
532
552
  debug(...args) {
533
- log.debug(logLabel, ...args);
553
+ log.debug(logLabel, ...args.map(serializeArg));
534
554
  },
535
555
  /**
536
556
  * Log an info message
@@ -538,7 +558,7 @@ function runScript(script, name, origin) {
538
558
  * @param {...*}
539
559
  */
540
560
  info(...args) {
541
- log.info(logLabel, ...args);
561
+ log.info(logLabel, ...args.map(serializeArg));
542
562
  },
543
563
  /**
544
564
  * Log a warning message
@@ -546,7 +566,7 @@ function runScript(script, name, origin) {
546
566
  * @param {...*}
547
567
  */
548
568
  warn(...args) {
549
- log.warn(logLabel, ...args);
569
+ log.warn(logLabel, ...args.map(serializeArg));
550
570
  },
551
571
  /**
552
572
  * Log an error message
@@ -554,7 +574,7 @@ function runScript(script, name, origin) {
554
574
  * @param {...*}
555
575
  */
556
576
  error(...args) {
557
- log.error(logLabel, ...args);
577
+ log.error(logLabel, ...args.map(serializeArg));
558
578
  },
559
579
 
560
580
  /**
@@ -980,7 +1000,7 @@ function runScript(script, name, origin) {
980
1000
  md(she, { scriptDomain, scriptName, scriptFile: name });
981
1001
  });
982
1002
 
983
- log.debug(name, 'contextifying sandbox');
1003
+ log.debug(logLabel, 'contextifying sandbox');
984
1004
  const context = vm.createContext(Sandbox);
985
1005
 
986
1006
  scriptDomain.on('error', (e) => {
@@ -1004,12 +1024,28 @@ function runScript(script, name, origin) {
1004
1024
  log.debug(logLabel, 'running');
1005
1025
  script.runInContext(context);
1006
1026
  });
1027
+
1028
+ // Log a summary of what was registered — symmetric with the unload summary
1029
+ const registeredCallbacks =
1030
+ subscriptions.filter((s) => s._script === name).length +
1031
+ varSubscriptions.filter((s) => s._script === name).length +
1032
+ mqttEventCallbacks.filter((c) => c._script === name).length;
1033
+ const registeredTimers =
1034
+ _myJobs.length +
1035
+ sunEvents.filter((e) => e._script === name).length +
1036
+ _myTimers.size;
1037
+ if (registeredCallbacks > 0 || registeredTimers > 0) {
1038
+ const parts = [];
1039
+ if (registeredCallbacks > 0) parts.push(`${registeredCallbacks} callback${registeredCallbacks !== 1 ? 's' : ''}`);
1040
+ if (registeredTimers > 0) parts.push(`${registeredTimers} timer${registeredTimers !== 1 ? 's' : ''}`);
1041
+ log.debug(logLabel, `registered ${parts.join(' and ')}`);
1042
+ }
1007
1043
  }
1008
1044
 
1009
1045
  function loadScript(file, origin) {
1010
1046
  origin = origin || 'user';
1011
1047
  file = file.replace(/\\/g, '/');
1012
- const loadLabel = (origin || 'user') + '::' + path.basename(file) + ':';
1048
+ const loadLabel = makeLabel(file);
1013
1049
  if (scripts[file]) {
1014
1050
  log.error(loadLabel, 'already loaded?!');
1015
1051
  return;
@@ -1037,7 +1073,7 @@ function loadScript(file, origin) {
1037
1073
  function unloadScript(file) {
1038
1074
  file = file.replace(/\\/g, '/');
1039
1075
  const origin = scriptOrigins.get(file) || 'user';
1040
- const unloadLabel = (origin || 'user') + '::' + path.basename(file) + ':';
1076
+ const unloadLabel = makeLabel(file);
1041
1077
  log.info(unloadLabel, 'unloading');
1042
1078
  scriptOrigins.delete(file);
1043
1079
 
@@ -1046,12 +1082,18 @@ function unloadScript(file) {
1046
1082
 
1047
1083
  // Remove MQTT subscriptions belonging to this script
1048
1084
  for (let i = subscriptions.length - 1; i >= 0; i--) {
1049
- if (subscriptions[i]._script === file) { subscriptions.splice(i, 1); removedCallbacks++; }
1085
+ if (subscriptions[i]._script === file) {
1086
+ subscriptions.splice(i, 1);
1087
+ removedCallbacks++;
1088
+ }
1050
1089
  }
1051
1090
 
1052
1091
  // Remove MQTT event callbacks (connect/disconnect) belonging to this script
1053
1092
  for (let i = mqttEventCallbacks.length - 1; i >= 0; i--) {
1054
- if (mqttEventCallbacks[i]._script === file) { mqttEventCallbacks.splice(i, 1); removedCallbacks++; }
1093
+ if (mqttEventCallbacks[i]._script === file) {
1094
+ mqttEventCallbacks.splice(i, 1);
1095
+ removedCallbacks++;
1096
+ }
1055
1097
  }
1056
1098
 
1057
1099
  // Remove HTTP routes registered by this script via she.api
@@ -1158,7 +1200,7 @@ function loadSandbox(callback) {
1158
1200
  sandboxWatcher.on('ready', () => log.debug('watch', dir, 'initialized'));
1159
1201
  sandboxWatcher.on('all', (event, filePath) => {
1160
1202
  sandboxWatcher.close();
1161
- log.info(filePath, 'sandbox change detected. exiting.');
1203
+ log.info(makeLabel(filePath), 'sandbox change detected. exiting.');
1162
1204
  process.exit(0);
1163
1205
  });
1164
1206
  }
@@ -1290,9 +1332,9 @@ function loadDir(dir) {
1290
1332
  // .shelib marker changes - warn only, manual restart required
1291
1333
  if (basename === '.shelib') {
1292
1334
  if (event === 'add') {
1293
- log.warn(filePath, 'library marker added - .js files in this directory will no longer load as scripts after daemon restart');
1335
+ log.warn(makeLabel(filePath), 'library marker added - .js files in this directory will no longer load as scripts after daemon restart');
1294
1336
  } else if (event === 'unlink') {
1295
- log.warn(filePath, 'library marker removed - .js files in this directory will load as scripts after daemon restart');
1337
+ log.warn(makeLabel(filePath), 'library marker removed - .js files in this directory will load as scripts after daemon restart');
1296
1338
  }
1297
1339
  return;
1298
1340
  }
@@ -1312,32 +1354,33 @@ function loadDir(dir) {
1312
1354
  return;
1313
1355
  }
1314
1356
 
1357
+ const fileLabel = makeLabel(filePath);
1315
1358
  if (event === 'change' && filePath.endsWith('.js')) {
1316
1359
  if (isLibFile(filePath, dir)) {
1317
- log.warn(filePath, 'is a library file - scripts that require() it will see the old version until they or the daemon are restarted');
1360
+ log.warn(fileLabel, 'is a library file - scripts that require() it will see the old version until they or the daemon are restarted');
1318
1361
  return;
1319
1362
  }
1320
1363
  if (isDisabledPath(filePath) || isInDisabledDir(filePath, dir)) {
1321
- log.debug(filePath, 'is disabled - ignoring change');
1364
+ log.debug(fileLabel, 'is disabled - ignoring change');
1322
1365
  return;
1323
1366
  }
1324
- log.info(filePath, 'change detected. hot-reloading.');
1367
+ log.info(fileLabel, 'change detected. hot-reloading.');
1325
1368
  unloadScript(filePath);
1326
1369
  loadScript(filePath);
1327
1370
  } else if (event === 'add' && filePath.endsWith('.js')) {
1328
1371
  if (isLibFile(filePath, dir)) {
1329
- log.debug(filePath, 'is a library file - not loading as script');
1372
+ log.debug(fileLabel, 'is a library file - not loading as script');
1330
1373
  return;
1331
1374
  }
1332
1375
  if (isDisabledPath(filePath) || isInDisabledDir(filePath, dir)) {
1333
- log.debug(filePath, 'is disabled - not loading as script');
1376
+ log.debug(fileLabel, 'is disabled - not loading as script');
1334
1377
  return;
1335
1378
  }
1336
- log.info(filePath, 'added. loading.');
1379
+ log.info(fileLabel, 'added. loading.');
1337
1380
  loadScript(filePath);
1338
1381
  } else if (event === 'unlink' && filePath.endsWith('.js')) {
1339
1382
  if (scripts[filePath]) {
1340
- log.info(filePath, 'removed. unloading.');
1383
+ log.info(fileLabel, 'removed. unloading.');
1341
1384
  unloadScript(filePath);
1342
1385
  }
1343
1386
  }
@@ -164,6 +164,11 @@ router.get('/search', (req, res) => {
164
164
  obj.package.links?.homepage ||
165
165
  obj.package.links?.npm ||
166
166
  `https://www.npmjs.com/package/${encodeURIComponent(obj.package.name)}`,
167
+ author:
168
+ obj.package.publisher?.username ||
169
+ obj.package.author?.name ||
170
+ (obj.package.maintainers?.[0]?.username ?? null),
171
+ date: obj.package.date ?? null,
167
172
  }));
168
173
  res.json(results);
169
174
  } catch {
@@ -70,13 +70,26 @@ router.get('/status', async (req, res) => {
70
70
  const [statusOut, branchOut] = await Promise.all([git(['status', '--porcelain', '-u'], gitRoot), git(['rev-parse', '--abbrev-ref', 'HEAD'], gitRoot)]);
71
71
 
72
72
  const branch = branchOut.stdout.trim();
73
+
74
+ // Compute the path of scriptDir relative to gitRoot (forward slashes)
75
+ // so returned file paths are relative to scriptDir, matching the frontend tree.
76
+ const scriptRelToRoot = path.relative(gitRoot, scriptDir).replace(/\\/g, '/');
77
+
73
78
  const changes = statusOut.stdout
74
79
  .split('\n')
75
80
  .filter(Boolean)
76
- .map((line) => ({
77
- status: line.slice(0, 2).trim(),
78
- file: line.slice(3),
79
- }));
81
+ .map((line) => {
82
+ let file = line.slice(3);
83
+ if (scriptRelToRoot) {
84
+ if (file.startsWith(scriptRelToRoot + '/')) {
85
+ file = file.slice(scriptRelToRoot.length + 1);
86
+ } else {
87
+ return null; // outside scriptDir
88
+ }
89
+ }
90
+ return { status: line.slice(0, 2).trim(), file };
91
+ })
92
+ .filter(Boolean);
80
93
 
81
94
  let ahead = 0;
82
95
  let behind = 0;
@@ -46,6 +46,7 @@ function walk(dir, base, parentIsLib) {
46
46
  for (const entry of entries) {
47
47
  if (entry.name === '.shelib') continue;
48
48
  if (entry.name.startsWith('.shedisable-')) continue;
49
+ if (entry.isDirectory() && entry.name.startsWith('.')) continue;
49
50
  const rel = base ? `${base}/${entry.name}` : entry.name;
50
51
  if (entry.isDirectory()) {
51
52
  results.push(...walk(path.join(dir, entry.name), rel, lib));
@@ -75,6 +76,7 @@ function buildTree(dir, base, parentIsLib) {
75
76
  for (const entry of entries) {
76
77
  if (entry.name === '.shelib') continue;
77
78
  if (entry.name.startsWith('.shedisable-')) continue;
79
+ if (entry.isDirectory() && entry.name.startsWith('.')) continue;
78
80
  const rel = base ? `${base}/${entry.name}` : entry.name;
79
81
  const abs = path.join(dir, entry.name);
80
82
  if (entry.isDirectory()) {