Skip to content

Commit

Permalink
feat: Linux deb — specify license, package url #242
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Apr 2, 2016
1 parent b151ffc commit c62683a
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 65 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ osx_image: xcode7.3

env:
- NODE_VERSION=4
- NODE_VERSION=5.8
- NODE_VERSION=5

language: ruby

Expand All @@ -33,6 +33,7 @@ install:
- nvm install $NODE_VERSION
- npm install npm -g
- npm prune
- npm install Microsoft/TypeScript
- npm install

script:
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ install:
- node -v
- npm -v
- npm prune
- npm install Microsoft/TypeScript
- npm install

build: off
Expand Down
35 changes: 23 additions & 12 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,37 @@ Don't customize paths to background and icon, — just follow conventions (if yo
Here documented only `electron-builder` specific options:

<!-- do not edit. start of generated block -->
<a class="anchor" id="user-content-AppMetadata" href="#AppMetadata" aria-hidden="true"></a>

<a name="#AppMetadata"></a>
# Application `package.json`
| Name | Description
| --- | ---
| <a class="anchor" id="user-content-AppMetadata-name" href="#AppMetadata-name" aria-hidden="true"></a>name | The application name.
| <a class="anchor" id="user-content-AppMetadata-productName" href="#AppMetadata-productName" aria-hidden="true"></a>productName | <p>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}).</p>
<a class="anchor" id="user-content-DevMetadata" href="#DevMetadata" aria-hidden="true"></a>
| <a name="#AppMetadata-name"></a>name | The application name.
| <a name="#AppMetadata-productName"></a>productName | <p>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}).</p>

<a name="#DevMetadata"></a>
# Development `package.json`
| Name | Description
| --- | ---
| <a class="anchor" id="user-content-DevMetadata-homepage" href="#DevMetadata-homepage" aria-hidden="true"></a>homepage | The url to the project homepage (NuGet Package `projectUrl` or Linux Package URL).
| <a class="anchor" id="user-content-DevMetadata-build" href="#DevMetadata-build" aria-hidden="true"></a>build | See [BuildMetadata](#BuildMetadata).
<a class="anchor" id="user-content-BuildMetadata" href="#BuildMetadata" aria-hidden="true"></a>
| <a name="#DevMetadata-homepage"></a>homepage | <p>The url to the project [homepage](https://docs.npmjs.com/files/package.json#homepage) (NuGet Package <code>projectUrl</code> (optional) or Linux Package URL (required)).</p> <p>If not specified and your project repository is public on GitHub, it will be <code>https://github.com/${user}/${project}</code> by default.</p>
| <a name="#DevMetadata-license"></a>license | *linux-only.* The [license](https://docs.npmjs.com/files/package.json#license) name for this package.
| <a name="#DevMetadata-build"></a>build | See [.build](#BuildMetadata).

<a name="#BuildMetadata"></a>
## `.build`
| Name | Description
| --- | ---
| <a class="anchor" id="user-content-BuildMetadata-iconUrl" href="#BuildMetadata-iconUrl" aria-hidden="true"></a>iconUrl | <p>*windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel &gt; Programs and Features). Defaults to the Atom icon.</p> <p>Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.</p> <ul> <li>If you don’t plan to build windows installer, you can omit it.</li> <li>If your project repository is public on GitHub, it will be <code>https://raw.githubusercontent.com/${info.user}/${info.project}/master/build/icon.ico</code> by default.</li> </ul>
| <a class="anchor" id="user-content-BuildMetadata-productName" href="#BuildMetadata-productName" aria-hidden="true"></a>productName | See [AppMetadata.productName](#AppMetadata-productName).
| <a class="anchor" id="user-content-BuildMetadata-extraResources" href="#BuildMetadata-extraResources" aria-hidden="true"></a>extraResources | <p>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 (<code>Contents/Resources</code> for OS X).</p> <p>You can use <code>${os}</code> (expanded to osx, linux or win according to current platform) and <code>${arch}</code> in the pattern.</p> <p>If directory matched, all contents are copied. So, you can just specify <code>foo</code> to copy <code>&lt;project_dir&gt;/foo</code> directory.</p> <p>May be specified in the platform options (i.e. in the <code>build.osx</code>).</p>
| <a class="anchor" id="user-content-BuildMetadata-osx" href="#BuildMetadata-osx" aria-hidden="true"></a>osx | See [OS X options](https://www.npmjs.com/package/appdmg#json-specification)
| <a class="anchor" id="user-content-BuildMetadata-win" href="#BuildMetadata-win" aria-hidden="true"></a>win | See [windows-installer options](https://github.com/electronjs/windows-installer#usage)
| <a name="#BuildMetadata-iconUrl"></a>iconUrl | <p>*windows-only.* A URL to an ICO file to use as the application icon (displayed in Control Panel &gt; Programs and Features). Defaults to the Atom icon.</p> <p>Please note — [local icon file url is not accepted](https://github.com/atom/grunt-electron-installer/issues/73), must be https/http.</p> <ul> <li>If you don’t plan to build windows installer, you can omit it.</li> <li>If your project repository is public on GitHub, it will be <code>https://raw.githubusercontent.com/${user}/${project}/master/build/icon.ico</code> by default.</li> </ul>
| <a name="#BuildMetadata-productName"></a>productName | See [AppMetadata.productName](#AppMetadata-productName).
| <a name="#BuildMetadata-extraResources"></a>extraResources | <p>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 (<code>Contents/Resources</code> for OS X).</p> <p>You can use <code>${os}</code> (expanded to osx, linux or win according to current platform) and <code>${arch}</code> in the pattern.</p> <p>If directory matched, all contents are copied. So, you can just specify <code>foo</code> to copy <code>&lt;project_dir&gt;/foo</code> directory.</p> <p>May be specified in the platform options (i.e. in the <code>build.osx</code>).</p>
| <a name="#BuildMetadata-osx"></a>osx | See [OS X options](https://www.npmjs.com/package/appdmg#json-specification)
| <a name="#BuildMetadata-win"></a>win | See [windows-installer options](https://github.com/electronjs/windows-installer#usage)
| <a name="#BuildMetadata-linux"></a>linux | See [.linux](#DebOptions).

<a name="#DebOptions"></a>
### `.build.linux`
| Name | Description
| --- | ---
| <a name="#DebOptions-compression"></a>compression | *deb-only.* The compression type to use, must be one of gz, bzip2, xz. (default: `xz`)

<!-- end of generated block -->
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -85,15 +86,16 @@
"pre-commit": "^1.1.2",
"semantic-release": "^4.3.5",
"should": "^8.3.0",
"ts-babel": "^0.6.5",
"ts-babel": "^0.6.8",
"tsconfig-glob": "^0.4.3",
"tslint": "next",
"typescript": "^1.9.0-dev.20160325",
"typescript": "^1.9.0-dev.20160402",
"validate-commit-msg": "^2.5.0"
},
"babel": {
"plugins": [
"transform-es2015-parameters",
"transform-es2015-spread",
"array-includes"
]
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -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"
export { AppMetadata, DevMetadata, Platform, getProductName, BuildMetadata, DebOptions } from "./metadata"
44 changes: 23 additions & 21 deletions src/linuxPackager.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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<T>(value: T, task: (it: T) => void) {
if (value != null) {
task(value)
}
}

async function writeConfigFile(tempDir: string, templatePath: string, options: any): Promise<string> {
const config = template(await readFile(templatePath, "utf8"),
{
Expand All @@ -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
}
37 changes: 34 additions & 3 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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<string>
}
Expand Down
23 changes: 21 additions & 2 deletions src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
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
}
Expand Down Expand Up @@ -158,11 +158,30 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
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<any>

protected async computePackageUrl(): Promise<string> {
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 {
Expand Down
6 changes: 4 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
})
Expand Down
2 changes: 1 addition & 1 deletion src/winPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default class WinPackager extends PlatformPackager<WinBuildOptions> {
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,
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/test-app-one/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "electron ."
},
"author": "Foo Bar <foo@example.com>",
"license": "MIT",
"devDependencies": {
"electron-prebuilt": "^0.37.3"
},
Expand Down
60 changes: 40 additions & 20 deletions test/src/helpers/packTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,33 +106,53 @@ 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))
}
}
}

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<string>
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 <foo@example.com>",
Package: "testapp",
Description: "Test Application",
})
}

async function checkOsXResult(packager: Packager, artifacts: Array<ArtifactCreated>) {
const productName = getProductName(packager.metadata, packager.devMetadata)
const packedAppDir = path.join(path.dirname(artifacts[0].file), (productName || packager.metadata.name) + ".app")
Expand Down

0 comments on commit c62683a

Please sign in to comment.