iobroker.eos-admin 7.9.95 → 7.9.96
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.
- package/INSTALL_TEST_V7.9.96.md +11 -0
- package/MERGE_UPDATE_README_V7.9.96.md +5 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +18 -23
- package/NexoWatt_EOS_Admin_EMS_OVERVIEW_MERGE_VALIDATION.md +44 -0
- package/PUBLISH_STABLE_V7.9.96.md +10 -0
- package/RC73_EOS_ADMIN_MERGE_VALIDATION.md +78 -0
- package/README_EMS_OVERVIEW_MERGE.md +45 -0
- package/README_STABILITY_V7.9.96.md +11 -0
- package/RELEASE_ACCEPTANCE_V7.9.96.md +10 -0
- package/RELEASE_NOTES_V7.9.96.md +12 -0
- package/VALIDATION_EMS_OVERVIEW_MERGE.md +35 -0
- package/adminWww/assets/Intro-DkwRiz1n-v84.js +2 -2
- package/adminWww/assets/index-D2ymscJA-v84.js +1 -1
- package/adminWww/css/eos-ems-overview.css +62 -0
- package/adminWww/css/nexowatt-native-shell.css +111 -13
- package/adminWww/index.html +15 -13
- package/adminWww/js/eos-assistant.js +1 -1
- package/adminWww/js/eos-ems-overview.js +140 -0
- package/adminWww/js/eos-role-bootstrap.js +4 -4
- package/adminWww/js/eos-role-ui.js +34 -38
- package/adminWww/js/nexowatt-native-shell.js +11 -6
- package/adminWww/static/js/nexowatt-stable-login.js +12 -12
- package/adminWww/static/js/nexowatt-stable-v96.js +82 -0
- package/io-package.json +10 -5
- package/package.json +38 -12
- package/tools/nexowatt-account-management-selftest.cjs +60 -0
- package/tools/nexowatt-apply-ems-overview.cjs +115 -0
- package/tools/nexowatt-clean-legacy-runtime.cjs +151 -0
- package/tools/nexowatt-ems-overview-selftest.cjs +35 -0
- package/tools/nexowatt-ensure-release-defaults.cjs +72 -0
- package/tools/nexowatt-generate-eos-admin-repo-entry.cjs +37 -0
- package/tools/nexowatt-generate-repo-entry.cjs +3 -0
- package/tools/nexowatt-internal-reserve-selftest.cjs +55 -0
- package/tools/nexowatt-login-layout-selftest.cjs +83 -0
- package/tools/nexowatt-merge-update-selftest.cjs +69 -0
- package/tools/nexowatt-modern-ui-selftest.cjs +49 -0
- package/tools/nexowatt-patch-repo.cjs +54 -0
- package/tools/nexowatt-publish-channel-guard.cjs +58 -0
- package/tools/nexowatt-release-defaults-selftest.cjs +29 -0
- package/tools/nexowatt-runtime-cleanup-selftest.cjs +47 -0
- package/tools/nexowatt-stable-v96-selftest.cjs +13 -0
- package/tools/nexowatt-sync-release-version.cjs +181 -0
- package/tools/nexowatt-ui-overview-runtime-selftest.cjs +16 -0
- package/tools/nexowatt-validate-package.cjs +277 -0
- package/tools/nexowatt-version-sync-selftest.cjs +86 -0
- package/INSTALL_TEST_V7.9.95.md +0 -15
- package/NEXOWATT_EOS_STABLE_7.9.94.md +0 -16
- package/PUBLISH_STABLE_V7.9.95.md +0 -16
- package/README_STABILITY_V7.9.95.md +0 -12
- package/RELEASE_ACCEPTANCE_V7.9.95.md +0 -11
- package/RELEASE_NOTES_V7.9.95.md +0 -12
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const root = path.resolve(__dirname, '..');
|
|
6
|
+
let failed = false;
|
|
7
|
+
const fail = message => { console.error(`[NexoWatt EMS Overview Selftest] ERROR: ${message}`); failed = true; };
|
|
8
|
+
const read = rel => { try { return fs.readFileSync(path.join(root, rel), 'utf8'); } catch (error) { fail(`${rel} nicht lesbar: ${error.message}`); return ''; } };
|
|
9
|
+
const intro = read('src-admin/src/tabs/Intro.tsx');
|
|
10
|
+
const component = read('src-admin/src/components/Intro/NexoWattEmsOverview.tsx');
|
|
11
|
+
const css = read('src-admin/src/index.css');
|
|
12
|
+
for (const marker of [
|
|
13
|
+
"import NexoWattEmsOverview from '@/components/Intro/NexoWattEmsOverview';",
|
|
14
|
+
'<NexoWattEmsOverview',
|
|
15
|
+
]) if (!intro.includes(marker)) fail(`Intro-Vertrag fehlt: ${marker}`);
|
|
16
|
+
for (const marker of [
|
|
17
|
+
'nexowatt-ems-overview-v1',
|
|
18
|
+
"getForeignStates('nexowatt-ui.*.info.adminOverview.*')",
|
|
19
|
+
'document.visibilityState',
|
|
20
|
+
'window.setInterval(() => void load(), 5_000)',
|
|
21
|
+
'Diagnose ist rein lesend',
|
|
22
|
+
'Vollständige EMS-Diagnose öffnen',
|
|
23
|
+
'system.adapter.nexowatt-ui.*.alive',
|
|
24
|
+
]) if (!component.includes(marker)) fail(`Komponenten-Vertrag fehlt: ${marker}`);
|
|
25
|
+
if ((component.match(/NexoWatt EMS · Live-Diagnose/g) || []).length !== 1) fail('EMS-Überschrift ist doppelt oder fehlt.');
|
|
26
|
+
if (!css.includes('/* nexowatt-ems-overview-v1 */')) fail('Responsive EMS-CSS fehlt.');
|
|
27
|
+
if (/setState\(|setForeignState\(|sendToHost\(/.test(component)) fail('EMS-Overview darf keine ioBroker-Schreiboperation enthalten.');
|
|
28
|
+
const assetDir = path.join(root, 'admin', 'assets');
|
|
29
|
+
if (fs.existsSync(assetDir)) {
|
|
30
|
+
const scripts = fs.readdirSync(assetDir).filter(name => name.endsWith('.js'));
|
|
31
|
+
const built = scripts.some(name => fs.readFileSync(path.join(assetDir, name), 'utf8').includes('nexowatt-ems-overview-v1'));
|
|
32
|
+
if (!built) fail('Gebautes Admin-Frontend enthält die EMS-Overview-Komponente nicht. Bitte npm run build ausführen.');
|
|
33
|
+
}
|
|
34
|
+
if (failed) process.exit(1);
|
|
35
|
+
console.log('[NexoWatt EMS Overview Selftest] OK: read-only Diagnosekarte, Intro-Einbindung und responsive Darstellung geprüft.');
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const CANONICAL_NATIVE_DEFAULTS = Object.freeze({
|
|
8
|
+
auth: true,
|
|
9
|
+
port: 8081,
|
|
10
|
+
eosLockLegacyAdmin: true,
|
|
11
|
+
eosHideLegacyAdminFromNonAdmins: true,
|
|
12
|
+
eosHideLegacyBackupFromNonAdmins: true,
|
|
13
|
+
nexowattHideLegacyBackupFromNonAdmins: true,
|
|
14
|
+
eosRequireFirstLoginPassword: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Enforce mandatory source defaults for every NexoWatt EOS sales release.
|
|
19
|
+
* This normalizes the repository io-package.json only. Existing installed
|
|
20
|
+
* instance configuration is not overwritten during adapter startup.
|
|
21
|
+
*/
|
|
22
|
+
function ensureReleaseDefaults(ioPackage) {
|
|
23
|
+
if (!ioPackage || typeof ioPackage !== 'object' || Array.isArray(ioPackage)) {
|
|
24
|
+
throw new TypeError('io-package.json must contain a JSON object');
|
|
25
|
+
}
|
|
26
|
+
const native = ioPackage.native && typeof ioPackage.native === 'object' && !Array.isArray(ioPackage.native)
|
|
27
|
+
? ioPackage.native
|
|
28
|
+
: (ioPackage.native = {});
|
|
29
|
+
const changes = [];
|
|
30
|
+
for (const [key, value] of Object.entries(CANONICAL_NATIVE_DEFAULTS)) {
|
|
31
|
+
if (native[key] !== value) {
|
|
32
|
+
changes.push({ key, previous: native[key], value });
|
|
33
|
+
native[key] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
ioPackage,
|
|
38
|
+
changed: changes.length > 0,
|
|
39
|
+
fields: changes.map(change => `native.${change.key}`),
|
|
40
|
+
changes,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ensureFile(filePath) {
|
|
45
|
+
const absolute = path.resolve(filePath);
|
|
46
|
+
const parsed = JSON.parse(fs.readFileSync(absolute, 'utf8'));
|
|
47
|
+
const result = ensureReleaseDefaults(parsed);
|
|
48
|
+
if (result.changed) {
|
|
49
|
+
fs.writeFileSync(absolute, `${JSON.stringify(result.ioPackage, null, 2)}\n`, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
return { file: absolute, ...result };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
const root = path.resolve(__dirname, '..');
|
|
56
|
+
const target = process.argv[2] || path.join(root, 'io-package.json');
|
|
57
|
+
try {
|
|
58
|
+
const result = ensureFile(target);
|
|
59
|
+
if (result.changed) {
|
|
60
|
+
console.log(
|
|
61
|
+
`[NexoWatt EOS release defaults] repaired ${result.changes.length} canonical value(s): ${result.changes.map(change => change.key).join(', ')}`,
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
console.log('[NexoWatt EOS release defaults] OK (canonical sales defaults already present)');
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`[NexoWatt EOS release defaults] ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { CANONICAL_NATIVE_DEFAULTS, ensureReleaseDefaults, ensureFile };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const pkg = require('../package.json');
|
|
4
|
+
const io = require('../io-package.json');
|
|
5
|
+
const version = io.common.version || pkg.version;
|
|
6
|
+
const base = (process.argv[2] || `https://unpkg.com/iobroker.eos-admin@${version}`).replace(/\/$/, '');
|
|
7
|
+
const entry = {
|
|
8
|
+
'eos-admin': {
|
|
9
|
+
name: 'eos-admin',
|
|
10
|
+
version,
|
|
11
|
+
title: 'NexoWatt EOS Admin',
|
|
12
|
+
titleLang: io.common.titleLang,
|
|
13
|
+
desc: io.common.desc,
|
|
14
|
+
news: io.common.news,
|
|
15
|
+
meta: `${base}/io-package.json`,
|
|
16
|
+
icon: `${base}/admin/admin.png`,
|
|
17
|
+
extIcon: `${base}/admin/admin.svg`,
|
|
18
|
+
type: io.common.type || 'general',
|
|
19
|
+
mode: io.common.mode || 'daemon',
|
|
20
|
+
platform: io.common.platform || 'Javascript/Node.js',
|
|
21
|
+
connectionType: io.common.connectionType || 'local',
|
|
22
|
+
dataSource: io.common.dataSource || 'push',
|
|
23
|
+
adminUI: io.common.adminUI || { config: 'json' },
|
|
24
|
+
license: io.common.license || 'NexoWatt Proprietary',
|
|
25
|
+
licenseInformation: io.common.licenseInformation,
|
|
26
|
+
dependencies: io.common.dependencies || [{ 'js-controller': '>=6.0.11' }],
|
|
27
|
+
stopBeforeUpdate: false,
|
|
28
|
+
dontDelete: false,
|
|
29
|
+
nondeletable: false,
|
|
30
|
+
readme: `${base}/README.md`,
|
|
31
|
+
packetName: 'iobroker.eos-admin',
|
|
32
|
+
npmPackage: 'iobroker.eos-admin',
|
|
33
|
+
allowAdapterUpdate: true,
|
|
34
|
+
allowAdapterDelete: false,
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const root = path.resolve(__dirname, '..');
|
|
6
|
+
const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
7
|
+
const json = rel => JSON.parse(read(rel));
|
|
8
|
+
let bad = false;
|
|
9
|
+
const fail = msg => { console.error(`[NexoWatt EOS internal reserve] ${msg}`); bad = true; };
|
|
10
|
+
|
|
11
|
+
const mainSource = read('src/main.ts');
|
|
12
|
+
const mainBuilt = read('build/main.js');
|
|
13
|
+
const webSource = read('src/lib/web.ts');
|
|
14
|
+
const webBuilt = read('build/lib/web.js');
|
|
15
|
+
const role = read('adminWww/js/eos-role-ui.js');
|
|
16
|
+
const boot = read('adminWww/js/eos-role-bootstrap.js');
|
|
17
|
+
const drawer = read('src-admin/src/components/Drawer.tsx');
|
|
18
|
+
const assist = read('adminWww/js/eos-assistant.js');
|
|
19
|
+
const io = json('io-package.json');
|
|
20
|
+
|
|
21
|
+
for (const code of [mainSource, mainBuilt]) {
|
|
22
|
+
for (const marker of [
|
|
23
|
+
"LEGACY_BACKUP_ADAPTER_NAME = 'backitup'", 'shouldHideLegacyBackupFromNonAdmins',
|
|
24
|
+
'getLegacyBackupAclObjectIds', 'ensureLegacyBackupVisibleOnlyToAdmins',
|
|
25
|
+
'EOS ACL guard restricted internal', '/^system\\.adapter\\.(?:admin|backitup)(?:\\.|$)/',
|
|
26
|
+
'/^(?:admin|backitup)\\.\\d+(?:\\.|$)/', 'internal Service reserve is Admin/Service-only',
|
|
27
|
+
]) if (!code.includes(marker)) fail(`backend reserve marker missing: ${marker}`);
|
|
28
|
+
}
|
|
29
|
+
for (const code of [webSource, webBuilt]) {
|
|
30
|
+
for (const marker of [
|
|
31
|
+
"LEGACY_BACKUP_ADAPTER_NAME = 'backitup'", "CUSTOMER_BACKUP_ADAPTER_NAMES = ['nexowatt-backup', 'eos-backup']",
|
|
32
|
+
'nexowattBackup: true', "internalBackupReserve: role === 'admin'", 'hideLegacyBackupFromNonAdmins',
|
|
33
|
+
'legacyBackupAdapter', 'customerBackupAdapters',
|
|
34
|
+
]) if (!code.includes(marker)) fail(`web reserve marker missing: ${marker}`);
|
|
35
|
+
}
|
|
36
|
+
for (const marker of [
|
|
37
|
+
'isOfficialAdminTab', 'isOfficialBackupTab', 'isOfficialReserveTab', 'isCustomerBackupTab',
|
|
38
|
+
'system.adapter.backitup', 'tab-backitup', 'nexowatt-backup', 'eos-backup',
|
|
39
|
+
'exact visible instance ID', 'data-eos-official-reserve-hidden', 'Reused virtual rows must be unhidden',
|
|
40
|
+
]) if (!role.includes(marker)) fail(`role UI reserve marker missing: ${marker}`);
|
|
41
|
+
for (const marker of ['isOfficialReserveTab', 'isCustomerBackupTab', 'backitup', 'nexowatt-backup']) {
|
|
42
|
+
if (!boot.includes(marker)) fail(`bootstrap reserve marker missing: ${marker}`);
|
|
43
|
+
}
|
|
44
|
+
for (const marker of ['System-Notfallsicherung', 'NexoWatt Sicherung', '^tab-backitup', 'nexowatt-backup|eos-backup']) {
|
|
45
|
+
if (!drawer.includes(marker)) fail(`Drawer reserve marker missing: ${marker}`);
|
|
46
|
+
}
|
|
47
|
+
if (!assist.includes('NEXOWATT_EOS_ASSIST_DISABLED')) fail('EOS Assist must remain disabled in the stable reserve test');
|
|
48
|
+
if (io.native?.eosHideLegacyBackupFromNonAdmins !== true) fail('eosHideLegacyBackupFromNonAdmins must default to true');
|
|
49
|
+
if (io.native?.nexowattHideLegacyBackupFromNonAdmins !== true) fail('nexowattHideLegacyBackupFromNonAdmins must default to true');
|
|
50
|
+
const reserve = (io.native?.eosProtectedAdapters || []).find(entry => entry.adapter === 'backitup');
|
|
51
|
+
if (!reserve?.enabled || !/Notfallreserve/i.test(String(reserve.note))) fail('backitup reserve is not clearly marked in the protected adapter defaults');
|
|
52
|
+
if (role.includes("/^tab-(?:admin|backitup|nexowatt-backup)/")) fail('customer backup must not be grouped with internal reserves');
|
|
53
|
+
|
|
54
|
+
if (bad) process.exit(1);
|
|
55
|
+
console.log('[NexoWatt EOS internal reserve] OK');
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
9
|
+
let bad = false;
|
|
10
|
+
const fail = message => {
|
|
11
|
+
console.error(`[NexoWatt EOS login layout] ${message}`);
|
|
12
|
+
bad = true;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const css = read('adminWww/css/nexowatt-native-shell.css');
|
|
16
|
+
const sourceCss = read('src-admin/public/css/nexowatt-native-shell.css');
|
|
17
|
+
const login = read('src-admin/src/login/Login.tsx');
|
|
18
|
+
const bootstrap = read('adminWww/js/eos-role-bootstrap.js');
|
|
19
|
+
const index = read('adminWww/index.html');
|
|
20
|
+
const buildInfo = JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));
|
|
21
|
+
const shellTag = String(buildInfo.shellCacheTag || buildInfo.shellCacheVersion || '');
|
|
22
|
+
|
|
23
|
+
if (css !== sourceCss) fail('login CSS source/build drift');
|
|
24
|
+
if (!css.includes('outer frame is decorative only')) fail('decorative compact-frame contract is missing');
|
|
25
|
+
if (css.includes('html.eos-login .MuiPaper-root:has(#username)')) {
|
|
26
|
+
fail('broad :has selector still styles the outer login Paper');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const outer = css.match(/html\.eos-login main\.MuiPaper-root\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
30
|
+
for (const marker of [
|
|
31
|
+
'position: relative',
|
|
32
|
+
'width: 100%',
|
|
33
|
+
'min-height: 100vh',
|
|
34
|
+
'place-items: center',
|
|
35
|
+
'background: transparent !important',
|
|
36
|
+
'box-shadow: none !important',
|
|
37
|
+
]) {
|
|
38
|
+
if (!outer.includes(marker)) fail(`transparent viewport host rule missing: ${marker}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const decorative = css.match(/html\.eos-login main\.MuiPaper-root::before\s*\{([\s\S]*?)\n\}/)?.[1] || '';
|
|
42
|
+
for (const marker of [
|
|
43
|
+
"content: ''",
|
|
44
|
+
'width: min(520px, calc(100vw - 24px)) !important',
|
|
45
|
+
'height: min(640px, calc(100dvh - 24px)) !important',
|
|
46
|
+
'transform: translate(-50%, -50%)',
|
|
47
|
+
'pointer-events: none',
|
|
48
|
+
]) {
|
|
49
|
+
if (!decorative.includes(marker)) fail(`decorative rear frame rule missing: ${marker}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const marker of [
|
|
53
|
+
'html.eos-login main.MuiPaper-root > .eos-login-card-modern',
|
|
54
|
+
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#username)',
|
|
55
|
+
'html.eos-login main.MuiPaper-root > .MuiPaper-root:has(#password)',
|
|
56
|
+
'width: min(460px, calc(100vw - 56px)) !important',
|
|
57
|
+
'min-height: 540px !important',
|
|
58
|
+
'height: auto !important',
|
|
59
|
+
'overflow: visible !important',
|
|
60
|
+
'max-height: none !important',
|
|
61
|
+
]) {
|
|
62
|
+
if (!css.includes(marker)) fail(`inner compact card rule missing: ${marker}`);
|
|
63
|
+
}
|
|
64
|
+
if (!css.includes('.eos-login-first-status:empty { display: none; min-height: 0; margin: 0; }')) {
|
|
65
|
+
fail('empty first-login status still reserves vertical space');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!login.includes('Keep the familiar compact login page')) fail('native Login source lacks spacious-page contract');
|
|
69
|
+
if (login.includes('eos-login-role-selector-native') || login.includes('data-eos-account=')) {
|
|
70
|
+
fail('native Login source still renders role-selector buttons');
|
|
71
|
+
}
|
|
72
|
+
if (!bootstrap.includes('compact normal-login first activation')) fail('runtime compact first-login contract missing');
|
|
73
|
+
if (!bootstrap.includes("card.querySelectorAll('.eos-login-role-selector').forEach(element => element.remove())")) {
|
|
74
|
+
fail('runtime does not remove stale role-selector surfaces');
|
|
75
|
+
}
|
|
76
|
+
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('runtime still injects a second role-selector panel');
|
|
77
|
+
|
|
78
|
+
if (!shellTag || !index.includes(`nexowatt-native-shell.css?v=${shellTag}`)) {
|
|
79
|
+
fail('login CSS cache key is not aligned with build information');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (bad) process.exit(1);
|
|
83
|
+
console.log('[NexoWatt EOS login layout] OK');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const fail = message => {
|
|
9
|
+
console.error(`[NexoWatt EOS merge update selftest] ${message}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
};
|
|
12
|
+
const read = file => fs.readFileSync(path.join(root, file), 'utf8');
|
|
13
|
+
const exists = file => fs.existsSync(path.join(root, file));
|
|
14
|
+
|
|
15
|
+
for (const file of [
|
|
16
|
+
'MERGE_UPDATE.ps1',
|
|
17
|
+
'MERGE_UPDATE.cmd',
|
|
18
|
+
'MERGE_UPDATE.sh',
|
|
19
|
+
'MERGE_UPDATE_README_V7.9.96.md',
|
|
20
|
+
'tools/nexowatt-apply-ems-overview.cjs',
|
|
21
|
+
'tools/nexowatt-sync-release-version.cjs',
|
|
22
|
+
'tools/nexowatt-version-sync-selftest.cjs',
|
|
23
|
+
]) {
|
|
24
|
+
if (!exists(file)) fail(`${file} is missing`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ps = read('MERGE_UPDATE.ps1');
|
|
28
|
+
for (const marker of [
|
|
29
|
+
'$PSScriptRoot',
|
|
30
|
+
'nexowatt-apply-ems-overview.cjs',
|
|
31
|
+
'nexowatt-sync-release-version.cjs',
|
|
32
|
+
'npm run check:eos-package',
|
|
33
|
+
'npm run check:eos-stability',
|
|
34
|
+
'npm pack --dry-run',
|
|
35
|
+
]) {
|
|
36
|
+
if (!ps.includes(marker)) fail(`MERGE_UPDATE.ps1 is missing ${marker}`);
|
|
37
|
+
}
|
|
38
|
+
if (/npm\s+publish/i.test(ps)) fail('MERGE_UPDATE.ps1 must never publish automatically');
|
|
39
|
+
|
|
40
|
+
const cmd = read('MERGE_UPDATE.cmd');
|
|
41
|
+
if (!cmd.includes('MERGE_UPDATE.ps1') || !/ExecutionPolicy\s+Bypass/i.test(cmd)) {
|
|
42
|
+
fail('MERGE_UPDATE.cmd does not start the guarded PowerShell verifier');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sh = read('MERGE_UPDATE.sh');
|
|
46
|
+
for (const marker of [
|
|
47
|
+
'set -euo pipefail',
|
|
48
|
+
'nexowatt-apply-ems-overview.cjs',
|
|
49
|
+
'npm run sync:eos-version',
|
|
50
|
+
'npm run check:eos-package',
|
|
51
|
+
'npm run check:eos-stability',
|
|
52
|
+
'npm pack --dry-run',
|
|
53
|
+
]) {
|
|
54
|
+
if (!sh.includes(marker)) fail(`MERGE_UPDATE.sh is missing ${marker}`);
|
|
55
|
+
}
|
|
56
|
+
if (/npm\s+publish/i.test(sh)) fail('MERGE_UPDATE.sh must never publish automatically');
|
|
57
|
+
|
|
58
|
+
const pkg = JSON.parse(read('package.json'));
|
|
59
|
+
if (pkg.scripts['sync:eos-version'] !== 'node tools/nexowatt-sync-release-version.cjs') {
|
|
60
|
+
fail('sync:eos-version script is not canonical');
|
|
61
|
+
}
|
|
62
|
+
if (!pkg.scripts['precheck:eos-package']?.startsWith('npm run sync:eos-version')) {
|
|
63
|
+
fail('package validation does not self-heal stale merged version files');
|
|
64
|
+
}
|
|
65
|
+
if (!pkg.scripts.prepublishOnly?.startsWith('npm run sync:eos-version')) {
|
|
66
|
+
fail('npm publishing does not self-heal stale merged version files');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('[NexoWatt EOS merge update selftest] OK (flat overwrite + EMS merge + version repair + guarded verification)');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const root = path.resolve(__dirname, '..');
|
|
6
|
+
const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
7
|
+
let bad = false;
|
|
8
|
+
const fail = msg => { console.error(`[NexoWatt EOS modern UI] ${msg}`); bad = true; };
|
|
9
|
+
const css = read('adminWww/css/nexowatt-native-shell.css');
|
|
10
|
+
const sourceCss = read('src-admin/public/css/nexowatt-native-shell.css');
|
|
11
|
+
const index = read('adminWww/index.html');
|
|
12
|
+
const accountUi = read('adminWww/js/eos-account-management.js');
|
|
13
|
+
const bootstrap = read('adminWww/js/eos-role-bootstrap.js');
|
|
14
|
+
const shell = read('adminWww/js/nexowatt-native-shell.js');
|
|
15
|
+
const role = read('adminWww/js/eos-role-ui.js');
|
|
16
|
+
const assist = read('adminWww/js/eos-assistant.js');
|
|
17
|
+
|
|
18
|
+
for (const marker of [
|
|
19
|
+
'--nx-shadow-soft', '--nx-shadow-float', 'backdrop-filter: blur', '#app-paper', '.MuiDialog-paper',
|
|
20
|
+
'.MuiButton-root', '.MuiTableRow-root', ':focus-visible', 'border-radius: 24px', 'linear-gradient',
|
|
21
|
+
'@media (max-width: 720px)', '.eos-activation-steps', '.eos-account-row:hover',
|
|
22
|
+
'main.MuiPaper-root::before', 'width: min(460px, calc(100vw - 56px)) !important', '.eos-assist-header-root', '.eos-overview-hero', '.eos-overview-grid',
|
|
23
|
+
'.eos-account-management-entry', 'pointer-events: auto !important', 'touch-action: manipulation', 'overflow: visible',
|
|
24
|
+
]) if (!css.includes(marker)) fail(`modern styling marker missing: ${marker}`);
|
|
25
|
+
if (sourceCss !== css) fail('modern CSS source/build drift');
|
|
26
|
+
for (const marker of ['eos-account-management.js','nexowatt-native-shell.css','eos-role-bootstrap.js','eos-assistant.js']) {
|
|
27
|
+
if (!index.includes(marker)) fail(`modern UI asset not active: ${marker}`);
|
|
28
|
+
}
|
|
29
|
+
for (const marker of ['NexoWatt EOS', 'Zugänge & Rollen', 'Endkunden-Zugänge', 'ensureEntrySurface']) {
|
|
30
|
+
if (!accountUi.includes(marker)) fail(`modern account surface missing label: ${marker}`);
|
|
31
|
+
}
|
|
32
|
+
for (const marker of ['installIntegratedFirstLogin','v96 compact normal-login first activation','eos-passwordless-card','Idempotent writes are essential here']) {
|
|
33
|
+
if (!bootstrap.includes(marker)) fail(`integrated first-activation surface missing: ${marker}`);
|
|
34
|
+
}
|
|
35
|
+
if (/data-eos-account=|selector\.innerHTML/.test(bootstrap)) fail('compact login still injects role buttons or a second panel');
|
|
36
|
+
if (bootstrap.includes("launcher.className = 'eos-passwordless-launcher'")) fail('old bottom first-login launcher is still rendered');
|
|
37
|
+
if (!shell.includes('ensureModernOverview') || !shell.includes("currentTab() !== 'tab-intro'")) fail('current overview is not modernized');
|
|
38
|
+
if (!role.includes('never cover the real Admin Intro') || role.includes('overview.innerHTML = `') || !role.includes('navigateToTab')) fail('native role overview contract is missing');
|
|
39
|
+
if (!role.includes('ensureReserveObserver') || !role.includes('state.reserveObserver.observe(paper')) fail('scoped delayed-row reserve filter is missing');
|
|
40
|
+
if (!assist.includes('NEXOWATT_EOS_ASSIST_DISABLED') || !assist.includes('element.remove()')) fail('EOS Assist stable-disable guard is missing');
|
|
41
|
+
|
|
42
|
+
const observerCount = [
|
|
43
|
+
'eos-policy-client.js', 'eos-branding-sanitizer.js', 'nexowatt-native-shell.js', 'eos-native-security.js',
|
|
44
|
+
'eos-basic-settings.js', 'eos-role-ui.js', 'eos-account-management.js', 'eos-assistant.js',
|
|
45
|
+
].reduce((count, file) => count + (read(`adminWww/js/${file}`).match(/new MutationObserver/g) || []).length, 0);
|
|
46
|
+
if (observerCount !== 2) fail(`expected one shared observer plus one scoped reserve-row observer, found ${observerCount}`);
|
|
47
|
+
|
|
48
|
+
if (bad) process.exit(1);
|
|
49
|
+
console.log('[NexoWatt EOS modern UI] OK');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const input = process.argv[2];
|
|
6
|
+
const output = process.argv[3] || input;
|
|
7
|
+
const version = process.argv[4] || require('../package.json').version;
|
|
8
|
+
const baseUrl = (process.argv[5] || `https://unpkg.com/iobroker.eos-admin@${version}`).replace(/\/$/, '');
|
|
9
|
+
if (!input) {
|
|
10
|
+
console.error('Usage: node tools/nexowatt-patch-repo.cjs <repo-nexowatt.json> [output.json] [version] [base-url]');
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
const repo = JSON.parse(fs.readFileSync(input, 'utf8'));
|
|
14
|
+
const ioPath = path.join(__dirname, '..', 'io-package.json');
|
|
15
|
+
const io = JSON.parse(fs.readFileSync(ioPath, 'utf8'));
|
|
16
|
+
repo['eos-admin'] = {
|
|
17
|
+
...(repo['eos-admin'] || {}),
|
|
18
|
+
name: 'eos-admin',
|
|
19
|
+
version,
|
|
20
|
+
title: 'NexoWatt EOS Admin',
|
|
21
|
+
titleLang: io.common.titleLang,
|
|
22
|
+
desc: {
|
|
23
|
+
de: 'Eigenständige NexoWatt EOS Administrationsoberfläche mit zuverlässigem In-App-Updatepfad für Energy Operation System Installationen.',
|
|
24
|
+
en: 'Standalone NexoWatt EOS administration interface with reliable in-app update flow for Energy Operation System installations.',
|
|
25
|
+
},
|
|
26
|
+
news: io.common.news,
|
|
27
|
+
meta: `${baseUrl}/io-package.json`,
|
|
28
|
+
icon: `${baseUrl}/admin/admin.png`,
|
|
29
|
+
extIcon: `${baseUrl}/admin/admin.svg`,
|
|
30
|
+
type: 'general',
|
|
31
|
+
mode: 'daemon',
|
|
32
|
+
platform: 'Javascript/Node.js',
|
|
33
|
+
connectionType: 'local',
|
|
34
|
+
dataSource: 'push',
|
|
35
|
+
adminUI: { config: 'json' },
|
|
36
|
+
license: 'NexoWatt Proprietary',
|
|
37
|
+
licenseInformation: io.common.licenseInformation,
|
|
38
|
+
dependencies: [{ 'js-controller': '>=6.0.11' }],
|
|
39
|
+
stopBeforeUpdate: false,
|
|
40
|
+
dontDelete: false,
|
|
41
|
+
nondeletable: false,
|
|
42
|
+
readme: `${baseUrl}/README.md`,
|
|
43
|
+
packetName: 'iobroker.eos-admin',
|
|
44
|
+
npmPackage: 'iobroker.eos-admin',
|
|
45
|
+
allowAdapterUpdate: true,
|
|
46
|
+
allowAdapterDelete: false,
|
|
47
|
+
};
|
|
48
|
+
fs.writeFileSync(output, JSON.stringify(repo, null, 2) + '\n');
|
|
49
|
+
console.log(`Patched ${input} -> ${output}`);
|
|
50
|
+
console.log(`repo["eos-admin"].version=${version}`);
|
|
51
|
+
console.log(`repo["eos-admin"].meta=${repo['eos-admin'].meta}`);
|
|
52
|
+
console.log('repo["eos-admin"].stopBeforeUpdate=false');
|
|
53
|
+
console.log('repo["eos-admin"].dontDelete=false');
|
|
54
|
+
console.log('repo["eos-admin"].nondeletable=false');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
function validatePublishChannel(pkg, env = process.env) {
|
|
8
|
+
const version = String(pkg?.version || '').trim();
|
|
9
|
+
const configuredTag = String(pkg?.publishConfig?.tag || '').trim();
|
|
10
|
+
const requestedTag = String(env.npm_config_tag || env.NPM_CONFIG_TAG || configuredTag || 'latest').trim();
|
|
11
|
+
const isPrerelease = version.includes('-');
|
|
12
|
+
const policy = pkg?.nexowattReleasePolicy || {};
|
|
13
|
+
|
|
14
|
+
if (!version) {
|
|
15
|
+
return { ok: false, message: 'package.json version is missing' };
|
|
16
|
+
}
|
|
17
|
+
if (configuredTag !== 'latest' || requestedTag !== 'latest') {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
message: `${version} is approved only for npm dist-tag latest, got configured=${configuredTag || '<unset>'}, requested=${requestedTag || '<unset>'}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isPrerelease) {
|
|
25
|
+
if (policy.distTag !== 'latest' || policy.acceptedPrerelease !== version) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
message: `Prerelease ${version} may move latest only when nexowattReleasePolicy explicitly accepts this exact version`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, version, tag: 'latest', prerelease: true, explicitlyAccepted: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (policy.acceptedPrerelease) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
message: `Stable version ${version} must remove stale acceptedPrerelease policy ${policy.acceptedPrerelease}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { ok: true, version, tag: 'latest', prerelease: false, explicitlyAccepted: false };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (require.main === module) {
|
|
44
|
+
const root = path.resolve(__dirname, '..');
|
|
45
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
46
|
+
const result = validatePublishChannel(pkg, process.env);
|
|
47
|
+
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
console.error(`[NexoWatt EOS publish guard] ${result.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(
|
|
54
|
+
`[NexoWatt EOS publish guard] OK (${result.version}, npm dist-tag latest${result.explicitlyAccepted ? '; exact prerelease acceptance recorded' : ''})`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { validatePublishChannel };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { CANONICAL_NATIVE_DEFAULTS, ensureReleaseDefaults } = require('./nexowatt-ensure-release-defaults.cjs');
|
|
6
|
+
|
|
7
|
+
const unsafe = {
|
|
8
|
+
common: { name: 'eos-admin' },
|
|
9
|
+
native: {
|
|
10
|
+
auth: false,
|
|
11
|
+
port: 8091,
|
|
12
|
+
eosLockLegacyAdmin: false,
|
|
13
|
+
eosHideLegacyAdminFromNonAdmins: false,
|
|
14
|
+
eosHideLegacyBackupFromNonAdmins: false,
|
|
15
|
+
nexowattHideLegacyBackupFromNonAdmins: false,
|
|
16
|
+
eosRequireFirstLoginPassword: false,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const repaired = ensureReleaseDefaults(unsafe);
|
|
20
|
+
assert.ok(repaired.changes.length >= 7, 'unsafe defaults must be repaired');
|
|
21
|
+
for (const [key, expected] of Object.entries(CANONICAL_NATIVE_DEFAULTS)) {
|
|
22
|
+
assert.equal(repaired.ioPackage.native[key], expected, `${key} must be normalized`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const canonical = { native: { ...CANONICAL_NATIVE_DEFAULTS } };
|
|
26
|
+
const unchanged = ensureReleaseDefaults(canonical);
|
|
27
|
+
assert.equal(unchanged.changes.length, 0, 'canonical defaults must remain unchanged');
|
|
28
|
+
|
|
29
|
+
console.log('[NexoWatt EOS release defaults selftest] OK');
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { cleanLegacyRuntime } = require('./nexowatt-clean-legacy-runtime.cjs');
|
|
9
|
+
|
|
10
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'nexowatt-runtime-cleanup-'));
|
|
11
|
+
try {
|
|
12
|
+
fs.mkdirSync(path.join(temp, 'adminWww', 'assets'), { recursive: true });
|
|
13
|
+
fs.mkdirSync(path.join(temp, 'adminWww', 'js'), { recursive: true });
|
|
14
|
+
fs.mkdirSync(path.join(temp, 'adminWww', 'css'), { recursive: true });
|
|
15
|
+
fs.mkdirSync(path.join(temp, 'src-admin', 'public', 'js'), { recursive: true });
|
|
16
|
+
fs.writeFileSync(path.join(temp, 'NEXOWATT_EOS_BUILD_INFO.json'), JSON.stringify({ runtimeEntry: 'v82' }));
|
|
17
|
+
|
|
18
|
+
const keep = [
|
|
19
|
+
'adminWww/assets/bootstrap-current-v82.js',
|
|
20
|
+
'adminWww/assets/Objects-current-v82.js',
|
|
21
|
+
'adminWww/remoteEntry-v82.js',
|
|
22
|
+
'adminWww/remoteEntry.js',
|
|
23
|
+
];
|
|
24
|
+
const remove = [
|
|
25
|
+
'adminWww/assets/bootstrap-old-v54.js',
|
|
26
|
+
'adminWww/assets/Objects-old-v79.js',
|
|
27
|
+
'adminWww/assets/index-DPWrite53.js',
|
|
28
|
+
'adminWww/remoteEntry-v61.js',
|
|
29
|
+
'adminWww/js/eos-branding.js',
|
|
30
|
+
'adminWww/css/eos-branding.css',
|
|
31
|
+
'src-admin/public/js/eos-security-ui.js',
|
|
32
|
+
];
|
|
33
|
+
for (const rel of [...keep, ...remove]) {
|
|
34
|
+
const file = path.join(temp, rel);
|
|
35
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
36
|
+
fs.writeFileSync(file, '// fixture');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = cleanLegacyRuntime({ root: temp, quiet: true, silent: true });
|
|
40
|
+
assert.strictEqual(result.activeRuntime, 82);
|
|
41
|
+
assert.strictEqual(result.removed.length, remove.length);
|
|
42
|
+
for (const rel of keep) assert.ok(fs.existsSync(path.join(temp, rel)), `current runtime removed: ${rel}`);
|
|
43
|
+
for (const rel of remove) assert.ok(!fs.existsSync(path.join(temp, rel)), `legacy file remained: ${rel}`);
|
|
44
|
+
console.log('[NexoWatt EOS runtime cleanup selftest] OK');
|
|
45
|
+
} finally {
|
|
46
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
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 must=(ok,msg)=>{if(!ok){console.error(`[NexoWatt EOS stable v96] ${msg}`);process.exit(1)}};
|
|
4
|
+
const pkg=JSON.parse(read('package.json'));const io=JSON.parse(read('io-package.json'));const info=JSON.parse(read('NEXOWATT_EOS_BUILD_INFO.json'));const css=read('adminWww/css/nexowatt-native-shell.css');const boot=read('adminWww/js/eos-role-bootstrap.js');const role=read('adminWww/js/eos-role-ui.js');const stable=read('adminWww/static/js/nexowatt-stable-v96.js');const index=read('adminWww/index.html');
|
|
5
|
+
must(pkg.version==='7.9.96'&&io.version==='7.9.96'&&io.common.version==='7.9.96'&&info.version==='7.9.96','version drift');
|
|
6
|
+
must(info.shellCacheVersion===96&&info.shellCacheTag==='96','shell cache is not v96');
|
|
7
|
+
must(css.includes('width: min(460px, calc(100vw - 56px)) !important')&&css.includes('overflow: visible !important'),'spacious login override missing');
|
|
8
|
+
must(!/showFirstLoginPassword[\s\S]{0,5000}form\.elements\.username\.value = claim\.userName/.test(boot),'undefined claim variable remains in first-password flow');
|
|
9
|
+
must(boot.includes('form.elements.username.value = userName;')&&boot.includes('controller.abort(), 8000'),'first-password repair incomplete');
|
|
10
|
+
must(role.includes('never cover the real Admin Intro')&&!role.includes('overview.innerHTML = `'),'old action-tile overview remains');
|
|
11
|
+
must(stable.includes("VERSION = 'v96-spacious-login-first-password-fix'")&&stable.includes('width:min(460px'),'stable v96 login runtime incomplete');
|
|
12
|
+
must(index.includes('nexowatt-stable-v96.js?v=96'),'stable v96 runtime not loaded');
|
|
13
|
+
console.log('[NexoWatt EOS stable v96] OK');
|