Compare commits

...
11 Commits
10 changed files with 125 additions and 49 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
pnpm-lock.yaml
CHANGELOG.md
CHANGELOG.md
release-notes.md
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "2.2.0"
".": "2.2.1"
}
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## [2.2.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.2.0...2.2.1) (2025-04-04)
### Bug Fixes
* Fix issue with some settings when migrating from 1.x.x to 2.2.x ([#26947](https://github.com/Koenkk/zigbee2mqtt/issues/26947)) ([9e481fe](https://github.com/Koenkk/zigbee2mqtt/commit/9e481fe5e57a8e664dd58cff7765b5365630c7c1))
* Fix settings being overwriting when env var is set to a ref ([#26988](https://github.com/Koenkk/zigbee2mqtt/issues/26988)) ([d6a5128](https://github.com/Koenkk/zigbee2mqtt/commit/d6a5128aa819a85e2a06e57307329d2b35938397))
* **ignore:** update zigbee-herdsman-converters to 23.19.0 ([#26938](https://github.com/Koenkk/zigbee2mqtt/issues/26938)) ([0b7d003](https://github.com/Koenkk/zigbee2mqtt/commit/0b7d0039f709215ac1a4895f15f06db41b4d4998))
* **ignore:** update zigbee-herdsman-converters to 23.19.1 ([#26961](https://github.com/Koenkk/zigbee2mqtt/issues/26961)) ([e90afb2](https://github.com/Koenkk/zigbee2mqtt/commit/e90afb286a15c5416fd1126864acdfc4c03c3a64))
* **ignore:** update zigbee-herdsman-converters to 23.20.0 ([#26978](https://github.com/Koenkk/zigbee2mqtt/issues/26978)) ([37927e6](https://github.com/Koenkk/zigbee2mqtt/commit/37927e698abcb242965423e9ee37623255feadab))
* **ignore:** update zigbee-herdsman-converters to 23.20.1 ([#26989](https://github.com/Koenkk/zigbee2mqtt/issues/26989)) ([9d0ee03](https://github.com/Koenkk/zigbee2mqtt/commit/9d0ee03f38ee6881dce3b17c219a6580303607ef))
* **ignore:** update zigbee2mqtt-frontend to 0.9.6 ([#26959](https://github.com/Koenkk/zigbee2mqtt/issues/26959)) ([fb4d01e](https://github.com/Koenkk/zigbee2mqtt/commit/fb4d01ebced7046919d6657c7609a196d39a1a6c))
* **ignore:** update zigbee2mqtt-frontend to 0.9.7 ([#26976](https://github.com/Koenkk/zigbee2mqtt/issues/26976)) ([89cdb1f](https://github.com/Koenkk/zigbee2mqtt/commit/89cdb1f180bb343b56a50c94dc6a23dda1345582))
## [2.2.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.1.3...2.2.0) (2025-04-01)
+4 -10
View File
@@ -526,14 +526,14 @@ export async function onboard(): Promise<boolean> {
}
const confExists = existsSync(data.joinPath('configuration.yaml'));
let checkMigration = true;
if (!confExists) {
settings.writeMinimalDefaults();
// don't check for migration if conf was just written
checkMigration = false;
} else {
// migrate first
const {migrateIfNecessary} = await import('./settingsMigration.js');
migrateIfNecessary();
// trigger initial writing of `ZIGBEE2MQTT_CONFIG_*` ENVs
settings.write();
}
@@ -550,12 +550,6 @@ export async function onboard(): Promise<boolean> {
settings.reRead();
if (checkMigration) {
const {migrateIfNecessary} = await import('./settingsMigration.js');
migrateIfNecessary();
}
const errors = settings.validate();
if (errors.length > 0) {
+8 -7
View File
@@ -181,24 +181,22 @@ export function write(): void {
const settings = getPersistedSettings();
const toWrite: KeyValue = objectAssignDeep({}, settings);
applyEnvironmentVariables(toWrite);
// Read settings to check if we have to split devices/groups into separate file.
const actual = yaml.read(CONFIG_FILE_PATH);
// In case the setting is defined in a separate file (e.g. !secret network_key) update it there.
for (const path of [
for (const [ns, key] of [
['mqtt', 'server'],
['mqtt', 'user'],
['mqtt', 'password'],
['advanced', 'network_key'],
['frontend', 'auth_token'],
]) {
if (actual[path[0]] && actual[path[0]][path[1]]) {
const ref = parseValueRef(actual[path[0]][path[1]]);
if (actual[ns] && actual[ns][key]) {
const ref = parseValueRef(actual[ns][key]);
if (ref) {
yaml.updateIfChanged(data.joinPath(ref.filename), ref.key, toWrite[path[0]][path[1]]);
toWrite[path[0]][path[1]] = actual[path[0]][path[1]];
yaml.updateIfChanged(data.joinPath(ref.filename), ref.key, toWrite[ns][key]);
toWrite[ns][key] = actual[ns][key];
}
}
}
@@ -226,6 +224,9 @@ export function write(): void {
writeDevicesOrGroups('devices');
writeDevicesOrGroups('groups');
applyEnvironmentVariables(toWrite);
yaml.writeIfChanged(CONFIG_FILE_PATH, toWrite);
_settings = read();
+10 -7
View File
@@ -491,7 +491,7 @@ function migrateToFour(
* Should allow the most flexibility whenever combination of migrations is necessary (e.g. Transfer + Change)
*/
export function migrateIfNecessary(): void {
let currentSettings = settings.getPersistedSettings();
const currentSettings = settings.getPersistedSettings();
if (!SUPPORTED_VERSIONS.includes(currentSettings.version)) {
throw new Error(
@@ -502,7 +502,11 @@ export function migrateIfNecessary(): void {
/* v8 ignore next */
const finalVersion = process.env.VITEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
// when same version as current, nothing left to do
if (currentSettings.version === finalVersion) {
// when same version as current, nothing to do
return;
}
while (currentSettings.version !== finalVersion) {
let migrationNotesFileName: string | undefined;
// don't duplicate outputs
@@ -577,10 +581,9 @@ export function migrateIfNecessary(): void {
console.log(`Migration notes written in ${migrationNotesFilePath}`);
}
// don't throw to allow stepping through versions (validates against current schema)
settings.apply(currentSettings as unknown as Record<string, unknown>, false);
settings.reRead();
currentSettings = settings.getPersistedSettings();
}
// don't throw, onboarding will validate at end of process
settings.apply(currentSettings as unknown as Record<string, unknown>, false);
settings.reRead();
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "2.2.0",
"version": "2.2.1",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"packageManager": "pnpm@10.4.1",
@@ -63,8 +63,8 @@
"winston-transport": "^4.9.0",
"ws": "^8.18.1",
"zigbee-herdsman": "3.4.11",
"zigbee-herdsman-converters": "23.18.0",
"zigbee2mqtt-frontend": "0.9.5"
"zigbee-herdsman-converters": "23.20.1",
"zigbee2mqtt-frontend": "0.9.7"
},
"devDependencies": {
"@eslint/core": "^0.12.0",
+10 -10
View File
@@ -78,11 +78,11 @@ importers:
specifier: 3.4.11
version: 3.4.11
zigbee-herdsman-converters:
specifier: 23.18.0
version: 23.18.0
specifier: 23.20.1
version: 23.20.1
zigbee2mqtt-frontend:
specifier: 0.9.5
version: 0.9.5
specifier: 0.9.7
version: 0.9.7
devDependencies:
'@eslint/core':
specifier: ^0.12.0
@@ -1823,8 +1823,8 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zigbee-herdsman-converters@23.18.0:
resolution: {integrity: sha512-AL3jFKWWefk9PTOb2jX6IfyWCfvLbIIEqvvfLiC2gqXzPnjmo1Qd4kseEMM9z5Ia5IjoKjKZRV0Uf9FNAFBSFg==}
zigbee-herdsman-converters@23.20.1:
resolution: {integrity: sha512-cNlOnZcY3Hyqhbd28llqzaJzRMrtjk/CxbXBK2ADcJFztARpx1L2h5ySdvcrzLvUXGlWvJXNRy5M7larwDUvZg==}
zigbee-herdsman@3.4.11:
resolution: {integrity: sha512-OzJXvkmXXWgy0rBUaKAeSPvggGJnr+0XfWf1gUaiBrCvlbpSPYRzAqpiqM1dgfX3Cg6zJ5cofy5eEd9UW0sBlQ==}
@@ -1833,8 +1833,8 @@ packages:
resolution: {integrity: sha512-RP1LNZynW4C2y79W5qihw5/NNV8lVmmu4wAr7Kitj3isDFOAt7RXX2eiDrz98wiLmbFwBIOhfArrG/Pb/jaZIA==}
engines: {node: '>=20.17.0'}
zigbee2mqtt-frontend@0.9.5:
resolution: {integrity: sha512-pTHL9mvMSUbjXRiV1IL9hhXZeCd3nngmum/pX5gd/nn4kccTXhiMjQRSmXuAWNr+8n/gk6hTB1QeDYY+u7aGFQ==}
zigbee2mqtt-frontend@0.9.7:
resolution: {integrity: sha512-IqwoLEMp6lE9FUN0X2Ton73d9p2AMPeIj983Mh40YfAmr4KN/DLjjr35jJKZodYO3v9AGsPSvicE7HPvuOOWHw==}
engines: {node: '>=20.11'}
snapshots:
@@ -3491,7 +3491,7 @@ snapshots:
yocto-queue@0.1.0: {}
zigbee-herdsman-converters@23.18.0:
zigbee-herdsman-converters@23.20.1:
dependencies:
buffer-crc32: 1.0.0
iconv-lite: 0.6.3
@@ -3516,4 +3516,4 @@ snapshots:
zigbee-on-host@0.1.10: {}
zigbee2mqtt-frontend@0.9.5: {}
zigbee2mqtt-frontend@0.9.7: {}
+27
View File
@@ -166,6 +166,8 @@ describe('Onboarding', () => {
delete process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_PAN_ID;
delete process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_EXT_PAN_ID;
delete process.env.ZIGBEE2MQTT_CONFIG_FRONTEND_PORT;
delete process.env.ZIGBEE2MQTT_CONFIG_FRONTEND;
delete process.env.ZIGBEE2MQTT_CONFIG_HOMEASSISTANT_ENABLED;
data.writeDefaultConfiguration(SAMPLE_SETTINGS_INIT);
data.removeState();
@@ -697,6 +699,30 @@ describe('Onboarding', () => {
expect(settings.get().version).toStrictEqual(settings.CURRENT_VERSION);
});
it('runs 1.x.x conflict migrations', async () => {
data.writeDefaultConfiguration({
mqtt: {
server: 'mqtt://core-mosquitto:1883',
},
homeassistant: true,
advanced: {
network_key: 'GENERATE',
pan_id: 'GENERATE',
ext_pan_id: 'GENERATE',
},
});
settings.reRead();
process.env.ZIGBEE2MQTT_CONFIG_FRONTEND = '{"enabled":true,"port": 8099}';
process.env.ZIGBEE2MQTT_CONFIG_HOMEASSISTANT_ENABLED = 'true';
const p = onboard();
await expect(p).resolves.toStrictEqual(true);
expect(settings.get().version).toStrictEqual(settings.CURRENT_VERSION);
expect(settings.get().homeassistant).toMatchObject({enabled: true});
expect(settings.get().frontend).toMatchObject({enabled: true, port: 8099});
});
it('handles validation failure', async () => {
settings.set(['serial', 'adapter'], 'emberz');
@@ -719,6 +745,7 @@ describe('Onboarding', () => {
it('handles creating data path', async () => {
rmSync(data.mockDir, {force: true, recursive: true});
settings.testing.clear();
let p;
await new Promise<[string, string]>((resolve, reject) => {
+46 -10
View File
@@ -105,6 +105,8 @@ describe('Settings', () => {
});
it('Should apply environment variables as overrides', () => {
write(secretFile, {password: 'the-password'}, false);
process.env.ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD = '!secret.yaml password';
process.env.ZIGBEE2MQTT_CONFIG_SERIAL_DISABLE_LED = 'true';
process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_CHANNEL = '15';
process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_OUTPUT = 'attribute_and_json';
@@ -122,12 +124,6 @@ describe('Settings', () => {
},
};
write(configurationFile, {});
write(devicesFile, contentDevices);
expect(settings.write()); // trigger writing of ENVs
expect(settings.validate()).toStrictEqual([]);
const s = settings.get();
// @ts-expect-error workaround
const expected = objectAssignDeep.noMutate({}, settings.testing.defaults);
expected.devices = {
@@ -144,14 +140,54 @@ describe('Settings', () => {
expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'};
expected.mqtt.base_topic = 'testtopic';
expected.mqtt.server = 'testserver';
expected.mqtt.password = 'the-password';
expected.advanced.network_key = 'GENERATE';
expect(s).toStrictEqual(expected);
write(configurationFile, {mqtt: {password: 'config-password'}});
write(devicesFile, contentDevices);
settings.set(['advanced', 'channel'], 25);
const writeAndCheck = (): void => {
expect(settings.write()); // trigger writing of ENVs
expect(settings.validate()).toStrictEqual([]);
expect(settings.get()).toStrictEqual(expected);
expect(settings.get().advanced.channel).toStrictEqual(15);
expect(read(configurationFile)).toMatchObject({advanced: {channel: 15}});
settings.set(['advanced', 'channel'], 25);
expect(settings.get().advanced.channel).toStrictEqual(15);
expect(read(configurationFile)).toMatchObject({advanced: {channel: 15}});
expect(read(secretFile)).toMatchObject({password: 'the-password'});
expect(read(configurationFile)).toHaveProperty('mqtt.password', '!secret.yaml password');
};
// Write trice to ensure there are no side effects.
writeAndCheck();
writeAndCheck();
writeAndCheck();
});
it('Should write environment variables as overrides to configuration.yaml, not in the ref file', () => {
write(secretFile, {password: 'password-in-secret-file'}, false);
write(configurationFile, {mqtt: {password: '!secret password', server: 'server'}});
process.env.ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD = 'password-in-env-var';
const writeAndCheck = (): void => {
expect(settings.write()); // trigger writing of ENVs
expect(settings.validate()).toStrictEqual([]);
const s = settings.get();
// @ts-expect-error workaround
const expected = objectAssignDeep.noMutate({groups: {}, devices: {}}, settings.testing.defaults);
expected.mqtt.password = 'password-in-env-var';
expected.mqtt.server = 'server';
expect(s).toStrictEqual(expected);
expect(read(secretFile)).toMatchObject({password: 'password-in-secret-file'});
expect(read(configurationFile)).toMatchObject({mqtt: {password: 'password-in-env-var', server: 'server'}});
};
// Write trice to ensure there are no side effects.
writeAndCheck();
writeAndCheck();
writeAndCheck();
});
it('Should add devices', () => {