diff --git a/dependency-lint.yml b/dependency-lint.yml index 8286b8bcb..ff28f414c 100644 --- a/dependency-lint.yml +++ b/dependency-lint.yml @@ -21,6 +21,7 @@ ignoreErrors: - '@typescript-eslint/eslint-plugin' # peer dependency of standard-with-typescript - '@typescript-eslint/parser' # peer dependency of @typescript-eslint/eslint-plugin - '@types/*' # type definitions + - bluebird # features/generator_step_definitions.feature - coffeescript # features/compiler.feature - eslint-config-prettier # .eslintrc.yml - extends - prettier - eslint-config-standard-with-typescript # .eslintrc.yml - extends - standard-with-typescript diff --git a/docs/support_files/timeouts.md b/docs/support_files/timeouts.md index a0eb78324..4154821d0 100644 --- a/docs/support_files/timeouts.md +++ b/docs/support_files/timeouts.md @@ -32,14 +32,13 @@ Given(/^a slow step$/, {timeout: 60 * 1000}, function() { Disable timeouts by setting it to -1. If you use this, you need to implement your own timeout protection. Otherwise the test suite may end prematurely or hang indefinitely. +The helper `wrapPromiseWithTimeout`, which cucumber-js itself uses to enforce timeouts is available if needed. ```javascript -var {Before, Given} = require('@cucumber/cucumber'); -var Promise = require('bluebird'); +var {Before, Given, wrapPromiseWithTimeout} = require('@cucumber/cucumber'); Given('the operation completes within {n} minutes', {timeout: -1}, function(minutes) { const milliseconds = (minutes + 1) * 60 * 1000 - const message = `operation did not complete within ${minutes} minutes` - return Promise(this.verifyOperationComplete()).timeout(milliseconds, message); + return wrapPromiseWithTimeout(this.verifyOperationComplete(), milliseconds); }); ``` diff --git a/features/attachments.feature b/features/attachments.feature index 86bf33ae5..739134c6e 100644 --- a/features/attachments.feature +++ b/features/attachments.feature @@ -128,17 +128,17 @@ Feature: Attachments Given a file named "features/support/hooks.js" with: """ const {After} = require('@cucumber/cucumber') - const Promise = require('bluebird') After(function() { - // Do not return the promise so that the attach happens after the hook completes - Promise.delay(100).then(() => { + // Do not use the callback / promise interface so that the attach happens after the hook completes + setTimeout(() => { this.attach("text") - }) + }, 100) }) """ When I run cucumber-js - Then the error output contains the text: + Then it fails + And the error output contains the text: """ Cannot attach when a step/hook is not running. Ensure your step/hook waits for the attach to finish. """ diff --git a/features/before_after_all_hook_interfaces.feature b/features/before_after_all_hook_interfaces.feature index 683881627..1b0dfc9e3 100644 --- a/features/before_after_all_hook_interfaces.feature +++ b/features/before_after_all_hook_interfaces.feature @@ -121,7 +121,6 @@ Feature: before / after all hook interfaces Given a file named "features/step_definitions/failing_steps.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function(callback) { return Promise.resolve() @@ -145,7 +144,6 @@ Feature: before / after all hook interfaces Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return Promise.resolve() @@ -163,7 +161,6 @@ Feature: before / after all hook interfaces Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return Promise.reject(new Error('my error')) @@ -185,7 +182,6 @@ Feature: before / after all hook interfaces Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return Promise.reject() @@ -208,7 +204,6 @@ Feature: before / after all hook interfaces Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return new Promise(function() { diff --git a/features/failing_steps.feature b/features/failing_steps.feature index f2df82d0b..f2971635b 100644 --- a/features/failing_steps.feature +++ b/features/failing_steps.feature @@ -93,7 +93,6 @@ Feature: Failing steps Given a file named "features/step_definitions/failing_steps.js" with: """ const {When} = require('@cucumber/cucumber') - const Promise = require('bluebird') When(/^a failing step$/, function(callback) { return Promise.resolve() @@ -113,7 +112,6 @@ Feature: Failing steps Given a file named "features/step_definitions/failing_steps.js" with: """ const {When} = require('@cucumber/cucumber') - const Promise = require('bluebird') When(/^a failing step$/, function() { return new Promise(function() { @@ -134,7 +132,6 @@ Feature: Failing steps Given a file named "features/step_definitions/failing_steps.js" with: """ const {When} = require('@cucumber/cucumber') - const Promise = require('bluebird') When(/^a failing step$/, function() { return Promise.reject(new Error('my error')) diff --git a/features/hook_interface.feature b/features/hook_interface.feature index 9fab4031d..7fcd1b92a 100644 --- a/features/hook_interface.feature +++ b/features/hook_interface.feature @@ -129,7 +129,6 @@ Feature: After hook interface Given a file named "features/step_definitions/failing_steps.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function(scenario, callback) { return Promise.resolve() @@ -153,7 +152,6 @@ Feature: After hook interface Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return Promise.resolve() @@ -171,7 +169,6 @@ Feature: After hook interface Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function(){ return Promise.reject(new Error('my error')) @@ -193,7 +190,6 @@ Feature: After hook interface Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function() { return Promise.reject() @@ -216,7 +212,6 @@ Feature: After hook interface Given a file named "features/support/hooks.js" with: """ const {} = require('@cucumber/cucumber') - const Promise = require('bluebird') (function(){ return new Promise(function() { diff --git a/features/parallel.feature b/features/parallel.feature index 686627ede..cc2f92045 100644 --- a/features/parallel.feature +++ b/features/parallel.feature @@ -4,7 +4,6 @@ Feature: Running scenarios in parallel Given a file named "features/step_definitions/cucumber_steps.js" with: """ const {Given} = require('@cucumber/cucumber') - const Promise = require('bluebird') Given(/^a slow step$/, function(callback) { setTimeout(callback, 1000) @@ -27,7 +26,6 @@ Feature: Running scenarios in parallel Given a file named "features/step_definitions/cucumber_steps.js" with: """ const {BeforeAll, Given} = require('@cucumber/cucumber') - const Promise = require('bluebird') Given(/^a slow step$/, function(callback) { setTimeout(callback, 1000) diff --git a/features/parameter_types.feature b/features/parameter_types.feature index 50e6d1a2a..f77e14063 100644 --- a/features/parameter_types.feature +++ b/features/parameter_types.feature @@ -113,7 +113,6 @@ Feature: Parameter types Given a file named "features/step_definitions/particular_steps.js" with: """ const {defineParameterType} = require('@cucumber/cucumber') - const Promise = require('bluebird') defineParameterType({ regexp: /particular/, diff --git a/features/passing_steps.feature b/features/passing_steps.feature index 9d33627f5..1b08c4bca 100644 --- a/features/passing_steps.feature +++ b/features/passing_steps.feature @@ -34,7 +34,6 @@ Feature: Passing steps Given a file named "features/step_definitions/passing_steps.js" with: """ const {Given} = require('@cucumber/cucumber') - const Promise = require('bluebird') Given(/^a passing step$/, function() { return Promise.resolve() diff --git a/features/step_definition_timeouts.feature b/features/step_definition_timeouts.feature index 39a45864c..916af5be6 100644 --- a/features/step_definition_timeouts.feature +++ b/features/step_definition_timeouts.feature @@ -4,7 +4,6 @@ Feature: Step definition timeouts Given a file named "features/step_definitions/cucumber_steps.js" with: """ const {Given, setDefaultTimeout} = require('@cucumber/cucumber') - const Promise = require('bluebird') setDefaultTimeout(500) @@ -21,15 +20,21 @@ Feature: Step definition timeouts }) Given(/^a promise step runs slowly$/, function() { - return Promise.resolve().delay(1000) + return new Promise(resolve => { + setTimeout(resolve, 1000) + }) }) Given(/^a promise step runs slowly with an increased timeout$/, {timeout: 1500}, function() { - return Promise.resolve().delay(1000) + return new Promise(resolve => { + setTimeout(resolve, 1000) + }) }) Given(/^a promise step with a disabled timeout$/, {timeout: -1}, function() { - return Promise.resolve().delay(1000) + return new Promise(resolve => { + setTimeout(resolve, 1000) + }) }) """ diff --git a/src/cli/configuration_builder.ts b/src/cli/configuration_builder.ts index c30b93b03..6aa44b95a 100644 --- a/src/cli/configuration_builder.ts +++ b/src/cli/configuration_builder.ts @@ -5,7 +5,6 @@ import ArgvParser, { import fs from 'mz/fs' import path from 'path' import OptionSplitter from './option_splitter' -import bluebird from 'bluebird' import glob from 'glob' import { promisify } from 'util' import { IPickleFilterOptions } from '../pickle_filter' @@ -119,21 +118,22 @@ export default class ConfigurationBuilder { unexpandedPaths: string[], defaultExtension: string ): Promise { - const expandedPaths = await bluebird.map( - unexpandedPaths, - async (unexpandedPath) => { + const expandedPaths = await Promise.all( + unexpandedPaths.map(async (unexpandedPath) => { const matches = await promisify(glob)(unexpandedPath, { absolute: true, cwd: this.cwd, }) - const expanded = await bluebird.map(matches, async (match) => { - if (path.extname(match) === '') { - return await promisify(glob)(`${match}/**/*${defaultExtension}`) - } - return [match] - }) + const expanded = await Promise.all( + matches.map(async (match) => { + if (path.extname(match) === '') { + return await promisify(glob)(`${match}/**/*${defaultExtension}`) + } + return [match] + }) + ) return expanded.flat() - } + }) ) return expandedPaths.flat().map((x) => path.normalize(x)) } @@ -206,15 +206,17 @@ export default class ConfigurationBuilder { async getUnexpandedFeaturePaths(): Promise { if (this.args.length > 0) { - const nestedFeaturePaths = await bluebird.map(this.args, async (arg) => { - const filename = path.basename(arg) - if (filename[0] === '@') { - const filePath = path.join(this.cwd, arg) - const content = await fs.readFile(filePath, 'utf8') - return content.split('\n').map((x) => x.trim()) - } - return [arg] - }) + const nestedFeaturePaths = await Promise.all( + this.args.map(async (arg) => { + const filename = path.basename(arg) + if (filename[0] === '@') { + const filePath = path.join(this.cwd, arg) + const content = await fs.readFile(filePath, 'utf8') + return content.split('\n').map((x) => x.trim()) + } + return [arg] + }) + ) const featurePaths = nestedFeaturePaths.flat() if (featurePaths.length > 0) { return featurePaths.filter((x) => x !== '') diff --git a/src/cli/index.ts b/src/cli/index.ts index 259d3b528..6a42d82c0 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -16,18 +16,18 @@ import FormatterBuilder from '../formatter/builder' import fs from 'mz/fs' import path from 'path' import PickleFilter from '../pickle_filter' -import bluebird from 'bluebird' import ParallelRuntimeCoordinator from '../runtime/parallel/coordinator' import Runtime from '../runtime' import supportCodeLibraryBuilder from '../support_code_library_builder' import { IdGenerator } from '@cucumber/messages' -import { IFormatterStream } from '../formatter' +import Formatter, { IFormatterStream } from '../formatter' import { WriteStream as TtyWriteStream } from 'tty' import { doesNotHaveValue } from '../value_checker' import { GherkinStreams } from '@cucumber/gherkin-streams' import { ISupportCodeLibrary } from '../support_code_library_builder/types' import { IParsedArgvFormatOptions } from './argv_parser' import HttpStream from '../formatter/http_stream' +import { promisify } from 'util' import { Writable } from 'stream' const { incrementing, uuid } = IdGenerator @@ -88,9 +88,8 @@ export default class Cli { formats, supportCodeLibrary, }: IInitializeFormattersRequest): Promise<() => Promise> { - const formatters = await bluebird.map( - formats, - async ({ type, outputTo }) => { + const formatters: Formatter[] = await Promise.all( + formats.map(async ({ type, outputTo }) => { let stream: IFormatterStream = this.stdout if (outputTo !== '') { if (outputTo.match(/^https?:\/\//) !== null) { @@ -129,7 +128,7 @@ export default class Cli { cleanup: stream === this.stdout ? async () => await Promise.resolve() - : bluebird.promisify(stream.end.bind(stream)), + : promisify(stream.end.bind(stream)), supportCodeLibrary, } if (doesNotHaveValue(formatOptions.colorsEnabled)) { @@ -145,12 +144,10 @@ export default class Cli { type = 'progress' } return FormatterBuilder.build(type, typeOptions) - } + }) ) return async function () { - await bluebird.each(formatters, async (formatter) => { - await formatter.finished() - }) + await Promise.all(formatters.map(async (f) => await f.finished())) } } diff --git a/src/formatter/progress_bar_formatter_spec.ts b/src/formatter/progress_bar_formatter_spec.ts index a083c2d1c..ce1fd9a3c 100644 --- a/src/formatter/progress_bar_formatter_spec.ts +++ b/src/formatter/progress_bar_formatter_spec.ts @@ -20,7 +20,7 @@ import ProgressBarFormatter from './progress_bar_formatter' import { doesHaveValue, doesNotHaveValue } from '../value_checker' import { PassThrough } from 'stream' import ProgressBar from 'progress' -import bluebird from 'bluebird' +import { promisify } from 'util' interface ITestProgressBarFormatterOptions { runtimeOptions?: Partial @@ -65,7 +65,7 @@ async function testProgressBarFormatter({ log: logFn, parsedArgvOptions: {}, stream: passThrough, - cleanup: bluebird.promisify(passThrough.end.bind(passThrough)), + cleanup: promisify(passThrough.end.bind(passThrough)), supportCodeLibrary, }) as ProgressBarFormatter let mocked = false diff --git a/src/index.ts b/src/index.ts index 2aa62abc6..3272703ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,8 +47,12 @@ export { IWorld, IWorldOptions, } from './support_code_library_builder/world' + export { ITestCaseHookParameter, ITestStepHookParameter, } from './support_code_library_builder/types' export const Status = messages.TestStepResultStatus + +// Time helpers +export { wrapPromiseWithTimeout } from './time' diff --git a/src/models/step_definition.ts b/src/models/step_definition.ts index 2789e2741..42b7ae111 100644 --- a/src/models/step_definition.ts +++ b/src/models/step_definition.ts @@ -7,7 +7,6 @@ import Definition, { } from './definition' import { parseStepArgument } from '../step_arguments' import { Expression } from '@cucumber/cucumber-expressions' -import bluebird from 'bluebird' import { doesHaveValue } from '../value_checker' export default class StepDefinition extends Definition implements IDefinition { @@ -24,7 +23,7 @@ export default class StepDefinition extends Definition implements IDefinition { step, world, }: IGetInvocationDataRequest): Promise { - const parameters = await bluebird.all( + const parameters = await Promise.all( this.expression.match(step.text).map((arg) => arg.getValue(world)) ) if (doesHaveValue(step.argument)) { diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 1508edc3b..2ee4cb5b9 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,5 +1,4 @@ import { EventDataCollector, formatLocation } from '../formatter/helpers' -import bluebird from 'bluebird' import StackTraceFilter from '../stack_trace_filter' import UserCodeRunner from '../user_code_runner' import VError from 'verror' @@ -77,27 +76,24 @@ export default class Runtime { if (this.options.dryRun) { return } - await bluebird.each( - definitions, - async (hookDefinition: TestRunHookDefinition) => { - const { error } = await UserCodeRunner.run({ - argsArray: [], - fn: hookDefinition.code, - thisArg: null, - timeoutInMilliseconds: valueOrDefault( - hookDefinition.options.timeout, - this.supportCodeLibrary.defaultTimeout - ), - }) - if (doesHaveValue(error)) { - const location = formatLocation(hookDefinition) - throw new VError( - error, - `${name} hook errored, process exiting: ${location}` - ) - } + for (const hookDefinition of definitions) { + const { error } = await UserCodeRunner.run({ + argsArray: [], + fn: hookDefinition.code, + thisArg: null, + timeoutInMilliseconds: valueOrDefault( + hookDefinition.options.timeout, + this.supportCodeLibrary.defaultTimeout + ), + }) + if (doesHaveValue(error)) { + const location = formatLocation(hookDefinition) + throw new VError( + error, + `${name} hook errored, process exiting: ${location}` + ) } - ) + } } async runTestCase( @@ -148,9 +144,9 @@ export default class Runtime { ), supportCodeLibrary: this.supportCodeLibrary, }) - await bluebird.each(this.pickleIds, async (pickleId) => { + for (const pickleId of this.pickleIds) { await this.runTestCase(pickleId, assembledTestCases[pickleId]) - }) + } await this.runTestRunHooks( this.supportCodeLibrary.afterTestRunHookDefinitions.slice(0).reverse(), 'an AfterAll' diff --git a/src/runtime/parallel/worker.ts b/src/runtime/parallel/worker.ts index b5c371d5b..629bf4a2d 100644 --- a/src/runtime/parallel/worker.ts +++ b/src/runtime/parallel/worker.ts @@ -6,7 +6,6 @@ import { IWorkerCommandRun, } from './command_types' import { EventEmitter } from 'events' -import bluebird from 'bluebird' import StackTraceFilter from '../../stack_trace_filter' import supportCodeLibraryBuilder from '../../support_code_library_builder' import TestCaseRunner from '../test_case_runner' @@ -145,7 +144,7 @@ export default class Worker { if (this.options.dryRun) { return } - await bluebird.each(testRunHookDefinitions, async (hookDefinition) => { + for (const hookDefinition of testRunHookDefinitions) { const { error } = await UserCodeRunner.run({ argsArray: [], fn: hookDefinition.code, @@ -163,6 +162,6 @@ export default class Worker { `${name} hook errored on worker ${this.id}, process exiting: ${location}` ) } - }) + } } } diff --git a/src/time.ts b/src/time.ts index a836862a2..ab3d50cda 100644 --- a/src/time.ts +++ b/src/time.ts @@ -37,4 +37,23 @@ export function durationBetweenTimestamps( return messages.TimeConversion.millisecondsToDuration(durationMillis) } +export async function wrapPromiseWithTimeout( + promise: Promise, + timeoutInMilliseconds: number, + timeoutMessage: string = '' +): Promise { + let timeoutId: NodeJS.Timeout + if (timeoutMessage === '') { + timeoutMessage = `Action did not complete within ${timeoutInMilliseconds} milliseconds` + } + const timeoutPromise = new Promise((resolve, reject) => { + timeoutId = methods.setTimeout(() => { + reject(new Error(timeoutMessage)) + }, timeoutInMilliseconds) + }) + return await Promise.race([promise, timeoutPromise]).finally(() => + methods.clearTimeout(timeoutId) + ) +} + export default methods diff --git a/src/time_spec.ts b/src/time_spec.ts new file mode 100644 index 000000000..6e1e94ad3 --- /dev/null +++ b/src/time_spec.ts @@ -0,0 +1,64 @@ +import { describe, it } from 'mocha' +import { expect } from 'chai' +import { wrapPromiseWithTimeout } from './time' + +describe.only('wrapPromiseWithTimeout()', () => { + describe('promise times out (default timeout message)', () => { + it('rejects the promise', async () => { + // Arrange + const promise = new Promise((resolve) => { + setTimeout(resolve, 50) + }) + + // Act + let error: Error = null + try { + await wrapPromiseWithTimeout(promise, 25) + } catch (e) { + error = e + } + + // Assert + expect(error).to.exist() + expect(error.message).to.eql( + 'Action did not complete within 25 milliseconds' + ) + }) + }) + + describe('promise times out (supplied timeout message)', () => { + it('rejects the promise', async () => { + // Arrange + const promise = new Promise((resolve) => { + setTimeout(resolve, 50) + }) + + // Act + let error: Error = null + try { + await wrapPromiseWithTimeout(promise, 25, 'custom timeout message') + } catch (e) { + error = e + } + + // Assert + expect(error).to.exist() + expect(error.message).to.eql('custom timeout message') + }) + }) + + describe('promise does not time out', () => { + it('resolves the promise', async () => { + // Arrange + const promise = new Promise((resolve) => { + setTimeout(() => resolve('value'), 10) + }) + + // Act + const result = await wrapPromiseWithTimeout(promise, 25) + + // Assert + expect(result).to.eql('value') + }) + }) +}) diff --git a/src/user_code_runner.ts b/src/user_code_runner.ts index 274c80017..99d05ab93 100644 --- a/src/user_code_runner.ts +++ b/src/user_code_runner.ts @@ -1,5 +1,4 @@ -import bluebird from 'bluebird' -import Time from './time' +import { wrapPromiseWithTimeout } from './time' import UncaughtExceptionManager from './uncaught_exception_manager' import util from 'util' import { doesHaveValue } from './value_checker' @@ -69,23 +68,22 @@ const UserCodeRunner = { }) racingPromises.push(uncaughtExceptionPromise) - let timeoutId + let finalPromise = Promise.race(racingPromises) if (timeoutInMilliseconds >= 0) { - const timeoutPromise = new Promise((resolve, reject) => { - timeoutId = Time.setTimeout(() => { - const timeoutMessage = - 'function timed out, ensure the ' + - (callbackInterface ? 'callback is executed' : 'promise resolves') + - ` within ${timeoutInMilliseconds.toString()} milliseconds` - reject(new Error(timeoutMessage)) - }, timeoutInMilliseconds) - }) - racingPromises.push(timeoutPromise) + const timeoutMessage = + 'function timed out, ensure the ' + + (callbackInterface ? 'callback is executed' : 'promise resolves') + + ` within ${timeoutInMilliseconds.toString()} milliseconds` + finalPromise = wrapPromiseWithTimeout( + finalPromise, + timeoutInMilliseconds, + timeoutMessage + ) } let error, result try { - result = await bluebird.race(racingPromises) + result = await finalPromise } catch (e) { if (e instanceof Error) { error = e @@ -96,7 +94,6 @@ const UserCodeRunner = { } } - Time.clearTimeout(timeoutId) UncaughtExceptionManager.unregisterHandler(exceptionHandler) return { error, result } diff --git a/src/user_code_runner_spec.ts b/src/user_code_runner_spec.ts index f40bd4973..fe028ddee 100644 --- a/src/user_code_runner_spec.ts +++ b/src/user_code_runner_spec.ts @@ -1,7 +1,6 @@ import { describe, it } from 'mocha' import { expect } from 'chai' import UserCodeRunner, { IRunRequest, IRunResponse } from './user_code_runner' -import bluebird from 'bluebird' import semver from 'semver' async function testUserCodeRunner( @@ -242,7 +241,9 @@ describe('UserCodeRunner', () => { it('returns timeout as an error', async function () { // Arrange const fn = async function (): Promise { - return await bluebird.resolve('result').delay(200) + return await new Promise((resolve) => { + setTimeout(() => resolve('result'), 200) + }) } // Act @@ -261,7 +262,9 @@ describe('UserCodeRunner', () => { it('disables timeout protection', async function () { // Arrange const fn = async function (): Promise { - return await bluebird.resolve('result').delay(200) + return await new Promise((resolve) => { + setTimeout(() => resolve('result'), 200) + }) } // Act diff --git a/test/formatter_helpers.ts b/test/formatter_helpers.ts index f947e5846..6b1005de4 100644 --- a/test/formatter_helpers.ts +++ b/test/formatter_helpers.ts @@ -12,7 +12,7 @@ import { doesNotHaveValue } from '../src/value_checker' import { IParsedArgvFormatOptions } from '../src/cli/argv_parser' import { PassThrough } from 'stream' import { emitSupportCodeMessages } from '../src/cli/helpers' -import bluebird from 'bluebird' +import { promisify } from 'util' const { uuid } = IdGenerator @@ -67,7 +67,7 @@ export async function testFormatter({ log: logFn, parsedArgvOptions, stream: passThrough, - cleanup: bluebird.promisify(passThrough.end.bind(passThrough)), + cleanup: promisify(passThrough.end.bind(passThrough)), supportCodeLibrary, }) let pickleIds: string[] = []