Skip to content

Commit

Permalink
feat: file mappings
Browse files Browse the repository at this point in the history
Close #1119
  • Loading branch information
develar committed Jan 31, 2017
1 parent 02a96f4 commit 55e2f0d
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 83 deletions.
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,29 @@ A complete solution to package and build a ready for distribution Electron app f
* [Native application dependencies](http://electron.atom.io/docs/latest/tutorial/using-native-node-modules/) compilation (including [Yarn](http://yarnpkg.com/) support).
* Development dependencies are never included. You don't need to ignore them explicitly.
* [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing) on a CI server or development machine.
* [Auto Update](#auto-update) ready application packaging.
* [Auto Update](https://github.com/electron-userland/electron-builder/wiki/Auto-Update) ready application packaging.
* [Build version management](https://github.com/electron-userland/electron-builder/wiki/Options#build-version-management).
* Numerous target formats:
* All platforms: `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`, `dir` (unpacked directory).
* [macOS](https://github.com/electron-userland/electron-builder/wiki/Options#MacOptions-target): `dmg`, `pkg`, `mas`.
* [Linux](https://github.com/electron-userland/electron-builder/wiki/Options#LinuxBuildOptions-target): [AppImage](http://appimage.org), [snap](http://snapcraft.io), debian package (`deb`), `rpm`, `freebsd`, `pacman`, `p5p`, `apk`.
* [Windows](https://github.com/electron-userland/electron-builder/wiki/Options#WinBuildOptions-target): NSIS, AppX (Windows Store), Squirrel.Windows.
* [Two package.json Structure](https://github.com/electron-userland/electron-builder/wiki/Two-package.json-Structure) is supported, but you are not forced to use it even if you have native production dependencies.
* [Two package.json structure](https://github.com/electron-userland/electron-builder/wiki/Two-package.json-Structure) is supported, but you are not forced to use it even if you have native production dependencies.
* [Publishing artifacts](https://github.com/electron-userland/electron-builder/wiki/Publishing-Artifacts) to GitHub Releases and Bintray.
* Pack in a distributable format [already packaged app](#pack-only-in-a-distributable-format).

_Note: Platform specific `7zip-bin-*` packages are `optionalDependencies`, which may require manual install if you have npm configured to [not install optional deps by default](https://docs.npmjs.com/misc/config#optional)._
| Question | Answer |
|--------|-------|
| “I want to configure electron-builder” | [See options](https://github.com/electron-userland/electron-builder/wiki/Options) |
| “I have a question” | [Open an issue](https://github.com/electron-userland/electron-builder/issues) or [join the chat](https://electron-builder.slack.com/shared_invite/MTMzMTc5NTcyNTAzLTE0ODUzNzE4MTctYzBhNGY1NjljYg) |
| “I found a bug” | [Open an issue](https://github.com/cyclejs/cyclejs/issues/new) |
| “I want to donate” | [Donate with PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W6V79R2RGCCHL) |

Real project example — [onshape-desktop-shell](https://github.com/develar/onshape-desktop-shell).

[Yarn](http://yarnpkg.com/) is recommended instead of npm.

## Configuration

See [options](https://github.com/electron-userland/electron-builder/wiki/Options) for a full reference but consider following the simple guide outlined below first.
[Yarn](http://yarnpkg.com/) is [strongly](https://github.com/electron-userland/electron-builder/issues/1147#issuecomment-276284477) recommended instead of npm.

For an app that will be shipped to production, you should sign your application. See [Where to buy code signing certificates](https://github.com/electron-userland/electron-builder/wiki/Code-Signing#where-to-buy-code-signing-certificate).
_Note: Platform specific `7zip-bin-*` packages are `optionalDependencies`, which may require manual install if you have npm configured to [not install optional deps by default](https://docs.npmjs.com/misc/config#optional)._

## Quick Setup Guide

Expand Down Expand Up @@ -69,6 +70,8 @@ For an app that will be shipped to production, you should sign your application.

Please note that everything is packaged into an asar archive [by default](https://github.com/electron-userland/electron-builder/wiki/Options#Config-asar).

For an app that will be shipped to production, you should sign your application. See [Where to buy code signing certificates](https://github.com/electron-userland/electron-builder/wiki/Code-Signing#where-to-buy-code-signing-certificate).

## Auto Update
`electron-builder` produces all required artifacts, for example, for macOS:

Expand Down Expand Up @@ -156,10 +159,6 @@ and other distributable formats.

`--project` (the path to project directory) option also can be useful.

## Donations

[Donate with PayPal.](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W6V79R2RGCCHL)

## Community

[electron-builder](https://electron-builder.slack.com/shared_invite/MTMzMTc5NTcyNTAzLTE0ODUzNzE4MTctYzBhNGY1NjljYg) on Slack (please use [threads](https://get.slack.help/hc/en-us/articles/115000769927-Message-threads)).
Expand Down
4 changes: 3 additions & 1 deletion docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ Note this only works for `extraFiles` and `extraResources`.
If `from` is given as a relative path, it is relative to the project directory.
If `to` is given as a relative path, it is relative to the app's content directory for `extraFiles` and the app's resource directory for `extraResources`.

You can you `${os}` and `${arch}` in the `from` and `to` fields as well.
`from` and `to` can be files and you can use this to [rename](https://github.com/electron-userland/electron-builder/issues/1119) a file while packaging.

You can use `${os}` and `${arch}` in the `from` and `to` fields as well.

## Build Version Management
`CFBundleVersion` (MacOS) and `FileVersion` (Windows) will be set automatically to `version`.`build_number` on CI server (Travis, AppVeyor and CircleCI supported).
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@
"devDependencies": {
"@types/electron": "^1.4.31",
"@types/ini": "^1.3.29",
"@types/jest": "^16.0.5",
"@types/jest": "^18.1.0",
"@types/js-yaml": "^3.5.29",
"@types/source-map-support": "^0.2.28",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-async-to-module-method": "^6.16.0",
"babel-plugin-transform-es2015-destructuring": "^6.19.0",
"babel-plugin-transform-es2015-parameters": "^6.21.0",
"babel-plugin-transform-es2015-spread": "^6.8.0",
"babel-plugin-transform-async-to-module-method": "^6.22.0",
"babel-plugin-transform-es2015-destructuring": "^6.22.0",
"babel-plugin-transform-es2015-parameters": "^6.22.0",
"babel-plugin-transform-es2015-spread": "^6.22.0",
"babel-plugin-transform-inline-imports-commonjs": "^1.2.0",
"convert-source-map": "^1.3.0",
"decompress-zip": "^0.3.0",
Expand All @@ -71,7 +71,7 @@
"jest-cli": "^18.1.0",
"jest-environment-node-debug": "^0.0.2",
"path-sort": "^0.1.0",
"source-map-support": "^0.4.10",
"source-map-support": "^0.4.11",
"ts-babel": "^1.3.5",
"tslint": "^4.4.2",
"typescript": "^2.1.5",
Expand Down
61 changes: 44 additions & 17 deletions packages/electron-builder/src/fileMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import * as path from "path"
import { createFilter, hasMagic } from "./util/filter"
import { Minimatch } from "minimatch"
import { asArray } from "electron-builder-util"
import { Filter } from "electron-builder-util/out/fs"

export interface FilePattern {
from?: string
to?: string
filter?: Array<string> | string
}
import BluebirdPromise from "bluebird-lst-c"
import { statOrNull, copyDir, copyFile, Filter } from "electron-builder-util/out/fs"
import { warn } from "electron-builder-util/out/log"
import { mkdirs } from "fs-extra-p"

export interface FileMatchOptions {
arch: string,
Expand Down Expand Up @@ -44,34 +41,39 @@ export class FileMatcher {
return !this.isEmpty() && this.patterns.find(it => !it.startsWith("!")) == null
}

getParsedPatterns(fromDir?: string): Array<Minimatch> {
computeParsedPatterns(result: Array<Minimatch>, fromDir?: string): void {
// https://github.com/electron-userland/electron-builder/issues/733
const minimatchOptions = {dot: true}

const parsedPatterns: Array<Minimatch> = []
const pathDifference = fromDir ? path.relative(fromDir, this.from) : null
const relativeFrom = fromDir == null ? null : path.relative(fromDir, this.from)

if (this.patterns.length === 0 && relativeFrom != null) {
// file mappings, from here is a file
result.push(new Minimatch(relativeFrom, minimatchOptions))
return
}

for (const p of this.patterns) {
let expandedPattern = this.expandPattern(p)
if (pathDifference != null) {
expandedPattern = path.join(pathDifference, expandedPattern)
if (relativeFrom != null) {
expandedPattern = path.join(relativeFrom, expandedPattern)
}

const parsedPattern = new Minimatch(expandedPattern, minimatchOptions)
parsedPatterns.push(parsedPattern)
result.push(parsedPattern)

if (!hasMagic(parsedPattern)) {
// https://github.com/electron-userland/electron-builder/issues/545
// add **/*
parsedPatterns.push(new Minimatch(`${expandedPattern}/**/*`, minimatchOptions))
result.push(new Minimatch(`${expandedPattern}/**/*`, minimatchOptions))
}
}

return parsedPatterns
}

createFilter(ignoreFiles?: Set<string>, rawFilter?: (file: string) => boolean, excludePatterns?: Array<Minimatch> | n): Filter {
return createFilter(this.from, this.getParsedPatterns(), ignoreFiles, rawFilter, excludePatterns)
const parsedPatterns: Array<Minimatch> = []
this.computeParsedPatterns(parsedPatterns)
return createFilter(this.from, parsedPatterns, ignoreFiles, rawFilter, excludePatterns)
}

private expandPattern(pattern: string): string {
Expand All @@ -82,6 +84,31 @@ export class FileMatcher {
}
}

export function copyFiles(patterns: Array<FileMatcher> | null): Promise<any> {
if (patterns == null || patterns.length === 0) {
return BluebirdPromise.resolve()
}

return BluebirdPromise.map(patterns, async pattern => {
const fromStat = await statOrNull(pattern.from)
if (fromStat == null) {
warn(`File source ${pattern.from} doesn't exist`)
return
}

if (fromStat.isFile()) {
await mkdirs(path.dirname(pattern.to))
return await copyFile(pattern.from, pattern.to, fromStat)
}

if (pattern.isEmpty() || pattern.containsOnlyIgnore()) {
pattern.addAllPattern()
}
return await copyDir(pattern.from, pattern.to, pattern.createFilter())
})
}


export function deprecatedUserIgnoreFilter(ignore: Array<RegExp> | ((file: string) => boolean), appDir: string) {
let ignoreFunc: any
if (typeof ignore === "function") {
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-builder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export { PackagerOptions, ArtifactCreated, BuildInfo, SourceRepositoryInfo } fro
export { getArchSuffix, Platform, Arch, archFromString, Target } from "electron-builder-core"
export { BuildOptions, build, CliOptions, createTargets } from "./builder"
export { PublishOptions, Publisher } from "./publish/publisher"
export { Metadata, Config, CompressionLevel } from "./metadata"
export { Metadata, Config, CompressionLevel, FilePattern } from "./metadata"
export { MacOptions, DmgOptions, MasBuildOptions, MacOsTargetName } from "./options/macOptions"
export { WinBuildOptions, NsisOptions, SquirrelWindowsOptions, AppXOptions } from "./options/winOptions"
export { LinuxBuildOptions } from "./options/linuxOptions"
Expand Down
14 changes: 10 additions & 4 deletions packages/electron-builder/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export interface AuthorMetadata {

export type CompressionLevel = "store" | "normal" | "maximum"

export interface FilePattern {
from?: string
to?: string
filter?: Array<string> | string
}

/*
## Configuration Options
*/
Expand Down Expand Up @@ -112,12 +118,12 @@ export interface Config extends PlatformSpecificBuildOptions {
Glob rules the same as for [files](#multiple-glob-patterns).
*/
readonly extraResources?: Array<string> | string | null
readonly extraResources?: Array<FilePattern> | FilePattern | Array<string> | string | null

/*
The same as [extraResources](#Config-extraResources) but copy into the app's content directory (`Contents` for MacOS, root directory for Linux/Windows).
*/
readonly extraFiles?: Array<string> | string | null
readonly extraFiles?: Array<FilePattern> | FilePattern | Array<string> | string | null

/*
Whether to package the application's source code into an archive, using [Electron's archive format](http://electron.atom.io/docs/tutorial/application-packaging/). Defaults to `true`.
Expand Down Expand Up @@ -310,8 +316,8 @@ export interface MetadataDirectories {

export interface PlatformSpecificBuildOptions {
readonly files?: Array<string> | string | null
readonly extraFiles?: Array<string> | string | null
readonly extraResources?: Array<string> | string | null
readonly extraFiles?: Array<FilePattern> | FilePattern | Array<string> | string | null
readonly extraResources?: Array<FilePattern> | FilePattern | Array<string> | string | null

readonly asarUnpack?: Array<string> | string | null

Expand Down
39 changes: 12 additions & 27 deletions packages/electron-builder/src/platformPackager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PlatformSpecificBuildOptions, FileAssociation, Config, AsarOptions } from "./metadata"
import { PlatformSpecificBuildOptions, FileAssociation, Config, AsarOptions, FilePattern } from "./metadata"
import BluebirdPromise from "bluebird-lst-c"
import * as path from "path"
import { readdir, remove, rename } from "fs-extra-p"
Expand All @@ -8,7 +8,7 @@ import { checkFileInArchive, createAsarArchive } from "./asarUtil"
import { warn, log } from "electron-builder-util/out/log"
import { AppInfo } from "./appInfo"
import { unpackElectron } from "./packager/dirPackager"
import { FileMatchOptions, FileMatcher, FilePattern, deprecatedUserIgnoreFilter } from "./fileMatcher"
import { FileMatchOptions, FileMatcher, deprecatedUserIgnoreFilter, copyFiles } from "./fileMatcher"
import { deepAssign } from "electron-builder-util/out/deepAssign"
import { statOrNull, unlinkIfExists, copyDir } from "electron-builder-util/out/fs"
import { Arch, Target, getArchSuffix, Platform } from "electron-builder-core"
Expand Down Expand Up @@ -174,21 +174,19 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
rawFilter = deprecatedUserIgnoreFilter(deprecatedIgnore, appDir)
}

let excludePatterns: Array<Minimatch> = []
const excludePatterns: Array<Minimatch> = []
if (extraResourceMatchers != null) {
for (let i = 0; i < extraResourceMatchers.length; i++) {
const patterns = extraResourceMatchers[i].getParsedPatterns(this.info.projectDir)
excludePatterns = excludePatterns.concat(patterns)
for (const matcher of extraResourceMatchers) {
matcher.computeParsedPatterns(excludePatterns, this.info.projectDir)
}
}
if (extraFileMatchers != null) {
for (let i = 0; i < extraFileMatchers.length; i++) {
const patterns = extraFileMatchers[i].getParsedPatterns(this.info.projectDir)
excludePatterns = excludePatterns.concat(patterns)
for (const matcher of extraFileMatchers) {
matcher.computeParsedPatterns(excludePatterns, this.info.projectDir)
}
}

const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length ? excludePatterns : null)
const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length > 0 ? excludePatterns : null)
let promise
if (asarOptions == null) {
promise = copyDir(appDir, path.join(resourcesPath, "app"), filter)
Expand All @@ -215,8 +213,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
await (<any>require("./packager/mac")).createApp(this, appOutDir)
}

await this.doCopyExtraFiles(extraResourceMatchers)
await this.doCopyExtraFiles(extraFileMatchers)
await copyFiles(extraResourceMatchers)
await copyFiles(extraFileMatchers)

await this.info.afterPack({
appOutDir: appOutDir,
Expand Down Expand Up @@ -274,19 +272,6 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
return deepAssign({}, result, defaultOptions)
}

private doCopyExtraFiles(patterns: Array<FileMatcher> | null): Promise<any> {
if (patterns == null || patterns.length === 0) {
return BluebirdPromise.resolve()
}

return BluebirdPromise.map(patterns, pattern => {
if (pattern.isEmpty() || pattern.containsOnlyIgnore()) {
pattern.addAllPattern()
}
return copyDir(pattern.from, pattern.to, pattern.createFilter())
})
}

private getFileMatchers(name: "files" | "extraFiles" | "extraResources" | "asarUnpack", defaultSrc: string, defaultDest: string, allowAdvancedMatching: boolean, fileMatchOptions: FileMatchOptions, customBuildOptions: DC): Array<FileMatcher> | null {
const globalPatterns: Array<string | FilePattern> | string | n | FilePattern = (<any>this.config)[name]
const platformSpecificPatterns: Array<string | FilePattern> | string | n = (<any>customBuildOptions)[name]
Expand All @@ -312,8 +297,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
defaultMatcher.addPattern(pattern)
}
else if (allowAdvancedMatching) {
const from = pattern.from ? (path.isAbsolute(pattern.from) ? pattern.from : path.join(defaultSrc, pattern.from)) : defaultSrc
const to = pattern.to ? (path.isAbsolute(pattern.to) ? pattern.to : path.join(defaultDest, pattern.to)) : defaultDest
const from = pattern.from == null ? defaultSrc : path.resolve(defaultSrc, pattern.from)
const to = pattern.to == null ? defaultDest : path.resolve(defaultDest, pattern.to)
fileMatchers.push(new FileMatcher(from, to, fileMatchOptions, pattern.filter))
}
else {
Expand Down
22 changes: 22 additions & 0 deletions test/src/filesTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ test.ifDevOrLinuxCi("files", app({
},
}))

test.ifDevOrLinuxCi("map resources", app({
targets: Platform.LINUX.createTarget(DIR_TARGET),
config: {
asar: false,
extraResources: {
from: "foo/old",
to: "foo/new",
},
}
}, {
projectDirCreated: projectDir => BluebirdPromise.all([
outputFile(path.join(projectDir, "foo", "old"), "data"),
]),
packed: context => {
const resources = path.join(context.getResources(Platform.LINUX))
return BluebirdPromise.all([
assertThat(path.join(resources, "app", "foo", "old")).doesNotExist(),
assertThat(path.join(resources, "foo", "new")).isFile(),
])
},
}))

test.ifNotCiWin("extraResources", async () => {
for (const platform of getPossiblePlatforms().keys()) {
const osName = platform.buildConfigurationKey
Expand Down
8 changes: 4 additions & 4 deletions test/src/helpers/fileAssert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { stat, lstat, Stats } from "fs-extra-p"
import { stat, lstat } from "fs-extra-p"
import * as path from "path"
import { exists } from "electron-builder-util/out/fs"

Expand All @@ -22,21 +22,21 @@ class Assertions {
}

async isFile() {
const info: Stats = await stat(this.actual)
const info = await stat(this.actual)
if (!info.isFile()) {
throw new Error(`Path ${this.actual} is not a file`)
}
}

async isSymbolicLink() {
const info: Stats = await lstat(this.actual)
const info = await lstat(this.actual)
if (!info.isSymbolicLink()) {
throw new Error(`Path ${this.actual} is not a symlink`)
}
}

async isDirectory() {
const info: Stats = await stat(this.actual)
const info = await stat(this.actual)
if (!info.isDirectory()) {
throw new Error(`Path ${this.actual} is not a directory`)
}
Expand Down
Loading

0 comments on commit 55e2f0d

Please sign in to comment.