iobroker.eos-admin 7.10.0 → 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 (65) 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 +19 -21
  4. package/NEXOWATT_EOS_PREBUILT_MANIFEST.json +1410 -0
  5. package/PUBLISH_STABLE_V7.10.2.md +21 -0
  6. package/README.md +16 -6
  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/adminWww/assets/Intro-DkwRiz1n-v84.js +1 -1
  11. package/adminWww/assets/bootstrap-COulQZax-v84.js +2 -2
  12. package/adminWww/css/eos-ems-overview.css +5 -3
  13. package/adminWww/css/nexowatt-native-shell.css +85 -413
  14. package/adminWww/index.html +20 -21
  15. package/adminWww/js/eos-account-management.js +28 -19
  16. package/adminWww/js/eos-assistant.js +1 -1
  17. package/adminWww/js/eos-ems-overview.js +1 -1
  18. package/adminWww/js/eos-role-bootstrap.js +17 -80
  19. package/adminWww/js/eos-role-ui.js +28 -4
  20. package/adminWww/js/nexowatt-native-shell.js +1 -1
  21. package/adminWww/static/js/nexowatt-stable-login.js +43 -70
  22. package/adminWww/static/js/nexowatt-stable-v7101.js +27 -0
  23. package/build/lib/eosAutoUpdate.js +418 -20
  24. package/build/lib/eosAutoUpdate.js.map +1 -0
  25. package/build/lib/eosRequestSecurity.js +4 -1
  26. package/build/lib/eosRequestSecurity.js.map +1 -0
  27. package/build/lib/web.js +161 -254
  28. package/build/lib/web.js.map +1 -1
  29. package/build/main.js +39 -67
  30. package/io-package.json +72 -7
  31. package/package.json +24 -25
  32. package/tools/nexowatt-account-management-selftest.cjs +15 -19
  33. package/tools/nexowatt-account-password-flow-selftest.cjs +15 -0
  34. package/tools/nexowatt-apply-ems-overview.cjs +4 -2
  35. package/tools/nexowatt-auto-update-selftest.cjs +144 -32
  36. package/tools/nexowatt-backend-runtime-selftest.cjs +147 -0
  37. package/tools/nexowatt-clean-legacy-runtime.cjs +1 -1
  38. package/tools/nexowatt-ems-overview-selftest.cjs +1 -1
  39. package/tools/nexowatt-ensure-release-defaults.cjs +1 -1
  40. package/tools/nexowatt-login-layout-selftest.cjs +9 -13
  41. package/tools/nexowatt-merge-update-selftest.cjs +7 -1
  42. package/tools/nexowatt-modern-ui-selftest.cjs +10 -46
  43. package/tools/nexowatt-password-write-selftest.cjs +46 -16
  44. package/tools/nexowatt-prebuilt-release-selftest.cjs +292 -0
  45. package/tools/nexowatt-release-defaults-selftest.cjs +1 -1
  46. package/tools/nexowatt-runtime-cleanup-selftest.cjs +6 -8
  47. package/tools/nexowatt-stable-v7102-selftest.cjs +51 -0
  48. package/tools/nexowatt-sync-release-version.cjs +43 -5
  49. package/tools/nexowatt-ui-overview-runtime-selftest.cjs +2 -3
  50. package/tools/nexowatt-validate-package.cjs +54 -29
  51. package/tools/nexowatt-version-sync-selftest.cjs +18 -1
  52. package/INSTALL_TEST_V7.10.0.md +0 -11
  53. package/MERGE_UPDATE_README_V7.10.0.md +0 -5
  54. package/PUBLISH_STABLE_V7.10.0.md +0 -15
  55. package/README_STABILITY_V7.10.0.md +0 -3
  56. package/RELEASE_ACCEPTANCE_V7.10.0.md +0 -11
  57. package/RELEASE_NOTES_V7.10.0.md +0 -17
  58. package/adminWww/js/eos-release-watch.js +0 -77
  59. package/adminWww/static/js/nexowatt-stable-v7100.js +0 -111
  60. package/build/lib/eosPassword.js +0 -72
  61. package/build/lib/nexowattStableAuth.js +0 -74
  62. package/src/lib/eosAutoUpdate.ts +0 -320
  63. package/tools/nexowatt-cache-update-selftest.cjs +0 -12
  64. package/tools/nexowatt-request-security-selftest.cjs +0 -11
  65. package/tools/nexowatt-stable-v7100-selftest.cjs +0 -17
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('node:crypto');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+
9
+ const root = path.resolve(__dirname, '..');
10
+ const manifestFile = path.join(root, 'NEXOWATT_EOS_PREBUILT_MANIFEST.json');
11
+ const packageFile = path.join(root, 'package.json');
12
+ const trackedRoots = [
13
+ 'src',
14
+ 'build',
15
+ 'src-admin/src',
16
+ 'src-admin/public',
17
+ 'adminWww',
18
+ 'admin',
19
+ 'public',
20
+ 'tools',
21
+ ];
22
+ const trackedFiles = [
23
+ 'tasks.mts',
24
+ 'tsconfig.json',
25
+ 'tsconfig.build.json',
26
+ ];
27
+ const expectedPrepublishOnly =
28
+ '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';
29
+ const expectedPrepack =
30
+ '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';
31
+
32
+ function normalize(relative) {
33
+ return relative.replace(/\\/g, '/');
34
+ }
35
+
36
+ function walk(relative, output) {
37
+ const absolute = path.join(root, relative);
38
+ if (!fs.existsSync(absolute)) return;
39
+ const stat = fs.lstatSync(absolute);
40
+ if (stat.isSymbolicLink()) {
41
+ throw new Error(`symbolic links are not allowed in the sealed release: ${normalize(relative)}`);
42
+ }
43
+ if (stat.isFile()) {
44
+ output.push(normalize(relative));
45
+ return;
46
+ }
47
+ if (!stat.isDirectory()) return;
48
+ for (const name of fs.readdirSync(absolute).sort()) {
49
+ walk(path.join(relative, name), output);
50
+ }
51
+ }
52
+
53
+ function collectTrackedFiles() {
54
+ const files = [];
55
+ for (const relative of trackedRoots) walk(relative, files);
56
+ for (const relative of trackedFiles) {
57
+ const absolute = path.join(root, relative);
58
+ if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) {
59
+ throw new Error(`tracked release input is missing: ${relative}`);
60
+ }
61
+ files.push(normalize(relative));
62
+ }
63
+ return [...new Set(files)].sort();
64
+ }
65
+
66
+ function sha256(relative) {
67
+ return crypto.createHash('sha256').update(fs.readFileSync(path.join(root, relative))).digest('hex');
68
+ }
69
+
70
+ function createManifest() {
71
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
72
+ const files = collectTrackedFiles();
73
+ const hashes = {};
74
+ for (const relative of files) hashes[relative] = sha256(relative);
75
+ return {
76
+ format: 1,
77
+ package: pkg.name,
78
+ version: pkg.version,
79
+ algorithm: 'sha256',
80
+ fileCount: files.length,
81
+ trackedRoots,
82
+ trackedFiles,
83
+ files: hashes,
84
+ };
85
+ }
86
+
87
+ function writeManifest() {
88
+ const manifest = createManifest();
89
+ fs.writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
90
+ console.log(`[NexoWatt EOS prebuilt release] sealed ${manifest.fileCount} source/runtime file(s) for ${manifest.version}`);
91
+ }
92
+
93
+ function resolveLocal(fromFile, specifier) {
94
+ const base = path.resolve(path.dirname(fromFile), specifier);
95
+ const candidates = [base, `${base}.js`, `${base}.json`, path.join(base, 'index.js')];
96
+ if (fs.existsSync(base) && fs.statSync(base).isDirectory()) {
97
+ const nestedPackage = path.join(base, 'package.json');
98
+ if (fs.existsSync(nestedPackage)) {
99
+ try {
100
+ const value = JSON.parse(fs.readFileSync(nestedPackage, 'utf8'));
101
+ if (value.main) candidates.unshift(path.resolve(base, value.main));
102
+ } catch {
103
+ // The normal package validator reports malformed JSON in detail.
104
+ }
105
+ }
106
+ }
107
+ return candidates.find(candidate => fs.existsSync(candidate) && fs.statSync(candidate).isFile()) || null;
108
+ }
109
+
110
+ function inspectRuntimeClosure(failures) {
111
+ const entry = path.join(root, 'build', 'main.js');
112
+ const visited = new Set();
113
+ const runtimeFiles = new Set();
114
+
115
+ function inspect(file) {
116
+ const absolute = path.resolve(file);
117
+ if (visited.has(absolute)) return;
118
+ visited.add(absolute);
119
+ runtimeFiles.add(absolute);
120
+ if (!fs.existsSync(absolute)) {
121
+ failures.push(`missing runtime file ${normalize(path.relative(root, absolute))}`);
122
+ return;
123
+ }
124
+ if (!absolute.endsWith('.js')) return;
125
+
126
+ const parsed = spawnSync(process.execPath, ['--check', absolute], {
127
+ encoding: 'utf8',
128
+ windowsHide: true,
129
+ });
130
+ if (parsed.status !== 0) {
131
+ failures.push(
132
+ `${normalize(path.relative(root, absolute))} does not parse: ${(parsed.stderr || parsed.stdout || '').trim()}`,
133
+ );
134
+ }
135
+
136
+ const text = fs.readFileSync(absolute, 'utf8');
137
+ const patterns = [
138
+ /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g,
139
+ /\brequire\.resolve\(\s*['"]([^'"]+)['"]\s*\)/g,
140
+ /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g,
141
+ ];
142
+ for (const pattern of patterns) {
143
+ pattern.lastIndex = 0;
144
+ let match;
145
+ while ((match = pattern.exec(text))) {
146
+ const specifier = match[1];
147
+ if (!specifier.startsWith('.')) continue;
148
+ const target = resolveLocal(absolute, specifier);
149
+ if (!target) {
150
+ failures.push(`${normalize(path.relative(root, absolute))} -> ${specifier} cannot be resolved`);
151
+ continue;
152
+ }
153
+ inspect(target);
154
+ }
155
+ }
156
+ }
157
+
158
+ inspect(entry);
159
+ for (const required of [
160
+ 'build/main.js',
161
+ 'build/lib/web.js',
162
+ 'build/lib/eosAutoUpdate.js',
163
+ 'build/lib/eosRequestSecurity.js',
164
+ ]) {
165
+ if (!runtimeFiles.has(path.join(root, required))) {
166
+ failures.push(`entrypoint closure does not include required module ${required}`);
167
+ }
168
+ }
169
+ return runtimeFiles.size;
170
+ }
171
+
172
+ function verifyManifest(failures) {
173
+ if (!fs.existsSync(manifestFile)) {
174
+ failures.push('NEXOWATT_EOS_PREBUILT_MANIFEST.json is missing');
175
+ return 0;
176
+ }
177
+ let manifest;
178
+ try {
179
+ manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
180
+ } catch (error) {
181
+ failures.push(`prebuilt manifest is invalid JSON: ${error.message}`);
182
+ return 0;
183
+ }
184
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
185
+ if (manifest.format !== 1) failures.push(`unsupported prebuilt manifest format ${manifest.format}`);
186
+ if (manifest.package !== pkg.name) failures.push(`prebuilt manifest package ${manifest.package} does not match ${pkg.name}`);
187
+ if (manifest.version !== pkg.version) failures.push(`prebuilt manifest version ${manifest.version} does not match ${pkg.version}`);
188
+ if (manifest.algorithm !== 'sha256') failures.push(`prebuilt manifest algorithm must be sha256, got ${manifest.algorithm}`);
189
+
190
+ let currentFiles = [];
191
+ try {
192
+ currentFiles = collectTrackedFiles();
193
+ } catch (error) {
194
+ failures.push(error.message);
195
+ return 0;
196
+ }
197
+ const sealedFiles = Object.keys(manifest.files || {}).sort();
198
+ const currentSet = new Set(currentFiles);
199
+ const sealedSet = new Set(sealedFiles);
200
+ for (const relative of currentFiles) {
201
+ if (!sealedSet.has(relative)) failures.push(`unsealed release file: ${relative}`);
202
+ }
203
+ for (const relative of sealedFiles) {
204
+ if (!currentSet.has(relative)) failures.push(`sealed release file is missing: ${relative}`);
205
+ }
206
+ for (const relative of sealedFiles) {
207
+ if (!currentSet.has(relative)) continue;
208
+ const actual = sha256(relative);
209
+ if (actual !== manifest.files[relative]) failures.push(`sealed release file changed after validation: ${relative}`);
210
+ }
211
+ if (manifest.fileCount !== sealedFiles.length) {
212
+ failures.push(`prebuilt manifest fileCount ${manifest.fileCount} does not match ${sealedFiles.length}`);
213
+ }
214
+ return sealedFiles.length;
215
+ }
216
+
217
+ function verifyLifecycle(failures) {
218
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
219
+ const scripts = pkg.scripts || {};
220
+ if (scripts.prepublishOnly !== expectedPrepublishOnly) {
221
+ failures.push('prepublishOnly is not the canonical dependency-free direct-publish workflow');
222
+ }
223
+ if (scripts.prepack !== expectedPrepack) {
224
+ failures.push('prepack is not the canonical dependency-free prebuilt-artifact verifier');
225
+ }
226
+ if (scripts['check:eos-prebuilt-release'] !== 'node tools/nexowatt-prebuilt-release-selftest.cjs') {
227
+ failures.push('check:eos-prebuilt-release script is missing or incorrect');
228
+ }
229
+ if (scripts['seal:eos-prebuilt-release'] !== 'node tools/nexowatt-prebuilt-release-selftest.cjs --write') {
230
+ failures.push('seal:eos-prebuilt-release script is missing or incorrect');
231
+ }
232
+ for (const [name, command] of [
233
+ ['prepublishOnly', scripts.prepublishOnly || ''],
234
+ ['prepack', scripts.prepack || ''],
235
+ ]) {
236
+ for (const forbidden of [
237
+ /(?:^|\s)tsc(?:\s|$)/i,
238
+ /(?:^|\s)tsx(?:\s|$)/i,
239
+ /build:backend/i,
240
+ /build:frontend/i,
241
+ /(?:^|\s)npx(?:\s|$)/i,
242
+ /npm\s+(?:i|install|ci)(?:\s|$)/i,
243
+ ]) {
244
+ if (forbidden.test(command)) failures.push(`${name} must not require local development dependencies: ${command}`);
245
+ }
246
+ }
247
+ const backendRuntimeSelftestFile = path.join(root, 'tools', 'nexowatt-backend-runtime-selftest.cjs');
248
+ if (!fs.existsSync(backendRuntimeSelftestFile)) {
249
+ failures.push('tools/nexowatt-backend-runtime-selftest.cjs is missing');
250
+ } else {
251
+ const backendRuntimeSelftest = fs.readFileSync(backendRuntimeSelftestFile, 'utf8');
252
+ if (!backendRuntimeSelftest.includes('dependency-free on Windows')) {
253
+ failures.push('backend runtime selftest is missing the cross-platform publish marker');
254
+ }
255
+ if (/const\s+npm\s*=\s*process\.platform|spawnSync\(\s*npm\s*,|['"]npm\.cmd['"]/.test(backendRuntimeSelftest)) {
256
+ failures.push('backend runtime selftest must not spawn a nested npm process');
257
+ }
258
+ }
259
+ const packageFiles = new Set((pkg.files || []).map(value => String(value).replace(/\\/g, '/')));
260
+ if (![...packageFiles].some(value => value.replace(/\/+$/, '') === 'build')) {
261
+ failures.push('package.json files must include the complete build directory');
262
+ }
263
+ for (const required of [
264
+ 'tools/nexowatt-prebuilt-release-selftest.cjs',
265
+ 'NEXOWATT_EOS_PREBUILT_MANIFEST.json',
266
+ ]) {
267
+ if (!packageFiles.has(required)) failures.push(`package.json files is missing ${required}`);
268
+ }
269
+ }
270
+
271
+ if (process.argv.includes('--write')) {
272
+ try {
273
+ writeManifest();
274
+ } catch (error) {
275
+ console.error(`[NexoWatt EOS prebuilt release] ${error instanceof Error ? error.message : String(error)}`);
276
+ process.exit(1);
277
+ }
278
+ process.exit(0);
279
+ }
280
+
281
+ const failures = [];
282
+ verifyLifecycle(failures);
283
+ const sealedCount = verifyManifest(failures);
284
+ const runtimeCount = inspectRuntimeClosure(failures);
285
+
286
+ if (failures.length) {
287
+ console.error(`[NexoWatt EOS prebuilt release] ${failures.length} failure(s):\n${failures.join('\n')}`);
288
+ process.exit(1);
289
+ }
290
+ console.log(
291
+ `[NexoWatt EOS prebuilt release] OK (${sealedCount} sealed source/runtime files; ${runtimeCount} backend runtime files; no tsc/tsx required for npm publish)`,
292
+ );
@@ -13,7 +13,7 @@ const unsafe = {
13
13
  eosHideLegacyAdminFromNonAdmins: false,
14
14
  eosHideLegacyBackupFromNonAdmins: false,
15
15
  nexowattHideLegacyBackupFromNonAdmins: false,
16
- eosRequireFirstLoginPassword: false,
16
+ eosRequireFirstLoginPassword: true,
17
17
  },
18
18
  };
19
19
  const repaired = ensureReleaseDefaults(unsafe);
@@ -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: 7100 }));
18
+ fs.writeFileSync(path.join(temp, 'NEXOWATT_EOS_BUILD_INFO.json'), JSON.stringify({ runtimeEntry: 'v82', shellCacheVersion: 98 }));
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-v7100.js',
26
- 'src-admin/public/static/js/nexowatt-stable-v7100.js',
25
+ 'adminWww/static/js/nexowatt-stable-v98.js',
26
+ 'src-admin/public/static/js/nexowatt-stable-v98.js',
27
27
  'adminWww/static/js/nexowatt-stable-login.js',
28
28
  ];
29
29
  const remove = [
@@ -34,10 +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-v100.js',
38
- 'src-admin/public/static/js/nexowatt-stable-v99.js',
39
- 'adminWww/static/js/nexowatt-stable-v98.js',
40
- 'src-admin/public/static/js/nexowatt-stable-v97.js',
37
+ 'adminWww/static/js/nexowatt-stable-v97.js',
38
+ 'src-admin/public/static/js/nexowatt-stable-v95.js',
41
39
  ];
42
40
  for (const rel of [...keep, ...remove]) {
43
41
  const file = path.join(temp, rel);
@@ -47,7 +45,7 @@ try {
47
45
 
48
46
  const result = cleanLegacyRuntime({ root: temp, quiet: true, silent: true });
49
47
  assert.strictEqual(result.activeRuntime, 82);
50
- assert.strictEqual(result.activeShell, 7100);
48
+ assert.strictEqual(result.activeShell, 98);
51
49
  assert.strictEqual(result.removed.length, remove.length);
52
50
  for (const rel of keep) assert.ok(fs.existsSync(path.join(temp, rel)), `current runtime removed: ${rel}`);
53
51
  for (const rel of remove) assert.ok(!fs.existsSync(path.join(temp, rel)), `legacy file remained: ${rel}`);
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const root = path.resolve(__dirname, '..');
7
+ const read = relative => fs.readFileSync(path.join(root, relative), 'utf8');
8
+ const json = relative => JSON.parse(read(relative));
9
+ const must = (value, message) => {
10
+ if (!value) {
11
+ console.error(`[NexoWatt EOS stable v7102] ${message}`);
12
+ process.exit(1);
13
+ }
14
+ };
15
+
16
+ const pkg = json('package.json');
17
+ const io = json('io-package.json');
18
+ const info = json('NEXOWATT_EOS_BUILD_INFO.json');
19
+ const main = read('src/main.ts');
20
+ const web = read('src/lib/web.ts');
21
+ const boot = read('adminWww/js/eos-role-bootstrap.js');
22
+ const role = read('adminWww/js/eos-role-ui.js');
23
+ const css = read('adminWww/css/nexowatt-native-shell.css');
24
+ const index = read('adminWww/index.html');
25
+ const ems = read('adminWww/js/eos-ems-overview.js');
26
+ const intro = read('src-admin/src/tabs/Intro.tsx');
27
+ const bundle = read('adminWww/assets/Intro-DkwRiz1n-v84.js');
28
+ const stable = read('adminWww/static/js/nexowatt-stable-v7101.js');
29
+ const auto = read('src/lib/eosAutoUpdate.ts');
30
+
31
+ must(pkg.version === '7.10.2' && io.version === '7.10.2' && io.common.version === '7.10.2' && info.version === '7.10.2', 'version drift');
32
+ must(io.native.eosRequireFirstLoginPassword === false && io.native.eosRequireFirstLoginPasswordChange === false, 'forced first password still enabled');
33
+ must(!index.includes('eos-account-management.js'), 'custom reset UI still loaded');
34
+ must(!boot.includes('installIntegratedFirstLogin(base);') && !boot.includes('showFirstLoginPassword(resolved, base);'), 'forced first-login UI still active');
35
+ must(main.includes('standardPasswordMode: true') && main.includes('nexowattPasswordChangeRequired: false'), 'account migration missing');
36
+ must(role.includes("route === 'tab-users'") && main.includes("command === 'changePassword'"), 'role-aware native password editing missing');
37
+ must(css.includes('persistent native overview scrolling') && stable.includes("overflow-y','scroll"), 'persistent scroll missing');
38
+ must(intro.includes('eos-overview-edit-controls') && bundle.includes('eos-overview-edit-controls'), 'edit control not in bottom grid');
39
+ must(!ems.includes('Vollständige EMS-Diagnose öffnen'), 'unsupported EMS link remains');
40
+ must(index.includes('eos-auto-update.js?v=7101') && auto.includes("const POLICY: NexoWattAutoUpdatePolicy = 'major'"), 'auto update missing');
41
+
42
+ // 7.10.2 hotfix acceptance: no native writes on the running instance, delayed
43
+ // startup reconciliation, cleanup on Web.close and complete backend package.
44
+ must(auto.includes("const ENABLED_STATE_ID = 'info.nexowattStableUpdatesEnabled'"), 'restart-safe enabled state missing');
45
+ must(auto.includes("const STATUS_STATE_ID = 'info.nexowattStableUpdatesState'"), 'restart-safe status state missing');
46
+ must(auto.includes('const STARTUP_RECONCILE_DELAY_MS = 30_000'), 'startup grace period missing');
47
+ must(!auto.includes('extendForeignObjectAsync(this.instanceId'), 'runtime still writes the running instance native configuration');
48
+ must(web.includes('this.nexowattStableUpdateManager?.stop();'), 'auto-update manager is not stopped with the web server');
49
+ must(fs.existsSync(path.join(root, 'build/lib/eosRequestSecurity.js')), 'eosRequestSecurity runtime module missing');
50
+
51
+ console.log('[NexoWatt EOS stable v7102] OK (v7101 UI baseline plus restart-loop and package-completeness hotfix)');
@@ -19,6 +19,14 @@ 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
+
22
30
  function syncReleaseVersion(rootDir) {
23
31
  const root = path.resolve(rootDir);
24
32
  const packageFile = path.join(root, 'package.json');
@@ -32,10 +40,7 @@ function syncReleaseVersion(rootDir) {
32
40
  throw new Error(`Invalid package.json version: ${version || '<missing>'}`);
33
41
  }
34
42
 
35
- const parsed = version.match(/^(\d+)\.(\d+)\.(\d+)/);
36
- const releaseNo = parsed
37
- ? (parsed[1] === '7' && parsed[2] === '9' ? parsed[3] : `${parsed[1]}${parsed[2]}${parsed[3]}`)
38
- : version.replace(/[^0-9A-Za-z]+/g, '-');
43
+ const releaseNo = version.match(/^7\.9\.(\d+)/)?.[1] || version.replace(/[^0-9A-Za-z]+/g, '-');
39
44
  const expectedBase = `https://unpkg.com/iobroker.eos-admin@${version}`;
40
45
  const changes = [];
41
46
 
@@ -65,7 +70,40 @@ function syncReleaseVersion(rootDir) {
65
70
  update(json) {
66
71
  setValue(changes, 'package-lock.json version', json, 'version', version);
67
72
  if (json.packages?.['']) {
68
- setValue(changes, 'package-lock.json packages[""] version', json.packages[''], 'version', version);
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
+ }
69
107
  }
70
108
  },
71
109
  },
@@ -3,11 +3,10 @@
3
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 fail=msg=>{console.error(`[NexoWatt EOS UI overview runtime] ${msg}`);process.exit(1)};
4
4
  const runtime=read('adminWww/js/eos-ems-overview.js');const source=read('src-admin/public/js/eos-ems-overview.js');const css=read('adminWww/css/eos-ems-overview.css');const index=read('adminWww/index.html');const role=read('adminWww/js/eos-role-ui.js');const shell=read('adminWww/js/nexowatt-native-shell.js');const intro=read('src-admin/src/tabs/Intro.tsx');const introBundle=read('adminWww/assets/Intro-DkwRiz1n-v84.js');const bootstrap=read('adminWww/assets/bootstrap-COulQZax-v84.js');const generic=read('adminWww/assets/index-D2ymscJA-v84.js');
5
5
  if(runtime!==source)fail('runtime source/build drift');
6
+ if(runtime.includes('Vollständige EMS-Diagnose öffnen'))fail('unsupported EMS link remains');
6
7
  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
8
  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=7100','eos-ems-overview.js?v=7100','nexowatt-stable-v7100.js?v=7100','eos-release-watch.js?v=7100'])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
+ for(const marker of ['eos-ems-overview.css?v=7101','eos-ems-overview.js?v=7101','nexowatt-stable-v7101.js?v=7101'])if(!index.includes(marker))fail(`index marker missing: ${marker}`);
11
10
  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');
12
11
  if(role.includes('overview.innerHTML = `')||!role.includes('never cover the real Admin Intro'))fail('obsolete role overview still active');
13
12
  if(!shell.includes("label: 'Installateur'")||!shell.includes("label: 'Endkunde'")||!shell.includes('eosNativeReactOverview'))fail('native overview role bridge incomplete');