2022-02-14 10:19:20 +01:00
/ * *
* This file contains some utilities used from various build scripts in this directory .
* /
import fs from "fs-extra"
2023-04-20 17:14:30 +02:00
import path , { dirname } from "node:path"
import { fileURLToPath } from "node:url"
import stream from "node:stream"
import { spawn , spawnSync } from "node:child_process"
2022-12-27 15:37:40 +01:00
import { $ } from "zx"
2022-02-14 10:19:20 +01:00
const _ _dirname = dirname ( fileURLToPath ( import . meta . url ) )
/** global used by the measure() function to mark the start of measurement **/
var measureStartTime
/ * *
* Returns tutanota app version ( as in package . json ) .
2022-03-02 18:21:01 +01:00
* @ returns { string }
2022-02-14 10:19:20 +01:00
* /
2022-03-02 18:21:01 +01:00
export function getTutanotaAppVersion ( ) {
const packageJson = JSON . parse ( fs . readFileSync ( path . join ( _ _dirname , ".." , "package.json" ) , "utf8" ) )
return packageJson . version . trim ( )
2022-02-14 10:19:20 +01:00
}
/ * *
* Returns the version of electron used by the app ( as in package . json ) .
2022-05-02 17:17:33 +02:00
* @ returns Promise < { string } >
2022-02-14 10:19:20 +01:00
* /
2022-05-02 17:17:33 +02:00
export async function getElectronVersion ( log = console . log . bind ( console ) ) {
return await getInstalledModuleVersion ( "electron" , log )
2022-02-14 10:19:20 +01:00
}
2022-03-02 18:21:01 +01:00
/ * *
* Get the installed version of a module
* @ param module { string }
2022-05-02 17:17:33 +02:00
* @ returns Promise < { string } >
2022-03-02 18:21:01 +01:00
* /
2022-05-02 17:17:33 +02:00
export async function getInstalledModuleVersion ( module , log ) {
let json
const cachePath = "node_modules/.npm-deps-resolved"
if ( await fs . exists ( cachePath ) ) {
2022-05-10 16:36:19 +02:00
// Look for node_modules in current directory
2022-05-02 17:17:33 +02:00
const content = await fs . readFile ( cachePath , "utf8" )
json = JSON . parse ( content )
2022-05-11 11:43:28 +02:00
} else if ( await fs . exists ( path . join ( ".." , cachePath ) ) ) {
2022-05-10 16:36:19 +02:00
// Try to find node_modules in directory one level up (e.g. if we run tests). Should be probably more generic
2022-05-09 18:41:10 +02:00
const content = await fs . readFile ( path . join ( ".." , cachePath ) , "utf8" )
json = JSON . parse ( content )
2022-05-02 17:17:33 +02:00
} else {
console . log ( ` Using slow method to resolve dependency version. Add a postinstall script to dump 'npm list' into ${ cachePath } to speed things up. ` )
// npm list likes to error out for no reason so we just print a warning. If it really fails, we will see it.
// shell: true because otherwise Windows can't find npm.
2022-12-27 15:37:40 +01:00
const { stdout , stderr , status , error } = spawnSync ( "npm" , [ "list" , module , "--json" ] , { shell : true } )
2022-05-02 17:17:33 +02:00
if ( status !== 0 ) {
log ( ` npm list is not happy about ${ module } , but it doesn't mean anything ` , status , stderr , error )
}
json = JSON . parse ( stdout . toString ( ) . trim ( ) )
2022-03-02 13:49:49 +01:00
}
2022-05-02 17:17:33 +02:00
2022-05-11 11:43:28 +02:00
const version = findVersion ( json , module )
if ( version == null ) {
throw new Error ( ` Could not find version of ${ module } ` )
}
return version
2022-03-02 13:49:49 +01:00
}
// Unfortunately `npm list` is garbage and instead of just giving you the info about package it will give you some subtree with the thing you are looking for
// buried deep beneath. So we try to find it manually by descending into each dependency.
// This surfaces in admin client when keytar is not our direct dependency but rather through the tutanota-3
2022-12-27 15:37:40 +01:00
function findVersion ( { dependencies } , nodeModule ) {
2022-03-02 13:49:49 +01:00
if ( dependencies [ nodeModule ] ) {
return dependencies [ nodeModule ] . version
} else {
2022-05-11 11:43:28 +02:00
for ( const [ name , dep ] of Object . entries ( dependencies ) ) {
if ( "dependencies" in dep ) {
const found = findVersion ( dep , nodeModule )
if ( found ) {
return found
}
2022-03-02 13:49:49 +01:00
}
}
}
2022-02-14 10:19:20 +01:00
}
/ * *
* Returns the elapsed time between the last and current call of measure ( ) .
* @ returns { number }
* /
export function measure ( ) {
if ( ! measureStartTime ) {
measureStartTime = Date . now ( )
}
return ( Date . now ( ) - measureStartTime ) / 1000
}
/ * *
* Returns the ( absolute ) path to the default dist directory / prefix .
* @ returns { string }
* /
export function getDefaultDistDirectory ( ) {
2023-10-05 10:51:49 +02:00
return path . resolve ( "build" )
2022-02-14 10:19:20 +01:00
}
/** Throws if result has a value other than 0. **/
export function exitOnFail ( result ) {
if ( result . status !== 0 ) {
throw new Error ( "error invoking process" + JSON . stringify ( result ) )
}
}
2022-03-02 18:21:01 +01:00
/ * *
* Utility for writing to a logging function when a Writable is expected
* /
export class LogWriter extends stream . Writable {
/ * *
* @ param logger { ( string ) => void }
* /
constructor ( logger ) {
super ( {
autoDestroy : true ,
write ( chunk , encoding , callback ) {
logger ( chunk . toString ( ) . trim ( ) )
callback ( )
} ,
} )
}
}
/ * *
* Check if a file exists and is a normal file
* @ param filePath { string }
* @ returns { Promise < boolean > }
* /
export async function fileExists ( filePath ) {
2022-12-27 15:37:40 +01:00
return fs
. stat ( filePath )
. then ( ( stats ) => stats . isFile ( ) )
. catch ( ( ) => false )
2022-03-02 18:21:01 +01:00
}
/ * *
* There are various possibilities for how a given platform could be identified
* We need to make sure to be consistent at certain points , such as when caching files or processing CLI args
* @ param platformName { "mac" | "darwin" | "win" | "win32" | "linux" }
* @ returns { "darwin" | "win32" | "linux" }
* /
export function getCanonicalPlatformName ( platformName ) {
switch ( platformName ) {
case "mac" :
case "darwin" :
return "darwin"
case "win" :
case "win32" :
return "win32"
case "linux" :
return "linux"
default :
throw new Error ( ` Unknown platform name ${ platformName } ` )
}
2022-05-10 12:07:23 +02:00
}
2023-11-17 17:51:46 +01:00
/ * *
* Checks whether the combination of OS & architecture is supported by the build system
* @ param platformName { "darwin" | "win32" | "linux" }
2023-11-20 08:37:27 +01:00
* @ param architecture { "arm" | "arm64" | "ia32" | "mips" | "mipsel" | "ppc" | "ppc64" | "riscv64" | "s390" | "s390x" | "x64" | "universal" }
2023-11-17 17:51:46 +01:00
* @ returns { boolean }
* /
export function checkArchitectureIsSupported ( platformName , architecture ) {
switch ( architecture ) {
case "x64" :
return true
case "arm64" :
2023-11-20 08:37:27 +01:00
case "universal" :
2023-11-17 17:51:46 +01:00
return platformName === "darwin"
default :
return false
}
}
2022-05-10 12:07:23 +02:00
export async function runStep ( name , cmd ) {
const before = Date . now ( )
console . log ( "Build >" , name )
await cmd ( )
console . log ( "Build >" , name , "took" , Date . now ( ) - before , "ms" )
}
export function writeFile ( targetFile , content ) {
2022-12-27 15:37:40 +01:00
return fs . mkdir ( path . dirname ( targetFile ) , { recursive : true } ) . then ( ( ) => fs . writeFile ( targetFile , content , "utf-8" ) )
2022-05-27 16:17:01 +02:00
}
/ * *
* A little helper that runs the command . Unlike zx stdio is set to "inherit" and we don ' t pipe output .
* /
export async function sh ( pieces , ... args ) {
// If you need this function, but you can't use zx copy it from here
// https://github.com/google/zx/blob/a7417430013445592bcd1b512e1f3080a87fdade/src/guards.mjs
// (or more up-to-date version)
const fullCommand = formatCommand ( pieces , args )
console . log ( ` $ ${ fullCommand } ` )
2022-12-27 15:37:40 +01:00
const child = spawn ( fullCommand , { shell : true , stdio : "inherit" } )
2022-05-27 16:17:01 +02:00
return new Promise ( ( resolve , reject ) => {
child . on ( "close" , ( code ) => {
if ( code === 0 ) {
resolve ( )
} else {
reject ( new Error ( "Process failed with " + code ) )
}
} )
child . on ( "error" , ( error ) => {
reject ( ` Failed to spawn child: ${ error } ` )
} )
} )
}
function formatCommand ( pieces , args ) {
// Pieces are parts between arguments
// So if you have incvcation sh`command ${myArg} something ${myArg2}`
// then pieces will be ["command ", " something "]
// and the args will be [(valueOfMyArg1), (valueOfMyArg2)]
// There are always args.length + 1 pieces (if command ends with argument then the last piece is an empty string).
let fullCommand = pieces [ 0 ]
for ( let i = 0 ; i < args . length ; i ++ ) {
fullCommand += $ . quote ( args [ i ] ) + pieces [ i + 1 ]
}
return fullCommand
2022-12-27 15:37:40 +01:00
}