Skip to content

Commit 60e1406

Browse files
committed
feat: build prepackaged app.asar
Close #1102
1 parent 45c93bf commit 60e1406

File tree

6 files changed

+144
-72
lines changed

6 files changed

+144
-72
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"devDependencies": {
5656
"@types/electron": "^1.4.31",
5757
"@types/ini": "^1.3.29",
58-
"@types/jest": "^18.1.0",
58+
"@types/jest": "^18.1.1",
5959
"@types/js-yaml": "^3.5.29",
6060
"@types/source-map-support": "^0.2.28",
6161
"babel-plugin-array-includes": "^2.0.3",

packages/electron-builder/src/packager.ts

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import * as path from "path"
2-
import { computeDefaultAppDirectory, use, exec, isEmptyOrSpaces } from "electron-builder-util"
3-
import { all, executeFinally } from "electron-builder-util/out/promise"
4-
import { EventEmitter } from "events"
1+
import { extractFile } from "asar-electron-builder"
52
import BluebirdPromise from "bluebird-lst-c"
6-
import { Metadata, Config, AfterPackContext } from "./metadata"
7-
import { PlatformPackager } from "./platformPackager"
8-
import { WinPackager } from "./winPackager"
9-
import * as errorMessages from "./errorMessages"
10-
import * as util from "util"
3+
import { Arch, Platform, Target } from "electron-builder-core"
4+
import { computeDefaultAppDirectory, exec, isEmptyOrSpaces, use } from "electron-builder-util"
115
import { deepAssign } from "electron-builder-util/out/deepAssign"
6+
import { log, warn } from "electron-builder-util/out/log"
7+
import { all, executeFinally } from "electron-builder-util/out/promise"
8+
import { TmpDir } from "electron-builder-util/out/tmp"
9+
import { EventEmitter } from "events"
10+
import * as path from "path"
1211
import { lt as isVersionLessThan } from "semver"
13-
import { warn, log } from "electron-builder-util/out/log"
12+
import * as util from "util"
1413
import { AppInfo } from "./appInfo"
14+
import * as errorMessages from "./errorMessages"
1515
import MacPackager from "./macPackager"
16+
import { AfterPackContext, Config, Metadata } from "./metadata"
17+
import { ArtifactCreated, BuildInfo, PackagerOptions, SourceRepositoryInfo } from "./packagerApi"
18+
import { PlatformPackager } from "./platformPackager"
19+
import { getRepositoryInfo } from "./repositoryInfo"
1620
import { createTargets } from "./targets/targetFactory"
17-
import { readPackageJson, getElectronVersion, loadConfig } from "./util/readPackageJson"
18-
import { TmpDir } from "electron-builder-util/out/tmp"
21+
import { getElectronVersion, loadConfig, readPackageJson } from "./util/readPackageJson"
22+
import { WinPackager } from "./winPackager"
1923
import { getGypEnv, installOrRebuild } from "./yarn"
20-
import { Platform, Arch, Target } from "electron-builder-core"
21-
import { getRepositoryInfo } from "./repositoryInfo"
22-
import { SourceRepositoryInfo, ArtifactCreated, BuildInfo, PackagerOptions } from "./packagerApi"
2324

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

3233
metadata: Metadata
3334

35+
private _isPrepackedAppAsar: boolean
36+
37+
get isPrepackedAppAsar(): boolean {
38+
return this._isPrepackedAppAsar
39+
}
40+
3441
private devMetadata: Metadata
3542

3643
private _config: Config
@@ -93,7 +100,8 @@ export class Packager implements BuildInfo {
93100
configFromOptions = devMetadataFromOptions.build
94101
}
95102

96-
const fileOrPackageConfig = await loadConfig(this.projectDir)
103+
const projectDir = this.projectDir
104+
const fileOrPackageConfig = await loadConfig(projectDir)
97105
const config = fileOrPackageConfig == null ? configFromOptions : deepAssign(fileOrPackageConfig, configFromOptions)
98106

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

113121
this._config = config
114-
this.appDir = await computeDefaultAppDirectory(this.projectDir, use(config.directories, it => it!.app))
122+
this.appDir = await computeDefaultAppDirectory(projectDir, use(config.directories, it => it!.app))
115123

116-
this.isTwoPackageJsonProjectLayoutUsed = this.appDir !== this.projectDir
117-
if (this.isTwoPackageJsonProjectLayoutUsed) {
118-
119-
}
124+
this.isTwoPackageJsonProjectLayoutUsed = this.appDir !== projectDir
120125

121-
const devPackageFile = path.join(this.projectDir, "package.json")
126+
const devPackageFile = path.join(projectDir, "package.json")
122127
const appPackageFile = this.isTwoPackageJsonProjectLayoutUsed ? path.join(this.appDir, "package.json") : devPackageFile
123-
this.metadata = deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata, extraMetadata)
128+
129+
await this.readProjectMetadata(appPackageFile, extraMetadata)
130+
124131
if (this.isTwoPackageJsonProjectLayoutUsed) {
125132
this.devMetadata = deepAssign(await readPackageJson(devPackageFile), devMetadataFromOptions)
126133
}
@@ -137,13 +144,40 @@ export class Packager implements BuildInfo {
137144
this.checkMetadata(appPackageFile, devPackageFile)
138145
checkConflictingOptions(this.config)
139146

140-
this.electronVersion = await getElectronVersion(this.config, this.projectDir)
147+
this.electronVersion = await getElectronVersion(this.config, projectDir, this.isPrepackedAppAsar ? this.metadata : null)
141148

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

154+
private async readProjectMetadata(appPackageFile: string, extraMetadata: any) {
155+
try {
156+
this.metadata = deepAssign(await readPackageJson(appPackageFile), this.options.appMetadata, extraMetadata)
157+
}
158+
catch (e) {
159+
if (e.code !== "ENOENT") {
160+
throw e
161+
}
162+
163+
try {
164+
const file = extractFile(path.join(this.projectDir, "app.asar"), "package.json")
165+
if (file != null) {
166+
this.metadata = JSON.parse(file.toString())
167+
this._isPrepackedAppAsar = true
168+
return
169+
}
170+
}
171+
catch (e) {
172+
if (e.code !== "ENOENT") {
173+
throw e
174+
}
175+
}
176+
177+
throw new Error(`Cannot find package.json in the ${path.dirname(appPackageFile)}`)
178+
}
179+
}
180+
147181
private async doBuild(cleanupTasks: Array<() => Promise<any>>): Promise<Map<Platform, Map<String, Target>>> {
148182
const distTasks: Array<Promise<any>> = []
149183
const outDir = path.resolve(this.projectDir, use(this.config.directories, it => it!.output) || "dist")

packages/electron-builder/src/packagerApi.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,26 @@ export interface PackagerOptions {
4040
}
4141

4242
export interface BuildInfo {
43-
options: PackagerOptions
43+
readonly options: PackagerOptions
4444

45-
metadata: Metadata
45+
readonly metadata: Metadata
4646

47-
config: Config
47+
readonly config: Config
4848

49-
projectDir: string
50-
appDir: string
49+
readonly projectDir: string
50+
readonly appDir: string
5151

52-
electronVersion: string
52+
readonly electronVersion: string
5353

54-
isTwoPackageJsonProjectLayoutUsed: boolean
54+
readonly isTwoPackageJsonProjectLayoutUsed: boolean
5555

56-
appInfo: AppInfo
56+
readonly appInfo: AppInfo
5757

5858
readonly tempDirManager: TmpDir
5959

60-
repositoryInfo: Promise<SourceRepositoryInfo | null>
60+
readonly repositoryInfo: Promise<SourceRepositoryInfo | null>
61+
62+
readonly isPrepackedAppAsar: boolean
6163

6264
dispatchArtifactCreated(event: ArtifactCreated): void
6365

packages/electron-builder/src/platformPackager.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,24 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
128128
log(`Packaging for ${platformName} ${Arch[arch]} using electron ${this.info.electronVersion} to ${path.relative(this.projectDir, appOutDir)}`)
129129

130130
const appDir = this.info.appDir
131-
const ignoreFiles = new Set([path.resolve(appDir, outDir), path.resolve(appDir, this.buildResourcesDir)])
132-
// prune dev or not listed dependencies
133-
await BluebirdPromise.all([
134-
dependencies(appDir, ignoreFiles),
135-
unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion),
136-
])
137-
138-
if (debug.enabled) {
139-
const nodeModulesDir = path.join(appDir, "node_modules")
140-
debug(`Dev or extraneous dependencies: ${Array.from(ignoreFiles).slice(2).map(it => path.relative(nodeModulesDir, it)).join(", ")}`)
131+
const ignoreFiles = new Set([path.resolve(this.info.projectDir, outDir), path.resolve(this.info.projectDir, this.buildResourcesDir)])
132+
if (this.info.isPrepackedAppAsar) {
133+
await unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion)
134+
}
135+
else {
136+
// prune dev or not listed dependencies
137+
await BluebirdPromise.all([
138+
dependencies(appDir, ignoreFiles),
139+
unpackElectron(this, appOutDir, platformName, Arch[arch], this.info.electronVersion),
140+
])
141+
142+
if (debug.enabled) {
143+
const nodeModulesDir = path.join(appDir, "node_modules")
144+
debug(`Dev or extraneous dependencies: ${Array.from(ignoreFiles).slice(2).map(it => path.relative(nodeModulesDir, it)).join(", ")}`)
145+
}
141146
}
142147

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

189194
const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length > 0 ? excludePatterns : null)
190195
let promise
191-
if (asarOptions == null) {
196+
if (this.info.isPrepackedAppAsar) {
197+
promise = copyDir(appDir, path.join(resourcesPath), filter)
198+
}
199+
else if (asarOptions == null) {
192200
promise = copyDir(appDir, path.join(resourcesPath, "app"), filter)
193201
}
194202
else {

packages/electron-builder/src/util/readPackageJson.ts

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { extractFile } from "asar-electron-builder"
2+
import { log, warn } from "electron-builder-util/out/log"
3+
import { readFile, readJson } from "fs-extra-p"
4+
import { safeLoad } from "js-yaml"
15
import * as path from "path"
2-
import { readJson, readFile } from "fs-extra-p"
36
import { Config } from "../metadata"
4-
import { safeLoad } from "js-yaml"
5-
import { warn, log } from "electron-builder-util/out/log"
67

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

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

35+
function getConfigFromPackageData(metadata: any) {
36+
if (metadata.directories != null) {
37+
warn(`"directories" in the root is deprecated, please specify in the "build"`)
38+
if (metadata.build == null) {
39+
metadata.build = {directories: metadata.directories}
40+
}
41+
else if (metadata.build.directories == null) {
42+
metadata.build.directories = metadata.directories
43+
}
44+
delete metadata.directories
45+
}
46+
return metadata.build
47+
}
48+
3449
export async function loadConfig(projectDir: string): Promise<Config | null> {
3550
try {
3651
const configPath = path.join(projectDir, "electron-builder.yml")
@@ -44,39 +59,52 @@ export async function loadConfig(projectDir: string): Promise<Config | null> {
4459
}
4560
}
4661

47-
const metadata = await readPackageJson(path.join(projectDir, "package.json"))
48-
if (metadata.directories != null) {
49-
warn(`"directories" in the root is deprecated, please specify in the "build"`)
50-
if (metadata.build == null) {
51-
metadata.build = {directories: metadata.directories}
62+
try {
63+
return getConfigFromPackageData(await readPackageJson(path.join(projectDir, "package.json")))
64+
}
65+
catch (e) {
66+
if (e.code !== "ENOENT") {
67+
throw e
5268
}
53-
else if (metadata.build.directories == null) {
54-
metadata.build.directories = metadata.directories
69+
70+
try {
71+
const file = extractFile(path.join(projectDir, "app.asar"), "package.json")
72+
if (file != null) {
73+
return getConfigFromPackageData(JSON.parse(file.toString()))
74+
}
5575
}
56-
delete metadata.directories
76+
catch (e) {
77+
if (e.code !== "ENOENT") {
78+
throw e
79+
}
80+
}
81+
82+
throw new Error(`Cannot find package.json in the ${projectDir}`)
5783
}
58-
return metadata.build
5984
}
6085

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

67-
for (const name of ["electron", "electron-prebuilt", "electron-prebuilt-compile"]) {
68-
try {
69-
return (await readJson(path.join(projectDir, "node_modules", name, "package.json"))).version
70-
}
71-
catch (e) {
72-
if (e.code !== "ENOENT") {
73-
warn(`Cannot read electron version from ${name} package.json: ${e.message}`)
92+
// projectMetadata passed only for prepacked app asar and in this case no dev deps in the app.asar
93+
if (projectMetadata == null) {
94+
for (const name of ["electron", "electron-prebuilt", "electron-prebuilt-compile"]) {
95+
try {
96+
return (await readJson(path.join(projectDir, "node_modules", name, "package.json"))).version
97+
}
98+
catch (e) {
99+
if (e.code !== "ENOENT") {
100+
warn(`Cannot read electron version from ${name} package.json: ${e.message}`)
101+
}
74102
}
75103
}
76104
}
77105

78106
const packageJsonPath = path.join(projectDir, "package.json")
79-
const electronPrebuiltDep = findFromElectronPrebuilt(await readJson(packageJsonPath))
107+
const electronPrebuiltDep = findFromElectronPrebuilt(projectMetadata || await readJson(packageJsonPath))
80108
if (electronPrebuiltDep == null) {
81109
throw new Error(`Cannot find electron dependency to get electron version in the '${packageJsonPath}'`)
82110
}

yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
version "1.3.29"
3333
resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.29.tgz#1325e981e047d40d13ce0359b821475b97741d2f"
3434

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

3939
"@types/js-yaml@^3.5.29":
4040
version "3.5.29"
@@ -459,8 +459,8 @@ base64-js@1.1.2:
459459
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.1.2.tgz#d6400cac1c4c660976d90d07a04351d89395f5e8"
460460

461461
bcrypt-pbkdf@^1.0.0:
462-
version "1.0.0"
463-
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4"
462+
version "1.0.1"
463+
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
464464
dependencies:
465465
tweetnacl "^0.14.3"
466466

0 commit comments

Comments
 (0)