From 49f654e933e2989bfd81e4339a9069975e849fe0 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 24 May 2017 17:16:25 -0400 Subject: [PATCH 1/3] server: extract listening for project end so we cleanup ffmpeg processes --- packages/server/lib/modes/headless.coffee | 103 +++++++++--------- packages/server/lib/video.coffee | 50 +++++---- .../test/integration/cypress_spec.coffee | 2 +- 3 files changed, 82 insertions(+), 73 deletions(-) diff --git a/packages/server/lib/modes/headless.coffee b/packages/server/lib/modes/headless.coffee index ae1a6638b6..8ee75526b1 100644 --- a/packages/server/lib/modes/headless.coffee +++ b/packages/server/lib/modes/headless.coffee @@ -244,6 +244,32 @@ module.exports = { openProject.launch(browser, spec, browserOpts) + listenForProjectEnd: (project) -> + new Promise (resolve) -> + onEarlyExit = (errMsg) -> + ## probably should say we ended + ## early too: (Ended Early: true) + ## in the stats + obj = { + error: errors.stripAnsi(errMsg) + failures: 1 + tests: 0 + passes: 0 + pending: 0 + duration: 0 + failingTests: [] + } + + resolve(obj) + + onEnd = (obj) => + resolve(obj) + + ## when our project fires its end event + ## resolve the promise + project.once("end", onEnd) + project.once("exitEarlyWithErr", onEarlyExit) + waitForBrowserToConnect: (options = {}) -> { project, id, timeout } = options @@ -295,65 +321,40 @@ module.exports = { waitForTestsToFinishRunning: (options = {}) -> { project, gui, screenshots, started, end, name, cname, videoCompression } = options - new Promise (resolve, reject) => - ## dont ever end if we're in 'gui' debugging mode - return if gui + ## dont ever end if we're in 'gui' debugging mode + return Promise.resolve() if gui - onFinish = (obj) => - finish = -> - project - .getConfig() - .then (cfg) -> - obj.config = cfg - .finally -> - resolve(obj) + @listenForProjectEnd(project) + .then (obj) => + finish = -> + project + .getConfig() + .then (cfg) -> + obj.config = cfg + .return(obj) - if end - obj.video = name + if end + obj.video = name - if screenshots - obj.screenshots = screenshots + if screenshots + obj.screenshots = screenshots - @displayStats(obj) + @displayStats(obj) - if screenshots and screenshots.length - @displayScreenshots(screenshots) + if screenshots and screenshots.length + @displayScreenshots(screenshots) - ft = obj.failingTests + ft = obj.failingTests - if ft and ft.length - obj.failingTests = Reporter.setVideoTimestamp(started, ft) + if ft and ft.length + obj.failingTests = Reporter.setVideoTimestamp(started, ft) - if end - @postProcessRecording(end, name, cname, videoCompression) - .then(finish) - ## TODO: add a catch here - else - finish() - - onEarlyExit = (errMsg) -> - ## probably should say we ended - ## early too: (Ended Early: true) - ## in the stats - obj = { - error: errors.stripAnsi(errMsg) - failures: 1 - tests: 0 - passes: 0 - pending: 0 - duration: 0 - failingTests: [] - } - - onFinish(obj) - - onEnd = (obj) => - onFinish(obj) - - ## when our project fires its end event - ## resolve the promise - project.once("end", onEnd) - project.once("exitEarlyWithErr", onEarlyExit) + if end + @postProcessRecording(end, name, cname, videoCompression) + .then(finish) + ## TODO: add a catch here + else + finish() trashAssets: (options = {}) -> if options.trashAssetsBeforeHeadlessRuns is true diff --git a/packages/server/lib/video.coffee b/packages/server/lib/video.coffee index c1e73fb03b..a7375f0aad 100644 --- a/packages/server/lib/video.coffee +++ b/packages/server/lib/video.coffee @@ -23,6 +23,8 @@ module.exports = { ended = Promise.pending() done = false errored = false + written = false + logErrors = true wantsWrite = true skipped = 0 @@ -31,15 +33,20 @@ module.exports = { }) end = -> - pt.end() - done = true - if errored - errored.recordingVideoFailed = true - Promise.reject(errored) - else - Promise.resolve(ended.promise) + if not written + ## when no data has been written this will + ## result in an 'pipe:0: End of file' error + ## for ffmpeg so we need to account for that + ## and not log errors to the console + logErrors = false + + pt.end() + + ## return the ended promise which will eventually + ## get resolve or rejected + return ended.promise write = (data) -> ## make sure we haven't ended @@ -48,6 +55,9 @@ module.exports = { ## finishing the actual video return if done + ## we have written at least 1 byte + written = true + if wantsWrite if not wantsWrite = pt.write(data) pt.once "drain", -> @@ -64,28 +74,26 @@ module.exports = { .inputOptions("-use_wallclock_as_timestamps 1") .videoCodec("libx264") .outputOptions("-preset ultrafast") - .save(name) .on "start", (line) -> - # console.log "spawned ffmpeg", line + console.log "spawned ffmpeg", line started.resolve(new Date) - # .on "codecData", (data) -> + # .on "codecData", (data) -> # console.log "codec data", data - # .on("error", options.onError) + # .on("error", options.onError) .on "error", (err, stdout, stderr) -> + ## if we're supposed log errors then + ## bubble them up + # if logErrors options.onError(err, stdout, stderr) - errored = err - # ended.reject(err) - # console.log "error occured here", arguments - ## TODO: call into lib/errors here - # console.log "ffmpeg failed", err - # ended.reject(err) + err.recordingVideoFailed = true + + ## reject the ended promise + ended.reject(err) + .on "end", -> ended.resolve() - - # setTimeout -> - # cmd.kill() - # , 1000 + .save(name) return { cmd: cmd diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index 5f8b837cd8..06a029822e 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -199,7 +199,7 @@ describe "lib/cypress", -> beforeEach -> @sandbox.stub(electron.app, "on").withArgs("ready").yieldsAsync() @sandbox.stub(headless, "waitForSocketConnection") - @sandbox.stub(headless, "waitForTestsToFinishRunning").resolves({failures: 0}) + @sandbox.stub(headless, "listenForProjectEnd").resolves({failures: 0}) @sandbox.stub(browsers, "open") @sandbox.stub(git, "_getRemoteOrigin").resolves("remoteOrigin") From 4e1901375c733f3f2258376dbc528dac3c4dd885 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 24 May 2017 18:49:15 -0400 Subject: [PATCH 2/3] server: fixes failing integration tests --- packages/server/lib/modes/headless.coffee | 6 +++--- packages/server/lib/video.coffee | 4 ++-- packages/server/test/integration/cypress_spec.coffee | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/server/lib/modes/headless.coffee b/packages/server/lib/modes/headless.coffee index 8ee75526b1..ff8a29b778 100644 --- a/packages/server/lib/modes/headless.coffee +++ b/packages/server/lib/modes/headless.coffee @@ -246,6 +246,9 @@ module.exports = { listenForProjectEnd: (project) -> new Promise (resolve) -> + ## dont ever end if we're in 'gui' debugging mode + return if gui + onEarlyExit = (errMsg) -> ## probably should say we ended ## early too: (Ended Early: true) @@ -321,9 +324,6 @@ module.exports = { waitForTestsToFinishRunning: (options = {}) -> { project, gui, screenshots, started, end, name, cname, videoCompression } = options - ## dont ever end if we're in 'gui' debugging mode - return Promise.resolve() if gui - @listenForProjectEnd(project) .then (obj) => finish = -> diff --git a/packages/server/lib/video.coffee b/packages/server/lib/video.coffee index a7375f0aad..827bf874bb 100644 --- a/packages/server/lib/video.coffee +++ b/packages/server/lib/video.coffee @@ -83,8 +83,8 @@ module.exports = { .on "error", (err, stdout, stderr) -> ## if we're supposed log errors then ## bubble them up - # if logErrors - options.onError(err, stdout, stderr) + if logErrors + options.onError(err, stdout, stderr) err.recordingVideoFailed = true diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index 06a029822e..0972021d3a 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -234,7 +234,7 @@ describe "lib/cypress", -> @expectExitWith(0) it "runs project headlessly and exits with exit code 10", -> - headless.waitForTestsToFinishRunning.resolves({failures: 10}) + headless.listenForProjectEnd.resolves({failures: 10}) Project.add(@todosPath) .then => @@ -609,7 +609,7 @@ describe "lib/cypress", -> describe "--port", -> beforeEach -> - headless.waitForTestsToFinishRunning.resolves({failures: 0}) + headless.listenForProjectEnd.resolves({failures: 0}) Project.add(@todosPath) @@ -641,7 +641,7 @@ describe "lib/cypress", -> process.env = _.omit(process.env, "CYPRESS_DEBUG") - headless.waitForTestsToFinishRunning.resolves({failures: 0}) + headless.listenForProjectEnd.resolves({failures: 0}) Project.add(@todosPath) From f0526ffc3fabe4b77a33c9c9cea75afd2594ef0a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 24 May 2017 22:26:58 -0400 Subject: [PATCH 3/3] Refactor browser detection (#91) * refactor browser detection to have single facade function for each platform * refactor found browser * update to tslint@5.3.2 * short detection code without duplicates * deleted obsolete individual browser files * launcher: detect and grab custom alias google-chrome-stable * keep same browser name, and set human name as displayName property * if mdfind fails, try using /Applications path to detect * remove duplicate code * detect and remove duplicate browser detections * refactor for readability * change chromium version property --- packages/launcher/lib/browsers.ts | 47 ++++++++++++++-- packages/launcher/lib/darwin/canary.ts | 22 -------- packages/launcher/lib/darwin/chrome.ts | 28 ---------- packages/launcher/lib/darwin/chromium.ts | 25 --------- packages/launcher/lib/darwin/index.ts | 45 ++++++++++++--- packages/launcher/lib/darwin/util.ts | 46 +++++++++++++++- packages/launcher/lib/detect.ts | 70 ++++++++---------------- packages/launcher/lib/linux/index.ts | 54 ++++++++++-------- packages/launcher/lib/types.ts | 19 ++++++- packages/launcher/package.json | 2 +- 10 files changed, 195 insertions(+), 163 deletions(-) delete mode 100644 packages/launcher/lib/darwin/canary.ts delete mode 100644 packages/launcher/lib/darwin/chrome.ts delete mode 100644 packages/launcher/lib/darwin/chromium.ts diff --git a/packages/launcher/lib/browsers.ts b/packages/launcher/lib/browsers.ts index 7f328648bb..8c3a550186 100644 --- a/packages/launcher/lib/browsers.ts +++ b/packages/launcher/lib/browsers.ts @@ -1,12 +1,7 @@ import {log} from './log' import {find, map} from 'lodash' import cp = require('child_process') -import {BrowserNotFoundError} from './types' - -type FoundBrowser = { - name: string, - path?: string -} +import {Browser, FoundBrowser, BrowserNotFoundError} from './types' const browserNotFoundErr = (browsers: FoundBrowser[], name: string): BrowserNotFoundError => { const available = map(browsers, 'name').join(', ') @@ -17,6 +12,46 @@ const browserNotFoundErr = (browsers: FoundBrowser[], name: string): BrowserNotF return err } +const googleChromeStable: Browser = { + name: 'Google Chrome Stable', + versionRegex: /Google Chrome (\S+)/, + profile: true, + binary: 'google-chrome-stable' +} + +const googleChromeAlias: Browser = { + name: 'Google Chrome', + versionRegex: /Google Chrome (\S+)/, + profile: true, + binary: 'chrome' +} + +/** list of all browsers we can detect and use */ +export const browsers: Browser[] = [ + { + name: 'chrome', + displayName: 'Chrome', + versionRegex: /Google Chrome (\S+)/, + profile: true, + binary: 'google-chrome' + },{ + name: 'chromium', + displayName: 'Chromium', + versionRegex: /Chromium (\S+)/, + profile: true, + binary: 'chromium-browser' + },{ + name: 'canary', + displayName: 'Canary', + versionRegex: /Google Chrome Canary (\S+)/, + profile: true, + binary: 'google-chrome-canary' + }, + // a couple of fallbacks + googleChromeStable, + googleChromeAlias +] + /** starts a browser by name and opens URL if given one */ export function launch (browsers: FoundBrowser[], name: string, url?: string, args: string[] = []) { diff --git a/packages/launcher/lib/darwin/canary.ts b/packages/launcher/lib/darwin/canary.ts deleted file mode 100644 index 79957f2ae6..0000000000 --- a/packages/launcher/lib/darwin/canary.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {parse, find} from './util' -import path = require('path') -import Promise = require('bluebird') - -const canary = { - version: (p: string) => - parse(p, 'KSVersion'), - - path: () => find('com.google.Chrome.canary'), - - get (executable: string) { - return this.path() - .then (p => { - return Promise.props({ - path: path.join(p, executable), - version: this.version(p) - }) - }) - } -} - -export default canary diff --git a/packages/launcher/lib/darwin/chrome.ts b/packages/launcher/lib/darwin/chrome.ts deleted file mode 100644 index 5a37a8ee05..0000000000 --- a/packages/launcher/lib/darwin/chrome.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {log} from '../log' - -import {parse, find} from './util' -import path = require('path') -import Promise = require('bluebird') - -const chrome = { - version (p: string) { - return parse(p, 'KSVersion') - }, - - path () { - return find('com.google.Chrome') - }, - - get (executable: string) { - log('Looking for Chrome %s', executable) - return this.path() - .then(p => { - return Promise.props({ - path: path.join(p, executable), - version: this.version(p) - }) - }) - } -} - -export default chrome diff --git a/packages/launcher/lib/darwin/chromium.ts b/packages/launcher/lib/darwin/chromium.ts deleted file mode 100644 index 8fde0501f4..0000000000 --- a/packages/launcher/lib/darwin/chromium.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {find, parse} from './util' -import path = require('path') -import Promise = require('bluebird') - -const chromium = { - version (p: string) { - return parse(p, 'CFBundleShortVersionString') - }, - - path () { - return find('org.chromium.Chromium') - }, - - get (executable: string) { - return this.path() - .then(p => - Promise.props({ - path: path.join(p, executable), - version: this.version(p) - }) - ) - } -} - -export default chromium diff --git a/packages/launcher/lib/darwin/index.ts b/packages/launcher/lib/darwin/index.ts index 52f3a7e322..ea183db996 100644 --- a/packages/launcher/lib/darwin/index.ts +++ b/packages/launcher/lib/darwin/index.ts @@ -1,11 +1,40 @@ -import canary from './canary' -import chrome from './chrome' -import chromium from './chromium' +import {findApp} from './util' +import {Browser} from '../types' +import {detectBrowserLinux} from '../linux' +import {log} from '../log' +import {merge, partial} from 'ramda' -const browsers = { - chrome, - canary, - chromium +const detectCanary = partial(findApp, + ['Contents/MacOS/Google Chrome Canary', 'com.google.Chrome.canary', 'KSVersion']) +const detectChrome = partial(findApp, + ['Contents/MacOS/Google Chrome', 'com.google.Chrome', 'KSVersion']) +const detectChromium = partial(findApp, + ['Contents/MacOS/Chromium', 'org.chromium.Chromium', 'CFBundleShortVersionString']) + +type Detectors = { + [index: string]: Function } -export default browsers +const browsers: Detectors = { + chrome: detectChrome, + canary: detectCanary, + chromium: detectChromium +} + +export function detectBrowserDarwin (browser: Browser) { + let fn = browsers[browser.name] + + if (!fn) { + // ok, maybe it is custom alias? + log('detecting custom browser %s on darwin', browser.name) + return detectBrowserLinux(browser) + } + + return fn() + .then(merge({name: browser.name})) + .catch(() => { + log('could not detect %s using traditional Mac methods', browser.name) + log('trying linux search') + return detectBrowserLinux(browser) + }) +} diff --git a/packages/launcher/lib/darwin/util.ts b/packages/launcher/lib/darwin/util.ts index 71af9ac376..c247f15cc1 100644 --- a/packages/launcher/lib/darwin/util.ts +++ b/packages/launcher/lib/darwin/util.ts @@ -7,7 +7,8 @@ import fs = require('fs-extra') import path = require('path') import plist = require('plist') -export function parse (p: string, property: string) { +/** parses Info.plist file from given application and returns a property */ +export function parse (p: string, property: string): Promise { const pl = path.join(p, 'Contents', 'Info.plist') log('reading property file "%s"', pl) @@ -26,7 +27,8 @@ export function parse (p: string, property: string) { .catch(failed) } -export function find (id: string): Promise { +/** uses mdfind to find app using Ma app id like 'com.google.Chrome.canary' */ +export function mdfind (id: string): Promise { const cmd = `mdfind 'kMDItemCFBundleIdentifier=="${id}"' | head -1` log('looking for bundle id %s using command: %s', id, cmd) @@ -47,3 +49,43 @@ export function find (id: string): Promise { .then(tap(logFound)) .catch(failedToFind) } + +export type AppInfo = { + path: string, + version: string +} + +function formApplicationPath (executable: string) { + const parts = executable.split('/') + const name = parts[parts.length - 1] + const appName = `${name}.app` + return path.join('/Applications', appName) +} + +/** finds an application and its version */ +export function findApp (executable: string, appId: string, versionProperty: string): Promise { + log('looking for app %s id %s', executable, appId) + + const findVersion = (foundPath: string) => + parse(foundPath, versionProperty) + .then((version) => { + return { + path: path.join(foundPath, executable), + version + } + }) + + const tryMdFind = () => { + return mdfind(appId) + .then(findVersion) + } + + const tryFullApplicationFind = () => { + const applicationPath = formApplicationPath(executable) + log('looking for application %s', applicationPath) + return findVersion(applicationPath) + } + + return tryMdFind() + .catch(tryFullApplicationFind) +} diff --git a/packages/launcher/lib/detect.ts b/packages/launcher/lib/detect.ts index 5770b9be8c..851e0fa97a 100644 --- a/packages/launcher/lib/detect.ts +++ b/packages/launcher/lib/detect.ts @@ -1,35 +1,13 @@ -import {linuxBrowser} from './linux' -import darwin from './darwin' +import {detectBrowserLinux} from './linux' +import {detectBrowserDarwin} from './darwin' import {log} from './log' import {Browser, NotInstalledError} from './types' +import {browsers} from './browsers' import * as Bluebird from 'bluebird' -import {merge, pick, tap} from 'ramda' +import {merge, pick, tap, uniqBy, prop} from 'ramda' import _ = require('lodash') import os = require('os') -// import Promise = require('bluebird') - -const browsers: Browser[] = [ - { - name: 'chrome', - re: /Google Chrome (\S+)/, - profile: true, - binary: 'google-chrome', - executable: 'Contents/MacOS/Google Chrome' - },{ - name: 'chromium', - re: /Chromium (\S+)/, - profile: true, - binary: 'chromium-browser', - executable: 'Contents/MacOS/Chromium' - },{ - name: 'canary', - re: /Google Chrome Canary (\S+)/, - profile: true, - binary: 'google-chrome-canary', - executable: 'Contents/MacOS/Google Chrome Canary' - } -] const setMajorVersion = (obj: Browser) => { if (obj.version) { @@ -40,33 +18,29 @@ const setMajorVersion = (obj: Browser) => { return obj } -type MacBrowserName = 'chrome' | 'chromium' | 'canary' +type BrowserDetector = (browser: Browser) => Promise +type Detectors = { + [index: string]: BrowserDetector +} +const detectors: Detectors = { + darwin: detectBrowserDarwin, + linux: detectBrowserLinux +} -function lookup (platform: string, obj: Browser): Promise { +function lookup (platform: NodeJS.Platform, obj: Browser): Promise { log('looking up %s on %s platform', obj.name, platform) - switch (platform) { - case 'darwin': - const browserName: MacBrowserName = obj.name as MacBrowserName - const fn = darwin[browserName] - if (fn) { - return fn.get(obj.executable) as any as Promise - } - const err: NotInstalledError = - new Error(`Browser not installed: ${obj.name}`) as NotInstalledError - err.notInstalled = true - throw err - case 'linux': - return linuxBrowser.get(obj.binary, obj.re) as any as Promise - default: - throw new Error(`Cannot lookup browser ${obj.name} on ${platform}`) + const detector = detectors[platform] + if (!detector) { + throw new Error(`Cannot lookup browser ${obj.name} on ${platform}`) } + return detector(obj) } function checkOneBrowser (browser: Browser) { const platform = os.platform() - const pickBrowserProps = pick(['name', 'type', 'version', 'path']) + const pickBrowserProps = pick(['name', 'displayName', 'type', 'version', 'path']) - const logBrowser = (props: object) => { + const logBrowser = (props: any) => { log('setting major version for %j', props) } @@ -88,8 +62,12 @@ function checkOneBrowser (browser: Browser) { /** returns list of detected browsers */ function detectBrowsers (): Bluebird { + // we can detect same browser under different aliases + // tell them apart by the full version property + const removeDuplicates = uniqBy(prop('version')) return Bluebird.mapSeries(browsers, checkOneBrowser) - .then(_.compact) as Bluebird + .then(_.compact) + .then(removeDuplicates) as Bluebird } export default detectBrowsers diff --git a/packages/launcher/lib/linux/index.ts b/packages/launcher/lib/linux/index.ts index a435b920ea..793952cbc0 100644 --- a/packages/launcher/lib/linux/index.ts +++ b/packages/launcher/lib/linux/index.ts @@ -1,30 +1,40 @@ -import cp = require('child_process') -import Promise = require('bluebird') -import {NotInstalledError} from '../types' - -const execAsync = Promise.promisify(cp.exec) +import {log} from '../log' +import {prop, trim} from 'ramda' +import {FoundBrowser, Browser, NotInstalledError} from '../types' +import execa = require('execa') const notInstalledErr = (name: string) => { - const err: NotInstalledError = new Error(`Browser not installed: ${name}`) as NotInstalledError + const err: NotInstalledError = + new Error(`Browser not installed: ${name}`) as NotInstalledError err.notInstalled = true throw err } -export const linuxBrowser = { - get: (binary: string, re: RegExp): Promise => { - return execAsync(`${binary} --version`) - .call('trim') - .then (stdout => { - const m = re.exec(stdout) - if (m) { - return { - path: binary, - version: m[1] - } - } else { - return notInstalledErr(binary) - } - }) - .catch(() => notInstalledErr(binary)) +function getLinuxBrowser (name: string, binary: string, versionRegex: RegExp): Promise { + const getVersion = (stdout: string) => { + const m = versionRegex.exec(stdout) + if (m) { + return m[1] + } + return notInstalledErr(binary) } + + const cmd = `${binary} --version` + log('looking using command "%s"', cmd) + return execa.shell(cmd) + .then(prop('stdout')) + .then(trim) + .then(getVersion) + .then((version) => { + return { + name, + version, + path: binary + } + }) + .catch(() => notInstalledErr(binary)) +} + +export function detectBrowserLinux (browser: Browser) { + return getLinuxBrowser(browser.name, browser.binary, browser.versionRegex) } diff --git a/packages/launcher/lib/types.ts b/packages/launcher/lib/types.ts index 09968fc510..47e28e2bf6 100644 --- a/packages/launcher/lib/types.ts +++ b/packages/launcher/lib/types.ts @@ -1,14 +1,27 @@ +/** TODO this are typical browser names, not just Mac */ +export type MacBrowserName = 'chrome' | 'chromium' | 'canary' | string + +export type PlatformName = 'darwin' | 'linux' + export type Browser = { - name: string, - re: RegExp, + /** short browser name */ + name: MacBrowserName, + /** Optional display name */ + displayName?: string, + /** RegExp to use to extract version from something like "Google Chrome 58.0.3029.110" */ + versionRegex: RegExp, profile: boolean, binary: string, - executable: string, version?: string, majorVersion?: string, page?: string } +export type FoundBrowser = { + name: string, + path?: string +} + interface ExtraLauncherMethods { update: Function, detect: Function diff --git a/packages/launcher/package.json b/packages/launcher/package.json index 15fdf16bec..4d9f5a9899 100644 --- a/packages/launcher/package.json +++ b/packages/launcher/package.json @@ -32,7 +32,7 @@ "mocha": "^2.4.5", "sinon": "^1.17.3", "sinon-chai": "^2.8.0", - "tslint": "5.2.0", + "tslint": "5.3.2", "tslint-config-standard": "5.0.2", "typescript": "2.3.2" },