Skip to content

Commit

Permalink
feat: --extraMetadata for conditional compilation
Browse files Browse the repository at this point in the history
Closes #494
  • Loading branch information
develar committed Jul 31, 2016
1 parent d773077 commit da700d4
Show file tree
Hide file tree
Showing 20 changed files with 168 additions and 73 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cache:
directories:
- node_modules
- $HOME/.electron
- "test/fixtures/app-executable-deps/app/node_modules"

before_install:
- curl -L https://dl.bintray.com/develar/bin/7za -o /tmp/7za
Expand All @@ -32,6 +33,7 @@ install:
- if [[ "$TRAVIS_OS_NAME" == "osx" && "$NODE_VERSION" == "4" ]]; then npm install npm -g ; fi
- npm install
- npm prune
- (cd test/fixtures/app-executable-deps/app && npm install && npm prune)

script:
- sudo ntpdate -u time.apple.com
Expand Down
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,16 @@ For windows consider only [distributing 64-bit versions](https://github.com/elec
Execute `node_modules/.bin/build --help` to get actual CLI usage guide.
```
Building:
--mac, -m, -o, --osx Build for MacOS, accepts target list (see
https://goo.gl/HAnnq8). [array]
--linux, -l Build for Linux, accepts target list (see
https://goo.gl/O80IL2) [array]
--win, -w, --windows Build for Windows, accepts target list (see
https://goo.gl/dL4i8i) [array]
--x64 Build for x64 [boolean]
--ia32 Build for ia32 [boolean]
--dir Build unpacked dir. Useful to test. [boolean]
--mac, -m, -o, --osx, --macos Build for MacOS, accepts target list (see
https://goo.gl/HAnnq8). [array]
--linux, -l Build for Linux, accepts target list (see
https://goo.gl/O80IL2) [array]
--win, -w, --windows Build for Windows, accepts target list (see
https://goo.gl/dL4i8i) [array]
--x64 Build for x64 [boolean]
--ia32 Build for ia32 [boolean]
--dir Build unpacked dir. Useful to test. [boolean]
--extraMetadata, --em Inject properties to application package.json

Publishing:
--publish, -p Publish artifacts (to GitHub Releases), see
Expand All @@ -128,6 +129,7 @@ Examples:
build -mwl build for MacOS, Windows and Linux
build --linux deb tar.xz build deb and tar.xz for Linux
build --win --ia32 build for Windows ia32
build --em.foo=bar set application package.json property `foo` to `bar`
```
# Programmatic Usage
Expand Down
2 changes: 1 addition & 1 deletion docs/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Here documented only `electron-builder` specific options:
| --- | ---
| appId | <a name="BuildMetadata-appId"></a><p>The application id. Used as [CFBundleIdentifier](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070) for MacOS and as [Application User Model ID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx) for Windows.</p> <p>For windows only NSIS target supports it. Squirrel.Windows is not fixed yet.</p> <p>Defaults to <code>com.electron.${name}</code>. It is strongly recommended that an explicit ID be set.</p>
| app-category-type | <a name="BuildMetadata-app-category-type"></a><p>*macOS-only.* The application category type, as shown in the Finder via *View -&gt; Arrange by Application Category* when viewing the Applications directory.</p> <p>For example, <code>app-category-type=public.app-category.developer-tools</code> will set the application category to *Developer Tools*.</p> <p>Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).</p>
| asar | <a name="BuildMetadata-asar"></a><p>Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to <code>true</code>. Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron’s documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).</p> <p>Or you can pass object of any asar options.</p>
| asar | <a name="BuildMetadata-asar"></a><p>Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to <code>true</code>. Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron’s documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).</p> <p>Or you can pass object of any asar options.</p> <p>electron-builder detects node modules that must be unpacked automatically, you don’t need to explicitly set <code>asar.unpackDir</code> - please file issue if this doesn’t work.</p>
| productName | <a name="BuildMetadata-productName"></a>See [AppMetadata.productName](#AppMetadata-productName).
| files | <a name="BuildMetadata-files"></a><p>A [glob patterns](https://www.npmjs.com/package/glob#glob-primer) relative to the [app directory](#MetadataDirectories-app), which specifies which files to include when copying files to create the package. Defaults to <code>**\/\*</code> (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).</p> <p>Development dependencies are never copied in any case. You don’t need to ignore it explicitly.</p> <p>[Multiple patterns](#multiple-glob-patterns) are supported. You can use <code>${os}</code> (expanded to mac, linux or win according to current platform) and <code>${arch}</code> in the pattern. If directory matched, all contents are copied. So, you can just specify <code>foo</code> to copy <code>foo</code> directory.</p> <p>Remember that default pattern <code>\*\*\/\*</code> is not added to your custom, so, you have to add it explicitly — e.g. <code>[&quot;\*\*\/\*&quot;, &quot;!ignoreMe${/\*}&quot;]</code>.</p> <p>May be specified in the platform options (e.g. in the <code>build.mac</code>).</p>
| extraResources | <a name="BuildMetadata-extraResources"></a><p>A [glob patterns](https://www.npmjs.com/package/glob#glob-primer) relative to the project directory, when specified, copy the file or directory with matching names directly into the app’s resources directory (<code>Contents/Resources</code> for MacOS, <code>resources</code> for Linux/Windows).</p> <p>Glob rules the same as for [files](#BuildMetadata-files).</p>
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"chalk": "^1.1.3",
"chromium-pickle-js": "^0.1.0",
"cli-cursor": "^1.0.2",
"cuint": "^0.2.1",
"debug": "^2.2.0",
"electron-download": "^2.1.2",
"electron-osx-sign": "^0.4.0-beta4",
Expand Down Expand Up @@ -105,13 +106,13 @@
]
},
"devDependencies": {
"@develar/semantic-release": "^6.3.1",
"@develar/semantic-release": "^6.3.2",
"@types/debug": "0.0.28",
"@types/mime": "0.0.28",
"@types/progress": "^1.1.27",
"@types/semver": "^4.3.26",
"@types/source-map-support": "^0.2.27",
"ava-tf": "^0.15.3",
"ava-tf": "^0.15.4",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-es2015-destructuring": "^6.9.0",
"babel-plugin-transform-es2015-parameters": "^6.11.4",
Expand Down
94 changes: 60 additions & 34 deletions src/asarUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { AsarFileInfo, listPackage, statFile, AsarOptions } from "asar-electron-builder"
import { statOrNull, debug } from "./util/util"
import { lstat, readdir, readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson } from "fs-extra-p"
import {
lstat, readdir, readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson,
writeFile
} from "fs-extra-p"
import { Promise as BluebirdPromise } from "bluebird"
import * as path from "path"
import pathSorter = require("path-sort")
Expand All @@ -10,6 +13,7 @@ import { Minimatch } from "minimatch"
const isBinaryFile: any = BluebirdPromise.promisify(require("isbinaryfile"))
const pickle = require ("chromium-pickle-js")
const Filesystem = require("asar-electron-builder/lib/filesystem")
const UINT64 = require("cuint").UINT64

//noinspection JSUnusedLocalSymbols
const __awaiter = require("./util/awaiter")
Expand Down Expand Up @@ -123,10 +127,11 @@ async function order(src: string, filenames: Array<string>, options: any) {
return filenamesSorted
}

async function detectUnpackedDirs(src: string, files: Array<string>, metadata: Map<string, Stats>, autoUnpackDirs: Set<string>, createDirPromises: Array<Promise<any>>, unpackedDest: string, packageFileToData: Map<string, BluebirdPromise<string>>) {
async function detectUnpackedDirs(src: string, files: Array<string>, metadata: Map<string, Stats>, autoUnpackDirs: Set<string>, createDirPromises: Array<Promise<any>>, unpackedDest: string, fileIndexToModulePackageData: Array<BluebirdPromise<string>>) {
const packageJsonStringLength = "package.json".length
const readPackageJsonPromises: Array<Promise<any>> = []
for (let file of files) {
for (let i = 0, n = files.length; i < n; i++) {
const file = files[i]
const index = file.lastIndexOf(NODE_MODULES_PATTERN)
if (index < 0) {
continue
Expand All @@ -151,7 +156,7 @@ async function detectUnpackedDirs(src: string, files: Array<string>, metadata: M
readPackageJsonPromises.length = 0
}
readPackageJsonPromises.push(promise)
packageFileToData.set(file, promise)
fileIndexToModulePackageData[i] = promise
}

if (autoUnpackDirs.has(nodeModuleDir)) {
Expand Down Expand Up @@ -200,17 +205,17 @@ async function detectUnpackedDirs(src: string, files: Array<string>, metadata: M
}
}

async function createPackageFromFiles(src: string, dest: string, files: Array<string>, metadata: Map<string, Stats>, options: any) {
async function createPackageFromFiles(src: string, dest: string, files: Array<string>, metadata: Map<string, Stats>, options: AsarOptions) {
// search auto unpacked dir
const autoUnpackDirs = new Set<string>()

const createDirPromises: Array<Promise<any>> = [ensureDir(path.dirname(dest))]
const unpackedDest = `${dest}.unpacked`
const changedFiles = new Map<string, string>()

const packageFileToData = new Map<string, BluebirdPromise<string>>()
const fileIndexToModulePackageData: Array<BluebirdPromise<string>> = new Array(files.length)
if (options.smartUnpack !== false) {
await detectUnpackedDirs(src, files, metadata, autoUnpackDirs, createDirPromises, unpackedDest, packageFileToData)
await detectUnpackedDirs(src, files, metadata, autoUnpackDirs, createDirPromises, unpackedDest, fileIndexToModulePackageData)
}

const unpackDir = options.unpackDir == null ? null : new Minimatch(options.unpackDir)
Expand All @@ -221,54 +226,75 @@ async function createPackageFromFiles(src: string, dest: string, files: Array<st
const toPack: Array<string> = []
const filesystem = new Filesystem(src)
const copyPromises: Array<Promise<any>> = []
for (let file of files) {
const mainPackageJson = path.join(src, "package.json")
for (let i = 0, n = files.length; i < n; i++) {
const file = files[i]
const stat = metadata.get(file)!
if (stat.isFile()) {
const dir = path.dirname(file)
const fileParent = path.dirname(file)
const dirNode = filesystem.searchNodeFromPath(fileParent)

let shouldUnpack = unpack != null && unpack.match(file)
if (shouldUnpack) {
const fileParent = path.dirname(file)
if (!autoUnpackDirs.has(fileParent)) {
// create parent dir to be able to copy file later without directory existence check
createDirPromises.push(ensureDir(path.join(unpackedDest, path.relative(src, fileParent))))
if (dirNode.unpacked && createDirPromises.length > 0) {
await BluebirdPromise.all(createDirPromises)
createDirPromises.length = 0
}

const packageDataPromise = fileIndexToModulePackageData[i]
let newData: any | null = null
if (packageDataPromise == null) {
if (options.extraMetadata != null && file === mainPackageJson) {
newData = JSON.stringify(Object.assign(await readJson(file), options.extraMetadata), null, 2)
}
}
else {
shouldUnpack = autoUnpackDirs.has(dir) || (unpackDir != null && isUnpackDir(path.relative(src, dir), unpackDir, options.unpackDir))
newData = cleanupPackageJson(packageDataPromise.value())
}

if (shouldUnpack) {
if (createDirPromises.length > 0) {
const fileSize = newData == null ? stat.size : Buffer.byteLength(newData)
const node = filesystem.searchNodeFromPath(file)
node.size = fileSize
if (dirNode.unpacked || (unpack != null && unpack.match(file))) {
node.unpacked = true

if (!dirNode.unpacked) {
createDirPromises.push(ensureDir(path.join(unpackedDest, path.relative(src, fileParent))))
await BluebirdPromise.all(createDirPromises)
createDirPromises.length = 0
}

copyPromises.push(copyFile(file, path.join(unpackedDest, path.relative(src, file)), stat))
// limit concurrency
const unpackedFile = path.join(unpackedDest, path.relative(src, file))
copyPromises.push(newData == null ? copyFile(file, unpackedFile, stat) : writeFile(unpackedFile, newData))
if (copyPromises.length > MAX_FILE_REQUESTS) {
await BluebirdPromise.all(copyPromises)
copyPromises.length = 0
}
}
else {
toPack.push(file)
}
if (newData != null) {
changedFiles.set(file, newData)
}

if (fileSize > 4294967295) {
throw new Error(`${file}: file size can not be larger than 4.2GB`)
}

const packageDataPromise = packageFileToData.get(file)
if (packageDataPromise != null) {
cleanupPackageJson(file, stat, packageDataPromise.value(), changedFiles)
node.offset = filesystem.offset.toString()
//noinspection JSBitwiseOperatorUsage
if (process.platform !== "win32" && stat.mode & 0x40) {
node.executable = true
}
toPack.push(file)
}

filesystem.insertFile(file, shouldUnpack, stat)
filesystem.offset.add(UINT64(fileSize))
}
else if (stat.isDirectory()) {
let unpacked = false
if (autoUnpackDirs.has(file)) {
unpacked = true
}
else {
unpacked = unpackDir != null && isUnpackDir(path.relative(src, file), unpackDir, options.unpackDir)
unpacked = unpackDir != null && isUnpackDir(path.relative(src, file), unpackDir, options.unpackDir!)
if (unpacked) {
createDirPromises.push(ensureDir(path.join(unpackedDest, path.relative(src, file))))
}
Expand Down Expand Up @@ -296,25 +322,25 @@ async function createPackageFromFiles(src: string, dest: string, files: Array<st
await writeAsarFile(filesystem, dest, toPack, changedFiles)
}

function cleanupPackageJson(file: string, stat: Stats, data: any, changedFiles: Map<string, string>) {
function cleanupPackageJson(data: any): any {
try {
let writeFile = false
let changed = false
for (let prop of Object.getOwnPropertyNames(data)) {
if (prop[0] === "_" || prop === "dist" || prop === "gitHead" || prop === "keywords") {
delete data[prop]
writeFile = true
changed = true
}
}

if (writeFile) {
const value = JSON.stringify(data, null, 2)
changedFiles.set(file, value)
stat.size = Buffer.byteLength(value)
if (changed) {
return JSON.stringify(data, null, 2)
}
}
catch (e) {
debug(e)
}

return null
}

function writeAsarFile(filesystem: any, dest: string, toPack: Array<string>, changedFiles: Map<string, string>): Promise<any> {
Expand Down
2 changes: 2 additions & 0 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ export function normalizeOptions(args: CliOptions): BuildOptions {
delete result.arch

const r = <any>result
delete r.em

delete r.m
delete r.o
delete r.l
Expand Down
7 changes: 7 additions & 0 deletions src/cliOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ const buildGroup = "Building:"
const deprecated = "Deprecated:"

export function createYargs(): any {
//noinspection ReservedWordAsName
return yargs
.example("build -mwl", "build for MacOS, Windows and Linux")
.example("build --linux deb tar.xz", "build deb and tar.xz for Linux")
.example("build --win --ia32", "build for Windows ia32")
.example("build --em.foo=bar", "set application package.json property `foo` to `bar`")
.option("mac", {
group: buildGroup,
alias: ["m", "o", "osx", "macos"],
Expand Down Expand Up @@ -71,6 +73,11 @@ export function createYargs(): any {
describe: "The target arch (preferred to use --x64 or --ia32)",
choices: ["ia32", "x64", "all"],
})
.option("extraMetadata", {
alias: ["em",],
group: buildGroup,
describe: "Inject properties to application package.json (asar only)",
})
.strict()
.group(["help", "version"], "Other:")
.help()
Expand Down
2 changes: 2 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export interface BuildMetadata {
Reasons why you may want to disable this feature are described in [an application packaging tutorial in Electron's documentation](http://electron.atom.io/docs/latest/tutorial/application-packaging/#limitations-on-node-api/).
Or you can pass object of any asar options.
electron-builder detects node modules that must be unpacked automatically, you don't need to explicitly set `asar.unpackDir` - please file issue if this doesn't work.
*/
readonly asar?: AsarOptions | boolean | null

Expand Down
6 changes: 4 additions & 2 deletions src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface PackagerOptions {
readonly appMetadata?: AppMetadata

readonly effectiveOptionComputed?: (options: any) => boolean

readonly extraMetadata?: any
}

export interface BuildInfo {
Expand Down Expand Up @@ -301,8 +303,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
}
}

return deepAssign(result, {

return Object.assign(result, {
extraMetadata: this.options.extraMetadata
})
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/app-executable-deps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.1.0",
"description": "app executable deps",
"main": "main.js",
"author": "Foo",
"author": "Foo <[email protected]>",
"dependencies": {
"keytar": "^3.0.2",
"node-notifier": "^4.6.0"
Expand Down
7 changes: 2 additions & 5 deletions test/fixtures/app-executable-deps/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"devDependencies": {
"electron-builder": "^5.17.0",
"electron-builder": "next",
"electron-prebuilt": "^1.3.1"
},
"build": {
"app-category-type": "public.app-category.business",
"win": {
"target": "nsis"
}
"app-category-type": "public.app-category.business"
}
}
2 changes: 1 addition & 1 deletion test/fixtures/test-app-one/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"author": "Foo Bar <[email protected]>",
"license": "MIT",
"build": {
"electronVersion": "1.2.6",
"electronVersion": "1.3.1",
"appId": "org.electron-builder.testApp",
"app-category-type": "your.app.category.type",
"iconUrl": "https://raw.githubusercontent.com/szwacz/electron-boilerplate/master/resources/windows/icon.ico",
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/test-app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"build": {
"electronVersion": "1.2.6",
"electronVersion": "1.3.1",
"appId": "org.electron-builder.testApp",
"app-category-type": "your.app.category.type",
"iconUrl": "https://raw.githubusercontent.com/szwacz/electron-boilerplate/master/resources/windows/icon.ico",
Expand Down
Loading

0 comments on commit da700d4

Please sign in to comment.