Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/launcher/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
**/dist
**/cjs
**/esm
**/*.d.ts
**/package-lock.json
**/tsconfig.json
Expand Down
2 changes: 2 additions & 0 deletions packages/launcher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cjs/
esm/
11 changes: 0 additions & 11 deletions packages/launcher/index.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/launcher/lib/browsers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Debug from 'debug'
import type * as cp from 'child_process'
import { utils } from './utils'
import { spawnWithArch } from './utils'
import type { FoundBrowser } from '@packages/types'
import type { Readable } from 'stream'

Expand Down Expand Up @@ -36,7 +36,7 @@ export function launch (

debug('spawning browser with opts %o', { browser, url, spawnOpts })

const proc = utils.spawnWithArch(browser.path, args, spawnOpts)
const proc = spawnWithArch(browser.path, args, spawnOpts)

proc.stdout.on('data', (buf) => {
debug('%s stdout: %s', browser.name, String(buf).trim())
Expand Down
12 changes: 7 additions & 5 deletions packages/launcher/lib/darwin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const getVersionNumber = linuxHelper.getVersionNumber

export const getPathData = linuxHelper.getPathData

export function detect (browser: Browser): Promise<DetectedBrowser> {
export async function detect (browser: Browser): Promise<DetectedBrowser> {
let findAppParams = get(browsers, [browser.name, browser.channel])

if (!findAppParams) {
Expand All @@ -113,11 +113,13 @@ export function detect (browser: Browser): Promise<DetectedBrowser> {
return linuxHelper.detect(browser)
}

return findApp(findAppParams)
.then((val) => ({ name: browser.name, ...val }))
.catch((err) => {
try {
const val = await findApp(findAppParams)

return { name: browser.name, ...val }
} catch (err) {
debugVerbose('could not detect %s using findApp %o, falling back to linux detection method', browser.name, err)

return linuxHelper.detect(browser)
})
}
}
70 changes: 40 additions & 30 deletions packages/launcher/lib/darwin/util.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Debug from 'debug'
import { notInstalledErr } from '../errors'
import { utils } from '../utils'
import execa from 'execa'
import fs from 'fs-extra'
import path from 'path'
import plist from 'plist'
import type { PlistValue } from 'plist'

const debugVerbose = Debug('cypress-verbose:launcher:darwin:util')

/** parses Info.plist file from given application and returns a property */
export function parsePlist (p: string, property: string): Promise<string> {
export async function parsePlist (p: string, property: string): Promise<string> {
const pl = path.join(p, 'Contents', 'Info.plist')

debugVerbose('reading property file "%s"', pl)
Expand All @@ -21,16 +22,18 @@ export function parsePlist (p: string, property: string): Promise<string> {
throw notInstalledErr('', msg)
}

return fs
.readFile(pl, 'utf8')
.then(plist.parse)
.then((val) => val[property])
.then(String) // explicitly convert value to String type
.catch(failed) // to make TS compiler happy
try {
const file = await fs.readFile(pl, 'utf8')
const val = plist.parse(file)

return String(val[property as keyof PlistValue]) // explicitly convert value to String type
} catch (err) {
return failed(err as Error) // to make TS compiler happy
}
}

/** uses mdfind to find app using Ma app id like 'com.google.Chrome.canary' */
export function mdfind (id: string): Promise<string> {
export async function mdfind (id: string): Promise<string> {
const cmd = `mdfind 'kMDItemCFBundleIdentifier=="${id}"' | head -1`

debugVerbose('looking for bundle id %s using command: %s', id, cmd)
Expand All @@ -46,16 +49,15 @@ export function mdfind (id: string): Promise<string> {
throw notInstalledErr(id)
}

return utils.execa(cmd)
.then((val) => {
return val.stdout
})
.then((val) => {
logFound(val)
try {
const val = await execa(cmd)

return val
})
.catch(failedToFind)
logFound(val.stdout)

return val.stdout
} catch (err) {
return failedToFind()
}
}

export type AppInfo = {
Expand All @@ -79,22 +81,24 @@ function formApplicationPath (appName: string) {
}

/** finds an application and its version */
export function findApp ({ appName, executable, bundleId, versionProperty }: FindAppParams): Promise<AppInfo> {
export async function findApp ({ appName, executable, bundleId, versionProperty }: FindAppParams): Promise<AppInfo> {
debugVerbose('looking for app %s bundle id %s', executable, bundleId)

const findVersion = (foundPath: string) => {
return parsePlist(foundPath, versionProperty).then((version) => {
debugVerbose('got plist: %o', { foundPath, version })
const findVersion = async (foundPath: string) => {
const version = await parsePlist(foundPath, versionProperty)

debugVerbose('got plist: %o', { foundPath, version })

return {
path: path.join(foundPath, executable),
version,
}
})
return {
path: path.join(foundPath, executable),
version,
}
}

const tryMdFind = () => {
return mdfind(bundleId).then(findVersion)
const tryMdFind = async () => {
const foundPath = await mdfind(bundleId)

return findVersion(foundPath)
}

const tryFullApplicationFind = () => {
Expand All @@ -105,5 +109,11 @@ export function findApp ({ appName, executable, bundleId, versionProperty }: Fin
return findVersion(applicationPath)
}

return tryMdFind().catch(tryFullApplicationFind)
try {
const val = await tryMdFind()

return val
} catch (err) {
return tryFullApplicationFind()
}
}
88 changes: 57 additions & 31 deletions packages/launcher/lib/detect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Bluebird from 'bluebird'
import _, { compact, extend, find } from 'lodash'
import _, { compact, extend, find, uniqBy } from 'lodash'
import os from 'os'
import { removeDuplicateBrowsers } from '@packages/data-context/src/sources/BrowserDataSource'
import { knownBrowsers } from './known-browsers'
import * as darwinHelper from './darwin'
import { notDetectedAtPathErr } from './errors'
Expand All @@ -27,8 +25,16 @@ type HasVersion = Omit<Partial<FoundBrowser>, 'version' | 'name'> & {
name: string
}

function getBrowserKey<T extends {name: string, version: string | number}> (browser: T) {
return `${browser.name}-${browser.version}`
}

function removeDuplicateBrowsers (browsers: FoundBrowser[]) {
return uniqBy(browsers, getBrowserKey)
}

export const getMajorVersion = (version: string): string => {
return version.split('.')[0]
return version.split('.')[0] as string
}

// Determines if found browser is supported by Cypress. If found to be
Expand Down Expand Up @@ -94,17 +100,19 @@ function lookup (
* one for each binary. If Windows is detected, only one `checkOneBrowser` will be called, because
* we don't use the `binary` field on Windows.
*/
function checkBrowser (browser: Browser): Bluebird<(boolean | HasVersion)[]> {
async function checkBrowser (browser: Browser): Promise<(boolean | HasVersion)[]> {
if (Array.isArray(browser.binary) && os.platform() !== 'win32') {
return Bluebird.map(browser.binary, (binary: string) => {
return checkOneBrowser(extend({}, browser, { binary }))
})
const checkedBrowsers = await Promise.all(browser.binary.map((binary) => checkOneBrowser(extend({}, browser, { binary }))))

return checkedBrowsers
}

return Bluebird.map([browser], checkOneBrowser)
const checkedBrowsers = await checkOneBrowser(browser)

return [checkedBrowsers]
}

function checkOneBrowser (browser: Browser): Promise<boolean | HasVersion> {
async function checkOneBrowser (browser: Browser): Promise<boolean | HasVersion> {
const platform = os.platform()
const pickBrowserProps = [
'name',
Expand All @@ -131,21 +139,25 @@ function checkOneBrowser (browser: Browser): Promise<boolean | HasVersion> {
throw err
}

return lookup(platform, browser)
.then((val) => ({ ...browser, ...val }))
.then((val) => _.pick(val, pickBrowserProps) as FoundBrowser)
.then((foundBrowser) => {
try {
const detectedBrowser = await lookup(platform, browser)

const browserWithDetected = { ...browser, ...detectedBrowser }

const foundBrowser = _.pick(browserWithDetected, pickBrowserProps) as FoundBrowser

foundBrowser.majorVersion = getMajorVersion(foundBrowser.version)

validateCypressSupport(browser.validator, foundBrowser, platform)

return foundBrowser
})
.catch(failed)
} catch (error) {
return failed(error as NotInstalledError)
}
}

/** returns list of detected browsers */
export const detect = (goalBrowsers?: Browser[]): Bluebird<FoundBrowser[]> => {
export const detect = async (goalBrowsers?: Browser[]): Promise<FoundBrowser[]> => {
// we can detect same browser under different aliases
// tell them apart by the name and the version property
if (!goalBrowsers) {
Expand All @@ -158,13 +170,27 @@ export const detect = (goalBrowsers?: Browser[]): Bluebird<FoundBrowser[]> => {

debug('detecting if the following browsers are present %o', goalBrowsers)

return Bluebird.mapSeries(goalBrowsers, checkBrowser)
.then((val) => _.flatten(val))
.then(compactFalse)
.then(removeDuplicateBrowsers)
let foundBrowsers: FoundBrowser[] = []

{
const hasVersionOrFalse: (boolean | HasVersion)[][] = []

for (const browser of goalBrowsers) {
const browserOrFalse = await checkBrowser(browser)

hasVersionOrFalse.push(browserOrFalse)
}

const flattenedFoundBrowsers = _.flatten(hasVersionOrFalse)
const compactedFoundBrowsers = compactFalse(flattenedFoundBrowsers)

foundBrowsers = removeDuplicateBrowsers(compactedFoundBrowsers)
}

return foundBrowsers
}

export const detectByPath = (
export const detectByPath = async (
path: string,
goalBrowsers?: Browser[],
): Promise<FoundBrowser> => {
Expand All @@ -180,7 +206,7 @@ export const detectByPath = (
})
}

const detectBrowserFromKey = (browserKey): Browser | undefined => {
const detectBrowserFromKey = (browserKey: string): Browser | undefined => {
return find(goalBrowsers, (goalBrowser) => {
return (
goalBrowser.name === browserKey ||
Expand Down Expand Up @@ -210,8 +236,9 @@ export const detectByPath = (

const pathData = helper.getPathData(path)

return helper.getVersionString(pathData.path)
.then((version) => {
try {
const version = await helper.getVersionString(pathData.path)

let browser

if (pathData.browserKey) {
Expand All @@ -227,12 +254,11 @@ export const detectByPath = (
}

return setCustomBrowserData(browser, pathData.path, version)
})
.catch((err: NotDetectedAtPathError) => {
if (err.notDetectedAtPath) {
throw err
} catch (error: any) {
if (error.notDetectedAtPath) {
throw error as NotDetectedAtPathError
}

throw notDetectedAtPathErr(err.message)
})
throw notDetectedAtPathErr(error.message)
}
}
9 changes: 9 additions & 0 deletions packages/launcher/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { detect, detectByPath } from './detect'

import { launch } from './browsers'

export {
detect,
detectByPath,
launch,
}
Loading