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/README.md +11 -9
- package/dist/web/assets/index-D8Y4S58o.js +225 -0
- package/dist/web/assets/index-Dzb10UAc.css +1 -0
- package/dist/web/assets/{tsMode-DnW9KumD.js → tsMode-Dv7xPOvO.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/service/install.sh +2 -2
- package/src/index.js +69 -26
- package/src/web/deps-api.js +5 -0
- package/src/web/git-api.js +17 -4
- package/src/web/scripts-api.js +2 -0
- package/dist/web/assets/index-BPk8Jr3B.css +0 -1
- package/dist/web/assets/index-C4NGzKsM.js +0 -225
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
|
-
|
|
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)
|
|
314
|
+
if (config.mqttCa) _mqttOpts.ca = config.mqttCa;
|
|
300
315
|
if (config.mqttCert) _mqttOpts.cert = config.mqttCert;
|
|
301
|
-
if (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
|
-
|
|
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(
|
|
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 = (
|
|
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(
|
|
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 = (
|
|
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 = (
|
|
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) {
|
|
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) {
|
|
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(
|
|
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(
|
|
1364
|
+
log.debug(fileLabel, 'is disabled - ignoring change');
|
|
1322
1365
|
return;
|
|
1323
1366
|
}
|
|
1324
|
-
log.info(
|
|
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(
|
|
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(
|
|
1376
|
+
log.debug(fileLabel, 'is disabled - not loading as script');
|
|
1334
1377
|
return;
|
|
1335
1378
|
}
|
|
1336
|
-
log.info(
|
|
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(
|
|
1383
|
+
log.info(fileLabel, 'removed. unloading.');
|
|
1341
1384
|
unloadScript(filePath);
|
|
1342
1385
|
}
|
|
1343
1386
|
}
|
package/src/web/deps-api.js
CHANGED
|
@@ -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 {
|
package/src/web/git-api.js
CHANGED
|
@@ -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
|
-
|
|
78
|
-
|
|
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;
|
package/src/web/scripts-api.js
CHANGED
|
@@ -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()) {
|