mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-06 23:29:51 -06:00
198 lines
5.6 KiB
TypeScript
198 lines
5.6 KiB
TypeScript
import _ from 'lodash'
|
|
import os from 'os'
|
|
import { app, nativeImage as image } from 'electron'
|
|
|
|
import * as cyIcons from '@packages/icons'
|
|
import * as savedState from '../saved_state'
|
|
import menu from '../gui/menu'
|
|
import * as Windows from '../gui/windows'
|
|
import { makeGraphQLServer } from '@packages/graphql/src/makeGraphQLServer'
|
|
import { globalPubSub, getCtx, clearCtx } from '@packages/data-context'
|
|
|
|
// eslint-disable-next-line no-duplicate-imports
|
|
import type { WebContents } from 'electron'
|
|
import type { LaunchArgs, Preferences } from '@packages/types'
|
|
|
|
import debugLib from 'debug'
|
|
import { getPathToDesktopIndex } from '@packages/resolve-dist'
|
|
|
|
const debug = debugLib('cypress:server:interactive')
|
|
|
|
const isDev = () => {
|
|
return Boolean(process.env['CYPRESS_INTERNAL_ENV'] === 'development')
|
|
}
|
|
|
|
export = {
|
|
isMac () {
|
|
return os.platform() === 'darwin'
|
|
},
|
|
|
|
getWindowArgs (url: string, state: Preferences) {
|
|
// Electron Window's arguments
|
|
// These options are passed to Electron's BrowserWindow
|
|
const minWidth = Math.round(/* 13" MacBook Air */ 1792 / 3) // Thirds
|
|
|
|
const preferredWidth = 1200
|
|
const minHeight = 400
|
|
const preferredHeight = 800
|
|
|
|
const chooseDimensions = ({ preferred, previous, minimum }) => {
|
|
// If the user doesn't have a previous size that's valid or big
|
|
// enough, use the preferred size instead.
|
|
if (!previous || previous < minimum) {
|
|
return preferred
|
|
}
|
|
|
|
return previous
|
|
}
|
|
|
|
const common = {
|
|
url,
|
|
// The backgroundColor should match the value we will show in the
|
|
// launchpad frontend.
|
|
|
|
// When we use a dist'd launchpad (production), this color won't be
|
|
// as visible. However, in our local dev setup (launchpad running via
|
|
// a dev server), the backgroundColor will flash if it is a
|
|
// different color.
|
|
backgroundColor: 'white',
|
|
|
|
// Dimensions of the Electron window on initial launch.
|
|
// Because we are migrating users that may have
|
|
// a width smaller than the min dimensions, we will
|
|
// force the dimensions to be within the minimum bounds.
|
|
//
|
|
// Doing this before launch (instead of relying on minW + minH)
|
|
// prevents the window from jumping.
|
|
width: chooseDimensions({
|
|
preferred: preferredWidth,
|
|
minimum: minWidth,
|
|
previous: state.appWidth,
|
|
}),
|
|
|
|
height: chooseDimensions({
|
|
preferred: preferredHeight,
|
|
minimum: minHeight,
|
|
previous: state.appHeight,
|
|
}),
|
|
|
|
minWidth,
|
|
minHeight,
|
|
|
|
x: state.appX,
|
|
y: state.appY,
|
|
type: 'INDEX',
|
|
devTools: state.isAppDevToolsOpen,
|
|
trackState: {
|
|
width: 'appWidth',
|
|
height: 'appHeight',
|
|
x: 'appX',
|
|
y: 'appY',
|
|
devTools: 'isAppDevToolsOpen',
|
|
},
|
|
onBlur (this: {webContents: WebContents}) {
|
|
if (this.webContents.isDevToolsOpened()) {
|
|
return
|
|
}
|
|
|
|
return Windows.hideAllUnlessAnotherWindowIsFocused()
|
|
},
|
|
onFocus () {
|
|
// hide internal dev tools if in production and previously focused
|
|
// window was the electron browser
|
|
menu.set({ withInternalDevTools: isDev() })
|
|
|
|
return Windows.showAll()
|
|
},
|
|
onClose () {
|
|
app.quit()
|
|
},
|
|
}
|
|
|
|
return _.extend(common, this.platformArgs())
|
|
},
|
|
|
|
platformArgs () {
|
|
const args = {
|
|
darwin: {
|
|
show: true,
|
|
frame: true,
|
|
transparent: false,
|
|
},
|
|
|
|
linux: {
|
|
show: true,
|
|
frame: true,
|
|
transparent: false,
|
|
icon: image.createFromPath(cyIcons.getPathToIcon('icon_64x64.png')),
|
|
},
|
|
}
|
|
|
|
return args[os.platform()]
|
|
},
|
|
|
|
async ready (options: LaunchArgs, launchpadPort: number) {
|
|
const { projectRoot } = options
|
|
const ctx = getCtx()
|
|
|
|
menu.set({
|
|
withInternalDevTools: isDev(),
|
|
onLogOutClicked () {
|
|
return globalPubSub.emit('menu:item:clicked', 'log:out')
|
|
},
|
|
getGraphQLPort: () => {
|
|
return ctx?.gqlServerPort
|
|
},
|
|
})
|
|
|
|
const State = await savedState.create(projectRoot, false)
|
|
const state = await State.get()
|
|
const url = getPathToDesktopIndex(launchpadPort)
|
|
const win = await Windows.open(projectRoot, this.getWindowArgs(url, state))
|
|
|
|
ctx?.actions.electron.setBrowserWindow(win)
|
|
|
|
return win
|
|
},
|
|
|
|
async run (options: LaunchArgs, _loading: Promise<void>) {
|
|
const [, port] = await Promise.all([
|
|
app.whenReady(),
|
|
makeGraphQLServer(),
|
|
])
|
|
|
|
// Before the electron app quits, we interrupt and ensure the current
|
|
// DataContext is completely destroyed prior to quitting the process.
|
|
// Parts of the DataContext teardown are asynchronous, particularly the
|
|
// closing of open file watchers, and not awaiting these can cause
|
|
// the electron process to throw.
|
|
// https://github.com/cypress-io/cypress/issues/22026
|
|
|
|
app.once('will-quit', (event: Event) => {
|
|
// We must call synchronously call preventDefault on the will-quit event
|
|
// to halt the current quit lifecycle
|
|
event.preventDefault()
|
|
|
|
debug('clearing DataContext prior to quit')
|
|
|
|
// We use setImmediate to guarantee that app.quit will be called asynchronously;
|
|
// synchronously calling app.quit in the will-quit handler prevent the subsequent
|
|
// close from occurring
|
|
setImmediate(async () => {
|
|
try {
|
|
await clearCtx()
|
|
} catch (e) {
|
|
// Silently handle clearCtx errors, we still need to quit the app
|
|
debug(`DataContext cleared with error: ${e?.message}`)
|
|
}
|
|
|
|
debug('DataContext cleared, quitting app')
|
|
|
|
app.quit()
|
|
})
|
|
})
|
|
|
|
return this.ready(options, port)
|
|
},
|
|
}
|