import { detectPackageManager , PackageManager } from "@alcalzone/pak" ; import { ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { getErrorMessage } from "@zwave-js/shared" ; import { isObject } from "alcalzone-shared/typeguards" ; import axios from "axios" ; import execa from "execa" ; import fs from "fs-extra" ; import os from "os" ; import * as path from "path" ; import * as lockfile from "proper-lockfile" ; import * as semver from "semver" ; import type { Readable } from "stream" ; /**
 * Checks whether there is a compatible update for the currently installed config package.
 * Returns the new version if there is an update, `undefined` otherwise.
 * Throws if the update check failed.
 */ export async function checkForConfigUpdates ( currentVersion ) { let registry ; try { registry = ( await axios . get < any > ( "https://registry.npmjs.org/@zwave-js/config" ) ) . data ; } catch ( e ) { throw new ZWaveError ( `Could not check for config updates: Failed to download package information!` , ZWaveErrorCodes . Config_Update_RegistryError ) ; } if ( ! isObject ( registry ) || ! isObject ( registry . versions ) ) { throw new ZWaveError ( `Could not check for config updates: Downloaded package information does not contain version information!` , ZWaveErrorCodes . Config_Update_RegistryError ) ; } const allVersions = Object . keys ( registry . versions ) . filter ( ( v ) => ! ! semver . valid ( v ) ) . filter ( ( v ) => /\-\d{8}$/ . test ( v ) ) ; const updateRange = `>${ currentVersion } <${ semver . inc ( currentVersion , "patch" ) }` ; const updateVersion = semver . maxSatisfying ( allVersions , updateRange , { includePrerelease : true } ) ; if ( updateVersion ) return updateVersion ; } /**
 * Installs the update for @zwave-js/config with the given version.
 */ export async function installConfigUpdate ( newVersion ) { let pak ; try { pak = await detectPackageManager ( { cwd : __dirname , requireLockfile : false , setCwdToPackageRoot : true } ) ; } catch { throw new ZWaveError ( `Config update failed: No package manager detected or package.json not found!` , ZWaveErrorCodes . Config_Update_PackageManagerNotFound ) ; } const packageJsonPath = path . join ( pak . cwd , "package.json" ) ; try { await lockfile . lock ( packageJsonPath , { onCompromised : ( ) => { } } ) ; } catch { throw new ZWaveError ( `Config update failed: Another installation is already in progress!` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } const result = await pak . overrideDependencies ( { "@zwave-js/config" : newVersion } ) ; try { if ( await lockfile . check ( packageJsonPath ) ) await lockfile . unlock ( packageJsonPath ) ; } catch { } if ( result . success ) return ; throw new ZWaveError ( `Config update failed: Package manager exited with code ${ result . exitCode }
${ result . stderr }` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } /**
 * Installs the update for @zwave-js/config with the given version.
 * Version for Docker images that does not mess up the container if there's no yarn cache
 */ export async function installConfigUpdateInDocker ( newVersion , external ? ) { let registryInfo ; try { registryInfo = ( await axios . get ( `https://registry.npmjs.org/@zwave-js/config/${ newVersion }` ) ) . data ; } catch { throw new ZWaveError ( `Config update failed: Could not fetch package info from npm registry!` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } const url = registryInfo ?. dist ?. tarball ; if ( typeof url !== "string" ) { throw new ZWaveError ( `Config update failed: Could not fetch package tarball URL from npm registry!` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } let lockfilePath ; let lockfileOptions ; if ( external ) { lockfilePath = external . cacheDir ; lockfileOptions = { lockfilePath : path . join ( external . cacheDir , "config-update.lock" ) } ; } else { let pak ; try { pak = await detectPackageManager ( { cwd : __dirname , requireLockfile : false , setCwdToPackageRoot : true } ) ; } catch { throw new ZWaveError ( `Config update failed: No package manager detected or package.json not found!` , ZWaveErrorCodes . Config_Update_PackageManagerNotFound ) ; } lockfilePath = path . join ( pak . cwd , "package.json" ) ; lockfileOptions = { } ; } try { await lockfile . lock ( lockfilePath , { ... lockfileOptions , onCompromised : ( ) => { } } ) ; } catch ( e ) { throw new ZWaveError ( `Config update failed: Another installation is already in progress!` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } const freeLock = async ( ) => { try { if ( await lockfile . check ( lockfilePath , lockfileOptions ) ) await lockfile . unlock ( lockfilePath , lockfileOptions ) ; } catch { } } ; const tmpDir = path . join ( os . tmpdir ( ) , "zjs-config-update" ) ; const tarFilename = path . join ( tmpDir , "zjs-config-update.tgz" ) ; const configModuleDir = path . dirname ( require . resolve ( "@zwave-js/config/package.json" ) ) ; const extractedDir = path . join ( tmpDir , "extracted" ) ; try { await fs . ensureDir ( tmpDir ) ; const fstream = fs . createWriteStream ( tarFilename , { autoClose : true } ) ; const response = await axios ( { method : "GET" , url , responseType : "stream" } ) ; const rstream = response . data as Readable ; rstream . pipe ( fstream ) ; await new Promise ( ( resolve , reject ) => { rstream . on ( "error" , reject ) ; rstream . on ( "end" , resolve ) ; } ) ; } catch ( e ) { await freeLock ( ) ; throw new ZWaveError ( `Config update failed: Could not download tarball. Reason: ${ getErrorMessage ( e ) }` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } function normalizeToUnixStyle ( path ) { path = path . replace ( /:/g , "" ) ; path = path . replace ( /\\/g , "/" ) ; if ( ! path . startsWith ( "/" ) ) path = `/${ path }` ; return path ; } try { await fs . emptyDir ( extractedDir ) ; await execa ( "tar" , [ "--strip-components=1" , "-xzf" , normalizeToUnixStyle ( tarFilename ) , "-C" , normalizeToUnixStyle ( extractedDir ) , ] ) ; if ( external ) { await fs . emptyDir ( external . configDir ) ; await fs . copy ( path . join ( extractedDir , "config" ) , external . configDir , { filter : async ( src ) => { if ( ! ( await fs . stat ( src ) ) . isFile ( ) ) return true ; return src . endsWith ( ".json" ) ; } } ) ; const externalVersionFilename = path . join ( external . configDir , "version" ) ; await fs . writeFile ( externalVersionFilename , newVersion , "utf8" ) ; } else { await fs . remove ( configModuleDir ) ; await fs . rename ( extractedDir , configModuleDir ) ; } } catch ( e ) { await freeLock ( ) ; throw new ZWaveError ( `Config update failed: Could not extract tarball` , ZWaveErrorCodes . Config_Update_InstallFailed ) ; } if ( ! external ) { try { const packageJsonPath = require . resolve ( "zwave-js/package.json" ) ; const json = await fs . readJSON ( packageJsonPath , { encoding : "utf8" } ) ; json . dependencies [ "@zwave-js/config" ] = newVersion ; await fs . writeJSON ( packageJsonPath , json , { encoding : "utf8" , spaces : 2 } ) ; } catch { } } void fs . remove ( tmpDir ) . catch ( ( ) => { } ) ; await freeLock ( ) ; }