diff --git a/package.json b/package.json index 7cebf6b0169..89b0a08d49c 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "debug": "^2.2.0", "deep-assign": "^2.0.0", "electron-osx-sign-tf": "0.6.0", - "electron-packager-tf": "~7.4.0", + "electron-packager-tf": "~7.5.1", "electron-winstaller-fixed": "~2.11.5", "fs-extra-p": "^1.0.3", "hosted-git-info": "^2.1.5", diff --git a/src/appInfo.ts b/src/appInfo.ts index 6f777f481ec..141e1da67e0 100644 --- a/src/appInfo.ts +++ b/src/appInfo.ts @@ -4,6 +4,7 @@ import { warn } from "./util/log" import { smarten } from "./platformPackager" import { isEmptyOrSpaces } from "./util/util" import { getRepositoryInfo } from "./repositoryInfo" +import sanitizeFileName = require("sanitize-filename") //noinspection JSUnusedLocalSymbols const __awaiter = require("./util/awaiter") @@ -23,6 +24,8 @@ export class AppInfo { readonly version: string readonly buildVersion: string + readonly productFilename: string + constructor(public metadata: AppMetadata, private devMetadata: DevMetadata) { let buildVersion = metadata.version this.version = buildVersion @@ -32,6 +35,8 @@ export class AppInfo { buildVersion += `.${buildNumber}` } this.buildVersion = buildVersion + + this.productFilename = sanitizeFileName(this.productName) } get companyName() { diff --git a/src/macPackager.ts b/src/macPackager.ts index d752c96a340..817775f4ff5 100644 --- a/src/macPackager.ts +++ b/src/macPackager.ts @@ -166,7 +166,7 @@ export default class MacPackager extends PlatformPackager { const identity = codeSigningInfo.name const baseSignOptions: BaseSignOptions = { - app: path.join(appOutDir, `${this.appInfo.productName}.app`), + app: path.join(appOutDir, `${this.appInfo.productFilename}.app`), platform: masOptions == null ? "darwin" : "mas", keychain: codeSigningInfo.keychainName, version: this.info.electronVersion @@ -202,7 +202,7 @@ export default class MacPackager extends PlatformPackager { await task(`Signing app (identity: ${identity})`, this.doSign(signOptions)) if (masOptions != null) { - const pkg = path.join(appOutDir, `${this.appInfo.productName}-${this.appInfo.version}.pkg`) + const pkg = path.join(appOutDir, `${this.appInfo.productFilename}-${this.appInfo.version}.pkg`) await this.doFlat(Object.assign({ pkg: pkg, identity: codeSigningInfo.installerName, diff --git a/src/platformPackager.ts b/src/platformPackager.ts index d5a04fccdc8..e6bb060733f 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -81,7 +81,6 @@ export abstract class PlatformPackager readonly projectDir: string readonly buildResourcesDir: string - readonly metadata: AppMetadata readonly devMetadata: DevMetadata readonly platformSpecificBuildOptions: DC @@ -96,7 +95,6 @@ export abstract class PlatformPackager this.appInfo = info.appInfo this.options = info.options this.projectDir = info.projectDir - this.metadata = info.appInfo.metadata this.devMetadata = info.devMetadata this.buildResourcesDir = path.resolve(this.projectDir, this.relativeBuildResourcesDirname) @@ -299,7 +297,7 @@ export abstract class PlatformPackager } private async doCopyExtraFiles(isResources: boolean, appOutDir: string, arch: Arch, customBuildOptions: DC): Promise { - const base = isResources ? this.getResourcesDir(appOutDir) : this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productName}.app`, "Contents") : appOutDir + const base = isResources ? this.getResourcesDir(appOutDir) : this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents") : appOutDir const patterns = this.getFilePatterns(isResources ? "extraResources" : "extraFiles", customBuildOptions) return patterns == null || patterns.length === 0 ? null : copyFiltered(this.projectDir, base, createFilter(this.projectDir, this.getParsedPatterns(patterns, arch))) } @@ -334,7 +332,7 @@ export abstract class PlatformPackager } private getOSXResourcesDir(appOutDir: string): string { - return path.join(appOutDir, `${this.appInfo.productName}.app`, "Contents", "Resources") + return path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents", "Resources") } private async checkFileInPackage(resourcesDir: string, file: string, isAsar: boolean) { @@ -363,12 +361,12 @@ export abstract class PlatformPackager throw new Error(`Output directory "${appOutDir}" is not a directory. Seems like a wrong configuration.`) } - const mainFile = this.metadata.main || "index.js" + const mainFile = this.appInfo.metadata.main || "index.js" await this.checkFileInPackage(this.getResourcesDir(appOutDir), mainFile, isAsar) } protected async archiveApp(format: string, appOutDir: string, outFile: string): Promise { - return archiveApp(this.devMetadata.build.compression, format, outFile, this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productName}.app`) : appOutDir) + return archiveApp(this.devMetadata.build.compression, format, outFile, this.platform === Platform.MAC ? path.join(appOutDir, `${this.appInfo.productFilename}.app`) : appOutDir) } generateName(ext: string, arch: Arch, deployment: boolean): string { @@ -387,7 +385,7 @@ export abstract class PlatformPackager } generateName2(ext: string, classifier: string | n, deployment: boolean): string { - return `${deployment ? this.appInfo.name : this.appInfo.productName}-${this.metadata.version}${classifier == null ? "" : `-${classifier}`}.${ext}` + return `${deployment ? this.appInfo.name : this.appInfo.productFilename}-${this.appInfo.version}${classifier == null ? "" : `-${classifier}`}.${ext}` } protected async getDefaultIcon(ext: string) { diff --git a/src/targets/dmg.ts b/src/targets/dmg.ts index 057ba48e9ee..2b48deb21a2 100644 --- a/src/targets/dmg.ts +++ b/src/targets/dmg.ts @@ -33,7 +33,7 @@ export class DmgTarget extends Target { async build(appOutDir: string) { const appInfo = this.packager.appInfo - const artifactPath = path.join(appOutDir, `${appInfo.productName}-${appInfo.version}.dmg`) + const artifactPath = path.join(appOutDir, `${appInfo.productFilename}-${appInfo.version}.dmg`) await new BluebirdPromise(async(resolve, reject) => { log("Creating DMG") const dmgOptions = { @@ -78,7 +78,7 @@ export class DmgTarget extends Target { } } - specification.contents[1].path = path.join(appOutDir, `${packager.appInfo.productName}.app`) + specification.contents[1].path = path.join(appOutDir, `${packager.appInfo.productFilename}.app`) return specification } } \ No newline at end of file diff --git a/src/targets/fpm.ts b/src/targets/fpm.ts index 2c263910dcd..d54ee09d5ea 100644 --- a/src/targets/fpm.ts +++ b/src/targets/fpm.ts @@ -88,7 +88,7 @@ export class FpmTarget extends Target { const templateOptions = Object.assign({ // old API compatibility - executable: this.packager.appInfo.productName, + executable: this.packager.appInfo.productFilename, }, this.packager.platformSpecificBuildOptions) const afterInstallTemplate = this.packager.platformSpecificBuildOptions.afterInstall || path.join(defaultTemplatesDir, "after-install.tpl") @@ -111,7 +111,7 @@ export class FpmTarget extends Target { } const options = this.options - const author = options.maintainer || `${packager.metadata.author.name} <${packager.metadata.author.email}>` + const author = options.maintainer || `${packager.appInfo.metadata.author.name} <${packager.appInfo.metadata.author.email}>` const synopsis = options.synopsis const args = [ "-s", "dir", @@ -163,28 +163,28 @@ export class FpmTarget extends Target { args.push("--depends", dep) } - use(packager.metadata.license || packager.devMetadata.license, it => args.push("--license", it!)) + use(packager.appInfo.metadata.license || packager.devMetadata.license, it => args.push("--license", it!)) use(appInfo.buildNumber, it => args.push("--iteration", it!)) use(options.fpm, it => args.push(...it)) - args.push(`${appOutDir}/=${installPrefix}/${appInfo.productName}`) + args.push(`${appOutDir}/=${installPrefix}/${appInfo.productFilename}`) args.push(...(await this.packageFiles)!) await exec(await this.fpmPath, args) } private async computeDesktop(tempDir: string): Promise> { const appInfo = this.packager.appInfo - const tempFile = path.join(tempDir, appInfo.productName + ".desktop") + const tempFile = path.join(tempDir, `${appInfo.productFilename}.desktop`) await outputFile(tempFile, this.packager.platformSpecificBuildOptions.desktop || `[Desktop Entry] Name=${appInfo.productName} Comment=${this.packager.platformSpecificBuildOptions.description || appInfo.description} -Exec="${installPrefix}/${appInfo.productName}/${appInfo.productName}" +Exec="${installPrefix}/${appInfo.productFilename}/${appInfo.productFilename}" Terminal=false Type=Application Icon=${appInfo.name} `) - return [`${tempFile}=/usr/share/applications/${appInfo.productName}.desktop`] + return [`${tempFile}=/usr/share/applications/${appInfo.productFilename}.desktop`] } private async createFromIcns(tempDir: string): Promise> { diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index 71ea1b21bf6..a6df2147dbf 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -9,7 +9,6 @@ import { Target } from "../platformPackager" import { archiveApp } from "./archive" import { subTask, task } from "../util/log" import { unlink } from "fs-extra-p" -import sanitizeFileName = require("sanitize-filename") import semver = require("semver") //noinspection JSUnusedLocalSymbols @@ -53,15 +52,14 @@ export default class NsisTarget extends Target { const iconPath = await packager.getIconPath() const appInfo = packager.appInfo const version = appInfo.version - const installerPath = path.join(this.outDir, `${appInfo.productName} Setup ${version}.exe`) + const installerPath = path.join(this.outDir, `${appInfo.productFilename} Setup ${version}.exe`) const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id}) - const productName = appInfo.productName const defines: any = { APP_ID: appInfo.id, APP_GUID: guid, - PRODUCT_NAME: productName, - INST_DIR_NAME: sanitizeFileName(productName), + PRODUCT_NAME: appInfo.productName, + INST_DIR_NAME: appInfo.productFilename, APP_DESCRIPTION: appInfo.description, VERSION: version, diff --git a/src/targets/squirrelWindows.ts b/src/targets/squirrelWindows.ts index 17ec18d7332..17f8e8bad23 100644 --- a/src/targets/squirrelWindows.ts +++ b/src/targets/squirrelWindows.ts @@ -23,7 +23,7 @@ export default class SquirrelWindowsTarget extends Target { const appInfo = this.packager.appInfo const version = appInfo.version const archSuffix = getArchSuffix(arch) - const setupFileName = `${appInfo.productName} Setup ${version}${archSuffix}.exe` + const setupFileName = `${appInfo.productFilename} Setup ${version}${archSuffix}.exe` const installerOutDir = path.join(appOutDir, "..", `win${getArchSuffix(arch)}`) await emptyDir(installerOutDir) @@ -45,7 +45,7 @@ export default class SquirrelWindowsTarget extends Target { const packager = this.packager let iconUrl = packager.platformSpecificBuildOptions.iconUrl || packager.devMetadata.build.iconUrl if (iconUrl == null) { - const info = await getRepositoryInfo(packager.metadata, packager.devMetadata) + const info = await getRepositoryInfo(packager.appInfo.metadata, packager.devMetadata) if (info != null) { iconUrl = `https://github.com/${info.user}/${info.project}/blob/master/${packager.relativeBuildResourcesDirname}/icon.ico?raw=true` } @@ -69,7 +69,7 @@ export default class SquirrelWindowsTarget extends Target { const options: any = Object.assign({ name: appInfo.name, productName: appInfo.productName, - exe: `${appInfo.productName}.exe`, + exe: `${appInfo.productFilename}.exe`, setupExe: setupExeName, msiExe: setupExeName.replace(".exe", ".msi"), title: appInfo.productName, diff --git a/src/winPackager.ts b/src/winPackager.ts index 0a7de7fb971..d70b2cbaefc 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -103,7 +103,7 @@ export class WinPackager extends PlatformPackager { const packOptions = await this.computePackOptions(outDir, appOutDir, arch) await this.doPack(packOptions, outDir, appOutDir, arch, this.platformSpecificBuildOptions) - await this.sign(path.join(appOutDir, `${this.appInfo.productName}.exe`)) + await this.sign(path.join(appOutDir, `${this.appInfo.productFilename}.exe`)) this.packageInDistributableFormat(outDir, appOutDir, arch, targets, postAsyncTasks) } diff --git a/test/fixtures/test-app-one/package.json b/test/fixtures/test-app-one/package.json index 4723672fa53..8d5d5021432 100755 --- a/test/fixtures/test-app-one/package.json +++ b/test/fixtures/test-app-one/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "TestApp", - "productName": "Test App", + "productName": "Test App A/B", "version": "1.1.0", "homepage": "http://foo.example.com", "description": "Test Application (test quite \" #378)", diff --git a/test/src/globTest.ts b/test/src/globTest.ts index 2c9ebefa48e..7acf7445a23 100644 --- a/test/src/globTest.ts +++ b/test/src/globTest.ts @@ -124,7 +124,7 @@ test("extraResources", async () => { packed: async (projectDir) => { const base = path.join(projectDir, outDirName, platform.buildConfigurationKey) let resourcesDir = path.join(base, "resources") - if (platform === Platform.OSX) { + if (platform === Platform.MAC) { resourcesDir = path.join(base, "TestApp.app", "Contents", "Resources") } else if (platform === Platform.WINDOWS) { diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index f64654636d2..d2190c48969 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -111,8 +111,8 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions } const nameToTarget = platformToTarget.get(platform) - if (platform === Platform.OSX) { - await checkOsXResult(packager, packagerOptions, checkOptions, artifacts.get(Platform.OSX)) + if (platform === Platform.MAC) { + await checkOsXResult(packager, packagerOptions, checkOptions, artifacts.get(Platform.MAC)) } else if (platform === Platform.LINUX) { await checkLinuxResult(projectDir, packager, checkOptions, artifacts.get(Platform.LINUX), arch) @@ -196,11 +196,10 @@ function parseDebControl(info: string): any { async function checkOsXResult(packager: Packager, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions, artifacts: Array) { const appInfo = packager.appInfo - const productName = appInfo.productName - const packedAppDir = path.join(path.dirname(artifacts[0].file), (productName || packager.metadata.name) + ".app") + const packedAppDir = path.join(path.dirname(artifacts[0].file), `${appInfo.productFilename}.app`) const info = parsePlist(await readFile(path.join(packedAppDir, "Contents", "Info.plist"), "utf8")) assertThat2(info).has.properties({ - CFBundleDisplayName: productName, + CFBundleDisplayName: appInfo.productName, CFBundleIdentifier: "org.electron-builder.testApp", LSApplicationCategoryType: "your.app.category.type", CFBundleVersion: `${appInfo.version}.${(process.env.TRAVIS_BUILD_NUMBER || process.env.CIRCLE_BUILD_NUM)}` @@ -217,8 +216,8 @@ async function checkOsXResult(packager: Packager, packagerOptions: PackagerOptio } else { assertThat(actualFiles).isEqualTo([ - `${productName}-${appInfo.version}-mac.zip`, - `${productName}-${appInfo.version}.dmg`, + `${appInfo.productFilename}-${appInfo.version}-mac.zip`, + `${appInfo.productFilename}-${appInfo.version}.dmg`, ].sort()) assertThat(artifacts.map(it => it.artifactName).sort()).isEqualTo([ @@ -234,7 +233,6 @@ function getFileNames(list: Array): Array { async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOptions, artifacts: Array, arch: Arch, nameToTarget: Map) { const appInfo = packager.appInfo - const productName = appInfo.productName let squirrel = false const artifactNames: Array = [] @@ -244,7 +242,7 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp for (let target of nameToTarget.keys()) { if (target === "squirrel") { squirrel = true - expectedFileNames.push("RELEASES", `${productName} Setup ${appInfo.version}${archSuffix}.exe`, `${appInfo.name}-${convertVersion(appInfo.version)}-full.nupkg`) + expectedFileNames.push("RELEASES", `${appInfo.productFilename} Setup ${appInfo.version}${archSuffix}.exe`, `${appInfo.name}-${convertVersion(appInfo.version)}-full.nupkg`) if (buildOptions != null && buildOptions.remoteReleases != null) { expectedFileNames.push(`${appInfo.name}-${convertVersion(appInfo.version)}-delta.nupkg`) @@ -253,11 +251,11 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp artifactNames.push(`${appInfo.name}-Setup-${appInfo.version}${archSuffix}.exe`) } else if (target === "nsis") { - expectedFileNames.push(`${productName} Setup ${appInfo.version}.exe`) + expectedFileNames.push(`${appInfo.productFilename} Setup ${appInfo.version}.exe`) artifactNames.push(`${appInfo.name}-Setup-${appInfo.version}.exe`) } else { - expectedFileNames.push(`${productName}-${appInfo.version}${archSuffix}-win.${target}`) + expectedFileNames.push(`${appInfo.productFilename}-${appInfo.version}${archSuffix}-win.${target}`) artifactNames.push(`${appInfo.name}-${appInfo.version}${archSuffix}-win.${target}`) } } @@ -280,7 +278,7 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp const expectedContents = checkOptions == null || checkOptions.expectedContents == null ? expectedWinContents : checkOptions.expectedContents assertThat(files).isEqualTo(expectedContents.map(it => { if (it === "lib/net45/TestApp.exe") { - return `lib/net45/${encodeURI(productName)}.exe` + return `lib/net45/${encodeURI(appInfo.productFilename)}.exe` } else { return it @@ -298,7 +296,7 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp TestApp ${convertVersion(appInfo.version)} - ${productName} + ${appInfo.productName} Foo Bar Foo Bar https://raw.githubusercontent.com/szwacz/electron-boilerplate/master/resources/windows/icon.ico @@ -343,7 +341,7 @@ export function signed(packagerOptions: PackagerOptions): PackagerOptions { export function getPossiblePlatforms(type?: string): Map> { const platforms = [Platform.fromString(process.platform)] - if (process.platform === Platform.OSX.nodeName) { + if (process.platform === Platform.MAC.nodeName) { if (process.env.LINUX_SKIP == null) { platforms.push(Platform.LINUX) } diff --git a/test/src/macPackagerTest.ts b/test/src/macPackagerTest.ts index 95ae9f5cadd..4a53bf60485 100644 --- a/test/src/macPackagerTest.ts +++ b/test/src/macPackagerTest.ts @@ -17,14 +17,14 @@ import { DmgTarget } from "out/targets/dmg" const __awaiter = require("out/util/awaiter") test.ifOsx("two-package", () => assertPack("test-app", signed({ - targets: createTargets([Platform.OSX], null, "all"), + targets: createTargets([Platform.MAC], null, "all"), }))) -test.ifOsx("one-package", () => assertPack("test-app-one", signed(platform(Platform.OSX)))) +test.ifOsx("one-package", () => assertPack("test-app-one", signed(platform(Platform.MAC)))) function createTargetTest(target: Array, expectedContents: Array) { let options: PackagerOptions = { - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), devMetadata: { build: { osx: { @@ -42,17 +42,17 @@ function createTargetTest(target: Array, expectedContents: Array }) } -test.ifOsx("only dmg", createTargetTest(["dmg"], ["Test App-1.1.0.dmg"])) -test.ifOsx("only zip", createTargetTest(["zip"], ["Test App-1.1.0-mac.zip"])) +test.ifOsx("only dmg", createTargetTest(["dmg"], ["Test App AB-1.1.0.dmg"])) +test.ifOsx("only zip", createTargetTest(["zip"], ["Test App AB-1.1.0-mac.zip"])) test.ifOsx("invalid target", (t: any) => t.throws(createTargetTest(["ttt"], [])(), "Unknown target: ttt")) -test.ifOsx("mas", createTargetTest(["mas"], ["Test App-1.1.0.pkg"])) -test.ifOsx("mas and 7z", createTargetTest(["mas", "7z"], ["Test App-1.1.0-mac.7z", "Test App-1.1.0.pkg"])) +test.ifOsx("mas", createTargetTest(["mas"], ["Test App AB-1.1.0.pkg"])) +test.ifOsx("mas and 7z", createTargetTest(["mas", "7z"], ["Test App AB-1.1.0-mac.7z", "Test App AB-1.1.0.pkg"])) test.ifOsx("custom mas", () => { let platformPackager: CheckingOsXPackager = null return assertPack("test-app-one", signed({ - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), devMetadata: { build: { @@ -81,7 +81,7 @@ test.ifOsx("custom mas", () => { test.ifOsx("entitlements in the package.json", () => { let platformPackager: CheckingOsXPackager = null return assertPack("test-app-one", signed({ - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), devMetadata: { build: { @@ -105,7 +105,7 @@ test.ifOsx("entitlements in the package.json", () => { test.ifOsx("entitlements in build dir", () => { let platformPackager: CheckingOsXPackager = null return assertPack("test-app-one", signed({ - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks), }), { tempDirCreated: projectDir => BluebirdPromise.all([ @@ -122,11 +122,11 @@ test.ifOsx("entitlements in build dir", () => { }) }) -test.ifOsx("no background", (t: any) => assertPack("test-app-one", platform(Platform.OSX), { +test.ifOsx("no background", (t: any) => assertPack("test-app-one", platform(Platform.MAC), { tempDirCreated: projectDir => deleteFile(path.join(projectDir, "build", "background.png")) })) -test.ifOsx("no build directory", (t: any) => assertPack("test-app-one", platform(Platform.OSX), { +test.ifOsx("no build directory", (t: any) => assertPack("test-app-one", platform(Platform.MAC), { tempDirCreated: projectDir => remove(path.join(projectDir, "build")) })) @@ -134,7 +134,7 @@ test.ifOsx("custom background - old way", () => { let platformPackager: CheckingOsXPackager = null const customBackground = "customBackground.png" return assertPack("test-app-one", { - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks) }, { tempDirCreated: projectDir => BluebirdPromise.all([ @@ -158,7 +158,7 @@ test.ifOsx("custom background - new way", () => { let platformPackager: CheckingOsXPackager = null const customBackground = "customBackground.png" return assertPack("test-app-one", { - targets: Platform.OSX.createTarget(), + targets: Platform.MAC.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingOsXPackager(packager, cleanupTasks) }, { tempDirCreated: projectDir => BluebirdPromise.all([