iobroker.eos-admin 7.9.92 → 7.9.95

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.9.92.md → INSTALL_TEST_V7.9.95.md} +2 -5
  2. package/NEXOWATT_EOS_BUILD_INFO.json +18 -14
  3. package/NEXOWATT_EOS_STABLE_7.9.94.md +16 -0
  4. package/PUBLISH_STABLE_V7.9.95.md +16 -0
  5. package/README_STABILITY_V7.9.95.md +12 -0
  6. package/RELEASE_ACCEPTANCE_V7.9.95.md +11 -0
  7. package/RELEASE_NOTES_V7.9.95.md +12 -0
  8. package/adminWww/css/nexowatt-native-shell.css +2 -2
  9. package/adminWww/index.html +2 -0
  10. package/adminWww/js/eos-assistant.js +5 -124
  11. package/adminWww/static/js/nexowatt-stable-login.js +79 -0
  12. package/build/lib/nexowattStableAuth.js +74 -0
  13. package/build/lib/web.js +24 -7
  14. package/build/main.js +122 -1
  15. package/io-package.json +24 -15
  16. package/package.json +22 -31
  17. package/PUBLISH_STABLE_V7.9.92.md +0 -19
  18. package/README_STABILITY_V7.9.92.md +0 -14
  19. package/RELEASE_ACCEPTANCE_V7.9.92.md +0 -12
  20. package/RELEASE_NOTES_V7.9.92.md +0 -17
  21. package/tools/nexowatt-account-management-selftest.cjs +0 -61
  22. package/tools/nexowatt-clean-legacy-runtime.cjs +0 -147
  23. package/tools/nexowatt-ensure-release-defaults.cjs +0 -72
  24. package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +0 -37
  25. package/tools/nexowatt-generate-repo-entry.cjs +0 -3
  26. package/tools/nexowatt-internal-reserve-selftest.cjs +0 -55
  27. package/tools/nexowatt-login-layout-selftest.cjs +0 -81
  28. package/tools/nexowatt-modern-ui-selftest.cjs +0 -50
  29. package/tools/nexowatt-patch-repo.cjs +0 -54
  30. package/tools/nexowatt-publish-channel-guard.cjs +0 -58
  31. package/tools/nexowatt-release-defaults-selftest.cjs +0 -29
  32. package/tools/nexowatt-runtime-cleanup-selftest.cjs +0 -47
  33. package/tools/nexowatt-validate-package.cjs +0 -246
@@ -1,246 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const root = path.resolve(__dirname, '..');
7
- const fail = msg => {
8
- console.error(`[NexoWatt EOS package validation] ${msg}`);
9
- process.exit(1);
10
- };
11
- const read = file => fs.readFileSync(path.join(root, file), 'utf8');
12
- const readJson = file => JSON.parse(read(file));
13
- const exists = file => fs.existsSync(path.join(root, file));
14
-
15
- const pkg = readJson('package.json');
16
- const pkgLock = readJson('package-lock.json');
17
- const io = readJson('io-package.json');
18
- const srcPkg = readJson('src-admin/package.json');
19
- const srcPkgLock = readJson('src-admin/package-lock.json');
20
- const srcVersion = readJson('src-admin/src/version.json');
21
- const buildInfo = readJson('NEXOWATT_EOS_BUILD_INFO.json');
22
-
23
- if (pkg.name !== 'iobroker.eos-admin') fail(`package.json name must be iobroker.eos-admin, got ${pkg.name}`);
24
- if (pkg.private !== false) fail('package.json private must be false for npm publishing');
25
-
26
- const prerelease = pkg.version.includes('-');
27
- if (pkg.publishConfig?.tag !== 'latest') fail(`package must publish through npm latest, got ${pkg.publishConfig?.tag || '<unset>'}`);
28
- if (!exists('.npmrc') || !/^\s*tag\s*=\s*latest\s*$/m.test(read('.npmrc'))) fail('repository must contain .npmrc with tag=latest');
29
- if (prerelease) {
30
- if (pkg.nexowattReleasePolicy?.distTag !== 'latest') fail('accepted prerelease must record distTag=latest');
31
- if (pkg.nexowattReleasePolicy?.acceptedPrerelease !== pkg.version) fail('acceptedPrerelease must match the exact package version');
32
- } else if (pkg.nexowattReleasePolicy?.acceptedPrerelease) {
33
- fail(`stable package must remove stale acceptedPrerelease ${pkg.nexowattReleasePolicy.acceptedPrerelease}`);
34
- }
35
- if (pkg.scripts['prepare:eos-release-defaults'] !== 'node tools/nexowatt-ensure-release-defaults.cjs') fail('release-default preparation script is missing');
36
- if (pkg.scripts['test:eos-release-defaults'] !== 'node tools/nexowatt-release-defaults-selftest.cjs') fail('release-default selftest script is missing');
37
- if (pkg.scripts['check:eos-publish-channel'] !== 'node tools/nexowatt-publish-channel-guard.cjs') fail('publish channel guard script is missing');
38
- if (!pkg.scripts.prepublishOnly?.startsWith('npm run prepare:eos-release-defaults && npm run check:eos-publish-channel')) fail('prepublishOnly must normalize release defaults before the publish channel guard');
39
- if (pkg.scripts['precheck:eos-package'] !== 'npm run prepare:eos-release-defaults && npm run clean:eos-runtime') fail('precheck:eos-package must normalize release defaults and clean stale runtime files');
40
- if (pkg.scripts['precheck:eos-stability'] !== 'npm run prepare:eos-release-defaults && npm run clean:eos-runtime') fail('precheck:eos-stability must normalize release defaults and clean stale runtime files');
41
- if (pkg.scripts['test:eos-publish-channel'] !== 'node tools/nexowatt-publish-channel-selftest.cjs') fail('publish channel selftest script is missing');
42
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-publish-channel-selftest.cjs')) fail('stability check must execute the publish channel selftest');
43
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-release-defaults-selftest.cjs')) fail('stability check must execute the release-default selftest');
44
- if (io.native?.auth !== true) fail('fresh sales installations must default to authenticated access');
45
- if (io.native?.eosHideLegacyBackupFromNonAdmins !== true) fail('internal BackItUp reserve must default to Admin/Service visibility');
46
- if (io.native?.nexowattHideLegacyBackupFromNonAdmins !== true) fail('NexoWatt backup visibility compatibility flag must default to true');
47
- for (const [label, value] of [
48
- ['io-package common.version', io.common?.version],
49
- ['io-package top-level version', io.version],
50
- ['package-lock version', pkgLock.version],
51
- ['package-lock root version', pkgLock.packages?.['']?.version],
52
- ['src-admin package version', srcPkg.version],
53
- ['src-admin lock version', srcPkgLock.version],
54
- ['src-admin lock root version', srcPkgLock.packages?.['']?.version],
55
- ['src-admin source version', srcVersion.version],
56
- ['build-info version', buildInfo.version],
57
- ]) if (value !== pkg.version) fail(`${label} must match package.json (${pkg.version}), got ${value}`);
58
-
59
- if (io.common?.name !== 'eos-admin') fail(`io-package common.name must be eos-admin, got ${io.common?.name}`);
60
- if (io.common?.packetName !== 'iobroker.eos-admin') fail(`io-package common.packetName must be iobroker.eos-admin, got ${io.common?.packetName}`);
61
- if (io.common?.npmPackage !== 'iobroker.eos-admin') fail(`io-package common.npmPackage must be iobroker.eos-admin, got ${io.common?.npmPackage}`);
62
- if (io.native?.port !== 8081) fail(`EOS Admin default port must be 8081, got ${io.native?.port}`);
63
- if (io.common?.stopBeforeUpdate !== false) fail('io-package common.stopBeforeUpdate must be false');
64
- if (io.common?.dontDelete === true || io.common?.nondeletable === true) fail('adapter-level delete flags must not block updates');
65
-
66
- const expectedBase = `https://unpkg.com/iobroker.eos-admin@${pkg.version}`;
67
- for (const [field, expected] of Object.entries({
68
- extIcon: `${expectedBase}/admin/admin.svg`,
69
- readme: `${expectedBase}/README.md`,
70
- meta: `${expectedBase}/io-package.json`,
71
- })) if (io.common?.[field] !== expected) fail(`io-package common.${field} must be ${expected}, got ${io.common?.[field]}`);
72
-
73
- const releaseNo = String(pkg.version).match(/^7\.9\.(\d+)/)?.[1];
74
- const repositoryEntryFile = buildInfo.repositoryEntry || `nexowatt-eos-admin-repository-entry-v${releaseNo}.json`;
75
- const repositoryEntry = readJson(repositoryEntryFile)['eos-admin'];
76
- if (!repositoryEntry) fail(`${repositoryEntryFile} is missing eos-admin`);
77
- if (repositoryEntry.version !== pkg.version) fail(`repository entry version must be ${pkg.version}, got ${repositoryEntry.version}`);
78
- for (const [field, expected] of Object.entries({
79
- meta: `${expectedBase}/io-package.json`,
80
- icon: `${expectedBase}/admin/admin.png`,
81
- extIcon: `${expectedBase}/admin/admin.svg`,
82
- readme: `${expectedBase}/README.md`,
83
- })) if (repositoryEntry[field] !== expected) fail(`repository entry ${field} must be ${expected}, got ${repositoryEntry[field]}`);
84
-
85
- for (const file of [
86
- 'adminWww/index.html',
87
- 'adminWww/js/eos-manual-write-policy.js',
88
- 'adminWww/js/eos-role-bootstrap.js',
89
- 'adminWww/js/eos-branding-sanitizer.js',
90
- 'adminWww/js/eos-policy-client.js',
91
- 'adminWww/js/nexowatt-native-shell.js',
92
- 'adminWww/js/eos-native-security.js',
93
- 'adminWww/js/eos-basic-settings.js',
94
- 'adminWww/js/eos-role-ui.js',
95
- 'adminWww/js/eos-account-management.js',
96
- 'adminWww/js/eos-assistant.js',
97
- 'adminWww/css/nexowatt-native-shell.css',
98
- 'adminWww/img/eos/nexowatt-192.png',
99
- 'src-admin/src/components/NexoWattNavIcon.tsx',
100
- 'src-admin/src/components/Drawer.tsx',
101
- 'src-admin/src/components/DrawerItem.tsx',
102
- 'admin/admin.svg',
103
- 'LICENSE',
104
- 'NEXOWATT_PROPRIETARY_LICENSE.md',
105
- 'THIRD_PARTY_NOTICES.md',
106
- 'README_STABILITY_V7.9.92.md',
107
- 'PUBLISH_STABLE_V7.9.92.md',
108
- 'INSTALL_TEST_V7.9.92.md',
109
- 'RELEASE_ACCEPTANCE_V7.9.92.md',
110
- 'tools/nexowatt-patch-built-frontend.cjs',
111
- 'tools/nexowatt-native-shell-selftest.cjs',
112
- 'tools/nexowatt-clean-legacy-runtime.cjs',
113
- 'tools/nexowatt-runtime-cleanup-selftest.cjs',
114
- 'tools/nexowatt-esm-syntax-selftest.cjs',
115
- 'tools/nexowatt-import-integrity-selftest.cjs',
116
- 'tools/nexowatt-entrypoint-smoke-selftest.cjs',
117
- 'tools/nexowatt-role-access-selftest.cjs',
118
- 'tools/nexowatt-first-login-selftest.cjs',
119
- 'tools/nexowatt-account-management-selftest.cjs',
120
- 'tools/nexowatt-modern-ui-selftest.cjs',
121
- 'tools/nexowatt-login-layout-selftest.cjs',
122
- 'tools/nexowatt-internal-reserve-selftest.cjs',
123
- 'tools/nexowatt-branding-selftest.cjs',
124
- 'tools/nexowatt-publish-channel-guard.cjs',
125
- 'tools/nexowatt-publish-channel-selftest.cjs',
126
- 'tools/nexowatt-ensure-release-defaults.cjs',
127
- 'tools/nexowatt-release-defaults-selftest.cjs',
128
- 'tools/nexowatt-default-port-selftest.cjs',
129
- '.npmrc',
130
- repositoryEntryFile,
131
- ]) if (!exists(file)) fail(`missing required file: ${file}`);
132
-
133
- const index = read('adminWww/index.html');
134
- const refs = [...index.matchAll(/(?:src|href)="\.\/([^"?#]+)(?:\?[^"#]*)?"/g)].map(m => m[1]);
135
- const missing = refs.filter(ref => !exists(path.join('adminWww', ref)));
136
- if (missing.length) fail(`adminWww/index.html references missing files:\n${missing.join('\n')}`);
137
-
138
- const runtime = buildInfo.runtimeEntry;
139
- const runtimeNumber = Number(String(runtime).replace(/^v/, ''));
140
- const shellCache = Number(buildInfo.shellCacheVersion ?? buildInfo.nativeShellVersion ?? runtimeNumber);
141
- const shellTag = String(buildInfo.shellCacheTag || shellCache);
142
- if (!runtime || !Number.isFinite(runtimeNumber)) fail(`invalid runtimeEntry ${runtime}`);
143
- if (!Number.isFinite(shellCache)) fail(`invalid shellCacheVersion ${buildInfo.shellCacheVersion}`);
144
- for (const marker of [
145
- `hostInit-${runtime}.js?v=${runtimeNumber}`,
146
- `index-CQZugZ1z-${runtime}.js?v=${runtimeNumber}`,
147
- `eos-manual-write-policy.js?v=${shellTag}`,
148
- `nexowatt-native-shell.css?v=${shellTag}`,
149
- `nexowatt-native-shell.js?v=${shellTag}`,
150
- `eos-native-security.js?v=${shellTag}`,
151
- ]) if (!index.includes(marker)) fail(`adminWww/index.html missing ${marker}`);
152
-
153
- if (!index.includes(`eos-role-bootstrap.js?v=${shellTag}`)) fail('role bootstrap cache key mismatch');
154
- if (!index.includes(`eos-branding-sanitizer.js?v=${shellTag}`)) fail('branding sanitizer cache key mismatch');
155
- if (!index.includes(`eos-role-ui.js?v=${shellTag}`)) fail('role UI cache key mismatch');
156
- if (!index.includes(`eos-account-management.js?v=${shellTag}`)) fail('account management cache key mismatch');
157
- if (!index.includes(`eos-assistant.js?v=${shellTag}`)) fail('EOS Assist cache key mismatch');
158
- if (index.indexOf(`eos-manual-write-policy.js?v=${shellTag}`) > index.indexOf(`hostInit-${runtime}.js?v=${runtimeNumber}`)) fail('manual-write policy must load before the React runtime');
159
- if (!index.includes('class="eos-native-shell"')) fail('native NexoWatt shell class missing');
160
- for (const legacy of ['eos-branding.js', 'eos-security-ui.js', 'eos-console-quiet.js', 'eos-objects-state-tools.js']) {
161
- if (index.includes(legacy)) fail(`legacy browser overlay still loaded: ${legacy}`);
162
- }
163
-
164
- const activeBootstrapFile = `adminWww/assets/bootstrap-COulQZax-${runtime}.js`;
165
- for (const file of [
166
- `adminWww/assets/hostInit-${runtime}.js`,
167
- `adminWww/assets/index-CQZugZ1z-${runtime}.js`,
168
- activeBootstrapFile,
169
- `adminWww/assets/Objects-DPan0bzw-${runtime}.js`,
170
- `adminWww/assets/index-D2ymscJA-${runtime}.js`,
171
- `adminWww/remoteEntry-${runtime}.js`,
172
- ]) if (!exists(file)) fail(`missing active runtime file ${file}`);
173
-
174
- const bootstrap = read(activeBootstrapFile);
175
- if (!bootstrap.includes('window.adapterName="eos-admin"')) fail('frontend bootstrap does not set window.adapterName="eos-admin"');
176
- if (bootstrap.includes('window.adapterName="admin"')) fail('frontend bootstrap still contains window.adapterName="admin"');
177
- for (const marker of ['NEXOWATT_TAB_ICON', 'nexowatt-native-nav-item', 'nexowatt-native-nav-icon', 'tabName:h.name', 'NexoWatt EOS', 'Zugänge & Rechte']) {
178
- if (!bootstrap.includes(marker)) fail(`active bootstrap missing native navigation marker ${marker}`);
179
- }
180
-
181
- const drawer = read('src-admin/src/components/Drawer.tsx');
182
- const drawerItem = read('src-admin/src/components/DrawerItem.tsx');
183
- const navIcon = read('src-admin/src/components/NexoWattNavIcon.tsx');
184
- for (const marker of ['getNexoWattTabTitle', 'getNexoWattTabIcon', "'tab-intro': 'Übersicht'", "'tab-users': 'Zugänge & Rechte'", 'System-Notfallsicherung', 'NexoWatt Sicherung']) {
185
- if (!drawer.includes(marker)) fail(`Drawer source missing ${marker}`);
186
- }
187
- if (!drawerItem.includes('className="nexowatt-native-nav-item"') || !drawerItem.includes('data-eos-tab={tabName}')) fail('DrawerItem is not native-shell aware');
188
- if (!navIcon.includes('eos-native-nav-icon-source') || !navIcon.includes('nexowatt-native-nav-icon')) fail('native SVG navigation icon component is incomplete');
189
-
190
- const shell = read('adminWww/js/nexowatt-native-shell.js');
191
- const shellCss = read('adminWww/css/nexowatt-native-shell.css');
192
- const nativeSecurity = read('adminWww/js/eos-native-security.js');
193
- const accountManagement = read('adminWww/js/eos-account-management.js');
194
- if (!shell.includes(`const VERSION = 'v${shellCache}-nexowatt-native-shell`)) fail(`native shell version marker v${shellCache} missing`);
195
- if (!shell.includes('Navigation labels and') || !shell.includes('rendered natively by Drawer.tsx')) fail('native shell ownership guard missing');
196
- if (shell.includes('innerHTML = cfg.svg') || shell.includes('textNode.textContent = cfg.label')) fail('native shell still rewrites navigation icons or labels after render');
197
- if (!shell.includes('NEXOWATT_EOS_DOM_COORDINATOR')) fail('native shell does not use the shared DOM coordinator');
198
- if (!shellCss.includes('.nexowatt-native-nav-item') || !shellCss.includes('.eos-native-nav-icon') || !shellCss.includes('.nexowatt-native-nav-icon')) fail('native shell CSS lacks React navigation selectors');
199
- if (!nativeSecurity.includes('shouldBlockInstanceDelete')) fail('minimal native security API missing instance protection');
200
- if (/addEventListener\(['"]click['"]/.test(nativeSecurity)) fail('native security must not globally intercept clicks');
201
- if (!accountManagement.includes('NEXOWATT_EOS_ACCOUNT_MANAGEMENT') || !accountManagement.includes('X-NexoWatt-EOS-Account-Reset') || !accountManagement.includes('ensureEntrySurface')) fail('account-management runtime is incomplete');
202
- if (accountManagement.includes("launcher.className = 'eos-account-management-launcher'")) fail('account management must not create a separate launcher');
203
- if (accountManagement.includes('new MutationObserver')) fail('account-management runtime adds a second broad DOM observer');
204
- if (read('src-admin/public/js/eos-account-management.js') !== accountManagement) fail('account-management source/build drift');
205
- const roleBootstrap = read('adminWww/js/eos-role-bootstrap.js');
206
- if (!roleBootstrap.includes('installIntegratedFirstLogin') || !roleBootstrap.includes('v91 compact normal-login first activation')) fail('compact integrated first-login is incomplete');
207
- if (/data-eos-account=|selector\.innerHTML/.test(roleBootstrap)) fail('login role buttons must not enlarge the normal login card');
208
- if (!roleBootstrap.includes('nexowatt/account/passwordless-status') || !roleBootstrap.includes('eligibility.allowed')) fail('server-checked first-login eligibility is incomplete');
209
- if (roleBootstrap.includes('installPasswordlessFirstLoginLauncher')) fail('old first-login launcher remains active');
210
- const assistant = read('adminWww/js/eos-assistant.js');
211
- if (!assistant.includes('eos-assist-header-root') || !assistant.includes('insertBefore(root, userAnchor)')) fail('EOS Assist is not header-integrated');
212
-
213
- const mf = read('adminWww/mf-manifest.json');
214
- if (!mf.includes(`remoteEntry-${runtime}.js`) || !mf.includes(`index-D2ymscJA-${runtime}.js`)) fail('module federation manifest is not on the active runtime');
215
-
216
- const webBuild = read('build/lib/web.js');
217
- if (!webBuild.includes('refreshLifetime: 60 * 60 * 24 * 7')) fail('build/lib/web.js must keep upstream-compatible refresh lifetime');
218
- if (!webBuild.includes('Follow upstream admin semantics again')) fail('build/lib/web.js lacks session compatibility fix');
219
- const mainBuild = read('build/main.js');
220
- if (!mainBuild.includes('v37 BackItUp/runtime-adapter compatibility')) fail('build/main.js lacks BackItUp compatibility guard');
221
-
222
- if (!pkg.scripts['nexowatt:patch-built-frontend']) fail('missing nexowatt:patch-built-frontend script');
223
- if (pkg.scripts['check:eos-esm'] !== 'node tools/nexowatt-esm-syntax-selftest.cjs') fail('check:eos-esm script is missing or incorrect');
224
- if (pkg.scripts['check:eos-imports'] !== 'node tools/nexowatt-import-integrity-selftest.cjs') fail('check:eos-imports script is missing or incorrect');
225
- if (pkg.scripts['check:eos-entry'] !== 'node tools/nexowatt-entrypoint-smoke-selftest.cjs') fail('check:eos-entry script is missing or incorrect');
226
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-esm-syntax-selftest.cjs')) fail('ESM syntax selftest is not part of check:eos-stability');
227
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-import-integrity-selftest.cjs')) fail('import integrity selftest is not part of check:eos-stability');
228
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-entrypoint-smoke-selftest.cjs')) fail('entrypoint smoke selftest is not part of check:eos-stability');
229
- if (pkg.scripts['clean:eos-runtime'] !== 'node tools/nexowatt-clean-legacy-runtime.cjs') fail('clean:eos-runtime script is missing or incorrect');
230
- if (pkg.scripts.prepack !== 'node tools/nexowatt-clean-legacy-runtime.cjs --quiet') fail('prepack must silently clean stale runtime files');
231
- if (!pkg.scripts.build?.includes('npm run clean:eos-runtime')) fail('build must finish with runtime cleanup');
232
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-role-access-selftest.cjs')) fail('role access selftest is not part of check:eos-stability');
233
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-first-login-selftest.cjs')) fail('first-login selftest is not part of check:eos-stability');
234
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-account-management-selftest.cjs')) fail('account-management selftest is not part of check:eos-stability');
235
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-modern-ui-selftest.cjs')) fail('modern-UI selftest is not part of check:eos-stability');
236
- if (pkg.scripts['test:eos-login-layout'] !== 'node tools/nexowatt-login-layout-selftest.cjs') fail('login-layout selftest script is missing or incorrect');
237
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-login-layout-selftest.cjs')) fail('login-layout selftest is not part of check:eos-stability');
238
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-internal-reserve-selftest.cjs')) fail('internal reserve selftest is not part of check:eos-stability');
239
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-branding-selftest.cjs')) fail('branding selftest is not part of check:eos-stability');
240
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-native-shell-selftest.cjs')) fail('native shell selftest is not part of check:eos-stability');
241
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-assistant-separation-selftest.cjs')) fail('assistant separation selftest is not part of check:eos-stability');
242
- if (!exists('adminWww/img/eos/nexowatt-eos-brand-wide.png')) fail('new NexoWatt EOS brand logo asset missing');
243
- if (!pkg.scripts['check:eos-stability']?.includes('nexowatt-runtime-cleanup-selftest.cjs')) fail('runtime cleanup selftest is not part of check:eos-stability');
244
- if (!read('tasks.mts').includes('patchNexoWattBuiltFrontend')) fail('tasks.mts does not execute the EOS post-build frontend patch');
245
-
246
- console.log('[NexoWatt EOS package validation] OK');