import Koa from 'koa' ; import { RawSourceMap , SourceMapConsumer , SourceMapGenerator } from 'source-map' ; import chalk from 'chalk' ; import fs from 'fs' ; import path from 'path' ; import http from 'http' ; import os from 'os' ; import net from 'net' ; import { ReboostInstance } from './index' ; export type DeepRequire < T > = T extends Record < string , any > ? { [ P in keyof T ] - ? : DeepRequire < T [ P ] > ; } : T ; export type DeepFrozen < T > = T extends Record < string , any > ? { readonly [ P in keyof T ] : DeepFrozen < T [ P ] > ; } : T extends any [ ] ? readonly T [ ] : T ; export type PromiseType < T extends Promise < any > > = Parameters < Parameters < T [ 'then' ] > [ 0 ] > [ 0 ] ; export const toPosix = ( pathString ) => pathString . replace ( /\\/g , '/' ) ; export const uniqueID = ( length = 32 ) => Array ( length ) . fill ( 0 ) . map ( ( ) => ( Math . random ( ) * 16 | 0 ) . toString ( 16 ) ) . join ( '' ) ; export const isPlainObject = ( data ) => ! ! data && data . constructor === Object ; export const merge = < T extends Record < string , any > > ( source , target ) => { for ( const key in target ) { if ( isPlainObject ( source [ key ] ) && isPlainObject ( target [ key ] ) ) { merge ( source [ key ] , target [ key ] ) ; } else { ( source as any ) [ key ] = target [ key ] ; } } return source ; } ; export const clone = < T = any > ( object ) : T => { const cloned = Array . isArray ( object ) ? [ ] : { } as any ; for ( const key in object ) { if ( isPlainObject ( object [ key ] ) || Array . isArray ( object [ key ] ) ) { cloned [ key ] = clone ( object [ key ] ) ; continue ; } cloned [ key ] = object [ key ] ; } return cloned ; } ; export const ensureDir = ( dirPath ) => { if ( ! fs . existsSync ( dirPath ) ) fs . mkdirSync ( dirPath , { recursive : true } ) ; } ; export const isDirectory = ( dirPath ) => fs . lstatSync ( dirPath ) . isDirectory ( ) ; export const rmDir = ( dirPath ) => { if ( ! fs . existsSync ( dirPath ) ) return ; fs . readdirSync ( dirPath ) . forEach ( ( file ) => { const filePath = path . join ( dirPath , file ) ; if ( isDirectory ( filePath ) ) return rmDir ( filePath ) ; fs . unlinkSync ( filePath ) ; } ) ; fs . rmdirSync ( dirPath ) ; } ; export const deepFreeze = ( obj ) => { for ( const key in obj ) { if ( isPlainObject ( obj [ key ] ) || Array . isArray ( obj [ key ] ) ) { deepFreeze ( obj [ key ] ) ; } } return Object . freeze ( obj ) ; } ; export const observable = < T extends Record < string , any > > ( object , onChange ) : T => { const handler = { set : ( target , key , value , receiver ) => { if ( typeof value === 'object' ) value = observable ( value , onChange ) ; const result = Reflect . set ( target , key , value , receiver ) ; onChange ( ) ; return result ; } } ; Object . keys ( object ) . forEach ( ( key ) => { if ( typeof object [ key ] === 'object' ) { ( object as any ) [ key ] = observable ( object [ key ] , onChange ) ; } } ) ; return new Proxy ( object , handler ) ; } ; export const objectPaths = ( object , pPath ? ) => { const paths = [ ] ; Object . keys ( object ) . forEach ( ( key ) => { const currentPath = ( pPath ? pPath + '.' : '' ) + key ; if ( typeof object [ key ] === 'object' ) { const nestedPaths = objectPaths ( object [ key ] , currentPath ) ; if ( nestedPaths . length ) return paths . push ( ... nestedPaths ) ; } paths . push ( currentPath ) ; } ) ; return paths ; } ; type ExcludePathObject = { [ key ] : ExcludePathObject ; } ; export const serializeObject = ( object , excludePaths ? , stringify = true , pPath ? ) => { if ( excludePaths && pPath !== '' ) { if ( ! Array . isArray ( excludePaths ) ) { excludePaths = objectPaths ( excludePaths ) ; } } const mapper = ( key ) => { const currentPath = ( pPath ? pPath + '.' : '' ) + key ; if ( excludePaths && ( excludePaths as any ) . includes ( currentPath ) ) return ; let value = object [ key ] ; if ( value && typeof value === 'object' ) { value = isPlainObject ( value ) || Array . isArray ( value ) ? serializeObject ( value , excludePaths , false , currentPath ) : value . toString ? value . toString ( ) : '' ; } else if ( typeof value === 'function' ) { value = ( value as ( ( ) => 0 ) ) . toString ( ) ; } return [ key , value ] ; } ; const serialized = ( Array . isArray ( object ) ? object . map ( ( _ , i ) => mapper ( i ) ) : Object . keys ( object ) . sort ( ) . map ( mapper ) ) . filter ( ( a ) => a ) ; return ( stringify ? JSON . stringify ( serialized ) : serialized ) as string ; } ; export const bind = < T extends Function > ( func , bindTo ) : OmitThisParameter < T > => func . bind ( bindTo ) ; export const diff = < T > ( oldA , newA ) => ( { added : newA . filter ( ( a ) => ! oldA . includes ( a ) ) , removed : oldA . filter ( ( a ) => ! newA . includes ( a ) ) } ) ; export const getTimestamp = ( ) => { const date = new Date ( ) ; const f = ( num ) => ( '' + num ) . length === 1 ? '0' + num : num ; return `[${ f ( date . getHours ( ) ) }:${ f ( date . getMinutes ( ) ) }:${ f ( date . getSeconds ( ) ) }]` ; } ; export const isVersionLessThan = ( version , toCompareWith ) => { const [ aMajor , aMinor , aPatch ] = version . split ( '.' ) . map ( Number ) ; const [ bMajor , bMinor , bPatch ] = toCompareWith . split ( '.' ) . map ( Number ) ; if ( aMajor < bMajor ) return true ; if ( aMajor > bMajor ) return false ; if ( aMinor < bMinor ) return true ; if ( aMinor > bMinor ) return false ; if ( aPatch < bPatch ) return true ; if ( aPatch > bPatch ) return false ; return false ; } ; export const onServerCreated = ( app , cb ) => { const defaultListenFunc = app . listen ; app . listen = ( ... args ) => { const server = defaultListenFunc . apply ( app , args ) ; cb ( server ) ; return server ; } ; } ; export const getExternalHost = async ( instance ) => { const { externalHost } = instance . config ; if ( typeof externalHost === 'string' ) { if ( ! net . isIPv4 ( externalHost ) ) { return instance . log ( 'info' , chalk . red ( `The provided external host address "${ externalHost }" is not a valid IPv4 address. ` + 'Please provide a different host address.' ) ) ; } const isServerUsable = await new Promise < boolean > ( ( resolve ) => { const server = net . createServer ( ) ; server . once ( 'listening' , ( ) => { server . close ( ) ; resolve ( true ) ; } ) ; server . once ( 'error' , ( err ) => { server . close ( ) ; if ( err . code === 'EADDRNOTAVAIL' || err . code === 'EINVAL' ) { resolve ( false ) ; } } ) ; server . listen ( 1678 , externalHost ) ; } ) ; if ( ! isServerUsable ) { return instance . log ( 'info' , chalk . red ( `The provided external host "${ externalHost }" is not available. ` + 'Please provide a different host address.' ) ) ; } return externalHost ; } if ( externalHost ) { const interfaces = os . networkInterfaces ( ) ; for ( const dev in interfaces ) { for ( const details of interfaces [ dev ] ) { if ( details . family === 'IPv4' && ! details . internal ) { return details . address ; } } } } } ; export const getReadableHRTime = ( [ seconds , nanoseconds ] ) => { if ( seconds ) { return `${ seconds }s ${ Math . floor ( nanoseconds / 1e6 ) }ms` ; } const ms = Math . floor ( nanoseconds / 1e6 ) ; return ( ms ? `${ ms }ms ` : '' ) + `${ Math . floor ( ( nanoseconds % 1e6 ) / 1e3 ) }μs` ; } ; const isUndefOrNull = ( d ) => d === null || d === undefined ; /**
 * Forked version of merge-source-map
 * Original author KATO Kei
 * Licensed under MIT License - https://github.com/keik/merge-source-map/blob/master/LICENSE
 */ export const mergeSourceMaps = async ( oldMap , newMap ) => { if ( ! oldMap ) return newMap ; if ( ! newMap ) return oldMap ; const oldMapConsumer = await new SourceMapConsumer ( oldMap ) ; const newMapConsumer = await new SourceMapConsumer ( newMap ) ; const mergedMapGenerator = new SourceMapGenerator ( ) ; newMapConsumer . eachMapping ( ( m ) => { if ( isUndefOrNull ( m . originalLine ) ) return ; const origPosInOldMap = oldMapConsumer . originalPositionFor ( { line : m . originalLine , column : m . originalColumn } ) ; if ( isUndefOrNull ( origPosInOldMap . source ) ) return ; mergedMapGenerator . addMapping ( { original : { line : origPosInOldMap . line , column : origPosInOldMap . column } , generated : { line : m . generatedLine , column : m . generatedColumn } , source : origPosInOldMap . source , name : origPosInOldMap . name } ) ; } ) ; const consumers = [ newMapConsumer , oldMapConsumer ] ; consumers . forEach ( ( consumer ) => { consumer . sources . forEach ( ( sourceFile ) => { const sourceContent = consumer . sourceContentFor ( sourceFile ) ; if ( ! isUndefOrNull ( sourceContent ) ) { mergedMapGenerator . setSourceContent ( sourceFile , sourceContent ) ; } } ) ; } ) ; return JSON . parse ( mergedMapGenerator . toString ( ) ) as RawSourceMap ; } ;