diff --git a/exampleTypescript/package.json b/exampleTypescript/package.json index 055487ec1..07d2f6e37 100644 --- a/exampleTypescript/package.json +++ b/exampleTypescript/package.json @@ -13,9 +13,5 @@ "jasmine": "^2.4.1", "protractor": "file:../", "typescript": "^2.0.0" - }, - "devDependencies": { - "@types/jasmine": "^2.2.33", - "@types/node": "^6.0.38" } } diff --git a/gulpfile.js b/gulpfile.js index 013a4e753..d945de1b4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,8 +88,11 @@ gulp.task('types', function(done) { var files = ['browser', 'element', 'locators', 'expectedConditions', 'config', 'plugins', 'ptor']; var outputFile = path.resolve(folder, 'index.d.ts'); - var contents = '/// \n'; - contents += 'import {By, WebDriver, WebElement, promise} from \'selenium-webdriver\';\n'; + var contents = ''; + contents += '/// \n'; + contents += '/// \n'; + contents += '/// \n'; + contents += 'import {ActionSequence, By, WebDriver, WebElement, WebElementPromise, promise, promise as wdpromise, until} from \'selenium-webdriver\';\n'; files.forEach(file => { contents += parseTypingsFile(folder, file); }); diff --git a/lib/browser.ts b/lib/browser.ts index ba5439403..efd58f4a8 100644 --- a/lib/browser.ts +++ b/lib/browser.ts @@ -1,5 +1,6 @@ // Util from NodeJs import * as net from 'net'; +import {ActionSequence, promise as wdpromise, until, WebDriver, WebElement} from 'selenium-webdriver'; import * as url from 'url'; import * as util from 'util'; @@ -33,20 +34,18 @@ for (let foo in webdriver) { // Explicitly define webdriver.WebDriver. export class Webdriver { - actions: () => webdriver.ActionSequence = webdriver.WebDriver.actions; + actions: () => ActionSequence = webdriver.WebDriver.actions; wait: - (condition: webdriver.promise.Promise|webdriver.util.Condition| - Function, + (condition: wdpromise.Promise|until.Condition|Function, opt_timeout?: number, opt_message?: - string) => webdriver.promise.Promise = webdriver.WebDriver.wait; - sleep: (ms: number) => webdriver.promise.Promise = - webdriver.WebDriver.sleep; + string) => wdpromise.Promise = webdriver.WebDriver.wait; + sleep: (ms: number) => wdpromise.Promise = webdriver.WebDriver.sleep; getCurrentUrl: - () => webdriver.promise.Promise = webdriver.WebDriver.getCurrentUrl; - getTitle: () => webdriver.promise.Promise = webdriver.WebDriver.getTitle; + () => wdpromise.Promise = webdriver.WebDriver.getCurrentUrl; + getTitle: () => wdpromise.Promise = webdriver.WebDriver.getTitle; takeScreenshot: - () => webdriver.promise.Promise = webdriver.WebDriver.takeScreenshot; + () => wdpromise.Promise = webdriver.WebDriver.takeScreenshot; } /** @@ -127,7 +126,7 @@ export class ProtractorBrowser extends Webdriver { * * @type {webdriver.WebDriver} */ - driver: webdriver.WebDriver; + driver: WebDriver; /** * Helper function for finding elements. @@ -197,7 +196,7 @@ export class ProtractorBrowser extends Webdriver { * * @type {q.Promise} Done when the new browser is ready for use */ - ready: webdriver.promise.Promise; + ready: wdpromise.Promise; /* * Set by the runner. @@ -257,7 +256,7 @@ export class ProtractorBrowser extends Webdriver { [key: string]: any; constructor( - webdriverInstance: webdriver.WebDriver, opt_baseUrl?: string, + webdriverInstance: WebDriver, opt_baseUrl?: string, opt_rootElement?: string, opt_untrackOutstandingTimeouts?: boolean) { super(); // These functions should delegate to the webdriver instance, but should @@ -322,7 +321,7 @@ export class ProtractorBrowser extends Webdriver { * @returns {webdriver.promise.Promise} A promise which resolves to the * capabilities object. */ - getProcessedConfig(): webdriver.promise.Promise { return null; } + getProcessedConfig(): wdpromise.Promise { return null; } /** * Fork another instance of browser for use in interactive tests. @@ -374,7 +373,7 @@ export class ProtractorBrowser extends Webdriver { */ private executeScript_( script: string|Function, description: string, - ...scriptArgs: any[]): webdriver.promise.Promise { + ...scriptArgs: any[]): wdpromise.Promise { if (typeof script === 'function') { script = 'return (' + script + ').apply(null, arguments);'; } @@ -401,7 +400,7 @@ export class ProtractorBrowser extends Webdriver { */ private executeAsyncScript_( script: string|Function, description: string, - ...scriptArgs: any[]): webdriver.promise.Promise { + ...scriptArgs: any[]): wdpromise.Promise { if (typeof script === 'function') { script = 'return (' + script + ').apply(null, arguments);'; } @@ -423,7 +422,7 @@ export class ProtractorBrowser extends Webdriver { * @returns {!webdriver.promise.Promise} A promise that will resolve to the * scripts return value. */ - waitForAngular(opt_description?: string): webdriver.promise.Promise { + waitForAngular(opt_description?: string): wdpromise.Promise { let description = opt_description ? ' - ' + opt_description : ''; if (this.ignoreSynchronization) { return this.driver.controlFlow().execute(() => { @@ -431,7 +430,7 @@ export class ProtractorBrowser extends Webdriver { }, 'Ignore Synchronization Protractor.waitForAngular()'); } - let runWaitForAngularScript: () => webdriver.promise.Promise = () => { + let runWaitForAngularScript: () => wdpromise.Promise = () => { if (this.plugins_.skipAngularStability()) { return webdriver.promise.fulfilled(); } else if (this.rootEl) { @@ -493,14 +492,14 @@ export class ProtractorBrowser extends Webdriver { errMsg += '\nWhile waiting for element with locator' + description; } - let pendingTimeoutsPromise: webdriver.promise.Promise; + let pendingTimeoutsPromise: wdpromise.Promise; if (this.trackOutstandingTimeouts_) { pendingTimeoutsPromise = this.executeScript_( 'return window.NG_PENDING_TIMEOUTS', 'Protractor.waitForAngular() - getting pending timeouts' + description); } else { - pendingTimeoutsPromise = webdriver.promise.fulfilled({}); + pendingTimeoutsPromise = wdpromise.fulfilled({}); } let pendingHttpsPromise = this.executeScript_( clientSideScripts.getPendingHttpRequests, @@ -508,7 +507,7 @@ export class ProtractorBrowser extends Webdriver { description, this.rootEl); - return webdriver.promise + return wdpromise .all([pendingTimeoutsPromise, pendingHttpsPromise]) .then( (arr: any[]) => { @@ -549,7 +548,7 @@ export class ProtractorBrowser extends Webdriver { * @returns {!webdriver.promise.Promise} A promise that will be resolved to * the located {@link webdriver.WebElement}. */ - findElement(locator: Locator): webdriver.WebElement { + findElement(locator: Locator): WebElement { return this.element(locator).getWebElement(); } diff --git a/lib/element.ts b/lib/element.ts index bb4a420b2..a8f82f18d 100644 --- a/lib/element.ts +++ b/lib/element.ts @@ -1,9 +1,12 @@ -let webdriver = require('selenium-webdriver'); -let clientSideScripts = require('./clientsidescripts'); +import {By, error, promise, WebDriver, WebElement, WebElementPromise} from 'selenium-webdriver'; -import {Logger} from './logger'; +import {ElementHelper} from './browser'; import {ProtractorBrowser} from './browser'; -import {Locator} from './locators'; +import {Locator, ProtractorBy} from './locators'; +import {Logger} from './logger'; + +let webdriver = require('selenium-webdriver'); +let clientSideScripts = require('./clientsidescripts'); let logger = new Logger('element'); @@ -16,28 +19,26 @@ let WEB_ELEMENT_FUNCTIONS = [ // Explicitly define webdriver.WebElement. export class WebdriverWebElement { - getDriver: () => webdriver.WebDriver; - getId: () => webdriver.promise.Promise; - getRawId: () => webdriver.promise.Promise; - serialize: () => webdriver.promise.Promise; - findElement: (subLocator: Locator) => webdriver.promise.Promise; - click: () => webdriver.promise.Promise; - sendKeys: (...args: (string|webdriver.promise.Promise)[]) => - webdriver.promise.Promise; - getTagName: () => webdriver.promise.Promise; - getCssValue: (cssStyleProperty: string) => webdriver.promise.Promise; - getAttribute: (attributeName: string) => webdriver.promise.Promise; - getText: () => webdriver.promise.Promise; - getSize: () => webdriver.promise.Promise<{width: number, height: number}>; - getLocation: () => webdriver.promise.Promise<{x: number, y: number}>; - isEnabled: () => webdriver.promise.Promise; - isSelected: () => webdriver.promise.Promise; - submit: () => webdriver.promise.Promise; - clear: () => webdriver.promise.Promise; - isDisplayed: () => webdriver.promise.Promise; - takeScreenshot: (opt_scroll?: boolean) => webdriver.promise.Promise; - getOuterHtml: () => webdriver.promise.Promise; - getInnerHtml: () => webdriver.promise.Promise; + getDriver: () => WebDriver; + getId: () => promise.Promise; + getRawId: () => promise.Promise; + serialize: () => promise.Promise; + findElement: (subLocator: Locator) => promise.Promise; + click: () => promise.Promise; + sendKeys: + (...args: (string|promise.Promise)[]) => promise.Promise; + getTagName: () => promise.Promise; + getCssValue: (cssStyleProperty: string) => promise.Promise; + getAttribute: (attributeName: string) => promise.Promise; + getText: () => promise.Promise; + getSize: () => promise.Promise<{width: number, height: number}>; + getLocation: () => promise.Promise<{x: number, y: number}>; + isEnabled: () => promise.Promise; + isSelected: () => promise.Promise; + submit: () => promise.Promise; + clear: () => promise.Promise; + isDisplayed: () => promise.Promise; + takeScreenshot: (opt_scroll?: boolean) => promise.Promise; } /** @@ -94,26 +95,25 @@ export class WebdriverWebElement { * @returns {ElementArrayFinder} */ export class ElementArrayFinder extends WebdriverWebElement { - getWebElements: Function; - constructor( - public browser_: ProtractorBrowser, getWebElements?: Function, + public browser_: ProtractorBrowser, + public getWebElements: () => promise.Promise = null, public locator_?: any, - public actionResults_: webdriver.promise.Promise = null) { + public actionResults_: promise.Promise = null) { super(); - this.getWebElements = getWebElements || null; // TODO(juliemr): might it be easier to combine this with our docs and just // wrap each // one explicity with its own documentation? WEB_ELEMENT_FUNCTIONS.forEach((fnName: string) => { - (this)[fnName] = (...args: any[]) => { + this[fnName] = (...args: any[]) => { let actionFn = (webElem: any) => { return webElem[fnName].apply(webElem, args); }; return this.applyAction_(actionFn); }; }); } + [key: string]: any; /** * Create a shallow copy of ElementArrayFinder. @@ -163,11 +163,11 @@ export class ElementArrayFinder extends WebdriverWebElement { */ all(locator: Locator): ElementArrayFinder { let ptor = this.browser_; - let getWebElements = () => { + let getWebElements = (): promise.Promise => { if (this.getWebElements === null) { // This is the first time we are looking for an element return ptor.waitForAngular('Locator: ' + locator) - .then((): webdriver.promise.Promise => { + .then((): promise.Promise => { if (locator.findElementsOverride) { return locator.findElementsOverride( ptor.driver, null, ptor.rootEl); @@ -192,7 +192,7 @@ export class ElementArrayFinder extends WebdriverWebElement { // Resolve the list of Promise> and merge // into // a single list - return webdriver.promise.all(childrenPromiseList) + return promise.all(childrenPromiseList) .then((resolved: webdriver.WebElement[]) => { return resolved.reduce( (childrenList: webdriver.WebElement[], @@ -239,10 +239,10 @@ export class ElementArrayFinder extends WebdriverWebElement { * of element that satisfy the filter function. */ filter(filterFn: Function): ElementArrayFinder { - let getWebElements = () => { - return this.getWebElements().then((parentWebElements: any) => { - let list = - parentWebElements.map((parentWebElement: any, index: number) => { + let getWebElements = (): promise.Promise => { + return this.getWebElements().then((parentWebElements: WebElement[]) => { + let list = parentWebElements.map( + (parentWebElement: WebElement, index: number) => { let elementFinder = ElementFinder.fromWebElement_( this.browser_, parentWebElement, this.locator_); @@ -250,7 +250,7 @@ export class ElementArrayFinder extends WebdriverWebElement { }); return webdriver.promise.all(list).then((resolvedList: any) => { return parentWebElements.filter( - (parentWebElement: any, index: number) => { + (parentWebElement: WebElement, index: number) => { return resolvedList[index]; }); }); @@ -292,7 +292,7 @@ export class ElementArrayFinder extends WebdriverWebElement { i = parentWebElements.length + i; } if (i < 0 || i >= parentWebElements.length) { - throw new webdriver.error.NoSuchElementError( + throw new error.NoSuchElementError( 'Index out of bound. ' + 'Trying to access element at index: ' + index + ', but there are ' + @@ -385,11 +385,12 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {!webdriver.promise.Promise} A promise which resolves to the * number of elements matching the locator. */ - count(): webdriver.promise.Promise { + count(): promise.Promise { return this.getWebElements().then( - (arr: any) => { return arr.length; }, - (err: any) => { - if (err.code == new webdriver.error.NoSuchElementError()) { + (arr: WebElement[]) => { return arr.length; }, + (err: IError) => { + if (err.code && + err.code == new webdriver.error.NoSuchElementError()) { return 0; } else { throw err; @@ -424,22 +425,26 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {ElementArrayFinder} * @private */ - applyAction_(actionFn: Function): ElementArrayFinder { + // map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: + // any): U[]; + applyAction_( + actionFn: (value: WebElement, index: number, array: WebElement[]) => any): + ElementArrayFinder { let callerError = new Error(); let actionResults = this.getWebElements() - .then((arr: any) => { + .then((arr: WebElement[]) => { return webdriver.promise.all(arr.map(actionFn)); }) - .then(null, (e: Error | string) => { + .then(null, (e: IError | string) => { let noSuchErr: any; let stack: string; if (e instanceof Error) { noSuchErr = e; - noSuchErr.stack = - noSuchErr.stack + callerError.stack; + noSuchErr.stack = noSuchErr.stack + + (callerError as IError).stack; } else { noSuchErr = new Error(e as string); - noSuchErr.stack = callerError.stack; + noSuchErr.stack = (callerError as IError).stack; } throw noSuchErr; }); @@ -453,9 +458,9 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {Array.} Return a promise, which resolves to a list * of ElementFinders specified by the locator. */ - asElementFinders_(): webdriver.promise.Promise { - return this.getWebElements().then((arr: webdriver.WebElement[]) => { - return arr.map((webElem: webdriver.WebElement) => { + asElementFinders_(): promise.Promise { + return this.getWebElements().then((arr: WebElement[]) => { + return arr.map((webElem: WebElement) => { return ElementFinder.fromWebElement_( this.browser_, webElem, this.locator_); }); @@ -486,7 +491,7 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {!webdriver.promise.Promise} A promise which will resolve to * an array of ElementFinders represented by the ElementArrayFinder. */ - then(fn: Function, errorFn: Function): webdriver.promise.Promise { + then(fn: Function, errorFn: Function): promise.Promise { if (this.actionResults_) { return this.actionResults_.then(fn, errorFn); } else { @@ -520,7 +525,8 @@ export class ElementArrayFinder extends WebdriverWebElement { * function has been called on all the ElementFinders. The promise will * resolve to null. */ - each(fn: Function): webdriver.promise.Promise { + each(fn: (elementFinder: ElementFinder, index: number) => any): + promise.Promise { return this.map(fn).then((): any => { return null; }); } @@ -556,14 +562,15 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {!webdriver.promise.Promise} A promise that resolves to an array * of values returned by the map function. */ - map(mapFn: Function): webdriver.promise.Promise { + map(mapFn: (elementFinder: ElementFinder, index: number) => any): + promise.Promise { return this.asElementFinders_().then((arr: ElementFinder[]) => { let list = arr.map((elementFinder: ElementFinder, index: number) => { let mapResult = mapFn(elementFinder, index); // All nested arrays and objects will also be fully resolved. - return webdriver.promise.fullyResolved(mapResult); + return promise.fullyResolved(mapResult); }); - return webdriver.promise.all(list); + return promise.all(list); }); }; @@ -599,9 +606,8 @@ export class ElementArrayFinder extends WebdriverWebElement { * @returns {!webdriver.promise.Promise} A promise that resolves to the final * value of the accumulator. */ - reduce(reduceFn: Function, initialValue: any): - webdriver.promise.Promise { - let valuePromise = webdriver.promise.fulfilled(initialValue); + reduce(reduceFn: Function, initialValue: any): promise.Promise { + let valuePromise = promise.fulfilled(initialValue); return this.asElementFinders_().then((arr: ElementFinder[]) => { return arr.reduce( (valuePromise: any, elementFinder: ElementFinder, index: number) => { @@ -633,7 +639,7 @@ export class ElementArrayFinder extends WebdriverWebElement { * will be returned as a WebElement. */ evaluate(expression: string): ElementArrayFinder { - let evaluationFn = (webElem: webdriver.WebElement) => { + let evaluationFn = (webElem: WebElement) => { return webElem.getDriver().executeScript( clientSideScripts.evaluate, webElem, expression); }; @@ -652,7 +658,7 @@ export class ElementArrayFinder extends WebdriverWebElement { * allowed. */ allowAnimations(value: boolean): ElementArrayFinder { - let allowAnimationsTestFn = (webElem: webdriver.WebElement) => { + let allowAnimationsTestFn = (webElem: WebElement) => { return webElem.getDriver().executeScript( clientSideScripts.allowAnimations, webElem, value); }; @@ -708,9 +714,7 @@ export class ElementArrayFinder extends WebdriverWebElement { export class ElementFinder extends WebdriverWebElement { parentElementArrayFinder: ElementArrayFinder; elementArrayFinder_: ElementArrayFinder; - then: - (fn: Function, - errorFn: Function) => webdriver.promise.Promise = null; + then: (fn: Function, errorFn: Function) => promise.Promise = null; constructor( public browser_: ProtractorBrowser, @@ -748,11 +752,11 @@ export class ElementFinder extends WebdriverWebElement { // This filter verifies that there is only 1 element returned by the // elementArrayFinder. It will warn if there are more than 1 element and // throw an error if there are no elements. - let getWebElements = (): webdriver.WebElement[] => { + let getWebElements = (): promise.Promise => { return elementArrayFinder.getWebElements().then( - (webElements: webdriver.WebElement[]) => { + (webElements: WebElement[]) => { if (webElements.length === 0) { - throw new webdriver.error.NoSuchElementError( + throw new error.NoSuchElementError( 'No element found using locator: ' + elementArrayFinder.locator().toString()); } else { @@ -774,19 +778,19 @@ export class ElementFinder extends WebdriverWebElement { elementArrayFinder.actionResults_); WEB_ELEMENT_FUNCTIONS.forEach((fnName: string) => { - (this)[fnName] = (...args: any[]) => { - return (this.elementArrayFinder_)[fnName] + (this)[fnName] = (...args: any[]) => { + return (this.elementArrayFinder_)[fnName] .apply(this.elementArrayFinder_, args) .toElementFinder_(); }; }); } + [key: string]: any; static fromWebElement_( - browser: ProtractorBrowser, webElem: webdriver.WebElement, + browser: ProtractorBrowser, webElem: WebElement, locator: Locator): ElementFinder { - let getWebElements = - () => { return webdriver.promise.fulfilled([webElem]); }; + let getWebElements = () => { return promise.fulfilled([webElem]); }; return new ElementArrayFinder(browser, getWebElements, locator) .toElementFinder_(); } @@ -827,12 +831,12 @@ export class ElementFinder extends WebdriverWebElement { * * @returns {webdriver.WebElement} */ - getWebElement(): any { + getWebElement(): WebElementPromise { let id = this.elementArrayFinder_.getWebElements().then( (parentWebElements: webdriver.WebElement[]) => { return parentWebElements[0]; }); - return new webdriver.WebElementPromise(this.browser_.driver, id); + return new WebElementPromise(this.browser_.driver, id); } /** @@ -961,9 +965,9 @@ export class ElementFinder extends WebdriverWebElement { * @returns {webdriver.promise.Promise} which resolves to whether * the element is present on the page. */ - isPresent(): webdriver.promise.Promise { + isPresent(): promise.Promise { return this.parentElementArrayFinder.getWebElements().then( - (arr: any) => { + (arr: any[]) => { if (arr.length === 0) { return false; } @@ -1001,7 +1005,7 @@ export class ElementFinder extends WebdriverWebElement { * @returns {webdriver.promise.Promise} which resolves to whether * the subelement is present on the page. */ - isElementPresent(subLocator: any): webdriver.promise.Promise { + isElementPresent(subLocator: Locator): promise.Promise { if (!subLocator) { throw new Error( 'SubLocator is not supplied as a parameter to ' + @@ -1047,10 +1051,11 @@ export class ElementFinder extends WebdriverWebElement { * @returns {!webdriver.promise.Promise.} A promise that will be * resolved to whether the two WebElements are equal. */ - equals(element: any): webdriver.promise.Promise { - return webdriver.WebElement.equals( - this.getWebElement(), - element.getWebElement ? element.getWebElement() : element); + equals(element: ElementFinder|WebElement): promise.Promise { + return WebElement.equals( + this.getWebElement(), (element as any).getWebElement ? + (element as ElementFinder).getWebElement() : + element as WebElement); } } @@ -1074,7 +1079,7 @@ export class ElementFinder extends WebdriverWebElement { * @returns {ElementFinder} which identifies the located * {@link webdriver.WebElement} */ -export let build$ = (element: any, by: any) => { +export let build$ = (element: ElementHelper, by: ProtractorBy) => { return (selector: string) => { return element(by.css(selector)); }; }; @@ -1103,6 +1108,6 @@ export let build$ = (element: any, by: any) => { * @returns {ElementArrayFinder} which identifies the * array of the located {@link webdriver.WebElement}s. */ -export let build$$ = (element: any, by: any) => { +export let build$$ = (element: ElementHelper, by: ProtractorBy) => { return (selector: string) => { return element.all(by.css(selector)); }; }; diff --git a/lib/globals.d.ts b/lib/globals.d.ts index c08304764..15dfa85e7 100644 --- a/lib/globals.d.ts +++ b/lib/globals.d.ts @@ -30,6 +30,11 @@ declare namespace NodeJS { } } +declare interface IError extends Error { + code?: number; + stack?: string; +} + declare interface String { startsWith: Function; } declare namespace webdriver { diff --git a/package.json b/package.json index bea76ce7b..97aee97af 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ ], "author": "Julie Ralph ", "dependencies": { + "@types/jasmine": "^2.2.31", + "@types/node": "^6.0.35", "adm-zip": "0.4.7", "chalk": "^1.1.3", "glob": "^7.0.3", @@ -27,10 +29,8 @@ "devDependencies": { "@types/chalk": "^0.4.28", "@types/glob": "^5.0.29", - "@types/jasmine": "^2.2.31", "@types/minimatch": "^2.0.28", "@types/minimist": "^1.1.28", - "@types/node": "^6.0.35", "@types/optimist": "0.0.28", "@types/q": "0.0.29", "body-parser": "~1.15.2", diff --git a/spec/install/package.json b/spec/install/package.json index 8af03cdea..9fa388d52 100644 --- a/spec/install/package.json +++ b/spec/install/package.json @@ -10,8 +10,6 @@ "author": "", "license": "MIT", "dependencies": { - "@types/jasmine": "^2.2.33", - "@types/node": "^6.0.38", "protractor": "file:../../", "rimraf": "^2.5.4" } diff --git a/tsconfig.json b/tsconfig.json index ace706165..a27265ecc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,12 @@ "declaration": true, "removeComments": false, "noImplicitAny": true, - "outDir": "built/" + "outDir": "built/", + "types": [ + "jasmine", "node", + "chalk", "glob", "minimatch", + "minimist", "optimist", "q" + ] }, "exclude": [ "built",