iobroker.eos-admin 7.9.98 → 7.9.99

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 (52) hide show
  1. package/INSTALL_TEST_V7.9.99.md +11 -0
  2. package/MERGE_UPDATE_README_V7.9.99.md +5 -0
  3. package/NEXOWATT_EOS_BUILD_INFO.json +21 -20
  4. package/PUBLISH_STABLE_V7.9.99.md +15 -0
  5. package/README.md +6 -16
  6. package/README_STABILITY_V7.9.99.md +18 -0
  7. package/RELEASE_ACCEPTANCE_V7.9.99.md +11 -0
  8. package/RELEASE_NOTES_V7.9.99.md +9 -0
  9. package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
  10. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  11. package/adminWww/css/eos-ems-overview.css +3 -5
  12. package/adminWww/css/nexowatt-native-shell.css +26 -112
  13. package/adminWww/index.html +19 -18
  14. package/adminWww/js/eos-account-management.js +19 -28
  15. package/adminWww/js/eos-assistant.js +1 -1
  16. package/adminWww/js/eos-ems-overview.js +2 -3
  17. package/adminWww/js/eos-release-watch.js +77 -0
  18. package/adminWww/js/eos-role-bootstrap.js +11 -8
  19. package/adminWww/js/eos-role-ui.js +2 -2
  20. package/adminWww/js/nexowatt-native-shell.js +1 -1
  21. package/adminWww/static/js/nexowatt-stable-login.js +3 -3
  22. package/adminWww/static/js/{nexowatt-stable-v98.js → nexowatt-stable-v99.js} +2 -2
  23. package/build/lib/eosPassword.js +62 -0
  24. package/build/lib/eosRequestSecurity.js +31 -0
  25. package/build/lib/web.js +149 -154
  26. package/build/main.js +2 -1
  27. package/io-package.json +5 -10
  28. package/package.json +17 -13
  29. package/tools/nexowatt-account-management-selftest.cjs +19 -15
  30. package/tools/nexowatt-apply-ems-overview.cjs +2 -4
  31. package/tools/nexowatt-cache-update-selftest.cjs +11 -0
  32. package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
  33. package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
  34. package/tools/nexowatt-login-layout-selftest.cjs +11 -11
  35. package/tools/nexowatt-merge-update-selftest.cjs +1 -1
  36. package/tools/nexowatt-modern-ui-selftest.cjs +2 -2
  37. package/tools/nexowatt-password-write-selftest.cjs +42 -44
  38. package/tools/nexowatt-request-security-selftest.cjs +11 -0
  39. package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -6
  40. package/tools/nexowatt-stable-v99-selftest.cjs +25 -0
  41. package/tools/nexowatt-sync-release-version.cjs +1 -42
  42. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +3 -1
  43. package/tools/nexowatt-validate-package.cjs +23 -23
  44. package/tools/nexowatt-version-sync-selftest.cjs +1 -18
  45. package/INSTALL_TEST_V7.9.98.md +0 -39
  46. package/MERGE_UPDATE_README_V7.9.98.md +0 -29
  47. package/PUBLISH_STABLE_V7.9.98.md +0 -33
  48. package/README_STABILITY_V7.9.98.md +0 -22
  49. package/RELEASE_ACCEPTANCE_V7.9.98.md +0 -48
  50. package/RELEASE_NOTES_V7.9.98.md +0 -32
  51. package/tools/nexowatt-account-password-flow-selftest.cjs +0 -186
  52. package/tools/nexowatt-stable-v98-selftest.cjs +0 -14
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
- "description": "NexoWatt EOS Admin – stable sales candidate with scrollable cockpit, clean login and verified account password writes.",
4
- "version": "7.9.98",
3
+ "description": "NexoWatt EOS Admin – stable candidate with truthful EMS overview, verified user passwords and deterministic browser updates.",
4
+ "version": "7.9.99",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -118,21 +118,22 @@
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.9.98.md",
122
- "RELEASE_NOTES_V7.9.98.md",
123
- "PUBLISH_STABLE_V7.9.98.md",
124
- "INSTALL_TEST_V7.9.98.md",
125
- "RELEASE_ACCEPTANCE_V7.9.98.md",
126
- "MERGE_UPDATE_README_V7.9.98.md",
121
+ "README_STABILITY_V7.9.99.md",
122
+ "RELEASE_NOTES_V7.9.99.md",
123
+ "PUBLISH_STABLE_V7.9.99.md",
124
+ "INSTALL_TEST_V7.9.99.md",
125
+ "RELEASE_ACCEPTANCE_V7.9.99.md",
126
+ "MERGE_UPDATE_README_V7.9.99.md",
127
127
  "README_EMS_OVERVIEW_MERGE.md",
128
128
  "RC73_EOS_ADMIN_MERGE_VALIDATION.md",
129
129
  "VALIDATION_EMS_OVERVIEW_MERGE.md",
130
130
  "NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md",
131
131
  "tools/nexowatt-apply-ems-overview.cjs",
132
132
  "tools/nexowatt-ui-overview-runtime-selftest.cjs",
133
- "tools/nexowatt-stable-v98-selftest.cjs",
133
+ "tools/nexowatt-stable-v99-selftest.cjs",
134
134
  "tools/nexowatt-password-write-selftest.cjs",
135
- "tools/nexowatt-account-password-flow-selftest.cjs"
135
+ "tools/nexowatt-request-security-selftest.cjs",
136
+ "tools/nexowatt-cache-update-selftest.cjs"
136
137
  ],
137
138
  "scripts": {
138
139
  "npm": "npm i -f && cd src-admin && npm i -f",
@@ -167,7 +168,7 @@
167
168
  "nexowatt:repo-entry": "node tools/nexowatt-generate-repo-entry.cjs",
168
169
  "nexowatt:patch-repo": "node tools/nexowatt-patch-repo.cjs",
169
170
  "nexowatt:repair-update": "node tools/nexowatt-repair-eos-admin-update.cjs",
170
- "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-first-login-selftest.cjs && node tools/nexowatt-account-management-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-v98 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
171
+ "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-first-login-selftest.cjs && node tools/nexowatt-account-management-selftest.cjs && node tools/nexowatt-cache-update-selftest.cjs && node tools/nexowatt-request-security-selftest.cjs && node tools/nexowatt-password-write-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 && npm run check:eos-stable-v99 && node tools/nexowatt-ems-overview-selftest.cjs && node tools/nexowatt-ui-overview-runtime-selftest.cjs",
171
172
  "nexowatt:patch-built-frontend": "node tools/nexowatt-patch-built-frontend.cjs",
172
173
  "clean:eos-runtime": "node tools/nexowatt-clean-legacy-runtime.cjs",
173
174
  "precheck:eos-package": "npm run sync:eos-version && npm run prepare:eos-release-defaults && npm run clean:eos-runtime",
@@ -191,7 +192,10 @@
191
192
  "sync:eos-version": "node tools/nexowatt-sync-release-version.cjs",
192
193
  "test:eos-version-sync": "node tools/nexowatt-version-sync-selftest.cjs",
193
194
  "verify:eos-merge": "node tools/nexowatt-merge-update-selftest.cjs",
194
- "check:eos-stable-v98": "node tools/nexowatt-stable-v98-selftest.cjs"
195
+ "check:eos-stable-v99": "node tools/nexowatt-stable-v99-selftest.cjs",
196
+ "test:eos-password-write": "node tools/nexowatt-password-write-selftest.cjs",
197
+ "test:eos-request-security": "node tools/nexowatt-request-security-selftest.cjs",
198
+ "test:eos-cache-update": "node tools/nexowatt-cache-update-selftest.cjs"
195
199
  },
196
200
  "author": "NexoWatt",
197
201
  "private": false,
@@ -202,7 +206,7 @@
202
206
  },
203
207
  "nexowattReleasePolicy": {
204
208
  "distTag": "latest",
205
- "purpose": "accepted stable NexoWatt EOS sales-product release 7.9.98"
209
+ "purpose": "accepted stable NexoWatt EOS sales-product release 7.9.99"
206
210
  },
207
211
  "publisher": "NexoWatt"
208
212
  }
@@ -1,19 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const fs=require('fs');const path=require('path');const root=path.resolve(__dirname,'..');const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const json=rel=>JSON.parse(read(rel));let bad=false;const fail=msg=>{console.error(`[NexoWatt EOS account management] ${msg}`);bad=true;};
4
- const mainSource=read('src/main.ts'),mainBuilt=read('build/main.js'),webSource=read('src/lib/web.ts'),webBuilt=read('build/lib/web.js'),sourceUi=read('src-admin/public/js/eos-account-management.js'),builtUi=read('adminWww/js/eos-account-management.js'),roleUi=read('adminWww/js/eos-role-ui.js'),css=read('adminWww/css/nexowatt-native-shell.css'),index=read('adminWww/index.html'),info=json('NEXOWATT_EOS_BUILD_INFO.json'),shellTag=String(info.shellCacheTag||info.shellCacheVersion);
5
- for(const code of [mainSource,mainBuilt])for(const marker of ['system.user.installer','system.user.guest','ensureEosDefaultRoleUsers','unknown random bootstrap secret','eosPasswordSetupRequired','personal password setup is required'])if(!code.includes(marker))fail(`main marker missing: ${marker}`);
3
+ const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const json=rel=>JSON.parse(read(rel));
4
+ const fail=m=>{console.error(`[NexoWatt EOS account management] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
5
+ const mainSource=read('src/main.ts'),mainBuilt=read('build/main.js'),webSource=read('src/lib/web.ts'),webBuilt=read('build/lib/web.js'),passwordSource=read('src/lib/eosPassword.ts');
6
+ const sourceUi=read('src-admin/public/js/eos-account-management.js'),builtUi=read('adminWww/js/eos-account-management.js'),roleUi=read('adminWww/js/eos-role-ui.js'),css=read('adminWww/css/nexowatt-native-shell.css'),index=read('adminWww/index.html'),info=json('NEXOWATT_EOS_BUILD_INFO.json'),shellTag=String(info.shellCacheTag||info.shellCacheVersion);
7
+ for(const code of [mainSource,mainBuilt])for(const marker of ['system.user.installer','system.user.guest','ensureEosDefaultRoleUsers','unknown random bootstrap secret','personal password setup is required'])must(code.includes(marker),`main marker missing: ${marker}`);
6
8
  for(const code of [webSource,webBuilt]){
7
- for(const marker of ['getEosManagedAccounts','sendEosAccountManagement','resetEosAccountPassword','/nexowatt/account/manage','/nexowatt/account/reset','x-nexowatt-eos-account-reset','canResetInstaller','canResetEndUser',"targetUserId === 'system.user.admin'",'targetUserId === access.userId',"access.role === 'installer' && explicitlyManagedRole !== 'enduser'",'passwordResetAt','passwordResetBy',"passwordResetMode = 'initial-password'","setEosUserPassword(targetUserId, 'nexowatt')",'passwordlessFirstLoginAllowed = false','initialPasswordRequired: true','accountDisabled','getEosPasswordUserName','setPasswordAsync(userName, password','checkPasswordAsync(userName, password','updateEosAccountMetadata','extendForeignObjectAsync','invalidRequestOrigin'])if(!code.includes(marker))fail(`web marker missing: ${marker}`);
8
- if(!code.includes('const explicitInstaller = targetAccess.groups.some')||!code.includes('const explicitEndUser = targetAccess.groups.some'))fail('explicit managed-group guard missing');
9
- const reset=code.slice(code.indexOf('resetEosAccountPassword'),code.indexOf('getEosHistoryInstances'));
10
- if(reset.includes('setForeignObjectAsync(targetUserId'))fail('reset replaces the complete user object after password write');
9
+ for(const marker of ['getEosManagedAccounts','sendEosAccountManagement','resetEosAccountPassword','/nexowatt/account/manage','/nexowatt/account/reset','x-nexowatt-eos-account-reset','canResetInstaller','canResetEndUser',"targetUserId === 'system.user.admin'",'targetUserId === access.userId','const managedAccounts = await this.getEosManagedAccounts(access.role)','const managedTarget = managedAccounts.find(account => account.id === targetUserId)',"managedTarget?.role === 'installer'","managedTarget?.role === 'enduser'",'passwordResetAt','passwordResetBy',"passwordResetMode = 'initial-password'","setEosUserPassword(targetUserId, 'nexowatt')",'passwordlessFirstLoginAllowed = false','initialPasswordRequired: true','accountDisabled','passwordMetadataNotPersisted']) must(code.includes(marker),`web marker missing: ${marker}`);
10
+ must(!code.includes('const explicitInstaller = targetAccess.groups.some'),'obsolete explicit installer re-classifier remains');
11
+ must(!code.includes('const explicitEndUser = targetAccess.groups.some'),'obsolete explicit end-user re-classifier remains');
12
+ must(!code.includes('setForeignObjectAsync(targetUserId, user)'),'reset still writes stale full user object');
11
13
  }
12
- for(const marker of ['v98-account-management-real-password-write',"['admin', 'installer']",'nexowatt/account/manage','nexowatt/account/reset','X-NexoWatt-EOS-Account-Reset','data.canResetInstaller === true',"account.role === 'enduser'",'Startpasswort „nexowatt“','accountErrorText','invalidRequestOrigin','credentials: \'include\'','NEXOWATT_EOS_ACCOUNT_MANAGEMENT','ensureEntrySurface',"currentRoute() !== 'tab-users'",'eos-account-management-entry'])if(!sourceUi.includes(marker))fail(`account UI marker missing: ${marker}`);
13
- if(sourceUi.includes("launcher.className = 'eos-account-management-launcher'"))fail('standalone account launcher remains');
14
- if(sourceUi.includes('new MutationObserver'))fail('account management adds a second broad MutationObserver');
15
- if(sourceUi!==builtUi)fail('account UI source/build drift');
16
- if(!roleUi.includes("route === 'tab-users'"))fail('installer cannot reach access page');
17
- if(!index.includes(`eos-account-management.js?v=${shellTag}`))fail('account asset cache tag mismatch');
18
- for(const marker of ['.eos-account-management-entry','.eos-account-management-overlay','.eos-account-management-dialog','.eos-account-row','.eos-account-reset'])if(!css.includes(marker))fail(`account CSS missing: ${marker}`);
19
- if(bad)process.exit(1);console.log('[NexoWatt EOS account management] OK');
14
+ for(const marker of ['setPasswordAsync(userName','checkPasswordAsync(userName','passwordNotPersisted','passwordVerificationFailed'])must(passwordSource.includes(marker),`password helper marker missing: ${marker}`);
15
+ for(const marker of ["VERSION = 'v99-account-management-password-write'","['admin', 'installer']",'nexowatt/account/manage','nexowatt/account/reset','X-NexoWatt-EOS-Account-Reset','data.canResetInstaller === true',"account.role === 'enduser'",'Admin/Service','Installateure','NEXOWATT_EOS_ACCOUNT_MANAGEMENT','ensureEntrySurface',"currentRoute() !== 'tab-users'",'eos-account-management-entry','Startpasswort „nexowatt“','passwordVerificationFailed'])must(sourceUi.includes(marker),`UI marker missing: ${marker}`);
16
+ must(sourceUi===builtUi,'account management source/build drift');
17
+ must(!sourceUi.includes("launcher.className = 'eos-account-management-launcher'"),'standalone account launcher remains');
18
+ must(!sourceUi.includes('new MutationObserver'),'account management adds a broad observer');
19
+ must(roleUi.includes("route === 'tab-users'"),'installer cannot reach Zugänge & Rechte');
20
+ must(!roleUi.includes('NEXOWATT_EOS_ACCOUNT_MANAGEMENT.open'),'overview opens account management outside Zugänge & Rechte');
21
+ must(index.includes(`eos-account-management.js?v=${shellTag}`),'account management cache tag stale');
22
+ for(const marker of ['.eos-account-management-entry','.eos-account-management-overlay','.eos-account-management-dialog','.eos-account-row','.eos-account-reset','html.eos-account-page-installer'])must(css.includes(marker),`CSS marker missing: ${marker}`);
23
+ console.log('[NexoWatt EOS account management] OK (managed-list authorization and real password reset)');
@@ -52,7 +52,7 @@ if (!css.includes(cssMarker)) {
52
52
  overflow: hidden;
53
53
  }
54
54
  .eos-ems-overview-card * { box-sizing: border-box; }
55
- .eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
55
+ .eos-ems-overview-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:20px; }
56
56
  .eos-ems-overview-header h2 { margin:5px 0 6px; font-size:clamp(1.2rem,2vw,1.65rem); color:#f5fbff; }
57
57
  .eos-ems-overview-header p { margin:0; max-width:760px; color:#9eb2c2; line-height:1.45; }
58
58
  .eos-ems-overview-eyebrow { color:#49e39e; font-size:.72rem; font-weight:900; letter-spacing:.13em; text-transform:uppercase; }
@@ -83,9 +83,7 @@ if (!css.includes(cssMarker)) {
83
83
  .eos-ems-overview-event time { color:#6f8799; font-size:.68rem; font-variant-numeric:tabular-nums; }
84
84
  .eos-ems-overview-event div { display:grid; gap:2px; min-width:0; }
85
85
  .eos-ems-overview-empty { color:#718797; font-size:.76rem; }
86
- .eos-ems-overview-footer { display:flex; justify-content:space-between; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
87
- .eos-ems-overview-footer a { color:#5ee0c2; font-weight:800; text-decoration:none; }
88
- .eos-ems-overview-footer a:hover,.eos-ems-overview-footer a:focus-visible { color:#9af2dd; text-decoration:underline; }
86
+ .eos-ems-overview-footer { display:flex; justify-content:flex-start; gap:12px; margin-top:12px; color:#60798a; font-size:.67rem; }
89
87
  @media (max-width: 1100px) {
90
88
  .eos-ems-overview-card { flex-basis:100%; max-width:none; }
91
89
  }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');
4
+ const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const fail=m=>{console.error(`[NexoWatt EOS cache update] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
5
+ const source=read('src/lib/web.ts'),built=read('build/lib/web.js'),index=read('adminWww/index.html'),dev=read('src-admin/index.html'),watch=read('adminWww/js/eos-release-watch.js');
6
+ for(const code of [source,built])for(const marker of ['isEosCurrentAsset','applyNoStoreHeaders','Surrogate-Control','this.indexHTML = await this.prepareIndex'])must(code.includes(marker),`server marker missing: ${marker}`);
7
+ for(const marker of ["const RELEASE = '7.9.99'",'nexowatt.eos.loadedRelease','cache: \'no-store\'','window.location.replace','navigator.serviceWorker?.getRegistrations'])must(watch.includes(marker),`watcher marker missing: ${marker}`);
8
+ for(const html of [index,dev])for(const marker of ['eos-release-watch.js?v=99','nexowatt-stable-v99.js?v=99','nexowatt-native-shell.css?v=99'])must(html.includes(marker),`index marker missing: ${marker}`);
9
+ must(index.includes('hostInit-v84.js?v=99')&&index.includes('index-CQZugZ1z-v84.js?v=99'),'productive module entry cache tags missing');
10
+ must(!/\?v=(?:97|98)(?:["'])/.test(index),'stale product cache tag remains');
11
+ console.log('[NexoWatt EOS cache update] OK (no-store entrypoints and release watcher)');
@@ -74,7 +74,7 @@ function isLegacyVersionedRuntime(file, activeRuntime, activeShell) {
74
74
  // Stable product overlays use the product release number in their filename,
75
75
  // while the compiled React runtime can intentionally remain on v84. Keep
76
76
  // only the active stable overlay and remove stale release assets left behind
77
- // by a flat ZIP update (for example v96 when installing v97).
77
+ // by a flat ZIP update (for example v98 when installing v99).
78
78
  const stableMatch = name.match(/^nexowatt-stable-v(\d+)\.js$/i);
79
79
  if (stableMatch) return Number(stableMatch[1]) !== activeShell;
80
80
  const versionMatch = name.match(/-v(\d+)\.(?:js|js\.map|css|css\.map)$/i);
@@ -19,11 +19,11 @@ for (const marker of [
19
19
  'document.visibilityState',
20
20
  'window.setInterval(() => void load(), 5_000)',
21
21
  'Diagnose ist rein lesend',
22
- 'Vollständige EMS-Diagnose öffnen',
23
22
  'system.adapter.nexowatt-ui.*.alive',
24
23
  ]) if (!component.includes(marker)) fail(`Komponenten-Vertrag fehlt: ${marker}`);
25
24
  if ((component.match(/NexoWatt EMS · Live-Diagnose/g) || []).length !== 1) fail('EMS-Überschrift ist doppelt oder fehlt.');
26
25
  if (!css.includes('/* nexowatt-ems-overview-v1 */')) fail('Responsive EMS-CSS fehlt.');
26
+ if (component.includes('Vollständige EMS-Diagnose öffnen')) fail('Nicht vorhandener Diagnose-Link darf nicht angeboten werden.');
27
27
  if (/setState\(|setForeignState\(|sendToHost\(/.test(component)) fail('EMS-Overview darf keine ioBroker-Schreiboperation enthalten.');
28
28
  const assetDir = path.join(root, 'admin', 'assets');
29
29
  if (fs.existsSync(assetDir)) {
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');
4
- const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');let bad=false;const fail=msg=>{console.error(`[NexoWatt EOS login layout] ${msg}`);bad=true;};
5
- const css=read('adminWww/css/nexowatt-native-shell.css');const sourceCss=read('src-admin/public/css/nexowatt-native-shell.css');const login=read('src-admin/src/login/Login.tsx');const stableLogin=read('adminWww/static/js/nexowatt-stable-login.js');const stable=read('adminWww/static/js/nexowatt-stable-v98.js');const index=read('adminWww/index.html');const info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));const bootstrap=read('adminWww/assets/bootstrap-COulQZax-v84.js');
6
- if(css!==sourceCss)fail('login CSS source/build drift');
7
- for(const marker of ['NexoWatt EOS 7.9.98 – stable scrolling','html.eos-app body','overflow: hidden !important','overflow-y: auto !important','width: min(456px','width: min(400px','min-height: 0 !important','overflow: visible !important','.MuiInputLabel-root { display: none !important; }','.MuiOutlinedInput-input::placeholder','margin-top: 28px !important'])if(!css.includes(marker))fail(`login marker missing: ${marker}`);
8
- if(!login.includes("minHeight: 0")||!login.includes("maxWidth: 400")||!login.includes('placeholder={`${I18n.t(\'enterLogin\')} *`}')||!login.includes('placeholder={`${I18n.t(\'enterPassword\')} *`}'))fail('native Login source does not use clean v98 placeholders');
9
- if(!bootstrap.includes('placeholder:')||!bootstrap.includes('I18n.t("enterLogin")+" *"')||!bootstrap.includes('I18n.t("enterPassword")+" *"'))fail('productive login bundle does not use placeholders');
10
- if(/label:.*I18n\.t\("enterLogin"\)|label:.*I18n\.t\("enterPassword"\)/.test(bootstrap.slice(1335000,1340500)))fail('productive login bundle still uses floating labels');
11
- if(/data-nw-login-card|data-nw-login-shell|min-height:590px|width:min\(460px|createElement\(['"]style/.test(stableLogin))fail('stable-login runtime mutates geometry');
12
- if(/width:min\(460px|min-height:540px|createElement\(['"]style/.test(stable))fail('stable v98 runtime injects geometry');
13
- if(!index.includes(`nexowatt-native-shell.css?v=${info.shellCacheTag}`)||!index.includes('nexowatt-stable-v98.js?v=98'))fail('v98 login assets not active');
14
- if(bad)process.exit(1);console.log('[NexoWatt EOS login layout] OK');
4
+ const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const fail=m=>{console.error(`[NexoWatt EOS login layout] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
5
+ const css=read('adminWww/css/nexowatt-native-shell.css'),sourceCss=read('src-admin/public/css/nexowatt-native-shell.css');
6
+ const login=read('src-admin/src/login/Login.tsx'),stableLogin=read('adminWww/static/js/nexowatt-stable-login.js'),stable=read('adminWww/static/js/nexowatt-stable-v99.js'),index=read('adminWww/index.html'),info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));
7
+ must(css===sourceCss,'login CSS source/build drift');
8
+ for(const marker of ['outer frame is decorative only','orderly login, moderately enlarged','width: min(440px, calc(100vw - 20px)) !important','height: min(580px, calc(100dvh - 20px)) !important','width: min(380px, calc(100vw - 52px)) !important','min-height: 510px !important','height: auto !important','overflow: visible !important'])must(css.includes(marker),`login marker missing: ${marker}`);
9
+ must(!css.includes('width: min(520px, calc(100vw - 24px))')&&!css.includes('width: min(460px, calc(100vw - 56px))'),'oversized v96 geometry remains');
10
+ must(!/data-nw-login-card|data-nw-login-shell|min-height:590px|width:min\(460px|createElement\(['"]style/.test(stableLogin),'stable-login runtime mutates geometry');
11
+ must(!/width:min\(460px|min-height:540px|createElement\(['"]style/.test(stable),'stable v99 runtime injects login geometry');
12
+ must(login.includes('minHeight: 510')&&login.includes("overflowY: 'visible'")&&login.includes("maxHeight: 'none'"),'native Login source does not match product contract');
13
+ must(index.includes(`nexowatt-native-shell.css?v=${info.shellCacheTag}`)&&index.includes('nexowatt-stable-v99.js?v=99'),'v99 login assets not active');
14
+ console.log('[NexoWatt EOS login layout] OK');
@@ -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.9.98.md',
19
+ 'MERGE_UPDATE_README_V7.9.99.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',
@@ -19,7 +19,7 @@ for (const marker of [
19
19
  '--nx-shadow-soft', '--nx-shadow-float', 'backdrop-filter: blur', '#app-paper', '.MuiDialog-paper',
20
20
  '.MuiButton-root', '.MuiTableRow-root', ':focus-visible', 'border-radius: 24px', 'linear-gradient',
21
21
  '@media (max-width: 720px)', '.eos-activation-steps', '.eos-account-row:hover',
22
- 'main.MuiPaper-root::before', 'width: min(400px, calc(100vw - 48px)) !important', '.eos-assist-header-root', '.eos-overview-hero', '.eos-overview-grid',
22
+ 'main.MuiPaper-root::before', 'width: min(380px, calc(100vw - 52px)) !important', '.eos-assist-header-root', '.eos-overview-hero', '.eos-overview-grid',
23
23
  '.eos-account-management-entry', 'pointer-events: auto !important', 'touch-action: manipulation', 'overflow: visible',
24
24
  ]) if (!css.includes(marker)) fail(`modern styling marker missing: ${marker}`);
25
25
  if (sourceCss !== css) fail('modern CSS source/build drift');
@@ -29,7 +29,7 @@ for (const marker of ['eos-account-management.js','nexowatt-native-shell.css','e
29
29
  for (const marker of ['NexoWatt EOS', 'Zugänge & Rollen', 'Endkunden-Zugänge', 'ensureEntrySurface']) {
30
30
  if (!accountUi.includes(marker)) fail(`modern account surface missing label: ${marker}`);
31
31
  }
32
- for (const marker of ['installIntegratedFirstLogin','v98 compact normal-login first activation','eos-passwordless-card','Idempotent writes are essential here']) {
32
+ for (const marker of ['installIntegratedFirstLogin','v99 normal-login first activation','eos-passwordless-card','Idempotent writes are essential here']) {
33
33
  if (!bootstrap.includes(marker)) fail(`integrated first-activation surface missing: ${marker}`);
34
34
  }
35
35
  if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('compact login still injects role buttons or a second panel');
@@ -1,48 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const assert = require('node:assert/strict');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
- const vm = require('node:vm');
7
- const root = path.resolve(__dirname, '..');
8
- const built = fs.readFileSync(path.join(root, 'build/lib/web.js'), 'utf8');
9
- const source = fs.readFileSync(path.join(root, 'src/lib/web.ts'), 'utf8');
10
- const begin = built.indexOf(' getEosPasswordUserName(userId) {');
11
- const end = built.indexOf(' async destroyEosRequestSessions', begin);
12
- assert(begin >= 0 && end > begin, 'password methods not found in built backend');
13
- const methods = built.slice(begin, end);
14
- const sandbox = { module: { exports: null }, EOS_PASSWORD_SERVICE_USER: 'system.user.admin' };
15
- vm.runInNewContext(`module.exports = class Harness {\nconstructor(adapter){this.adapter=adapter;}\n${methods}\n}`, sandbox);
16
- const Harness = sandbox.module.exports;
17
- (async () => {
18
- const calls = [];
19
- const userObject = { _id: 'system.user.user', common: { password: '$2b$hash' }, native: { keep: true } };
20
- const adapter = {
21
- setPasswordAsync: async (user, password, options) => calls.push(['set', user, password, options]),
22
- checkPasswordAsync: async (user, password, options) => { calls.push(['check', user, password, options]); return [true, `system.user.${user}`]; },
23
- getForeignObjectAsync: async id => { calls.push(['get', id]); return JSON.parse(JSON.stringify(userObject)); },
24
- extendForeignObjectAsync: async (id, patch, options) => calls.push(['extend', id, patch, options]),
25
- setForeignObjectAsync: async () => { throw new Error('full user object replacement must not be used'); },
3
+ const assert=require('node:assert/strict');
4
+ const fs=require('node:fs');
5
+ const path=require('node:path');
6
+ const {normalizeEosPasswordTarget,setEosUserPasswordWithVerification}=require('../build/lib/eosPassword.js');
7
+ const root=path.resolve(__dirname,'..');
8
+ const source=fs.readFileSync(path.join(root,'src/lib/eosPassword.ts'),'utf8');
9
+ const web=fs.readFileSync(path.join(root,'src/lib/web.ts'),'utf8');
10
+
11
+ function adapterFixture({verify=true,persist=true,callback=false}={}){
12
+ let password='old';
13
+ let hash=persist?'$old':'';
14
+ const calls=[];
15
+ const adapter={
16
+ calls,
17
+ getForeignObjectAsync:async id=>id==='system.user.user'?{_id:id,common:{password:hash},native:{}}:null,
18
+ checkPasswordAsync:async (user,value,options)=>{calls.push({kind:'check',user,value,options});return [verify&&user==='user'&&value===password,'system.user.user'];},
26
19
  };
27
- const harness = new Harness(adapter);
28
- await harness.setEosUserPassword('system.user.user', 'NexoWatt2025!');
29
- assert.deepEqual(calls[0].slice(0, 3), ['set', 'user', 'NexoWatt2025!']);
30
- assert.deepEqual(calls[1].slice(0, 3), ['check', 'user', 'NexoWatt2025!']);
31
- await harness.updateEosAccountMetadata('system.user.user', (native, account) => {
32
- account.passwordInitialized = true;
33
- native.nexowattPasswordChangeRequired = false;
34
- });
35
- const extend = calls.find(row => row[0] === 'extend');
36
- assert(extend, 'native metadata was not extended');
37
- assert.equal(extend[1], 'system.user.user');
38
- assert.equal(extend[2].native.keep, true);
39
- assert.equal(extend[2].native.nexowattEosAccount.passwordInitialized, true);
40
- assert(!Object.prototype.hasOwnProperty.call(extend[2], 'common'), 'password/common must not be overwritten by metadata update');
41
- for (const code of [source, built]) {
42
- assert.match(code, /setPasswordAsync\(userName, password/);
43
- assert.match(code, /checkPasswordAsync\(userName, password/);
44
- assert.match(code, /extendForeignObjectAsync/);
45
- assert.match(code, /passwordVerificationFailed/);
20
+ const set=async(user,value,options)=>{calls.push({kind:'set',user,value,options});if(user!=='user')throw new Error('shortUserNameRequired');if(persist){password=value;hash=`$hash:${value}`;}};
21
+ if(callback) adapter.setPassword=(user,value,options,cb)=>set(user,value,options).then(()=>cb(),cb);
22
+ else adapter.setPasswordAsync=set;
23
+ return adapter;
24
+ }
25
+
26
+ (async()=>{
27
+ assert.deepEqual(normalizeEosPasswordTarget('user'),{userId:'system.user.user',userName:'user'});
28
+ assert.deepEqual(normalizeEosPasswordTarget('system.user.user'),{userId:'system.user.user',userName:'user'});
29
+ assert.throws(()=>normalizeEosPasswordTarget('system.user.bad.name'),/invalidPasswordTarget/);
30
+ for(const fixture of [adapterFixture(),adapterFixture({callback:true})]){
31
+ const result=await setEosUserPasswordWithVerification(fixture,'system.user.user','Strong!2026');
32
+ assert.equal(result.userName,'user');
33
+ assert.equal(fixture.calls.find(c=>c.kind==='set').user,'user','password API must receive short user name');
34
+ assert.deepEqual(fixture.calls.find(c=>c.kind==='set').options,{user:'system.user.admin'});
35
+ assert.equal(fixture.calls.find(c=>c.kind==='check').user,'user','password check must receive short user name');
46
36
  }
47
- console.log('[NexoWatt EOS password write] OK: real account name, verified password API and metadata-only update');
48
- })().catch(error => { console.error(error.stack || error); process.exit(1); });
37
+ const noCheck=adapterFixture();delete noCheck.checkPasswordAsync;
38
+ await setEosUserPasswordWithVerification(noCheck,'user','NoCheck!2026');
39
+ await assert.rejects(()=>setEosUserPasswordWithVerification(adapterFixture({persist:false}),'user','Missing!2026'),/passwordNotPersisted/);
40
+ await assert.rejects(()=>setEosUserPasswordWithVerification(adapterFixture({verify:false}),'user','Verify!2026'),/passwordVerificationFailed/);
41
+ for(const marker of ['setPasswordAsync(userName','checkPasswordAsync(userName','common?.password','userId.replace(/^system\\.user\\./']) assert(source.includes(marker),`source marker missing: ${marker}`);
42
+ assert(!/setForeignObject|common\.password\s*=/.test(source),'helper must never hash/write common.password itself');
43
+ assert(web.includes('extendForeignObjectAsync')&&web.includes('passwordMetadataNotPersisted'),'first-login metadata persistence guard missing');
44
+ assert(!web.includes('setForeignObjectAsync(access.userId, userObject)'),'stale full-user first-login write remains');
45
+ console.log('[NexoWatt EOS password write] OK (short name, persistence, credential and native-only metadata)');
46
+ })().catch(error=>{console.error(error?.stack||error);process.exit(1);});
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const assert=require('node:assert/strict');
4
+ const {isEosSameOriginRequest}=require('../build/lib/eosRequestSecurity.js');
5
+ assert.equal(isEosSameOriginRequest({host:'192.168.10.124:8081'}),true);
6
+ assert.equal(isEosSameOriginRequest({origin:'http://192.168.10.124:8081',host:'192.168.10.124:8081','sec-fetch-site':'same-origin'}),true);
7
+ assert.equal(isEosSameOriginRequest({origin:'https://eos.example.test',host:'proxy:443','x-forwarded-host':'eos.example.test','sec-fetch-site':'same-site'}),true);
8
+ assert.equal(isEosSameOriginRequest({origin:'http://192.168.10.125:8081',host:'192.168.10.124:8081','sec-fetch-site':'same-origin'}),false);
9
+ assert.equal(isEosSameOriginRequest({origin:'http://192.168.10.124:8081',host:'192.168.10.124:8081','sec-fetch-site':'cross-site'}),false);
10
+ assert.equal(isEosSameOriginRequest({origin:'file:///tmp/eos',host:'192.168.10.124:8081'}),false);
11
+ console.log('[NexoWatt EOS request security] OK (LAN, proxy and cross-site guard)');
@@ -15,15 +15,15 @@ try {
15
15
  fs.mkdirSync(path.join(temp, 'src-admin', 'public', 'js'), { recursive: true });
16
16
  fs.mkdirSync(path.join(temp, 'src-admin', 'public', 'static', 'js'), { recursive: true });
17
17
  fs.mkdirSync(path.join(temp, 'adminWww', 'static', 'js'), { recursive: true });
18
- fs.writeFileSync(path.join(temp, 'NEXOWATT_EOS_BUILD_INFO.json'), JSON.stringify({ runtimeEntry: 'v82', shellCacheVersion: 98 }));
18
+ fs.writeFileSync(path.join(temp, 'NEXOWATT_EOS_BUILD_INFO.json'), JSON.stringify({ runtimeEntry: 'v82', shellCacheVersion: 99 }));
19
19
 
20
20
  const keep = [
21
21
  'adminWww/assets/bootstrap-current-v82.js',
22
22
  'adminWww/assets/Objects-current-v82.js',
23
23
  'adminWww/remoteEntry-v82.js',
24
24
  'adminWww/remoteEntry.js',
25
- 'adminWww/static/js/nexowatt-stable-v98.js',
26
- 'src-admin/public/static/js/nexowatt-stable-v98.js',
25
+ 'adminWww/static/js/nexowatt-stable-v99.js',
26
+ 'src-admin/public/static/js/nexowatt-stable-v99.js',
27
27
  'adminWww/static/js/nexowatt-stable-login.js',
28
28
  ];
29
29
  const remove = [
@@ -34,8 +34,8 @@ try {
34
34
  'adminWww/js/eos-branding.js',
35
35
  'adminWww/css/eos-branding.css',
36
36
  'src-admin/public/js/eos-security-ui.js',
37
- 'adminWww/static/js/nexowatt-stable-v97.js',
38
- 'src-admin/public/static/js/nexowatt-stable-v95.js',
37
+ 'adminWww/static/js/nexowatt-stable-v98.js',
38
+ 'src-admin/public/static/js/nexowatt-stable-v97.js',
39
39
  ];
40
40
  for (const rel of [...keep, ...remove]) {
41
41
  const file = path.join(temp, rel);
@@ -45,7 +45,7 @@ try {
45
45
 
46
46
  const result = cleanLegacyRuntime({ root: temp, quiet: true, silent: true });
47
47
  assert.strictEqual(result.activeRuntime, 82);
48
- assert.strictEqual(result.activeShell, 98);
48
+ assert.strictEqual(result.activeShell, 99);
49
49
  assert.strictEqual(result.removed.length, remove.length);
50
50
  for (const rel of keep) assert.ok(fs.existsSync(path.join(temp, rel)), `current runtime removed: ${rel}`);
51
51
  for (const rel of remove) assert.ok(!fs.existsSync(path.join(temp, rel)), `legacy file remained: ${rel}`);
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const fs=require('node:fs');const path=require('node:path');const root=path.resolve(__dirname,'..');
4
+ const read=rel=>fs.readFileSync(path.join(root,rel),'utf8');const exists=rel=>fs.existsSync(path.join(root,rel));
5
+ const fail=m=>{console.error(`[NexoWatt EOS stable v99] ${m}`);process.exit(1)};const must=(c,m)=>{if(!c)fail(m)};
6
+ const pkg=JSON.parse(read('package.json')),io=JSON.parse(read('io-package.json')),info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));
7
+ const index=read('adminWww/index.html'),css=read('adminWww/css/nexowatt-native-shell.css'),stable=read('adminWww/static/js/nexowatt-stable-v99.js');
8
+ const ems=read('adminWww/js/eos-ems-overview.js'),emsSource=read('src-admin/src/components/Intro/NexoWattEmsOverview.tsx');
9
+ const intro=read('src-admin/src/tabs/Intro.tsx'),bundle=read('adminWww/assets/Intro-DkwRiz1n-v84.js');
10
+ const web=read('src/lib/web.ts'),built=read('build/lib/web.js'),password=read('src/lib/eosPassword.ts'),bootstrap=read('adminWww/js/eos-role-bootstrap.js');
11
+ must(pkg.version==='7.9.99'&&io.version==='7.9.99'&&io.common.version==='7.9.99'&&info.version==='7.9.99','version drift');
12
+ must(info.shellCacheVersion===99&&info.nativeShellVersion===99&&String(info.shellCacheTag)==='99','shell cache is not v99');
13
+ must(index.includes('nexowatt-stable-v99.js?v=99')&&index.includes('eos-release-watch.js?v=99'),'v99 runtime/watcher not loaded');
14
+ must(!exists('adminWww/static/js/nexowatt-stable-v97.js')&&!exists('src-admin/public/static/js/nexowatt-stable-v97.js'),'legacy v97 runtime remains');
15
+ must(!ems.includes('Vollständige EMS-Diagnose öffnen')&&!emsSource.includes('Vollständige EMS-Diagnose öffnen'),'unsupported EMS link remains');
16
+ must(intro.includes('className="eos-overview-edit-controls"')&&intro.includes('eos-native-intro-grid'),'source pencil is not in the card grid');
17
+ must(bundle.includes('className:"eos-overview-edit-controls"')&&bundle.includes('className:"eos-native-intro-grid"'),'productive pencil is not in the card grid');
18
+ must(css.includes('edit controls belong to the lowest Intro card grid')&&css.includes('.eos-overview-edit-controls .MuiFab-root'),'pencil placement CSS missing');
19
+ must(password.includes('setPasswordAsync(userName')&&password.includes('checkPasswordAsync(userName'),'password API does not use short name');
20
+ must(web.includes('passwordMetadataNotPersisted')&&built.includes('passwordMetadataNotPersisted'),'password metadata verification missing');
21
+ must(!web.includes('setForeignObjectAsync(access.userId, userObject)')&&!built.includes('setForeignObjectAsync(access.userId, userObject)'),'stale full-user password write remains');
22
+ must(bootstrap.includes("VERSION = 'v99-first-login-password-persisted'")&&bootstrap.includes('passwordVerificationFailed'),'v99 first-login UI missing');
23
+ must(stable.includes('NEXOWATT_EOS_STABLE_V99'),'stable v99 global marker missing');
24
+ for(const rel of ['tools/nexowatt-password-write-selftest.cjs','tools/nexowatt-request-security-selftest.cjs','tools/nexowatt-cache-update-selftest.cjs'])must(exists(rel),`${rel} missing`);
25
+ console.log('[NexoWatt EOS stable v99] OK');
@@ -19,14 +19,6 @@ function setValue(changes, label, object, key, value) {
19
19
  }
20
20
  }
21
21
 
22
- function setJsonValue(changes, label, object, key, value) {
23
- const previous = object[key];
24
- if (JSON.stringify(previous) !== JSON.stringify(value)) {
25
- changes.push({ label, previous, value });
26
- object[key] = value === undefined ? undefined : JSON.parse(JSON.stringify(value));
27
- }
28
- }
29
-
30
22
  function syncReleaseVersion(rootDir) {
31
23
  const root = path.resolve(rootDir);
32
24
  const packageFile = path.join(root, 'package.json');
@@ -70,40 +62,7 @@ function syncReleaseVersion(rootDir) {
70
62
  update(json) {
71
63
  setValue(changes, 'package-lock.json version', json, 'version', version);
72
64
  if (json.packages?.['']) {
73
- const rootPackage = json.packages[''];
74
- setValue(changes, 'package-lock.json packages[""] version', rootPackage, 'version', version);
75
- // A merge archive can update package.json while leaving stale release scripts,
76
- // files and policy metadata in package-lock.json. npm reads the lock root during
77
- // packaging, so keep every release-critical root field canonical as well.
78
- for (const key of [
79
- 'name',
80
- 'description',
81
- 'license',
82
- 'author',
83
- 'private',
84
- 'engines',
85
- 'main',
86
- 'files',
87
- 'scripts',
88
- 'dependencies',
89
- 'devDependencies',
90
- 'overrides',
91
- 'keywords',
92
- 'publishConfig',
93
- 'nexowattReleasePolicy',
94
- 'publisher',
95
- 'copyright',
96
- ]) {
97
- if (Object.prototype.hasOwnProperty.call(pkg, key)) {
98
- setJsonValue(
99
- changes,
100
- `package-lock.json packages[""] ${key}`,
101
- rootPackage,
102
- key,
103
- pkg[key],
104
- );
105
- }
106
- }
65
+ setValue(changes, 'package-lock.json packages[""] version', json.packages[''], 'version', version);
107
66
  }
108
67
  },
109
68
  },
@@ -5,7 +5,9 @@ const runtime=read('adminWww/js/eos-ems-overview.js');const source=read('src-adm
5
5
  if(runtime!==source)fail('runtime source/build drift');
6
6
  for(const marker of ['nexowatt-ems-overview-v1','nexowatt-ui.*.info.adminOverview.*','system.adapter.nexowatt-ui.*.alive','POLL_MS = 5000','STALE_MS = 20000',"REQUIRED_UI_VERSION = '0.8.198'",'getForeignStates','Keine Kachel besitzt Schreibhoheit','currentDecisions','§14a-Fallback','nexowatt-eos-admin-socket-ready','readUiAdapterInfo','socketFromReact'])if(!runtime.includes(marker))fail(`runtime marker missing: ${marker}`);
7
7
  for(const forbidden of ['setForeignState','setState(','sendToHost','writeFile','extendObject'])if(runtime.includes(forbidden))fail(`read-only runtime contains ${forbidden}`);
8
- for(const marker of ['eos-ems-overview.css?v=98','eos-ems-overview.js?v=98','nexowatt-stable-v98.js?v=98'])if(!index.includes(marker))fail(`index marker missing: ${marker}`);
8
+ for(const marker of ['eos-ems-overview.css?v=99','eos-ems-overview.js?v=99','nexowatt-stable-v99.js?v=99','eos-release-watch.js?v=99'])if(!index.includes(marker))fail(`index marker missing: ${marker}`);
9
+ if(runtime.includes('Vollständige EMS-Diagnose öffnen'))fail('unsupported EMS destination remains visible');
10
+ if(!intro.includes('eos-overview-edit-controls')||!introBundle.includes('eos-overview-edit-controls'))fail('edit control is not inside bottom card grid');
9
11
  if(!css.includes('#eos-ems-overview-runtime')||!css.includes('.eos-ems-overview-metrics')||!css.includes('startup and compatibility messages stay compact'))fail('EMS card CSS incomplete');
10
12
  if(role.includes('overview.innerHTML = `')||!role.includes('never cover the real Admin Intro'))fail('obsolete role overview still active');
11
13
  if(!shell.includes("label: 'Installateur'")||!shell.includes("label: 'Endkunde'")||!shell.includes('eosNativeReactOverview'))fail('native overview role bridge incomplete');