import { ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { formatId , padVersion } from "@zwave-js/shared" ; import * as fs from "fs-extra" ; import path from "path" ; import * as semver from "semver" ; import type { DeviceConfigIndexEntry } from "./Devices" ; import type { ConfigLogger } from "./Logger" ; /** The absolute path of the embedded configuration directory */ export const configDir = path . resolve ( __dirname , "../config" ) ; /** The (optional) absolute path of an external configuration directory */ export function externalConfigDir ( ) { return process . env . ZWAVEJS_EXTERNAL_CONFIG ; } export const hexKeyRegexNDigits = /^0x[a-f0-9]+$/ ; export const hexKeyRegex4Digits = /^0x[a-f0-9]{4}$/ ; export const hexKeyRegex2Digits = /^0x[a-f0-9]{2}$/ ; export function throwInvalidConfig ( which , reason ? ) { throw new ZWaveError ( `The ${ which ? which + " " : "" }config file is malformed!` + ( reason ? `\n${ reason }` : "" ) , ZWaveErrorCodes . Config_Invalid ) ; } export function getDeviceEntryPredicate ( manufacturerId , productType , productId , firmwareVersion ? ) { return ( entry ) => { if ( entry . manufacturerId !== formatId ( manufacturerId ) ) return false ; if ( entry . productType !== formatId ( productType ) ) return false ; if ( entry . productId !== formatId ( productId ) ) return false ; if ( firmwareVersion != undefined ) { return ( semver . lte ( padVersion ( entry . firmwareVersion . min ) , padVersion ( firmwareVersion ) ) && semver . gte ( padVersion ( entry . firmwareVersion . max ) , padVersion ( firmwareVersion ) ) ) ; } return true ; } ; } export async function getEmbeddedConfigVersion ( ) { return ( await fs . readJSON ( path . join ( __dirname , "../package.json" ) ) ) . version ; } export type SyncExternalConfigDirResult = { success : false ; } | { success : true ; version : string ; } ; /**
 * Synchronizes or updates the external config directory and returns whether the directory is in a state that can be used
 */ export async function syncExternalConfigDir ( logger ) { const extConfigDir = externalConfigDir ( ) ; if ( ! extConfigDir ) return { success : false } ; try { await fs . ensureDir ( extConfigDir ) ; } catch { logger . print ( `Synchronizing external config dir failed - directory could not be created` , "error" ) ; return { success : false } ; } const externalVersionFilename = path . join ( extConfigDir , "version" ) ; const currentVersion = await getEmbeddedConfigVersion ( ) ; const supportedRange = `>=${ currentVersion } <${ semver . inc ( currentVersion , "patch" ) }` ; let wipe = false ; let externalVersion ; try { externalVersion = await fs . readFile ( externalVersionFilename , "utf8" ) ; if ( ! semver . valid ( externalVersion ) ) { wipe = true ; } else if ( ! semver . satisfies ( externalVersion , supportedRange , { includePrerelease : true } ) ) { wipe = true ; } } catch { wipe = true ; } if ( ! wipe ) return { success : true , version : externalVersion ! } ; try { logger . print ( `Synchronizing external config dir ${ extConfigDir }...` ) ; await fs . emptyDir ( extConfigDir ) ; await fs . copy ( configDir , extConfigDir , { filter : async ( src ) => { if ( ! ( await fs . stat ( src ) ) . isFile ( ) ) return true ; return src . endsWith ( ".json" ) ; } } ) ; await fs . writeFile ( externalVersionFilename , currentVersion , "utf8" ) ; externalVersion = currentVersion ; } catch { logger . print ( `Synchronizing external config dir failed - using embedded config` , "error" ) ; return { success : false } ; } return { success : true , version : externalVersion } ; }