Skip to content

Commit

Permalink
refactor(windows): extract Squirrel.Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
develar committed Jun 8, 2016
1 parent 0f7624d commit fc1587f
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 203 deletions.
2 changes: 1 addition & 1 deletion src/linuxPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class LinuxPackager extends PlatformPackager<LinuxBuildOptions> {
}
}

protected get supportedTargets(): Array<string> {
get supportedTargets(): Array<string> {
return ["deb", "rpm", "sh", "freebsd", "pacman", "apk", "p5p"]
}

Expand Down
27 changes: 14 additions & 13 deletions src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ export interface BuildMetadata {
/**
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 `\*\*\/\*` (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).
[Multiple patterns](#multiple-glob-patterns) are supported. You can use `${os}` (expanded to osx, linux or win according to current platform) and `${arch}` in the pattern.
Development dependencies are never copied in any case. You don't need to ignore it explicitly.
[Multiple patterns](#multiple-glob-patterns) are supported. You can use `${os}` (expanded to osx, linux or win according to current platform) and `${arch}` in the pattern.
If directory matched, all contents are copied. So, you can just specify `foo` to copy `foo` directory.
Remember that default pattern `\*\*\/\*` is not added to your custom, so, you have to add it explicitly — e.g. `["\*\*\/\*", "!ignoreMe${/\*}"]`.
Expand Down Expand Up @@ -164,11 +165,11 @@ export interface BuildMetadata {
*/
readonly afterPack?: (context: AfterPackContext) => Promise<any> | null

/*
Whether to [prune](https://docs.npmjs.com/cli/prune) native dependencies (`npm prune --production`) before starting to package the app.
Defaults to `true` if [two package.json structure](https://github.com/electron-userland/electron-builder#two-packagejson-structure) is not used.
*/
readonly npmPrune?: boolean
// /*
// Whether to [prune](https://docs.npmjs.com/cli/prune) dependencies (`npm prune --production`) before starting to package the app.
// Defaults to `false`.
// */
// readonly npmPrune?: boolean
// deprecated
readonly prune?: boolean

Expand Down Expand Up @@ -372,9 +373,9 @@ export interface PlatformSpecificBuildOptions {
}

export class Platform {
public static OSX = new Platform("osx", "osx", "darwin")
public static LINUX = new Platform("linux", "linux", "linux")
public static WINDOWS = new Platform("windows", "win", "win32")
static OSX = new Platform("osx", "osx", "darwin")
static LINUX = new Platform("linux", "linux", "linux")
static WINDOWS = new Platform("windows", "win", "win32")

constructor(public name: string, public buildConfigurationKey: string, public nodeName: string) {
}
Expand All @@ -387,19 +388,19 @@ export class Platform {
return this.name
}

public createTarget(type?: string | null, ...archs: Array<Arch>): Map<Platform, Map<Arch, Array<string>>> {
createTarget(type?: string | Array<string> | null, ...archs: Array<Arch>): Map<Platform, Map<Arch, Array<string>>> {
const archToType = new Map()
for (let arch of (archs == null || archs.length === 0 ? [archFromString(process.arch)] : archs)) {
archToType.set(arch, type == null ? [] : [type])
archToType.set(arch, type == null ? [] : (Array.isArray(type) ? type : [type]))
}
return new Map([[this, archToType]])
}

public static current(): Platform {
static current(): Platform {
return Platform.fromString(process.platform)
}

public static fromString(name: string): Platform {
static fromString(name: string): Platform {
switch (name) {
case Platform.OSX.nodeName:
case Platform.OSX.name:
Expand Down
8 changes: 3 additions & 5 deletions src/osxPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
return Platform.OSX
}

protected get supportedTargets(): Array<string> {
get supportedTargets(): Array<string> {
return ["dmg", "mas"]
}

Expand All @@ -42,7 +42,7 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
nonMasPromise = this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions)
.then(() => this.sign(appOutDir, null))
.then(() => {
postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, targets))
this.packageInDistributableFormat(appOutDir, targets, postAsyncTasks)
})
}

Expand Down Expand Up @@ -215,8 +215,7 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
return specification
}

protected packageInDistributableFormat(outDir: string, appOutDir: string, targets: Array<string>): Promise<any> {
const promises: Array<Promise<any>> = []
protected packageInDistributableFormat(appOutDir: string, targets: Array<string>, promises: Array<Promise<any>>): void {
for (let target of targets) {
if (target === "dmg" || target === "default") {
promises.push(this.createDmg(appOutDir))
Expand All @@ -233,7 +232,6 @@ export default class OsXPackager extends PlatformPackager<OsXBuildOptions> {
.then(() => this.dispatchArtifactCreated(outFile, `${this.metadata.name}-${this.metadata.version}-${classifier}.${format}`)))
}
}
return BluebirdPromise.all(promises)
}

private async createDmg(appOutDir: string) {
Expand Down
12 changes: 10 additions & 2 deletions src/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EventEmitter } from "events"
import { Promise as BluebirdPromise } from "bluebird"
import { InfoRetriever } from "./repositoryInfo"
import { AppMetadata, DevMetadata, Platform, Arch } from "./metadata"
import { PackagerOptions, PlatformPackager, BuildInfo, ArtifactCreated } from "./platformPackager"
import { PackagerOptions, PlatformPackager, BuildInfo, ArtifactCreated, computeEffectiveTargets, commonTargets } from "./platformPackager"
import OsXPackager from "./osxPackager"
import { WinPackager } from "./winPackager"
import * as errorMessages from "./errorMessages"
Expand Down Expand Up @@ -96,7 +96,15 @@ export class Packager implements BuildInfo {
}

// electron-packager uses productName in the directory name
await helper.pack(outDir, arch, helper.computeEffectiveTargets(targets), distTasks)}
const effectiveTargets = computeEffectiveTargets(targets, helper.customBuildOptions.target)
const supportedTargets = helper.supportedTargets.concat(commonTargets)
for (let target of effectiveTargets) {
if (target !== "default" && !supportedTargets.includes(target)) {
throw new Error(`Unknown target: ${target}`)
}
}
await helper.pack(outDir, arch, effectiveTargets, distTasks)
}
}

return await BluebirdPromise.all(distTasks)
Expand Down
30 changes: 11 additions & 19 deletions src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
protected readonly options: PackagerOptions

protected readonly projectDir: string
protected readonly buildResourcesDir: string
readonly buildResourcesDir: string

readonly metadata: AppMetadata
readonly devMetadata: DevMetadata
Expand All @@ -86,7 +86,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>

public abstract get platform(): Platform

constructor(protected info: BuildInfo) {
constructor(public info: BuildInfo) {
this.options = info.options
this.projectDir = info.projectDir
this.metadata = info.metadata
Expand Down Expand Up @@ -116,19 +116,6 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
}
}

public computeEffectiveTargets(rawList: Array<string>): Array<string> {
let targets = normalizeTargets(rawList.length === 0 ? this.customBuildOptions.target : rawList)
if (targets != null) {
const supportedTargets = this.supportedTargets.concat(commonTargets)
for (let target of targets) {
if (target !== "default" && !supportedTargets.includes(target)) {
throw new Error(`Unknown target: ${target}`)
}
}
}
return targets == null ? ["default"] : targets
}

protected hasOnlyDirTarget(): boolean {
for (let targets of this.options.targets!.get(this.platform)!.values()) {
for (let t of targets) {
Expand All @@ -142,17 +129,17 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
return targets != null && targets.length === 1 && targets[0] === "dir"
}

protected get relativeBuildResourcesDirname() {
get relativeBuildResourcesDirname() {
return use(this.devMetadata.directories, it => it!.buildResources) || "build"
}

protected abstract get supportedTargets(): Array<string>
abstract get supportedTargets(): Array<string>

protected computeAppOutDir(outDir: string, arch: Arch): string {
return path.join(outDir, `${this.platform.buildConfigurationKey}${arch === Arch.x64 ? "" : `-${Arch[arch]}`}`)
}

protected dispatchArtifactCreated(file: string, artifactName?: string) {
dispatchArtifactCreated(file: string, artifactName?: string) {
this.info.eventEmitter.emit("artifactCreated", {
file: file,
artifactName: artifactName,
Expand Down Expand Up @@ -406,7 +393,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
return patterns
}

protected async computePackageUrl(): Promise<string | null> {
async computePackageUrl(): Promise<string | null> {
const url = this.metadata.homepage || this.devMetadata.homepage
if (url != null) {
return url
Expand Down Expand Up @@ -620,4 +607,9 @@ function copyFiltered(src: string, destination: string, patterns: Array<Minimatc
return minimatchAll(relative, patterns)
}
})
}

export function computeEffectiveTargets(rawList: Array<string>, targetsFromMetadata: Array<string> | n): Array<string> {
let targets = normalizeTargets(rawList.length === 0 ? targetsFromMetadata : rawList)
return targets == null ? ["default"] : targets
}
122 changes: 122 additions & 0 deletions src/targets/squirrelWindows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { WinPackager } from "../winPackager"
import { getArchSuffix, smarten } from "../platformPackager"
import { ElectronPackagerOptions } from "electron-packager-tf"
import { Arch, WinBuildOptions } from "../metadata"
import { createWindowsInstaller, convertVersion } from "electron-winstaller-fixed"
import * as path from "path"
import { warn } from "../util"
import { emptyDir } from "fs-extra-p"

//noinspection JSUnusedLocalSymbols
const __awaiter = require("../awaiter")

export default class SquirrelWindowsTarget {
constructor(private packager: WinPackager, private appOutDir: string, private arch: Arch) {
}

async build(packOptions: ElectronPackagerOptions) {
const version = this.packager.metadata.version
const archSuffix = getArchSuffix(this.arch)
const setupExeName = `${this.packager.appName} Setup ${version}${archSuffix}.exe`

const installerOutDir = path.join(this.appOutDir, "..", `win${getArchSuffix(this.arch)}`)
await emptyDir(installerOutDir)

const distOptions = await this.computeEffectiveDistOptions(installerOutDir, packOptions, setupExeName)
await createWindowsInstaller(distOptions)
this.packager.dispatchArtifactCreated(path.join(installerOutDir, setupExeName), `${this.packager.metadata.name}-Setup-${version}${archSuffix}.exe`)

const packagePrefix = `${this.packager.metadata.name}-${convertVersion(version)}-`
this.packager.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}full.nupkg`))
if (distOptions.remoteReleases != null) {
this.packager.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}delta.nupkg`))
}

this.packager.dispatchArtifactCreated(path.join(installerOutDir, "RELEASES"))
}

async computeEffectiveDistOptions(installerOutDir: string, packOptions: ElectronPackagerOptions, setupExeName: string): Promise<WinBuildOptions> {
const packager = this.packager
let iconUrl = packager.customBuildOptions.iconUrl || packager.devMetadata.build.iconUrl
if (iconUrl == null) {
if (packager.info.repositoryInfo != null) {
const info = await packager.info.repositoryInfo.getInfo(packager)
if (info != null) {
iconUrl = `https://github.com/${info.user}/${info.project}/blob/master/${packager.relativeBuildResourcesDirname}/icon.ico?raw=true`
}
}

if (iconUrl == null) {
throw new Error("iconUrl is not specified, please see https://github.com/electron-userland/electron-builder/wiki/Options#WinBuildOptions-iconUrl")
}
}

checkConflictingOptions(packager.customBuildOptions)

const projectUrl = await packager.computePackageUrl()
const rceditOptions = {
"version-string": packOptions["version-string"],
"file-version": packOptions["build-version"],
"product-version": packOptions["app-version"],
}
rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"]

const cscInfo = await packager.cscInfo
const options: any = Object.assign({
name: packager.metadata.name,
productName: packager.appName,
exe: packager.appName + ".exe",
setupExe: setupExeName,
title: packager.appName,
appDirectory: this.appOutDir,
outputDirectory: installerOutDir,
version: packager.metadata.version,
description: smarten(packager.metadata.description),
authors: packager.metadata.author.name,
iconUrl: iconUrl,
setupIcon: await packager.iconPath,
certificateFile: cscInfo == null ? null : cscInfo.file,
certificatePassword: cscInfo == null ? null : cscInfo.password,
fixUpPaths: false,
skipUpdateIcon: true,
usePackageJson: false,
extraMetadataSpecs: projectUrl == null ? null : `\n <projectUrl>${projectUrl}</projectUrl>`,
copyright: packOptions["app-copyright"],
packageCompressionLevel: packager.devMetadata.build.compression === "store" ? 0 : 9,
sign: {
name: packager.appName,
site: projectUrl,
overwrite: true,
hash: packager.customBuildOptions.signingHashAlgorithms,
},
rcedit: rceditOptions,
}, packager.customBuildOptions)

if (!("loadingGif" in options)) {
const resourceList = await packager.resourceList
if (resourceList.includes("install-spinner.gif")) {
options.loadingGif = path.join(packager.buildResourcesDir, "install-spinner.gif")
}
}

return options
}
}

function checkConflictingOptions(options: any) {
for (let name of ["outputDirectory", "appDirectory", "exe", "fixUpPaths", "usePackageJson", "extraFileSpecs", "extraMetadataSpecs", "skipUpdateIcon", "setupExe"]) {
if (name in options) {
throw new Error(`Option ${name} is ignored, do not specify it.`)
}
}

if ("noMsi" in options) {
warn(`noMsi is deprecated, please specify as "msi": true if you want to create an MSI installer`)
options.msi = !options.noMsi
}

const msi = options.msi
if (msi != null && typeof msi !== "boolean") {
throw new Error(`msi expected to be boolean value, but string '"${msi}"' was specified`)
}
}
Loading

0 comments on commit fc1587f

Please sign in to comment.