Skip to content

Commit

Permalink
feat: build prepackaged app.asar
Browse files Browse the repository at this point in the history
Close #1102
  • Loading branch information
develar committed Feb 2, 2017
1 parent 45c93bf commit 60e1406
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"devDependencies": {
"@types/electron": "^1.4.31",
"@types/ini": "^1.3.29",
"@types/jest": "^18.1.0",
"@types/jest": "^18.1.1",
"@types/js-yaml": "^3.5.29",
"@types/source-map-support": "^0.2.28",
"babel-plugin-array-includes": "^2.0.3",
Expand Down
82 changes: 58 additions & 24 deletions packages/electron-builder/src/packager.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import * as path from "path"
import { computeDefaultAppDirectory, use, exec, isEmptyOrSpaces } from "electron-builder-util"
import { all, executeFinally } from "electron-builder-util/out/promise"
import { EventEmitter } from "events"
import { extractFile } from "asar-electron-builder"
import BluebirdPromise from "bluebird-lst-c"
import { Metadata, Config, AfterPackContext } from "./metadata"
import { PlatformPackager } from "./platformPackager"
import { WinPackager } from "./winPackager"
import * as errorMessages from "./errorMessages"
import * as util from "util"
import { Arch, Platform, Target } from "electron-builder-core"
import { computeDefaultAppDirectory, exec, isEmptyOrSpaces, use } from "electron-builder-util"
import { deepAssign } from "electron-builder-util/out/deepAssign"
import { log, warn } from "electron-builder-util/out/log"
import { all, executeFinally } from "electron-builder-util/out/promise"
import { TmpDir } from "electron-builder-util/out/tmp"
import { EventEmitter } from "events"
import * as path from "path"
import { lt as isVersionLessThan } from "semver"
import { warn, log } from "electron-builder-util/out/log"
import * as util from "util"
import { AppInfo } from "./appInfo"
import * as errorMessages from "./errorMessages"
import MacPackager from "./macPackager"
import { AfterPackContext, Config, Metadata } from "./metadata"
import { ArtifactCreated, BuildInfo, PackagerOptions, SourceRepositoryInfo } from "./packagerApi"
import { PlatformPackager } from "./platformPackager"
import { getRepositoryInfo } from "./repositoryInfo"
import { createTargets } from "./targets/targetFactory"
import { readPackageJson, getElectronVersion, loadConfig } from "./util/readPackageJson"
import { TmpDir } from "electron-builder-util/out/tmp"
import { getElectronVersion, loadConfig, readPackageJson } from "./util/readPackageJson"
import { WinPackager } from "./winPackager"
import { getGypEnv, installOrRebuild } from "./yarn"
import { Platform, Arch, Target } from "electron-builder-core"
import { getRepositoryInfo } from "./repositoryInfo"
import { SourceRepositoryInfo, ArtifactCreated, BuildInfo, PackagerOptions } from "./packagerApi"

function addHandler(emitter: EventEmitter, event: string, handler: Function) {
emitter.on(event, handler)
Expand All @@ -31,6 +32,12 @@ export class Packager implements BuildInfo {

metadata: Metadata

private _isPrepackedAppAsar: boolean

get isPrepackedAppAsar(): boolean {
return this._isPrepackedAppAsar
}

private devMetadata: Metadata

private _config: Config
Expand Down Expand Up @@ -93,7 +100,8 @@ export class Packager implements BuildInfo {
configFromOptions = devMetadataFromOptions.build
}

const fileOrPackageConfig = await loadConfig(this.projectDir)
const projectDir = this.projectDir
const fileOrPackageConfig = await loadConfig(projectDir)
const config = fileOrPackageConfig == null ? configFromOptions : deepAssign(fileOrPackageConfig, configFromOptions)

const extraMetadata = this.options.extraMetadata
Expand All @@ -111,16 +119,15 @@ export class Packager implements BuildInfo {
}

this._config = config
this.appDir = await computeDefaultAppDirectory(this.projectDir, use(config.directories, it => it!.app))
this.appDir = await computeDefaultAppDirectory(projectDir, use(config.directories, it => it!.app))

this.isTwoPackageJsonProjectLayoutUsed = this.appDir !== this.projectDir
if (this.isTwoPackageJsonProjectLayoutUsed) {

}
this.isTwoPackageJsonProjectLayoutUsed = this.appDir !== projectDir

const devPackageFile = path.join(this.projectDir, "package.json")
const devPackageFile = path.join(projectDir, "package.json")
const appPackageFile = this.isTwoPackageJsonProjectLayoutUsed ? path.join(this.appDir, "package.json") : devPackageFile
this.metadata = deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata, extraMetadata)

await this.readProjectMetadata(appPackageFile, extraMetadata)

if (this.isTwoPackageJsonProjectLayoutUsed) {
this.devMetadata = deepAssign(await readPackageJson(devPackageFile), devMetadataFromOptions)
}
Expand All @@ -137,13 +144,40 @@ export class Packager implements BuildInfo {
this.checkMetadata(appPackageFile, devPackageFile)
checkConflictingOptions(this.config)

this.electronVersion = await getElectronVersion(this.config, this.projectDir)
this.electronVersion = await getElectronVersion(this.config, projectDir, this.isPrepackedAppAsar ? this.metadata : null)

this.appInfo = new AppInfo(this.metadata, this)
const cleanupTasks: Array<() => Promise<any>> = []
return await executeFinally(this.doBuild(cleanupTasks), () => all(cleanupTasks.map(it => it()).concat(this.tempDirManager.cleanup())))
}

private async readProjectMetadata(appPackageFile: string, extraMetadata: any) {
try {
this.metadata = deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata, extraMetadata)
}
catch (e) {
if (e.code !== "ENOENT") {
throw e
}

try {
const file = extractFile(path.join(this.projectDir, "app.asar"), "package.json")
if (file != null) {
this.metadata = JSON.parse(file.toString())
this._isPrepackedAppAsar = true
return
}
}
catch (e) {
if (e.code !== "ENOENT") {
throw e
}
}

throw new Error(`Cannot find package.json in the ${path.dirname(appPackageFile)}`)
}
}

private async doBuild(cleanupTasks: Array<() => Promise<any>>): Promise<Map<Platform, Map<String, Target>>> {
const distTasks: Array<Promise<any>> = []
const outDir = path.resolve(this.projectDir, use(this.config.directories, it => it!.output) || "dist")
Expand Down
20 changes: 11 additions & 9 deletions packages/electron-builder/src/packagerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,26 @@ export interface PackagerOptions {
}

export interface BuildInfo {
options: PackagerOptions
readonly options: PackagerOptions

metadata: Metadata
readonly metadata: Metadata

config: Config
readonly config: Config

projectDir: string
appDir: string
readonly projectDir: string
readonly appDir: string

electronVersion: string
readonly electronVersion: string

isTwoPackageJsonProjectLayoutUsed: boolean
readonly isTwoPackageJsonProjectLayoutUsed: boolean

appInfo: AppInfo
readonly appInfo: AppInfo

readonly tempDirManager: TmpDir

repositoryInfo: Promise<SourceRepositoryInfo | null>
readonly repositoryInfo: Promise<SourceRepositoryInfo | null>

readonly isPrepackedAppAsar: boolean

dispatchArtifactCreated(event: ArtifactCreated): void

Expand Down
32 changes: 20 additions & 12 deletions packages/electron-builder/src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,24 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
log(`Packaging for ${platformName} ${Arch[arch]} using electron ${this.info.electronVersion} to ${path.relative(this.projectDir, appOutDir)}`)

const appDir = this.info.appDir
const ignoreFiles = new Set([path.resolve(appDir, outDir), path.resolve(appDir, this.buildResourcesDir)])
// prune dev or not listed dependencies
await BluebirdPromise.all([
dependencies(appDir, ignoreFiles),
unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion),
])

if (debug.enabled) {
const nodeModulesDir = path.join(appDir, "node_modules")
debug(`Dev or extraneous dependencies: ${Array.from(ignoreFiles).slice(2).map(it => path.relative(nodeModulesDir, it)).join(", ")}`)
const ignoreFiles = new Set([path.resolve(this.info.projectDir, outDir), path.resolve(this.info.projectDir, this.buildResourcesDir)])
if (this.info.isPrepackedAppAsar) {
await unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion)
}
else {
// prune dev or not listed dependencies
await BluebirdPromise.all([
dependencies(appDir, ignoreFiles),
unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion),
])

if (debug.enabled) {
const nodeModulesDir = path.join(appDir, "node_modules")
debug(`Dev or extraneous dependencies: ${Array.from(ignoreFiles).slice(2).map(it => path.relative(nodeModulesDir, it)).join(", ")}`)
}
}

const patterns = this.getFileMatchers("files", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions)
const patterns = this.info.isPrepackedAppAsar ? null : this.getFileMatchers("files", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions)
const defaultMatcher = patterns == null ? new FileMatcher(appDir, path.join(resourcesPath, "app"), fileMatchOptions) : patterns[0]
if (defaultMatcher.isEmpty() || defaultMatcher.containsOnlyIgnore()) {
defaultMatcher.addAllPattern()
Expand Down Expand Up @@ -188,7 +193,10 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>

const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length > 0 ? excludePatterns : null)
let promise
if (asarOptions == null) {
if (this.info.isPrepackedAppAsar) {
promise = copyDir(appDir, path.join(resourcesPath), filter)
}
else if (asarOptions == null) {
promise = copyDir(appDir, path.join(resourcesPath, "app"), filter)
}
else {
Expand Down
70 changes: 49 additions & 21 deletions packages/electron-builder/src/util/readPackageJson.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { extractFile } from "asar-electron-builder"
import { log, warn } from "electron-builder-util/out/log"
import { readFile, readJson } from "fs-extra-p"
import { safeLoad } from "js-yaml"
import * as path from "path"
import { readJson, readFile } from "fs-extra-p"
import { Config } from "../metadata"
import { safeLoad } from "js-yaml"
import { warn, log } from "electron-builder-util/out/log"

const normalizeData = require("normalize-package-data")

Expand Down Expand Up @@ -31,6 +32,20 @@ async function authors(file: string, data: any) {
.map(it => it.replace(/^\s*#.*$/, "").trim())
}

function getConfigFromPackageData(metadata: any) {
if (metadata.directories != null) {
warn(`"directories" in the root is deprecated, please specify in the "build"`)
if (metadata.build == null) {
metadata.build = {directories: metadata.directories}
}
else if (metadata.build.directories == null) {
metadata.build.directories = metadata.directories
}
delete metadata.directories
}
return metadata.build
}

export async function loadConfig(projectDir: string): Promise<Config | null> {
try {
const configPath = path.join(projectDir, "electron-builder.yml")
Expand All @@ -44,39 +59,52 @@ export async function loadConfig(projectDir: string): Promise<Config | null> {
}
}

const metadata = await readPackageJson(path.join(projectDir, "package.json"))
if (metadata.directories != null) {
warn(`"directories" in the root is deprecated, please specify in the "build"`)
if (metadata.build == null) {
metadata.build = {directories: metadata.directories}
try {
return getConfigFromPackageData(await readPackageJson(path.join(projectDir, "package.json")))
}
catch (e) {
if (e.code !== "ENOENT") {
throw e
}
else if (metadata.build.directories == null) {
metadata.build.directories = metadata.directories

try {
const file = extractFile(path.join(projectDir, "app.asar"), "package.json")
if (file != null) {
return getConfigFromPackageData(JSON.parse(file.toString()))
}
}
delete metadata.directories
catch (e) {
if (e.code !== "ENOENT") {
throw e
}
}

throw new Error(`Cannot find package.json in the ${projectDir}`)
}
return metadata.build
}

export async function getElectronVersion(config: Config | null | undefined, projectDir: string): Promise<string> {
export async function getElectronVersion(config: Config | null | undefined, projectDir: string, projectMetadata?: any | null): Promise<string> {
// build is required, but this check is performed later, so, we should check for null
if (config != null && config.electronVersion != null) {
return config.electronVersion
}

for (const name of ["electron", "electron-prebuilt", "electron-prebuilt-compile"]) {
try {
return (await readJson(path.join(projectDir, "node_modules", name, "package.json"))).version
}
catch (e) {
if (e.code !== "ENOENT") {
warn(`Cannot read electron version from ${name} package.json: ${e.message}`)
// projectMetadata passed only for prepacked app asar and in this case no dev deps in the app.asar
if (projectMetadata == null) {
for (const name of ["electron", "electron-prebuilt", "electron-prebuilt-compile"]) {
try {
return (await readJson(path.join(projectDir, "node_modules", name, "package.json"))).version
}
catch (e) {
if (e.code !== "ENOENT") {
warn(`Cannot read electron version from ${name} package.json: ${e.message}`)
}
}
}
}

const packageJsonPath = path.join(projectDir, "package.json")
const electronPrebuiltDep = findFromElectronPrebuilt(await readJson(packageJsonPath))
const electronPrebuiltDep = findFromElectronPrebuilt(projectMetadata || await readJson(packageJsonPath))
if (electronPrebuiltDep == null) {
throw new Error(`Cannot find electron dependency to get electron version in the '${packageJsonPath}'`)
}
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
version "1.3.29"
resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.29.tgz#1325e981e047d40d13ce0359b821475b97741d2f"

"@types/jest@^18.1.0":
version "18.1.0"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-18.1.0.tgz#ed18f4c75b3d76de966f543fb6e3bad77d3caa17"
"@types/jest@^18.1.1":
version "18.1.1"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-18.1.1.tgz#6f63488c64726900885ab9cd5697bb7fa1b416cc"

"@types/js-yaml@^3.5.29":
version "3.5.29"
Expand Down Expand Up @@ -459,8 +459,8 @@ base64-js@1.1.2:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.1.2.tgz#d6400cac1c4c660976d90d07a04351d89395f5e8"

bcrypt-pbkdf@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4"
version "1.0.1"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
dependencies:
tweetnacl "^0.14.3"

Expand Down

0 comments on commit 60e1406

Please sign in to comment.