iobroker.eos-admin 7.10.1 → 7.10.2

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 (33) hide show
  1. package/INSTALL_TEST_V7.10.2.md +22 -0
  2. package/MERGE_UPDATE_README_V7.10.2.md +11 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +15 -16
  4. package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +1410 -0
  5. package/PUBLISH_STABLE_V7.10.2.md +21 -0
  6. package/README.md +4 -4
  7. package/README_STABILITY_V7.10.2.md +20 -0
  8. package/RELEASE_ACCEPTANCE_V7.10.2.md +13 -0
  9. package/RELEASE_NOTES_V7.10.2.md +11 -0
  10. package/build/lib/eosAutoUpdate.js +418 -20
  11. package/build/lib/eosAutoUpdate.js.map +1 -0
  12. package/build/lib/eosRequestSecurity.js +4 -1
  13. package/build/lib/eosRequestSecurity.js.map +1 -0
  14. package/build/lib/web.js +40 -9
  15. package/build/lib/web.js.map +1 -1
  16. package/io-package.json +60 -5
  17. package/package.json +22 -16
  18. package/tools/nexowatt-auto-update-selftest.cjs +144 -32
  19. package/tools/nexowatt-backend-runtime-selftest.cjs +147 -0
  20. package/tools/nexowatt-merge-update-selftest.cjs +7 -1
  21. package/tools/nexowatt-prebuilt-release-selftest.cjs +292 -0
  22. package/tools/nexowatt-stable-v7102-selftest.cjs +51 -0
  23. package/tools/nexowatt-validate-package.cjs +35 -11
  24. package/INSTALL_TEST_V7.10.1.md +0 -10
  25. package/MERGE_UPDATE_README_V7.10.1.md +0 -10
  26. package/PUBLISH_STABLE_V7.10.1.md +0 -10
  27. package/README_STABILITY_V7.10.1.md +0 -10
  28. package/RELEASE_ACCEPTANCE_V7.10.1.md +0 -10
  29. package/RELEASE_NOTES_V7.10.1.md +0 -10
  30. package/adminWww/js/eos-release-watch.js +0 -77
  31. package/build/lib/eosPassword.js +0 -72
  32. package/build/lib/nexowattStableAuth.js +0 -74
  33. package/tools/nexowatt-stable-v7101-selftest.cjs +0 -15
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "eos-admin",
4
- "version": "7.10.1",
4
+ "version": "7.10.2",
5
5
  "titleLang": {
6
6
  "en": "NexoWatt EOS Admin",
7
7
  "de": "NexoWatt EOS Admin",
@@ -18,6 +18,11 @@
18
18
  "connectionType": "local",
19
19
  "dataSource": "push",
20
20
  "news": {
21
+ "7.10.2": {
22
+ "de": "Stable-Hotfix 7.10.2 beseitigt die EOS-Admin-Startschleife: Stable-Update-Status wird nicht mehr in die laufende Instanzkonfiguration geschrieben, der erste Abgleich startet verzögert und das Release prüft die vollständige Backend-Modulkette einschließlich eosRequestSecurity.",
23
+ "en": "Stable hotfix 7.10.2 removes the EOS Admin restart loop: stable-update status is no longer written to the running instance configuration, first reconciliation is delayed, and the release validates the complete backend module chain including eosRequestSecurity.",
24
+ "nl": "Stable-hotfix 7.10.2 verhelpt de EOS Admin-herstartlus: de status van stabiele updates wordt niet meer naar de configuratie van de actieve instantie geschreven, de eerste synchronisatie start vertraagd en de release controleert de volledige backendmoduleketen inclusief eosRequestSecurity."
25
+ },
21
26
  "7.10.1": {
22
27
  "de": "Stable-Kandidat 7.10.1 basiert wieder auf 7.9.98: native Passwortverwaltung ohne erzwungene Erstpasswortseite, dauerhafter Cockpit-Scrollbereich, Stift im unteren Kartenbereich und abschaltbare NexoWatt-Stable-Updates.",
23
28
  "en": "Stable candidate 7.10.1 returns to the 7.9.98 baseline: native password management without forced first-password page, persistent cockpit scrolling, in-grid edit control and optional NexoWatt stable updates.",
@@ -339,7 +344,7 @@
339
344
  "icon": "admin.svg",
340
345
  "messagebox": true,
341
346
  "enabled": true,
342
- "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.1/admin/admin.svg",
347
+ "extIcon": "https://unpkg.com/iobroker.eos-admin@7.10.2/admin/admin.svg",
343
348
  "keywords": [
344
349
  "NexoWatt",
345
350
  "EOS",
@@ -350,7 +355,7 @@
350
355
  "licensed"
351
356
  ],
352
357
  "compact": true,
353
- "readme": "https://unpkg.com/iobroker.eos-admin@7.10.1/README.md",
358
+ "readme": "https://unpkg.com/iobroker.eos-admin@7.10.2/README.md",
354
359
  "authors": [
355
360
  "NexoWatt"
356
361
  ],
@@ -418,7 +423,7 @@
418
423
  "nondeletable": false,
419
424
  "allowAdapterUpdate": true,
420
425
  "allowAdapterDelete": false,
421
- "meta": "https://unpkg.com/iobroker.eos-admin@7.10.1/io-package.json",
426
+ "meta": "https://unpkg.com/iobroker.eos-admin@7.10.2/io-package.json",
422
427
  "npmPackage": "iobroker.eos-admin",
423
428
  "packetName": "iobroker.eos-admin",
424
429
  "publisher": "NexoWatt"
@@ -848,6 +853,56 @@
848
853
  },
849
854
  "native": {}
850
855
  },
856
+ {
857
+ "_id": "info.nexowattStableUpdatesEnabled",
858
+ "type": "state",
859
+ "common": {
860
+ "name": {
861
+ "en": "NexoWatt stable automatic updates enabled",
862
+ "de": "Automatische NexoWatt-Stable-Updates aktiviert",
863
+ "ru": "Автоматические стабильные обновления NexoWatt включены",
864
+ "pt": "Atualizações estáveis automáticas da NexoWatt ativadas",
865
+ "nl": "Automatische stabiele NexoWatt-updates ingeschakeld",
866
+ "fr": "Mises à jour stables automatiques NexoWatt activées",
867
+ "it": "Aggiornamenti stabili automatici NexoWatt abilitati",
868
+ "es": "Actualizaciones estables automáticas de NexoWatt activadas",
869
+ "pl": "Automatyczne stabilne aktualizacje NexoWatt włączone",
870
+ "uk": "Автоматичні стабільні оновлення NexoWatt увімкнено",
871
+ "zh-cn": "已启用 NexoWatt 稳定版自动更新"
872
+ },
873
+ "role": "switch.enable",
874
+ "type": "boolean",
875
+ "read": true,
876
+ "write": false,
877
+ "def": true
878
+ },
879
+ "native": {}
880
+ },
881
+ {
882
+ "_id": "info.nexowattStableUpdatesState",
883
+ "type": "state",
884
+ "common": {
885
+ "name": {
886
+ "en": "NexoWatt stable automatic update runtime state",
887
+ "de": "Laufzeitstatus der automatischen NexoWatt-Stable-Updates",
888
+ "ru": "Состояние автоматических стабильных обновлений NexoWatt",
889
+ "pt": "Estado das atualizações estáveis automáticas da NexoWatt",
890
+ "nl": "Runtime-status van automatische stabiele NexoWatt-updates",
891
+ "fr": "État des mises à jour stables automatiques NexoWatt",
892
+ "it": "Stato degli aggiornamenti stabili automatici NexoWatt",
893
+ "es": "Estado de las actualizaciones estables automáticas de NexoWatt",
894
+ "pl": "Stan automatycznych stabilnych aktualizacji NexoWatt",
895
+ "uk": "Стан автоматичних стабільних оновлень NexoWatt",
896
+ "zh-cn": "NexoWatt 稳定版自动更新运行状态"
897
+ },
898
+ "role": "json",
899
+ "type": "json",
900
+ "read": true,
901
+ "write": false,
902
+ "def": "{}"
903
+ },
904
+ "native": {}
905
+ },
851
906
  {
852
907
  "_id": "info.newsFeed",
853
908
  "type": "state",
@@ -1128,5 +1183,5 @@
1128
1183
  }
1129
1184
  }
1130
1185
  },
1131
- "version": "7.10.1"
1186
+ "version": "7.10.2"
1132
1187
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
- "description": "NexoWatt EOS Admin – stable baseline with native account passwords, persistent cockpit scrolling and optional NexoWatt stable auto-updates.",
4
- "version": "7.10.1",
3
+ "description": "NexoWatt EOS Admin – repaired stable baseline with restart-safe automatic updates, native account passwords and persistent cockpit scrolling.",
4
+ "version": "7.10.2",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -118,24 +118,27 @@
118
118
  "tools/nexowatt-version-sync-selftest.cjs",
119
119
  "tools/nexowatt-merge-update-selftest.cjs",
120
120
  "tools/nexowatt-ems-overview-selftest.cjs",
121
- "README_STABILITY_V7.10.1.md",
122
- "RELEASE_NOTES_V7.10.1.md",
123
- "PUBLISH_STABLE_V7.10.1.md",
124
- "INSTALL_TEST_V7.10.1.md",
125
- "RELEASE_ACCEPTANCE_V7.10.1.md",
126
- "MERGE_UPDATE_README_V7.10.1.md",
127
121
  "README_EMS_OVERVIEW_MERGE.md",
128
122
  "RC73_EOS_ADMIN_MERGE_VALIDATION.md",
129
123
  "VALIDATION_EMS_OVERVIEW_MERGE.md",
130
124
  "NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
131
125
  "tools/nexowatt-apply-ems-overview.cjs",
132
126
  "tools/nexowatt-ui-overview-runtime-selftest.cjs",
133
- "tools/nexowatt-stable-v7101-selftest.cjs",
134
127
  "tools/nexowatt-password-write-selftest.cjs",
135
128
  "tools/nexowatt-account-password-flow-selftest.cjs",
136
129
  "tools/nexowatt-auto-update-selftest.cjs",
137
130
  "adminWww/css/eos-auto-update.css",
138
- "adminWww/js/eos-auto-update.js"
131
+ "adminWww/js/eos-auto-update.js",
132
+ "tools/nexowatt-backend-runtime-selftest.cjs",
133
+ "NEXOWATT_EOS_PREBUILT_MANIFEST.json",
134
+ "tools/nexowatt-prebuilt-release-selftest.cjs",
135
+ "tools/nexowatt-stable-v7102-selftest.cjs",
136
+ "README_STABILITY_V7.10.2.md",
137
+ "RELEASE_NOTES_V7.10.2.md",
138
+ "PUBLISH_STABLE_V7.10.2.md",
139
+ "INSTALL_TEST_V7.10.2.md",
140
+ "RELEASE_ACCEPTANCE_V7.10.2.md",
141
+ "MERGE_UPDATE_README_V7.10.2.md"
139
142
  ],
140
143
  "scripts": {
141
144
  "npm": "npm i -f && cd src-admin && npm i -f",
@@ -146,8 +149,8 @@
146
149
  "test:integration": "mocha test/integration --exit && npm run test:gui && npm run test:rule && npm run test:unit",
147
150
  "test:rule": "mocha test/rule --exit",
148
151
  "test:gui": "mocha test/*.gui.js --exit",
149
- "prepublishOnly": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
150
- "build": "npm run build:frontend && npm run build:backend && npm run clean:eos-runtime",
152
+ "prepublishOnly": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime && npm run check:eos-publish-channel && npm run check:eos-package && npm run check:eos-stability",
153
+ "build": "npm run build:frontend && npm run build:backend && npm run clean:eos-runtime && npm run seal:eos-prebuilt-release",
151
154
  "build:backend": "tsc -p tsconfig.build.json && tsx tasks.mts --backend-i18n",
152
155
  "build:frontend": "tsx tasks.mts",
153
156
  "clean": "rimraf src-admin/build",
@@ -170,12 +173,12 @@
170
173
  "nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
171
174
  "nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
172
175
  "nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
173
- "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7101 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs",
176
+ "check:eos-stability": "node tools/nexowatt-stability-check.cjs && node tools/nexowatt-esm-syntax-selftest.cjs && node tools/nexowatt-backend-runtime-selftest.cjs && node tools/nexowatt-prebuilt-release-selftest.cjs && node tools/nexowatt-import-integrity-selftest.cjs && node tools/nexowatt-entrypoint-smoke-selftest.cjs && node tools/nexowatt-frontend-graph-check.cjs && node tools/nexowatt-policy-selftest.cjs && node tools/nexowatt-publish-channel-selftest.cjs && node tools/nexowatt-release-defaults-selftest.cjs && node tools/nexowatt-role-access-selftest.cjs && node tools/nexowatt-internal-reserve-selftest.cjs && node tools/nexowatt-modern-ui-selftest.cjs && node tools/nexowatt-login-layout-selftest.cjs && node tools/nexowatt-branding-selftest.cjs && node tools/nexowatt-default-port-selftest.cjs && node tools/nexowatt-module-popup-selftest.cjs && node tools/nexowatt-datapoint-selftest.cjs && node tools/nexowatt-update-selftest.cjs && node tools/nexowatt-datapoint-interaction-selftest.cjs && node tools/nexowatt-manual-write-policy-selftest.cjs && node tools/nexowatt-value-column-selftest.cjs && node tools/nexowatt-native-shell-selftest.cjs && node tools/nexowatt-assistant-separation-selftest.cjs && node tools/nexowatt-runtime-cleanup-selftest.cjs && node tools/nexowatt-version-sync-selftest.cjs && node tools/nexowatt-merge-update-selftest.cjs && node tools/nexowatt-password-write-selftest.cjs && node tools/nexowatt-account-password-flow-selftest.cjs && npm run check:eos-stable-v7102 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs && node tools/nexowatt-auto-update-selftest.cjs",
174
177
  "nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
175
178
  "clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
176
179
  "precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
177
180
  "precheck:eos-stability": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
178
- "prepack": "node tools/nexowatt-sync-release-version.cjs --quiet && node tools/nexowatt-clean-legacy-runtime.cjs --quiet",
181
+ "prepack": "node tools/nexowatt-sync-release-version.cjs --quiet && node tools/nexowatt-ensure-release-defaults.cjs && node tools/nexowatt-clean-legacy-runtime.cjs --quiet && node tools/nexowatt-prebuilt-release-selftest.cjs",
179
182
  "check:eos-esm": "node tools/nexowatt-esm-syntax-selftest.cjs",
180
183
  "check:eos-imports": "node tools/nexowatt-import-integrity-selftest.cjs",
181
184
  "check:eos-release": "npm run check:eos-package && npm run check:eos-stability && npm pack --dry-run",
@@ -194,7 +197,10 @@
194
197
  "sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
195
198
  "test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
196
199
  "verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
197
- "check:eos-stable-v7101": "node tools/nexowatt-stable-v7101-selftest.cjs"
200
+ "check:eos-backend-runtime": "node tools/nexowatt-backend-runtime-selftest.cjs",
201
+ "check:eos-stable-v7102": "node tools/nexowatt-stable-v7102-selftest.cjs",
202
+ "check:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs",
203
+ "seal:eos-prebuilt-release": "node tools/nexowatt-prebuilt-release-selftest.cjs --write"
198
204
  },
199
205
  "author": "NexoWatt",
200
206
  "private": false,
@@ -205,7 +211,7 @@
205
211
  },
206
212
  "nexowattReleasePolicy": {
207
213
  "distTag": "latest",
208
- "purpose": "accepted stable NexoWatt EOS sales-product candidate 7.10.1"
214
+ "purpose": "accepted stable NexoWatt EOS sales-product candidate 7.10.2"
209
215
  },
210
216
  "publisher": "NexoWatt"
211
217
  }
@@ -1,34 +1,146 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const assert=require('node:assert/strict');
4
- const {NexoWattStableUpdateManager,isNexoWattRepositoryEntry}=require('../build/lib/eosAutoUpdate.js');
5
- const objects=new Map();
6
- objects.set('system.adapter.eos-admin.0',{_id:'system.adapter.eos-admin.0',common:{name:'eos-admin',version:'7.10.0'},native:{eosNexoWattAutoUpdate:true}});
7
- objects.set('system.config',{_id:'system.config',common:{activeRepo:['stable'],adapterAutoUpgrade:{repositories:{stable:false},defaultPolicy:'none'}},native:{}});
8
- objects.set('system.repositories',{_id:'system.repositories',native:{repositories:{stable:{json:{'eos-admin':{name:'eos-admin',version:'7.10.0',meta:'https://github.com/NexoWatt/ioBroker.eos-admin'},'nexowatt-ui':{name:'nexowatt-ui',version:'0.8.198',publisher:'NexoWatt'},'nexowatt-beta':{name:'nexowatt-beta',version:'1.0.0-beta.1',publisher:'NexoWatt'},admin:{name:'admin',version:'7.7.0',publisher:'ioBroker'}}}}}});
9
- objects.set('system.adapter.eos-admin',{_id:'system.adapter.eos-admin',common:{name:'eos-admin',version:'7.10.0',automaticUpgrade:'patch'},native:{}});
10
- objects.set('system.adapter.nexowatt-ui',{_id:'system.adapter.nexowatt-ui',common:{name:'nexowatt-ui',version:'0.8.197'},native:{}});
11
- objects.set('system.adapter.admin',{_id:'system.adapter.admin',common:{name:'admin',version:'7.7.0',automaticUpgrade:'minor'},native:{}});
12
- const copy=v=>JSON.parse(JSON.stringify(v));
13
- const refresh=[];
14
- const adapter={namespace:'eos-admin.0',host:'eos-host',log:{debug(){},info(){},warn(){},error(){}},getForeignObjectAsync:async id=>copy(objects.get(id)||null),setForeignObjectAsync:async(id,obj)=>{objects.set(id,copy(obj));},extendForeignObjectAsync:async(id,part)=>{const base=copy(objects.get(id)||{_id:id,common:{},native:{}});base.native={...(base.native||{}),...(part.native||{})};base.common={...(base.common||{}),...(part.common||{})};objects.set(id,base);},getObjectViewAsync:async()=>({rows:[...objects.entries()].filter(([id])=>/^system\.adapter\.[^.]+$/.test(id)).map(([id,value])=>({id,value:copy(value)}))}),sendToHostAsync:async(...args)=>{refresh.push(args);}};
15
- (async()=>{
16
- assert.equal(isNexoWattRepositoryEntry('nexowatt-ui',{publisher:'NexoWatt'}),true);
17
- assert.equal(isNexoWattRepositoryEntry('admin',{publisher:'ioBroker'}),false);
18
- const manager=new NexoWattStableUpdateManager(adapter);
19
- const enabled=await manager.reconcile('test');
20
- assert.equal(enabled.enabled,true);assert.equal(enabled.repository,'stable');assert.deepEqual(enabled.managedAdapters,['eos-admin','nexowatt-ui']);
21
- assert.equal(objects.get('system.adapter.eos-admin').common.automaticUpgrade,'major');
22
- assert.equal(objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade,'major');
23
- assert.equal(objects.get('system.adapter.admin').common.automaticUpgrade,'minor');
24
- assert.equal(objects.get('system.config').common.adapterAutoUpgrade.repositories.stable,true);
25
- // A manual user change while EOS management is active must not be overwritten on disable.
26
- objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade='minor';
27
- const disabled=await manager.setEnabled(false);assert.equal(disabled.enabled,false);
28
- assert.equal(objects.get('system.adapter.eos-admin').common.automaticUpgrade,'patch');
29
- assert.equal(objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade,'minor');
30
- assert.equal(objects.get('system.adapter.admin').common.automaticUpgrade,'minor');
31
- assert.equal(objects.get('system.config').common.adapterAutoUpgrade.repositories.stable,false);
32
- manager.stop();
33
- console.log('[NexoWatt EOS auto update] OK (native policy, stable-only scope, third-party isolation and restore)');
34
- })().catch(e=>{console.error(e.stack||e);process.exit(1)});
3
+
4
+ const assert = require('node:assert/strict');
5
+ const { NexoWattStableUpdateManager, isNexoWattRepositoryEntry } = require('../build/lib/eosAutoUpdate.js');
6
+
7
+ const copy = value => JSON.parse(JSON.stringify(value));
8
+
9
+ function createFixture() {
10
+ const objects = new Map();
11
+ const states = new Map();
12
+ const objectWrites = [];
13
+ const stateWrites = [];
14
+ const refresh = [];
15
+
16
+ objects.set('system.adapter.eos-admin.0', {
17
+ _id: 'system.adapter.eos-admin.0',
18
+ common: { name: 'eos-admin', version: '7.10.2' },
19
+ native: {
20
+ eosNexoWattAutoUpdate: true,
21
+ eosNexoWattAutoUpdateState: {
22
+ previousPolicies: {},
23
+ lastSync: 1,
24
+ },
25
+ },
26
+ });
27
+ objects.set('system.config', {
28
+ _id: 'system.config',
29
+ common: {
30
+ activeRepo: ['stable'],
31
+ adapterAutoUpgrade: { repositories: { stable: false }, defaultPolicy: 'none' },
32
+ },
33
+ native: {},
34
+ });
35
+ objects.set('system.repositories', {
36
+ _id: 'system.repositories',
37
+ native: {
38
+ repositories: {
39
+ stable: {
40
+ json: {
41
+ 'eos-admin': { name: 'eos-admin', version: '7.10.2', meta: 'https://github.com/NexoWatt/ioBroker.eos-admin' },
42
+ 'nexowatt-ui': { name: 'nexowatt-ui', version: '0.8.202', publisher: 'NexoWatt' },
43
+ 'nexowatt-beta': { name: 'nexowatt-beta', version: '1.0.0-beta.1', publisher: 'NexoWatt' },
44
+ admin: { name: 'admin', version: '7.7.0', publisher: 'ioBroker' },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ });
50
+ objects.set('system.adapter.eos-admin', {
51
+ _id: 'system.adapter.eos-admin',
52
+ common: { name: 'eos-admin', version: '7.10.2', automaticUpgrade: 'patch' },
53
+ native: {},
54
+ });
55
+ objects.set('system.adapter.nexowatt-ui', {
56
+ _id: 'system.adapter.nexowatt-ui',
57
+ common: { name: 'nexowatt-ui', version: '0.8.202' },
58
+ native: {},
59
+ });
60
+ objects.set('system.adapter.admin', {
61
+ _id: 'system.adapter.admin',
62
+ common: { name: 'admin', version: '7.7.0', automaticUpgrade: 'minor' },
63
+ native: {},
64
+ });
65
+
66
+ const adapter = {
67
+ namespace: 'eos-admin.0',
68
+ host: 'eos-host',
69
+ log: { debug() {}, info() {}, warn() {}, error() {} },
70
+ getForeignObjectAsync: async id => copy(objects.get(id) || null),
71
+ setForeignObjectAsync: async (id, object) => {
72
+ objectWrites.push(id);
73
+ objects.set(id, copy(object));
74
+ },
75
+ getObjectViewAsync: async () => ({
76
+ rows: [...objects.entries()]
77
+ .filter(([id]) => /^system\.adapter\.[^.]+$/.test(id))
78
+ .map(([id, value]) => ({ id, value: copy(value) })),
79
+ }),
80
+ getStateAsync: async id => copy(states.get(id) || null),
81
+ setStateAsync: async (id, value, ack) => {
82
+ const state = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'val')
83
+ ? { ...value }
84
+ : { val: value, ack: Boolean(ack) };
85
+ stateWrites.push(id);
86
+ states.set(id, copy(state));
87
+ },
88
+ sendToHostAsync: async (...args) => { refresh.push(args); },
89
+ };
90
+
91
+ return { adapter, objects, states, objectWrites, stateWrites, refresh };
92
+ }
93
+
94
+ (async () => {
95
+ assert.equal(isNexoWattRepositoryEntry('nexowatt-ui', { publisher: 'NexoWatt' }), true);
96
+ assert.equal(isNexoWattRepositoryEntry('admin', { publisher: 'ioBroker' }), false);
97
+
98
+ const fixture = createFixture();
99
+ const manager = new NexoWattStableUpdateManager(fixture.adapter);
100
+
101
+ const enabled = await manager.reconcile('test');
102
+ assert.equal(enabled.enabled, true);
103
+ assert.equal(enabled.repository, 'stable');
104
+ assert.deepEqual(enabled.managedAdapters, ['eos-admin', 'nexowatt-ui']);
105
+ assert.equal(fixture.objects.get('system.adapter.eos-admin').common.automaticUpgrade, 'major');
106
+ assert.equal(fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade, 'major');
107
+ assert.equal(fixture.objects.get('system.adapter.admin').common.automaticUpgrade, 'minor');
108
+ assert.equal(fixture.objects.get('system.config').common.adapterAutoUpgrade.repositories.stable, true);
109
+ assert.equal(fixture.states.get('info.nexowattStableUpdatesEnabled').val, true);
110
+ assert.ok(JSON.parse(fixture.states.get('info.nexowattStableUpdatesState').val).lastSync > 1);
111
+
112
+ // Regression guard for 7.10.1: runtime status must never be persisted into
113
+ // system.adapter.eos-admin.0.native, because that restarts the running adapter.
114
+ assert.equal(fixture.objectWrites.includes('system.adapter.eos-admin.0'), false);
115
+
116
+ // A second reconciliation may refresh state timestamps, but must not rewrite
117
+ // unchanged adapter policies or system.config.
118
+ const objectWriteCount = fixture.objectWrites.length;
119
+ await manager.reconcile('repeat');
120
+ assert.equal(fixture.objectWrites.length, objectWriteCount);
121
+
122
+ // A manual user change while EOS management is active must not be overwritten on disable.
123
+ fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade = 'minor';
124
+ const disabled = await manager.setEnabled(false);
125
+ assert.equal(disabled.enabled, false);
126
+ assert.equal(fixture.objects.get('system.adapter.eos-admin').common.automaticUpgrade, 'patch');
127
+ assert.equal(fixture.objects.get('system.adapter.nexowatt-ui').common.automaticUpgrade, 'minor');
128
+ assert.equal(fixture.objects.get('system.adapter.admin').common.automaticUpgrade, 'minor');
129
+ assert.equal(fixture.objects.get('system.config').common.adapterAutoUpgrade.repositories.stable, false);
130
+ assert.equal(fixture.states.get('info.nexowattStableUpdatesEnabled').val, false);
131
+ assert.equal(fixture.objectWrites.includes('system.adapter.eos-admin.0'), false);
132
+ manager.stop();
133
+
134
+ // Startup must only schedule reconciliation. It must not modify repository or
135
+ // adapter objects while the HTTP server is serving the first browser assets.
136
+ const startupFixture = createFixture();
137
+ const startupManager = new NexoWattStableUpdateManager(startupFixture.adapter);
138
+ await startupManager.start();
139
+ assert.deepEqual(startupFixture.objectWrites, []);
140
+ startupManager.stop();
141
+
142
+ console.log('[NexoWatt EOS auto update] OK (restart-safe state persistence, delayed startup, stable-only scope, third-party isolation and restore)');
143
+ })().catch(error => {
144
+ console.error(error.stack || error);
145
+ process.exit(1);
146
+ });
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+
8
+ const root = path.resolve(__dirname, '..');
9
+ const entry = path.join(root, 'build', 'main.js');
10
+ const packageFile = path.join(root, 'package.json');
11
+ const failures = [];
12
+ const visited = new Set();
13
+ const runtimeFiles = new Set();
14
+
15
+ function fail(message) {
16
+ failures.push(message);
17
+ }
18
+
19
+ function normalize(relative) {
20
+ return String(relative).replace(/\\/g, '/').replace(/^\.\//, '');
21
+ }
22
+
23
+ function resolveLocal(fromFile, specifier) {
24
+ const base = path.resolve(path.dirname(fromFile), specifier);
25
+ const candidates = [base, `${base}.js`, `${base}.json`, path.join(base, 'index.js')];
26
+ if (fs.existsSync(base) && fs.statSync(base).isDirectory()) {
27
+ const packageJsonFile = path.join(base, 'package.json');
28
+ if (fs.existsSync(packageJsonFile)) {
29
+ try {
30
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf8'));
31
+ if (packageJson.main) candidates.unshift(path.resolve(base, packageJson.main));
32
+ } catch (error) {
33
+ fail(`${normalize(path.relative(root, packageJsonFile))} is invalid JSON: ${error.message}`);
34
+ }
35
+ }
36
+ }
37
+ return candidates.find(candidate => fs.existsSync(candidate) && fs.statSync(candidate).isFile()) || null;
38
+ }
39
+
40
+ function inspect(file) {
41
+ const absolute = path.resolve(file);
42
+ if (visited.has(absolute)) return;
43
+ visited.add(absolute);
44
+ runtimeFiles.add(absolute);
45
+
46
+ if (!fs.existsSync(absolute)) {
47
+ fail(`missing runtime file ${normalize(path.relative(root, absolute))}`);
48
+ return;
49
+ }
50
+ if (!absolute.endsWith('.js')) return;
51
+
52
+ const text = fs.readFileSync(absolute, 'utf8');
53
+ const patterns = [
54
+ /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g,
55
+ /\brequire\.resolve\(\s*['"]([^'"]+)['"]\s*\)/g,
56
+ /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g,
57
+ ];
58
+ for (const pattern of patterns) {
59
+ pattern.lastIndex = 0;
60
+ let match;
61
+ while ((match = pattern.exec(text))) {
62
+ const specifier = match[1];
63
+ if (!specifier.startsWith('.')) continue;
64
+ const target = resolveLocal(absolute, specifier);
65
+ if (!target) {
66
+ fail(`${normalize(path.relative(root, absolute))} -> ${specifier} cannot be resolved`);
67
+ continue;
68
+ }
69
+ inspect(target);
70
+ }
71
+ }
72
+ }
73
+
74
+ function packageRules() {
75
+ if (!fs.existsSync(packageFile)) {
76
+ fail('package.json is missing');
77
+ return [];
78
+ }
79
+ try {
80
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
81
+ if (!Array.isArray(pkg.files) || pkg.files.length === 0) {
82
+ fail('package.json files whitelist is missing');
83
+ return [];
84
+ }
85
+ return pkg.files
86
+ .map(rule => normalize(rule).replace(/\/+$/, ''))
87
+ .filter(Boolean);
88
+ } catch (error) {
89
+ fail(`package.json is invalid JSON: ${error.message}`);
90
+ return [];
91
+ }
92
+ }
93
+
94
+ function isDeclaredForPackage(relative, rules) {
95
+ const value = normalize(relative);
96
+ return rules.some(rule => value === rule || value.startsWith(`${rule}/`));
97
+ }
98
+
99
+ if (!fs.existsSync(entry)) fail('build/main.js is missing');
100
+ else inspect(entry);
101
+
102
+ for (const file of runtimeFiles) {
103
+ if (!file.endsWith('.js')) continue;
104
+ const result = spawnSync(process.execPath, ['--check', file], {
105
+ encoding: 'utf8',
106
+ windowsHide: true,
107
+ });
108
+ if (result.error) {
109
+ fail(`${normalize(path.relative(root, file))} syntax check could not start: ${result.error.message}`);
110
+ } else if (result.status !== 0) {
111
+ fail(`${normalize(path.relative(root, file))} does not parse: ${(result.stderr || result.stdout || '').trim()}`);
112
+ }
113
+ }
114
+
115
+ // Do not start a nested `npm pack` process here. This self-test runs from
116
+ // prepublishOnly, and spawning npm.cmd from a Node child process is unreliable
117
+ // on Windows and can also re-enter npm lifecycle hooks. The final npm artifact
118
+ // is still verified externally by `npm publish --dry-run`; this check proves
119
+ // that every reachable local backend dependency is covered by package.json's
120
+ // explicit files whitelist without requiring npm, tsc, tsx or node_modules.
121
+ const rules = packageRules();
122
+ for (const file of runtimeFiles) {
123
+ const relative = normalize(path.relative(root, file));
124
+ if (!isDeclaredForPackage(relative, rules)) {
125
+ fail(`runtime dependency is not declared for the npm package: ${relative}`);
126
+ }
127
+ }
128
+
129
+ for (const required of [
130
+ 'build/main.js',
131
+ 'build/lib/web.js',
132
+ 'build/lib/eosAutoUpdate.js',
133
+ 'build/lib/eosRequestSecurity.js',
134
+ ]) {
135
+ if (!runtimeFiles.has(path.join(root, required))) {
136
+ fail(`entrypoint closure does not include required module ${required}`);
137
+ }
138
+ }
139
+
140
+ if (failures.length) {
141
+ console.error(`[NexoWatt EOS backend runtime] ${failures.length} failure(s):\n${failures.join('\n')}`);
142
+ process.exit(1);
143
+ }
144
+
145
+ console.log(
146
+ `[NexoWatt EOS backend runtime] OK (${runtimeFiles.size} local runtime files resolve, parse and are declared by the npm files whitelist; dependency-free on Windows)`,
147
+ );
@@ -16,7 +16,7 @@ for (const file of [
16
16
  'MERGE_UPDATE.ps1',
17
17
  'MERGE_UPDATE.cmd',
18
18
  'MERGE_UPDATE.sh',
19
- 'MERGE_UPDATE_README_V7.10.1.md',
19
+ 'MERGE_UPDATE_README_V7.10.2.md',
20
20
  'tools/nexowatt-apply-ems-overview.cjs',
21
21
  'tools/nexowatt-sync-release-version.cjs',
22
22
  'tools/nexowatt-version-sync-selftest.cjs',
@@ -67,5 +67,11 @@ if (!pkg.scripts['precheck:eos-package']?.startsWith('npm run sync:eos-version')
67
67
  if (!pkg.scripts.prepublishOnly?.startsWith('npm run sync:eos-version')) {
68
68
  fail('npm publishing does not self-heal stale merged version files');
69
69
  }
70
+ if (/\b(?:tsc|tsx)\b|build:(?:backend|frontend)|\bnpx\b|npm\s+(?:i|install|ci)\b/i.test(pkg.scripts.prepublishOnly || '')) {
71
+ fail('npm publishing must not require local development dependencies');
72
+ }
73
+ if (!pkg.scripts.prepack?.includes('nexowatt-prebuilt-release-selftest.cjs')) {
74
+ fail('npm packaging does not verify the sealed prebuilt release');
75
+ }
70
76
 
71
77
  console.log('[NexoWatt EOS merge update selftest] OK (flat overwrite + EMS merge + version repair + guarded verification)');