diff --git a/.travis.yml b/.travis.yml index 7d9b4f19b55..8266f2499dd 100755 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ osx_image: xcode7.3 env: - NODE_VERSION=4 - - NODE_VERSION=5.8 + - NODE_VERSION=5 language: ruby @@ -32,8 +32,8 @@ before_install: install: - nvm install $NODE_VERSION - npm install npm -g -- npm prune - npm install +- npm prune script: - npm run test diff --git a/docs/options.md b/docs/options.md index 6164909b15e..0f6e766a863 100644 --- a/docs/options.md +++ b/docs/options.md @@ -31,26 +31,37 @@ Don't customize paths to background and icon, — just follow conventions (if yo Here documented only `electron-builder` specific options: - + + # Application `package.json` | Name | Description | --- | --- -| name | The application name. -| productName |

As [name](#AppMetadata-name), but allows you to specify a product name for your executable which contains spaces and other special characters not allowed in the [name property](https://docs.npmjs.com/files/package.json#name}).

- +| name | The application name. +| productName |

As [name](#AppMetadata-name), but allows you to specify a product name for your executable which contains spaces and other special characters not allowed in the [name property](https://docs.npmjs.com/files/package.json#name}).

+ + # Development `package.json` | Name | Description | --- | --- -| homepage | The url to the project homepage (NuGet Package `projectUrl` or Linux Package URL). -| build | See [BuildMetadata](#BuildMetadata). - +| homepage |

The url to the project [homepage](https://docs.npmjs.com/files/package.json#homepage) (NuGet Package projectUrl (optional) or Linux Package URL (required)).

If not specified and your project repository is public on GitHub, it will be https://github.com/${user}/${project} by default.

+| license | *linux-only.* The [license](https://docs.npmjs.com/files/package.json#license) name for this package. +| build | See [.build](#BuildMetadata). + + ## `.build` | Name | Description | --- | --- -| iconUrl |

*windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Atom icon.

Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.

-| productName | See [AppMetadata.productName](#AppMetadata-productName). -| extraResources |

A [glob expression](https://www.npmjs.com/package/glob#glob-primer), when specified, copy the file or directory with matching names directly into the app’s directory (Contents/Resources for OS X).

You can use ${os} (expanded to osx, linux or win according to current platform) and ${arch} in the pattern.

If directory matched, all contents are copied. So, you can just specify foo to copy <project_dir>/foo directory.

May be specified in the platform options (i.e. in the build.osx).

-| osx | See [OS X options](https://www.npmjs.com/package/appdmg#json-specification) -| win | See [windows-installer options](https://github.com/electronjs/windows-installer#usage) +| iconUrl |

*windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel > Programs and Features). Defaults to the Atom icon.

Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.

+| productName | See [AppMetadata.productName](#AppMetadata-productName). +| extraResources |

A [glob expression](https://www.npmjs.com/package/glob#glob-primer), when specified, copy the file or directory with matching names directly into the app’s directory (Contents/Resources for OS X).

You can use ${os} (expanded to osx, linux or win according to current platform) and ${arch} in the pattern.

If directory matched, all contents are copied. So, you can just specify foo to copy <project_dir>/foo directory.

May be specified in the platform options (i.e. in the build.osx).

+| osx | See [OS X options](https://www.npmjs.com/package/appdmg#json-specification) +| win | See [windows-installer options](https://github.com/electronjs/windows-installer#usage) +| linux | See [.linux](#DebOptions). + + +### `.build.linux` +| Name | Description +| --- | --- +| compression | *deb-only.* The compression type to use, must be one of gz, bzip2, xz. (default: `xz`) diff --git a/package.json b/package.json index c44872b7803..b759b7faf7e 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "ava-tf": "^0.12.4-beta.6", "babel-plugin-array-includes": "^2.0.3", "babel-plugin-transform-es2015-parameters": "^6.7.0", + "babel-plugin-transform-es2015-spread": "^6.6.5", "decompress-zip": "^0.3.0", "electron-download": "^2.1.0", "json-parse-helpfulerror": "^1.0.3", @@ -85,7 +86,7 @@ "pre-commit": "^1.1.2", "semantic-release": "^4.3.5", "should": "^8.3.0", - "ts-babel": "^0.6.5", + "ts-babel": "^0.6.7", "tsconfig-glob": "^0.4.3", "tslint": "next", "typescript": "^1.9.0-dev.20160325", @@ -94,6 +95,7 @@ "babel": { "plugins": [ "transform-es2015-parameters", + "transform-es2015-spread", "array-includes" ] }, diff --git a/src/index.ts b/src/index.ts index a92758314c4..c030e46f500 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { Packager } from "./packager" export { PackagerOptions, ArtifactCreated } from "./platformPackager" export { BuildOptions, build, createPublisher } from "./builder" -export { AppMetadata, DevMetadata, Platform, getProductName } from "./metadata" \ No newline at end of file +export { AppMetadata, DevMetadata, Platform, getProductName, BuildMetadata, DebOptions } from "./metadata" \ No newline at end of file diff --git a/src/linuxPackager.ts b/src/linuxPackager.ts index 3c2007fbf33..e5b2d30fcf8 100755 --- a/src/linuxPackager.ts +++ b/src/linuxPackager.ts @@ -1,7 +1,7 @@ import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" import { PlatformPackager, BuildInfo } from "./platformPackager" -import { Platform } from "./metadata" +import { Platform, DebOptions } from "./metadata" import { dir as _tpmDir, TmpOptions } from "tmp" import { exec, log } from "./util" import { outputFile, readFile, readdir } from "fs-extra-p" @@ -172,7 +172,13 @@ Icon=${this.metadata.name} const target = "deb" const destination = path.join(outDir, `${this.metadata.name}-${this.metadata.version}-${archName}.${target}`) const scripts = await this.scriptFiles - await exec("fpm", [ + + const projectUrl = await this.computePackageUrl() + if (projectUrl == null) { + throw new Error("Please specify project homepage") + } + + const args = [ "-s", "dir", "-t", target, "--architecture", archName, @@ -186,12 +192,25 @@ Icon=${this.metadata.name} "--version", this.metadata.version, "--package", destination, "--deb-compression", options.compression || "xz", - appOutDir + "/=/opt/" + this.appName, - ].concat(await this.packageFiles)) + "--url", projectUrl, + ] + + use(this.devMetadata.license, it => args.push("--license", it)) + use(this.computeBuildNumber(), it => args.push("--iteration", it)) + + args.push(`${appOutDir}/=/opt/${this.appName}`) + args.push(...(await this.packageFiles)) + await exec("fpm", args) return destination } } +function use(value: T, task: (it: T) => void) { + if (value != null) { + task(value) + } +} + async function writeConfigFile(tempDir: string, templatePath: string, options: any): Promise { const config = template(await readFile(templatePath, "utf8"), { @@ -202,21 +221,4 @@ async function writeConfigFile(tempDir: string, templatePath: string, options: a const outputPath = path.join(tempDir, path.basename(templatePath, ".tpl")) await outputFile(outputPath, config) return outputPath -} - -export interface DebOptions { - name: string - comment: string - - maintainer: string - - /** - * .desktop file template - */ - desktop?: string - - afterInstall?: string - afterRemove?: string - - compression?: string } \ No newline at end of file diff --git a/src/metadata.ts b/src/metadata.ts index 5a140899580..8b3cfac0a9d 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -29,12 +29,19 @@ export interface AppMetadata extends Metadata { */ export interface DevMetadata extends Metadata { /** - The url to the project homepage (NuGet Package `projectUrl` or Linux Package URL). + The url to the project [homepage](https://docs.npmjs.com/files/package.json#homepage) (NuGet Package `projectUrl` (optional) or Linux Package URL (required)). + + If not specified and your project repository is public on GitHub, it will be `https://github.com/${user}/${project}` by default. */ readonly homepage?: string /** - See [BuildMetadata](#BuildMetadata). + *linux-only.* The [license](https://docs.npmjs.com/files/package.json#license) name for this package. + */ + readonly license?: string + + /** + See [.build](#BuildMetadata). */ readonly build?: BuildMetadata @@ -67,7 +74,7 @@ export interface BuildMetadata { Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http. * If you don't plan to build windows installer, you can omit it. - * If your project repository is public on GitHub, it will be `https://raw.githubusercontent.com/${info.user}/${info.project}/master/build/icon.ico` by default. + * If your project repository is public on GitHub, it will be `https://raw.githubusercontent.com/${user}/${project}/master/build/icon.ico` by default. */ readonly iconUrl: string @@ -97,9 +104,33 @@ export interface BuildMetadata { */ readonly win?: any, + /** + See [.linux](#DebOptions). + */ readonly linux?: any } +/** + ### `.build.linux` + */ +export interface DebOptions { + name: string + comment: string + + maintainer: string + + //.desktop file template + desktop?: string + + afterInstall?: string + afterRemove?: string + + /* + *deb-only.* The compression type to use, must be one of gz, bzip2, xz. (default: `xz`) + */ + readonly compression?: string +} + export interface PlatformSpecificBuildOptions { readonly extraResources?: Array } diff --git a/src/platformPackager.ts b/src/platformPackager.ts index d721f37ff1d..02540c20c0a 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -96,7 +96,7 @@ export abstract class PlatformPackager protected async doPack(outDir: string, arch: string) { const version = this.metadata.version let buildVersion = version - const buildNumber = process.env.TRAVIS_BUILD_NUMBER || process.env.APPVEYOR_BUILD_NUMBER || process.env.CIRCLE_BUILD_NUM + const buildNumber = this.computeBuildNumber() if (buildNumber != null) { buildVersion += "." + buildNumber } @@ -158,11 +158,30 @@ export abstract class PlatformPackager if (this.platform === Platform.OSX) { resourcesDir = path.join(resourcesDir, this.appName + ".app", "Contents", "Resources") } - return await BluebirdPromise.map(await this.getExtraResources(arch), it => copy(path.join(this.projectDir, it), path.join(resourcesDir, it))) } abstract packageInDistributableFormat(outDir: string, appOutDir: string, arch: string): Promise + + protected async computePackageUrl(): Promise { + const url = this.devMetadata.homepage + if (url != null) { + return url + } + + if (this.info.repositoryInfo != null) { + const info = await this.info.repositoryInfo.getInfo(this) + if (info != null) { + return `https://github.com/${info.user}/${info.project}` + } + } + return null + } + + //noinspection JSMethodCanBeStatic + protected computeBuildNumber(): string { + return process.env.TRAVIS_BUILD_NUMBER || process.env.APPVEYOR_BUILD_NUMBER || process.env.CIRCLE_BUILD_NUM + } } function checkConflictingOptions(options: any): void { diff --git a/src/util.ts b/src/util.ts index 62c34b7193f..3e5ef679e5e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -79,9 +79,11 @@ export function exec(file: string, args?: string[], options?: ExecOptions): Blue console.error(stdout.toString()) } if (stderr.length !== 0) { - console.error(stderr.toString()) + reject(new Error(stderr.toString() + "\n" + error.toString())) + } + else { + reject(error) } - reject(error) } }) }) diff --git a/src/winPackager.ts b/src/winPackager.ts index 3fae7e4e20b..d674f1649c0 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -115,7 +115,7 @@ export default class WinPackager extends PlatformPackager { const version = this.metadata.version const installerOutDir = WinPackager.computeDistOut(outDir, arch) const archSuffix = arch === "x64" ? "" : ("-" + arch) - const projectUrl = this.devMetadata.homepage + const projectUrl = await this.computePackageUrl() const options = Object.assign({ name: this.metadata.name, diff --git a/test/fixtures/test-app-one/package.json b/test/fixtures/test-app-one/package.json index a65af155d69..5b4199af852 100755 --- a/test/fixtures/test-app-one/package.json +++ b/test/fixtures/test-app-one/package.json @@ -8,6 +8,7 @@ "start": "electron ." }, "author": "Foo Bar ", + "license": "MIT", "devDependencies": { "electron-prebuilt": "^0.37.3" }, diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index a54952e0d60..46e81195955 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -106,26 +106,7 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions await checkOsXResult(packager, artifacts.get(Platform.OSX)) } else if (platform === "linux") { - const productName = getProductName(packager.metadata, packager.devMetadata) - const expectedContents = expectedLinuxContents.map(it => { - if (it === "/opt/TestApp/TestApp") { - return "/opt/" + productName + "/" + productName - } - else if (it === "/usr/share/applications/TestApp.desktop") { - return `/usr/share/applications/${productName}.desktop` - } - else { - return it.replace(new RegExp("/opt/TestApp/", "g"), `/opt/${productName}/`) - } - }) - - // console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb", productName), null, 2)) - // console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName), null, 2)) - - assertThat(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb", productName)).deepEqual(expectedContents) - if (packagerOptions.arch === "all" || packagerOptions.arch === "ia32") { - assertThat(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName)).deepEqual(expectedContents) - } + await checkLinuxResult(projectDir, packager, packagerOptions) } else if (platform === "win32") { await checkWindowsResult(packager, packagerOptions, checkOptions, artifacts.get(Platform.WINDOWS)) @@ -133,6 +114,45 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions } } +async function checkLinuxResult(projectDir: string, packager: Packager, packagerOptions: PackagerOptions) { + const productName = getProductName(packager.metadata, packager.devMetadata) + const expectedContents = expectedLinuxContents.map(it => { + if (it === "/opt/TestApp/TestApp") { + return "/opt/" + productName + "/" + productName + } + else if (it === "/usr/share/applications/TestApp.desktop") { + return `/usr/share/applications/${productName}.desktop` + } + else { + return it.replace(new RegExp("/opt/TestApp/", "g"), `/opt/${productName}/`) + } + }) + + // console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-amd64.deb", productName), null, 2)) + // console.log(JSON.stringify(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName), null, 2)) + + const packageFile = projectDir + "/dist/TestApp-1.0.0-amd64.deb" + assertThat(await getContents(packageFile, productName)).deepEqual(expectedContents) + if (packagerOptions.arch === "all" || packagerOptions.arch === "ia32") { + assertThat(await getContents(projectDir + "/dist/TestApp-1.0.0-i386.deb", productName)).deepEqual(expectedContents) + } + + const regexp = /^ *(\w+): *(.+)$/gm + const info = (await exec("dpkg", ["--info", packageFile])).toString() + let match: Array + const metadata: any = {} + while ((match = regexp.exec(info)) !== null) { + metadata[match[1]] = match[2] + } + assertThat(metadata).has.properties({ + License: "MIT", + Homepage: "http://foo.example.com", + Maintainer: "Foo Bar ", + Package: "testapp", + Description: "Test Application", + }) +} + async function checkOsXResult(packager: Packager, artifacts: Array) { const productName = getProductName(packager.metadata, packager.devMetadata) const packedAppDir = path.join(path.dirname(artifacts[0].file), (productName || packager.metadata.name) + ".app")