smart-home-engine 1.43.0 → 1.45.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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-Crx1MbU9.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-IEG5HdGq.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -172,10 +172,10 @@
172
172
  }
173
173
  })();
174
174
  </script>
175
- <script type="module" crossorigin src="/assets/index-Crx1MbU9.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-IEG5HdGq.js"></script>
176
176
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-DKJI_o9J.js">
177
177
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
178
- <link rel="stylesheet" crossorigin href="/assets/index-dFdInRyC.css">
178
+ <link rel="stylesheet" crossorigin href="/assets/index-C3PWzLWR.css">
179
179
  </head>
180
180
  <body>
181
181
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.43.0",
3
+ "version": "1.45.0",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -31,7 +31,7 @@
31
31
  # she-servicectl files <adapter> <instance> JSON listing of /etc/<adapter>/ and /var/lib/<adapter>/<instance>/
32
32
  # she-servicectl file <adapter> <instance> read|write <path> a file inside those two directories (write: content on stdin)
33
33
  # she-servicectl asset <adapter> <relpath> a file shipped in the adapter package (example, schema)
34
- # she-servicectl node update [--lts] install/refresh tj/n (downloaded, no npm), then n stable (or n lts) — JSON with the version before and after
34
+ # she-servicectl node update [--lts|--latest|--stable] install/refresh tj/n (downloaded, no npm), then n install <label> — JSON with the version before and after
35
35
  # she-servicectl restart-all restart every running instance on this host (after a node update, say)
36
36
  # she-servicectl self-update replace this script with the one on stdin (she ships it)
37
37
  # she-servicectl remove-key drop the public key on stdin from the calling user's authorized_keys
@@ -693,10 +693,13 @@ cmd_node() {
693
693
  action=$1
694
694
  shift
695
695
  [ "$action" = update ] || die "unknown node action: $action"
696
- spec=stable
696
+ # the label is handed to n as it is: stable and lts both resolve to the newest
697
+ # long-term-support release (stable is n's older name for it), latest to the newest of all
698
+ spec=lts
697
699
  while [ $# -gt 0 ]; do
698
700
  case "$1" in
699
701
  --lts) spec=lts ;;
702
+ --latest) spec=latest ;;
700
703
  --stable) spec=stable ;;
701
704
  *) die "unknown option: $1" ;;
702
705
  esac
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Which Node.js versions a host could be updated to — the two labels tj/n understands:
5
+ * `lts` (newest Long Term Support release) and `latest` (newest official release).
6
+ *
7
+ * she asks nodejs.org once for all hosts and caches the answer, so the Hosts tab can show
8
+ * what a click would install and hide the button when the host already runs it. Best-effort:
9
+ * every failure yields the last good answer, or nulls — nothing throws.
10
+ */
11
+
12
+ const INDEX_URL = 'https://nodejs.org/dist/index.json';
13
+ const TTL = 6 * 60 * 60 * 1000;
14
+
15
+ let _fetch = (...args) => fetch(...args);
16
+
17
+ /** Test hook: replace the fetch implementation. */
18
+ function setFetch(fn) {
19
+ _fetch = fn;
20
+ }
21
+
22
+ /** @type {{lts: string|null, ltsName: string|null, latest: string|null, ts: number, ok: boolean}|null} */
23
+ let _cache = null;
24
+ /** @type {Promise<object>|null} */
25
+ let _inflight = null;
26
+
27
+ function clearCache() {
28
+ _cache = null;
29
+ _inflight = null;
30
+ }
31
+
32
+ /**
33
+ * @param {{force?: boolean, now?: number}} [opts]
34
+ * @returns {Promise<{lts: string|null, ltsName: string|null, latest: string|null, fetchedAt: number|null, stale: boolean}>}
35
+ */
36
+ async function releases(opts = {}) {
37
+ const now = opts.now ?? Date.now();
38
+ if (_cache && !opts.force && now - _cache.ts < TTL) {
39
+ return { lts: _cache.lts, ltsName: _cache.ltsName, latest: _cache.latest, fetchedAt: _cache.ts, stale: !_cache.ok };
40
+ }
41
+ if (_inflight) return _inflight;
42
+
43
+ _inflight = (async () => {
44
+ try {
45
+ const res = await _fetch(INDEX_URL, { signal: AbortSignal.timeout(15000) });
46
+ if (!res.ok) throw new Error(String(res.status));
47
+ const list = await res.json();
48
+ if (!Array.isArray(list) || list.length === 0) throw new Error('unexpected index.json');
49
+ // index.json is ordered newest first; an LTS entry carries its codename in `lts`
50
+ const latest = typeof list[0].version === 'string' ? list[0].version : null;
51
+ const ltsEntry = list.find((e) => e && e.lts && typeof e.version === 'string') || null;
52
+ _cache = { lts: ltsEntry ? ltsEntry.version : null, ltsName: ltsEntry ? String(ltsEntry.lts) : null, latest, ts: now, ok: true };
53
+ } catch {
54
+ // keep whatever was known before, just mark it stale
55
+ _cache = _cache ? { ..._cache, ts: now, ok: false } : { lts: null, ltsName: null, latest: null, ts: now, ok: false };
56
+ } finally {
57
+ _inflight = null;
58
+ }
59
+ return { lts: _cache.lts, ltsName: _cache.ltsName, latest: _cache.latest, fetchedAt: _cache.ok ? _cache.ts : null, stale: !_cache.ok };
60
+ })();
61
+
62
+ return _inflight;
63
+ }
64
+
65
+ module.exports = { releases, setFetch, clearCache, INDEX_URL, TTL };
@@ -29,7 +29,8 @@
29
29
  * GET /she/services/ssh/pubkey public key of the services identity (I5)
30
30
  * POST /she/services/ssh/keygen generate it
31
31
  * POST /she/services/hosts/:host/test run `she-servicectl version` → ok / code
32
- * POST /she/services/hosts/:host/node/update { channel: stable|lts } update Node.js with tj/n (helper v13)
32
+ * GET /she/services/node/releases[?refresh=1] what a node update would install: the newest lts and latest release
33
+ * POST /she/services/hosts/:host/node/update { channel: stable|lts|latest } update Node.js with tj/n (helper v13)
33
34
  * POST /she/services/hosts/:host/instances/restart-all restart every running instance (after a node update)
34
35
  * POST /she/services/hosts/:host/helper/deploy scp the helper to a remote host, install it, print the sudoers line
35
36
  * POST /she/services/hosts/:host/helper/remove { mode: key|all, force? } remove she from the host (I11), drop the host entry
@@ -56,6 +57,7 @@ const dns = require('dns');
56
57
  const { analyzeServices, wipeTopics, LOG_LEVELS } = require('../lib/services-inventory');
57
58
  const { shapeDevices, discoverTarget } = require('../lib/device-discovery');
58
59
  const npmRegistry = require('../lib/npm-registry');
60
+ const nodeReleases = require('../lib/node-releases');
59
61
  const catalog = require('../lib/services-catalog');
60
62
  const os = require('os');
61
63
  const path = require('path');
@@ -1043,12 +1045,18 @@ function nodeHostError(res, err) {
1043
1045
  return hostError(res, err);
1044
1046
  }
1045
1047
 
1046
- // POST /she/services/hosts/:host/node/update { channel?: 'stable' | 'lts' }
1048
+ // GET /she/services/node/releases[?refresh=1] — what the update buttons would install
1049
+ router.get('/node/releases', async (req, res) => {
1050
+ res.json(await nodeReleases.releases({ force: req.query.refresh === '1' }));
1051
+ });
1052
+
1053
+ // POST /she/services/hosts/:host/node/update { channel?: 'lts' | 'latest' }
1047
1054
  router.post('/hosts/:host/node/update', async (req, res) => {
1048
1055
  const entry = resolve(req, res);
1049
1056
  if (!entry) return;
1050
- const channel = (req.body && req.body.channel) || 'stable';
1051
- if (channel !== 'stable' && channel !== 'lts') return res.status(400).json({ error: "channel must be 'stable' or 'lts'" });
1057
+ // n's own labels, passed through as they are: stable and lts both mean the newest LTS
1058
+ const channel = (req.body && req.body.channel) || 'lts';
1059
+ if (!['stable', 'lts', 'latest'].includes(channel)) return res.status(400).json({ error: "channel must be 'stable', 'lts' or 'latest'" });
1052
1060
  try {
1053
1061
  // downloading and unpacking a node build on a small host takes a while
1054
1062
  const { stdout } = await entry.driver.exec(['node', 'update', '--' + channel], { timeout: 900000 });