import '@lib/crash/reporter' ; import '@lib/logger/main' ; import '@lib/tracker/main' ; import Fs from 'fs' ; import Path from 'path' ; import { isEmpty , identity , pickBy } from 'lodash' ; import { app , BrowserWindow , dialog , ipcMain } from 'electron' ; import { applicationMenu , Menu as ContextMenu , ProjectMenu , RepositoryMenu , FrameworkMenu , SuiteMenu , TestMenu , FileMenu } from '@main/menu' ; import { ApplicationWindow } from '@main/application-window' ; import { Updater } from '@main/updater' ; import { LogLevel } from '@lib/logger/levels' ; import { mergeEnvFromShell } from '@lib/process/shell' ; import { state } from '@lib/state' ; import { log as writeLog } from '@lib/logger' ; import { ProjectIdentifier , ProjectActiveIdentifiers , ProjectEntities , ProjectOptions , IProject } from '@lib/frameworks/project' ; import { IRepository } from '@lib/frameworks/repository' ; import { Frameworks } from '@lib/frameworks' ; import { FrameworkOptions , FrameworkFilter } from '@lib/frameworks/framework' ; import { Nugget } from '@lib/frameworks/nugget' ; import { ISuite } from '@lib/frameworks/suite' ; import { ITest } from '@lib/frameworks/test' ; import { PotentialRepositoryOptions , RepositoryValidator , FrameworkValidator , PotentialFrameworkOptions } from '@lib/frameworks/validator' ; let currentWindow = null ; mergeEnvFromShell ( ) ; if ( ! __DEV__ ) { ( global as any ) . __static = Path . join ( __dirname , '/static' ) . replace ( /\\/g , '\\\\' ) ; } function getProject ( event ) { return ApplicationWindow . getProjectFromWebContents ( event . sender ) ! ; } function getRepository ( event , repositoryId ) { return new Promise ( async ( resolve , reject ) => { const project = getProject ( event ) ; const repository = project . getRepositoryById ( repositoryId ) ! ; if ( repository ) { resolve ( repository ! ) ; return ; } log . error ( `Error while getting repository ${ repositoryId }.` ) ; reject ( ) ; } ) ; } function entities ( event , frameworkId , identifiers = [ ] ) { return new Promise ( async ( resolve , reject ) => { try { const project = getProject ( event ) ; const context = project . getContextByFrameworkId ( frameworkId ) ; if ( context ) { const { repository , framework } = context ; const entities = { project , repository , framework } ; if ( ! identifiers . length ) { resolve ( entities ) ; return ; } let nugget ; const nuggets = [ ] ; do { nugget = nugget ? nugget . findTest ( identifiers . shift ( ) ! ) : framework . getSuiteById ( identifiers . shift ( ) ! ) ; if ( ! nugget ) { throw Error ; } nuggets . push ( nugget ) ; if ( ! nugget . expanded ) { await nugget . toggleExpanded ( true , false ) ; } } while ( identifiers . length > 0 ) ; resolve ( { ... entities , nuggets , nugget } ) ; return ; } } catch ( _ ) { } log . error ( `Unable to find requested entities '${ JSON . stringify ( { frameworkId , identifiers } ) }'` ) ; reject ( ) ; } ) ; } app . on ( 'ready' , ( ) => { track . screenview ( 'Application started' ) ; currentWindow = ApplicationWindow . init ( state . getCurrentProject ( ) ) ; applicationMenu . build ( currentWindow ) ; if ( ! __DEV__ ) { new Updater ( ) ; return ; } } ) ; ipcMain . on ( 'log' , ( event , level , message ) => { writeLog ( level , message ) ; } ) . on ( 'track-screenview' , ( event , screen ) => { track . screenview ( screen ) ; } ) . on ( 'track-event' , ( event , category , action , label , value ) => { track . event ( category , action , label , value ) ; } ) . on ( 'window-set' , ( event , args ) => { currentWindow = event as any ; } ) . on ( 'maximize' , ( event ) => { if ( currentWindow ) { if ( currentWindow . getChild ( ) . isMaximized ( ) ) { currentWindow . getChild ( ) . unmaximize ( ) ; return ; } currentWindow . getChild ( ) . maximize ( ) ; } } ) . on ( 'minimize' , ( event ) => { if ( currentWindow ) { currentWindow . getChild ( ) . minimize ( ) ; } } ) . on ( 'close' , ( event ) => { if ( currentWindow ) { currentWindow . getChild ( ) . close ( ) ; } } ) . on ( 'menu-refresh' , ( event ) => { applicationMenu . build ( currentWindow ) ; } ) . on ( 'menu-event' , ( event , args ) => { const { name , properties } = event as any ; if ( currentWindow ) { currentWindow . sendMenuEvent ( { name , properties } ) ; } } ) . on ( 'project-switch' , ( event , identifier ? ) => { const window = ApplicationWindow . getFromWebContents ( event . sender ) ! ; const project = window . getProject ( ) ; if ( identifier && ! isEmpty ( pickBy ( identifier , identity ) ) ) { window . setProject ( identifier ) ; state . set ( 'currentProject' , window . getProject ( ) ! . getId ( ) ) ; } else { window . clear ( ) ; } if ( project ) { project . stop ( ) ; } } ) . on ( 'project-repositories' , ( event , identifier ) => { getProject ( event ) . emitRepositoriesToRenderer ( ) ; } ) . on ( 'project-active-framework' , ( event , frameworkId ) => { const project = getProject ( event ) ; project . setActiveFramework ( frameworkId ) ; applicationMenu . setOptions ( project . getActive ( ) ) ; } ) . on ( 'repository-remove' , ( event , repositoryId ) => { const project = getProject ( event ) ; project . removeRepository ( repositoryId ) ; project . emitRepositoriesToRenderer ( ) ; ApplicationWindow . getFromWebContents ( event . sender ) ! . refreshActiveFramework ( ) ; } ) . on ( 'repository-toggle' , async ( event , repositoryId , toggle ) => { const repository = await getRepository ( event , repositoryId ) ; if ( toggle ) { repository . expand ( ) ; return ; } repository . collapse ( ) ; } ) . on ( 'framework-add' , async ( event , repositoryId , options ) => { const repository = await getRepository ( event , repositoryId ) ; repository . addFramework ( options ) . then ( framework => { framework . refresh ( ) ; } ) ; repository . emitFrameworksToRenderer ( ) ; ApplicationWindow . getFromWebContents ( event . sender ) ! . refreshActiveFramework ( ) ; } ) . on ( 'framework-remove' , async ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { repository , framework } ) => { repository . removeFramework ( framework . getId ( ) ) ; repository . emitFrameworksToRenderer ( ) ; ApplicationWindow . getFromWebContents ( event . sender ) ! . refreshActiveFramework ( ) ; } ) ; } ) . on ( 'framework-update' , ( event , frameworkId , options ) => { entities ( event , frameworkId ) . then ( async ( { repository , framework } ) => { await framework . updateOptions ( { ... options , repositoryPath : repository . getPath ( ) } ) ; repository . emitFrameworksToRenderer ( ) ; event . sender . send ( 'framework-options-updated' , framework . render ( ) ) ; framework . emitSuitesToRenderer ( ) ; } ) ; } ) . on ( 'framework-refresh' , ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . refresh ( ) ; } ) ; } ) . on ( 'framework-start' , ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . start ( ) ; } ) ; } ) . on ( 'framework-stop' , ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . stop ( ) ; } ) ; } ) . on ( 'framework-suites' , ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . emitSuitesToRenderer ( ) ; } ) ; } ) . on ( 'framework-filter' , ( event , frameworkId , key , value ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . setFilter ( key , value ) ; } ) ; } ) . on ( 'framework-reset-filters' , ( event , frameworkId ) => { entities ( event , frameworkId ) . then ( ( { framework } ) => { framework . resetFilters ( ) ; } ) ; } ) . on ( 'framework-toggle-child' , async ( event , frameworkId , identifiers , toggle ) => { entities ( event , frameworkId , identifiers ) . then ( ( { nugget } ) => { if ( toggle ) { nugget ! . emitTestsToRenderer ( ) ; return ; } nugget ! . toggleExpanded ( false , true ) ; } ) ; } ) . on ( 'framework-select' , async ( event , frameworkId , identifiers , toggle ) => { entities ( event , frameworkId , identifiers ) . then ( ( { nugget } ) => { nugget ! . toggleSelected ( toggle , true ) ; } ) ; } ) . on ( 'nugget-context-menu' , async ( event , frameworkId , identifiers ) => { entities ( event , frameworkId , identifiers ) . then ( ( { nuggets } ) => { if ( nuggets ) { if ( nuggets . length === 1 ) { new SuiteMenu ( ( nuggets . pop ( ) as ISuite ) , event . sender ) . open ( ) ; } else { new TestMenu ( ( nuggets . shift ( ) as ISuite ) , ( nuggets . pop ( ) as ITest ) , event . sender ) . open ( ) ; } } } ) ; } ) . on ( 'select-all' , ( event ) => { event . sender . selectAll ( ) ; } ) . on ( 'settings-update' , ( event , setting , value ) => { state . set ( setting , value ) ; event . sender . send ( 'settings-updated' , state . get ( ) ) ; } ) . on ( 'settings-reset' , async ( event ) => { await state . reset ( ) ; const window = ApplicationWindow . getFromWebContents ( event . sender ) ; if ( window ) { window . clear ( ) ; window . reload ( ) ; } } ) ; ipcMain . handle ( 'project-remove' , async ( event ) => { const project = getProject ( event ) ; await project . delete ( ) ; return state . removeProject ( project . getId ( ) ) ; } ) ; ipcMain . handle ( 'project-update' , async ( event , options ) => { const project = ApplicationWindow . getProjectFromWebContents ( event . sender ) ; if ( project ) { project . updateOptions ( options ) ; return project . render ( ) ; } return null ; } ) ; ipcMain . handle ( 'project-empty-repositories' , async ( event ) => { return getProject ( event ) . getEmptyRepositories ( ) . map ( repository => repository . render ( ) ) ; } ) ; ipcMain . handle ( 'project-add-repositories-menu' , async ( event ) => { return ( await dialog . showOpenDialog ( BrowserWindow . fromWebContents ( event . sender ) ! , { properties : [ 'openDirectory' , 'multiSelections' ] } ) ) . filePaths ; } ) ; ipcMain . handle ( 'project-context-menu' , async ( event ) : Promise < void > => { return new Promise ( resolve => { new ProjectMenu ( getProject ( event ) , event . sender ) . after ( ( ) => { resolve ( ) ; } ) . open ( ) ; } ) ; } ) ; ipcMain . handle ( 'repository-add' , async ( event , paths ) => { const project = getProject ( event ) ; const repositories = await Promise . all ( paths . map ( path => { return project . addRepository ( { path } ) ; } ) ) ; project . emitRepositoriesToRenderer ( ) ; return repositories . map ( repository => repository . render ( ) ) ; } ) ; ipcMain . handle ( 'repository-scan' , async ( event , repositoryId ) => { return await ( await getRepository ( event , repositoryId ) ) . scan ( ) ; } ) ; ipcMain . handle ( 'repository-validate' , async ( event , options ) => { const project = getProject ( event ) ; const validator = new RepositoryValidator ( project . repositories . map ( ( repository ) => repository . getPath ( ) ) ) ; return validator . validate ( options ) . getErrors ( ) ; } ) ; ipcMain . handle ( 'repository-exists' , async ( event , repositoryId ) => { return await ( await getRepository ( event , repositoryId ) ) . exists ( ) ; } ) ; ipcMain . handle ( 'repository-locate' , async ( event , repositoryId ) => { return ( await getRepository ( event , repositoryId ) ) . locate ( currentWindow ! . getChild ( ) ) ; } ) ; ipcMain . handle ( 'repository-frameworks' , async ( event , repositoryId ) => { return ( await getRepository ( event , repositoryId ) ) . frameworks . map ( framework => framework . render ( ) ) ; } ) ; ipcMain . handle ( 'repository-context-menu' , async ( event , repositoryId ) : Promise < void > => { const repository = await getRepository ( event , repositoryId ) ; return new Promise ( resolve => { new RepositoryMenu ( repository , event . sender ) . after ( ( ) => { resolve ( ) ; } ) . open ( ) ; } ) ; } ) ; ipcMain . handle ( 'framework-types' , async ( event ) => { return Frameworks . map ( framework => { return { ... framework . getDefaults ( ) , instructions : framework . instructions ( ) } ; } ) ; } ) ; ipcMain . handle ( 'framework-get' , async ( event , frameworkId ) => { const { framework } = await entities ( event , frameworkId ) ; return framework . render ( ) ; } ) ; ipcMain . handle ( 'framework-get-ledger' , async ( event , frameworkId ) => { const { framework } = await entities ( event , frameworkId ) ; return { ledger : framework . getLedger ( ) , status : framework . getStatusMap ( ) } ; } ) ; ipcMain . handle ( 'framework-validate' , async ( event , repositoryId , options ) => { const repository = await getRepository ( event , repositoryId ) ; const validator = new FrameworkValidator ( repository . getPath ( ) ) ; return validator . validate ( options ) . getErrors ( ) ; } ) ; ipcMain . handle ( 'framework-autoload-path-menu' , async ( event , defaultPath ) => { return ( await dialog . showOpenDialog ( BrowserWindow . fromWebContents ( event . sender ) ! , { properties : [ 'openFile' ] , defaultPath } ) ) . filePaths ; } ) ; ipcMain . handle ( 'framework-tests-path-menu' , async ( event , defaultPath ) => { return ( await dialog . showOpenDialog ( BrowserWindow . fromWebContents ( event . sender ) ! , { properties : [ 'createDirectory' , 'openDirectory' ] , defaultPath } ) ) . filePaths ; } ) ; ipcMain . handle ( 'framework-identity-menu' , async ( event ) => { return ( await dialog . showOpenDialog ( BrowserWindow . fromWebContents ( event . sender ) ! , { properties : [ 'openFile' , 'showHiddenFiles' ] , message : 'Choose a custom SSH key file to use with this connection.\nNote that ~/.ssh/id_rsa and identities defined in your SSH configuration are included by default.' } ) ) . filePaths ; } ) ; ipcMain . handle ( 'framework-context-menu' , async ( event , frameworkId , rect ? ) : Promise < void > => { const { repository , framework } = await entities ( event , frameworkId ) ; return new Promise ( resolve => { new FrameworkMenu ( repository , framework , event . sender ) . attachTo ( rect ) . after ( ( ) => { resolve ( ) ; } ) . open ( ) ; } ) ; } ) ; ipcMain . handle ( 'test-get' , async ( event , frameworkId , identifiers ) => { try { const { repository , framework , nuggets , nugget } = await entities ( event , frameworkId , identifiers ) ; return { repository : repository . render ( ) , framework : framework . render ( ) , nuggets : nuggets ! . map ( ( nugget ) => nugget . render ( false ) ) , nugget : nugget ! . render ( false ) , results : { ... ( nugget as ITest ) ! . getResult ( ) , 'suite-console' : ( nuggets ! [ 0 ] as ISuite ) . getConsole ( ) } } ; } catch ( _ ) { return { } ; } } ) ; ipcMain . handle ( 'file-context-menu' , async ( event , filePath ) : Promise < void > => { return new Promise ( resolve => { new FileMenu ( filePath , event . sender ) . after ( ( ) => { resolve ( ) ; } ) . open ( ) ; } ) ; } ) ; ipcMain . handle ( 'titlebar-menu' , ( event , item , rect ) : void => { const menu = applicationMenu . getSection ( item ) ; if ( ! menu ) { return ; } menu . attachTo ( rect , false ) . after ( ( ) => { event . sender . send ( 'titlebar-menu-closed' , item ) ; } ) . open ( ) ; } ) ; ipcMain . handle ( 'terms' , async ( event ) => { return Fs . readFileSync ( Path . join ( __static , '/LICENSE' ) , 'utf8' ) || '' ; } ) ; ipcMain . handle ( 'licenses' , async ( event ) => { return Fs . readFileSync ( Path . join ( __static , '/licenses.json' ) , 'utf8' ) ; } ) ; ipcMain . handle ( 'log-project' , async ( event ) => { const project = getProject ( event ) ; const projectState = state . project ( { id : project . getId ( ) } ) ; return { object : projectState . get ( ) , string : JSON . stringify ( projectState . get ( ) ) } ; } ) ; ipcMain . handle ( 'log-settings' , async ( event ) => { return { object : state . get ( ) , string : JSON . stringify ( state . get ( ) ) } ; } ) ;