From a55c5733bc06f586a3966e415cb8d1ef98dfc1c4 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 25 Nov 2016 17:07:17 +0100 Subject: [PATCH 01/43] =?UTF-8?q?feat:=20if=20only=20ignored=20patterns=20?= =?UTF-8?q?are=20specified=20=E2=80=94=20all=20not=20ignored=20files=20is?= =?UTF-8?q?=20copied?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #927 --- docs/Options.md | 13 ++- package.json | 1 + src/codeSign.ts | 2 +- src/fileMatcher.ts | 20 +++-- src/platformPackager.ts | 4 +- src/targets/appx.ts | 6 ++ src/util/filter.ts | 5 +- test/README.md | 2 - test/jestSetup.js | 5 +- test/src/ArtifactPublisherTest.ts | 6 ++ test/src/filesTest.ts | 137 +++++++++++++++--------------- test/src/helpers/runTests.ts | 1 - test/src/httpRequestTest.ts | 8 +- test/src/mac/macArchiveTest.ts | 4 +- test/src/nsisUpdaterTest.ts | 9 +- 15 files changed, 126 insertions(+), 97 deletions(-) diff --git a/docs/Options.md b/docs/Options.md index 245bfe8e4c5..93774232d3d 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -270,12 +270,12 @@ Windows specific build options. | Name | Description | --- | --- -| target | Target package type: list of `nsis`, `squirrel`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`, `dir`. Defaults to `nsis`. -| signingHashAlgorithms | Array of signing algorithms used. Defaults to `['sha1', 'sha256']` +| target |

Target package type: list of nsis, appx, squirrel, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2, dir. Defaults to nsis.

AppX package can be built only on Windows 10.

+| signingHashAlgorithms |

Array of signing algorithms used. Defaults to ['sha1', 'sha256']

Fo AppX sha256 is always used.

| icon | The path to application icon. Defaults to `build/icon.ico` (consider using this convention instead of complicating your configuration). | legalTrademarks | The trademarks and registered trademarks. -| certificateFile | The path to the *.pfx certificate you want to sign with. Required only if you build on macOS and need different certificate than the one set in `CSC_LINK` - see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing). -| certificatePassword | The password to the certificate provided in `certificateFile`. Required only if you build on macOS and need to use a different password than the one set in `CSC_KEY_PASSWORD` - see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing). +| certificateFile |

The path to the *.pfx certificate you want to sign with. Please use it only if you cannot use env variable CSC_LINK (WIN_CSC_LINK) for some reason. Please see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing).

+| certificatePassword |

The password to the certificate provided in certificateFile. Please use it only if you cannot use env variable CSC_KEY_PASSWORD (WIN_CSC_KEY_PASSWORD) for some reason. Please see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing).

| certificateSubjectName | The name of the subject of the signing certificate. Required only for EV Code Signing and works only on Windows. | rfc3161TimeStampServer | The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`. @@ -306,9 +306,8 @@ Development dependencies are never copied in any case. You don't need to ignore [Multiple patterns](#multiple-glob-patterns) are supported. You can use `${os}` (expanded to mac, 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${/*}"]`. - -`package.json` is added to your custom in any case. +Remember that default pattern `**/*` **is not added to your custom** if some of your patterns is not ignore (i.e. not starts with `!`). + `package.json` is added to your custom in any case. May be specified in the platform options (e.g. in the `build.mac`). diff --git a/package.json b/package.json index bee4294bdf0..94cfe1464b5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "update-wiki": "git subtree split -b wiki --prefix docs/ && git push -f wiki wiki:master", "whitespace": "whitespace 'src/**/*.ts'", "docker-images": "docker/build.sh", + "test-deps-mac": "brew install rpm dpkg mono lzip gnu-tar graphicsmagick xz && brew install wine --without-x11", "precommit": "validate-commit-msg" }, "repository": "electron-userland/electron-builder", diff --git a/src/codeSign.ts b/src/codeSign.ts index f9577d0601f..e787c02ffb0 100644 --- a/src/codeSign.ts +++ b/src/codeSign.ts @@ -207,7 +207,7 @@ async function _findIdentity(type: CertType, qualifier?: string | null, keychain export async function findIdentity(certType: CertType, qualifier?: string | null, keychain?: string | null): Promise { let identity = process.env.CSC_NAME || qualifier if (isEmptyOrSpaces(identity)) { - if (keychain == null && !isCi() && (process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false")) { + if (keychain == null && !isCi() && process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") { return null } else { diff --git a/src/fileMatcher.ts b/src/fileMatcher.ts index 355f9c28a0d..7774f44fd51 100644 --- a/src/fileMatcher.ts +++ b/src/fileMatcher.ts @@ -34,6 +34,10 @@ export class FileMatcher { return this.patterns.length === 0 } + containsOnlyIgnore(): boolean { + return !this.isEmpty() && this.patterns.find(it => !it.startsWith("!")) == null + } + getParsedPatterns(fromDir?: string): Array { // https://github.com/electron-userland/electron-builder/issues/733 const minimatchOptions = {dot: true} @@ -41,9 +45,9 @@ export class FileMatcher { const parsedPatterns: Array = [] const pathDifference = fromDir ? path.relative(fromDir, this.from) : null - for (let i = 0; i < this.patterns.length; i++) { - let expandedPattern = this.expandPattern(this.patterns[i]) - if (pathDifference) { + for (const p of this.patterns) { + let expandedPattern = this.expandPattern(p) + if (pathDifference != null) { expandedPattern = path.join(pathDifference, expandedPattern) } @@ -72,10 +76,10 @@ export class FileMatcher { } } -export function deprecatedUserIgnoreFilter(ignore: any, appDir: string) { +export function deprecatedUserIgnoreFilter(ignore: Array | ((file: string) => boolean), appDir: string) { let ignoreFunc: any - if (typeof (ignore) === "function") { - ignoreFunc = function (file: string) { return !ignore(file) } + if (typeof ignore === "function") { + ignoreFunc = function (file: string) { return !(ignore)(file) } } else { if (!Array.isArray(ignore)) { @@ -83,8 +87,8 @@ export function deprecatedUserIgnoreFilter(ignore: any, appDir: string) { } ignoreFunc = function (file: string) { - for (let i = 0; i < ignore.length; i++) { - if (file.match(ignore[i])) { + for (const i of >ignore) { + if (file.match(i)) { return false } } diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 360f63109a6..7254487d9aa 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -197,7 +197,7 @@ export abstract class PlatformPackager const patterns = this.getFileMatchers("files", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions) let defaultMatcher = patterns == null ? new FileMatcher(appDir, path.join(resourcesPath, "app"), fileMatchOptions) : patterns[0] - if (defaultMatcher.isEmpty()) { + if (defaultMatcher.isEmpty() || defaultMatcher.containsOnlyIgnore()) { defaultMatcher.addPattern("**/*") } else { @@ -252,7 +252,7 @@ export abstract class PlatformPackager //noinspection ES6MissingAwait const promises = [promise, unlinkIfExists(path.join(resourcesPath, "default_app.asar")), unlinkIfExists(path.join(appOutDir, "version")), this.postInitApp(appOutDir)] if (this.platform !== Platform.MAC) { - promises.push(rename(path.join(appOutDir, "LICENSE"), path.join(appOutDir, "LICENSE.electron.txt")) .catch(() => {/* ignore */})) + promises.push(rename(path.join(appOutDir, "LICENSE"), path.join(appOutDir, "LICENSE.electron.txt")).catch(() => {/* ignore */})) } if (this.info.electronVersion != null && this.info.electronVersion[0] === "0") { // electron release >= 0.37.4 - the default_app/ folder is a default_app.asar file diff --git a/src/targets/appx.ts b/src/targets/appx.ts index 1fa5b2d31a2..dc5f9c5027c 100644 --- a/src/targets/appx.ts +++ b/src/targets/appx.ts @@ -9,12 +9,18 @@ import BluebirdPromise from "bluebird-lst-c" import { Target } from "./targetFactory" import { getSignVendorPath } from "../windowsCodeSign" import sanitizeFileName from "sanitize-filename" +import { release } from "os" export default class AppXTarget extends Target { private readonly options: AppXOptions = Object.assign({}, this.packager.platformSpecificBuildOptions, this.packager.devMetadata.build.appx) constructor(private readonly packager: WinPackager, private readonly outDir: string) { super("appx") + + const osVersion = release() + if (process.platform !== "win32" || parseInt(osVersion.substring(0, osVersion.indexOf(".")), 10) < 10) { + throw new Error("AppX is supported only on Windows 10") + } } // no flatten - use asar or npm 3 or yarn diff --git a/src/util/filter.ts b/src/util/filter.ts index 19f65662ddd..07e97c66fcc 100644 --- a/src/util/filter.ts +++ b/src/util/filter.ts @@ -17,7 +17,7 @@ export function hasMagic(pattern: Minimatch) { return true } - for (let i of set[0]) { + for (const i of set[0]) { if (typeof i !== "string") { return true } @@ -44,7 +44,6 @@ export function createFilter(src: string, patterns: Array, ignoreFile } let relative = it.substring(src.length + 1) - if (path.sep === "\\") { relative = relative.replace(/\\/g, "/") } @@ -56,7 +55,7 @@ export function createFilter(src: string, patterns: Array, ignoreFile // https://github.com/joshwnj/minimatch-all/blob/master/index.js function minimatchAll(path: string, patterns: Array, stat: Stats): boolean { let match = false - for (let pattern of patterns) { + for (const pattern of patterns) { // If we've got a match, only re-test for exclusions. // if we don't have a match, only re-test for inclusions. if (match !== pattern.negate) { diff --git a/test/README.md b/test/README.md index 9d0f5caa2ff..58ac51ee519 100755 --- a/test/README.md +++ b/test/README.md @@ -1,5 +1,3 @@ -In addition to [required system packages](./multi-platform-build.md), on MacOS `dpkg` is required to run Linux tests: `brew install dpkg` - # Inspect output if test uses temporary directory Set environment variable `TEST_APP_TMP_DIR` (e.g. `/tmp/electron-builder-test`). Specified directory will be used instead of random temporary directory and *cleared* on each run. diff --git a/test/jestSetup.js b/test/jestSetup.js index 875417259ad..d62a3e52f89 100644 --- a/test/jestSetup.js +++ b/test/jestSetup.js @@ -33,4 +33,7 @@ test.ifNotCiMac = isCi && process.platform === "darwin" ? skip : test test.ifDevOrWinCi = !isCi || isWindows ? test : skip test.ifDevOrLinuxCi = !isCi || process.platform === "linux" ? test : skip -test.ifWinCi = isCi && isWindows ? test : skip \ No newline at end of file +test.ifWinCi = isCi && isWindows ? test : skip + +delete process.env.CSC_NAME +process.env.CSC_IDENTITY_AUTO_DISCOVERY = "false" \ No newline at end of file diff --git a/test/src/ArtifactPublisherTest.ts b/test/src/ArtifactPublisherTest.ts index 1cb4f1c5b0c..f5f10d31d5a 100644 --- a/test/src/ArtifactPublisherTest.ts +++ b/test/src/ArtifactPublisherTest.ts @@ -11,6 +11,12 @@ if (process.env.CI && process.platform === "win32") { }) } +if (process.env.ELECTRON_BUILDER_OFFLINE === "true") { + fit("Skip ArtifactPublisherTest suite — ELECTRON_BUILDER_OFFLINE is defined", () => { + console.warn("[SKIP] Skip ArtifactPublisherTest suite — ELECTRON_BUILDER_OFFLINE is defined") + }) +} + function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min } diff --git a/test/src/filesTest.ts b/test/src/filesTest.ts index 8260340b29b..8e469dfb36c 100644 --- a/test/src/filesTest.ts +++ b/test/src/filesTest.ts @@ -12,7 +12,7 @@ test.ifDevOrLinuxCi("files", app({ devMetadata: { build: { asar: false, - files: ["**/*", "!ignoreMe${/*}"] + files: ["!ignoreMe${/*}"] } } }, { @@ -25,7 +25,7 @@ test.ifDevOrLinuxCi("files", app({ })) test("extraResources", async () => { - for (let platform of getPossiblePlatforms().keys()) { + for (const platform of getPossiblePlatforms().keys()) { const osName = platform.buildConfigurationKey const winDirPrefix = "lib/net45/resources/" @@ -90,78 +90,79 @@ test("extraResources", async () => { } }) -test("extraResources - one-package", async () => { - for (let platform of [process.platform === "win32" ? Platform.WINDOWS : Platform.LINUX]) { - const osName = platform.buildConfigurationKey +test("extraResources - one-package", () => { + const platform = process.platform === "win32" ? Platform.WINDOWS : Platform.LINUX + const osName = platform.buildConfigurationKey - const winDirPrefix = "lib/net45/resources/" + const winDirPrefix = "lib/net45/resources/" - //noinspection SpellCheckingInspection - await assertPack("test-app-one", { - // to check NuGet package - targets: platform.createTarget(platform === Platform.WINDOWS ? "squirrel" : DIR_TARGET), - devMetadata: { - build: { - asar: true, - }, + //noinspection SpellCheckingInspection + return assertPack("test-app-one", { + // to check NuGet package + targets: platform.createTarget(platform === Platform.WINDOWS ? "squirrel" : DIR_TARGET), + devMetadata: { + build: { + asar: true, }, - }, { - projectDirCreated: projectDir => { - return BluebirdPromise.all([ - modifyPackageJson(projectDir, data => { - data.build.extraResources = [ - "foo", - "bar/hello.txt", - "bar/${arch}.txt", - "${os}/${arch}.txt", - ] + }, + }, { + projectDirCreated: projectDir => { + return BluebirdPromise.all([ + modifyPackageJson(projectDir, data => { + data.build.extraResources = [ + "foo", + "bar/hello.txt", + "bar/${arch}.txt", + "${os}/${arch}.txt", + ] - data.build[osName] = { - extraResources: [ - "platformSpecificR" - ], - extraFiles: [ - "platformSpecificF" - ], - } - }), - outputFile(path.join(projectDir, "foo/nameWithoutDot"), "nameWithoutDot"), - outputFile(path.join(projectDir, "bar/hello.txt"), "data"), - outputFile(path.join(projectDir, `bar/${process.arch}.txt`), "data"), - outputFile(path.join(projectDir, `${osName}/${process.arch}.txt`), "data"), - outputFile(path.join(projectDir, "platformSpecificR"), "platformSpecificR"), - outputFile(path.join(projectDir, "ignoreMe.txt"), "ignoreMe"), - ]) - }, - packed: async context => { - const base = path.join(context.outDir, platform.buildConfigurationKey + `${platform === Platform.MAC ? "" : "-unpacked"}`) - let resourcesDir = path.join(base, "resources") - if (platform === Platform.MAC) { - resourcesDir = path.join(base, "TestApp.app", "Contents", "Resources") - } - const appDir = path.join(resourcesDir, "app") + data.build[osName] = { + extraResources: [ + "platformSpecificR" + ], + extraFiles: [ + "platformSpecificF" + ], + } + }), + outputFile(path.join(projectDir, "foo/nameWithoutDot"), "nameWithoutDot"), + outputFile(path.join(projectDir, "bar/hello.txt"), "data"), + outputFile(path.join(projectDir, `bar/${process.arch}.txt`), "data"), + outputFile(path.join(projectDir, `${osName}/${process.arch}.txt`), "data"), + outputFile(path.join(projectDir, "platformSpecificR"), "platformSpecificR"), + outputFile(path.join(projectDir, "ignoreMe.txt"), "ignoreMe"), + ]) + }, + packed: async context => { + const base = path.join(context.outDir, platform.buildConfigurationKey + `${platform === Platform.MAC ? "" : "-unpacked"}`) + let resourcesDir = path.join(base, "resources") + if (platform === Platform.MAC) { + resourcesDir = path.join(base, "TestApp.app", "Contents", "Resources") + } + const appDir = path.join(resourcesDir, "app") - await assertThat(path.join(resourcesDir, "foo")).isDirectory() - await assertThat(path.join(appDir, "foo")).doesNotExist() + await assertThat(path.join(resourcesDir, "foo")).isDirectory() + await assertThat(path.join(appDir, "foo")).doesNotExist() - await assertThat(path.join(resourcesDir, "foo", "nameWithoutDot")).isFile() - await assertThat(path.join(appDir, "foo", "nameWithoutDot")).doesNotExist() + await assertThat(path.join(resourcesDir, "foo", "nameWithoutDot")).isFile() + await assertThat(path.join(appDir, "foo", "nameWithoutDot")).doesNotExist() - await assertThat(path.join(resourcesDir, "bar", "hello.txt")).isFile() - await assertThat(path.join(resourcesDir, "bar", `${process.arch}.txt`)).isFile() - await assertThat(path.join(appDir, "bar", `${process.arch}.txt`)).doesNotExist() + await assertThat(path.join(resourcesDir, "bar", "hello.txt")).isFile() + await assertThat(path.join(resourcesDir, "bar", `${process.arch}.txt`)).isFile() + await assertThat(path.join(appDir, "bar", `${process.arch}.txt`)).doesNotExist() - await assertThat(path.join(resourcesDir, osName, `${process.arch}.txt`)).isFile() - await assertThat(path.join(resourcesDir, "platformSpecificR")).isFile() - await assertThat(path.join(resourcesDir, "ignoreMe.txt")).doesNotExist() - }, - expectedContents: platform === Platform.WINDOWS ? pathSorter(expectedWinContents.concat( - winDirPrefix + "bar/hello.txt", - winDirPrefix + "bar/x64.txt", - winDirPrefix + "foo/nameWithoutDot", - winDirPrefix + "platformSpecificR", - winDirPrefix + "win/x64.txt" - )) : null, - }) - } + await BluebirdPromise.all([ + assertThat(path.join(resourcesDir, osName, `${process.arch}.txt`)).isFile(), + assertThat(path.join(resourcesDir, "platformSpecificR")).isFile(), + assertThat(path.join(resourcesDir, "ignoreMe.txt")).doesNotExist(), + ]) + }, + expectedContents: platform === Platform.WINDOWS ? pathSorter(expectedWinContents.concat( + winDirPrefix + "bar/hello.txt", + winDirPrefix + "bar/x64.txt", + winDirPrefix + "foo/nameWithoutDot", + winDirPrefix + "platformSpecificR", + winDirPrefix + "win/x64.txt" + )) : null, + }) }) \ No newline at end of file diff --git a/test/src/helpers/runTests.ts b/test/src/helpers/runTests.ts index 03e0d2da2a9..0c0ae20a3d9 100755 --- a/test/src/helpers/runTests.ts +++ b/test/src/helpers/runTests.ts @@ -107,7 +107,6 @@ async function runTests() { } process.env.SKIP_WIN = skipWin - process.env.CSC_IDENTITY_AUTO_DISCOVERY = "false" process.env.TEST_DIR = TEST_DIR const rootDir = path.join(__dirname, "..", "..", "..") diff --git a/test/src/httpRequestTest.ts b/test/src/httpRequestTest.ts index 3f9b30f537f..8372b6082fe 100644 --- a/test/src/httpRequestTest.ts +++ b/test/src/httpRequestTest.ts @@ -4,8 +4,14 @@ import { randomBytes } from "crypto" import { assertThat } from "./helpers/fileAssert" import * as path from "path" +if (process.env.ELECTRON_BUILDER_OFFLINE === "true") { + fit("Skip httpRequestTest — ELECTRON_BUILDER_OFFLINE is defined", () => { + console.warn("[SKIP] Skip httpRequestTest — ELECTRON_BUILDER_OFFLINE is defined") + }) +} + test.ifDevOrLinuxCi("download to nonexistent dir", async () => { - const tempFile = path.join(tmpdir(), `${process.pid}-${randomBytes(8).toString("hex")}`, Date.now().toString(), "foo.txt") + const tempFile = path.join(process.env.TEST_DIR || tmpdir(), `${process.pid}-${randomBytes(8).toString("hex")}`, Date.now().toString(16), "foo.txt") await download("https://drive.google.com/uc?export=download&id=0Bz3JwZ-jqfRONTkzTGlsMkM2TlE", tempFile) await assertThat(tempFile).isFile() }) \ No newline at end of file diff --git a/test/src/mac/macArchiveTest.ts b/test/src/mac/macArchiveTest.ts index fb3173af19e..c930f43b8ac 100644 --- a/test/src/mac/macArchiveTest.ts +++ b/test/src/mac/macArchiveTest.ts @@ -5,7 +5,9 @@ test.ifMac("invalid target", () => assertThat(createMacTargetTest(["ttt"], test("only zip", createMacTargetTest(["zip"], ["Test App ßW-1.1.0-mac.zip"])) -test.ifMac("pkg", createMacTargetTest(["pkg"], ["Test App ßW-1.1.0.pkg"])) +if (process.env.CSC_KEY_PASSWORD != null) { + test.ifMac("pkg", createMacTargetTest(["pkg"], ["Test App ßW-1.1.0.pkg"])) +} test("tar.gz", createMacTargetTest(["tar.gz"], ["Test App ßW-1.1.0-mac.tar.gz"])) diff --git a/test/src/nsisUpdaterTest.ts b/test/src/nsisUpdaterTest.ts index 23ed9adf8b7..0d1e612e0dc 100644 --- a/test/src/nsisUpdaterTest.ts +++ b/test/src/nsisUpdaterTest.ts @@ -4,10 +4,15 @@ import * as path from "path" import { TmpDir } from "out/util/tmp" import { outputFile } from "fs-extra-p" import { safeDump } from "js-yaml" -import { GenericServerOptions } from "out/options/publishOptions" -import { GithubOptions } from "out/options/publishOptions" +import { GenericServerOptions, GithubOptions } from "out/options/publishOptions" import BluebirdPromise from "bluebird-lst-c" +if (process.env.ELECTRON_BUILDER_OFFLINE === "true") { + fit("Skip ArtifactPublisherTest suite — ELECTRON_BUILDER_OFFLINE is defined", () => { + console.warn("[SKIP] Skip ArtifactPublisherTest suite — ELECTRON_BUILDER_OFFLINE is defined") + }) +} + const NsisUpdaterClass = require("../../nsis-auto-updater/out/nsis-auto-updater/src/NsisUpdater").NsisUpdater const g = (global) From e3cfa8e7a0a4d096370f654b102436850cdaa329 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sat, 26 Nov 2016 10:17:00 +0100 Subject: [PATCH 02/43] fix: Unable to build package because of asarUnpack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: asar.unpackDir, asar.unpack, asar-unpack-dir, asar-unpack were removed — please use build.asarUnpack instead Closes #937 --- src/asarUtil.ts | 91 ++++++++++++++-------------------- src/fileMatcher.ts | 5 ++ src/platformPackager.ts | 52 +++++++++++-------- test/jestSetup.js | 3 +- test/src/extraMetadataTest.ts | 56 ++++++++++----------- test/src/globTest.ts | 8 +-- test/src/helpers/packTester.ts | 4 ++ typings/asar.d.ts | 2 - 8 files changed, 106 insertions(+), 115 deletions(-) diff --git a/src/asarUtil.ts b/src/asarUtil.ts index 7b4ad9dd677..6417a302750 100644 --- a/src/asarUtil.ts +++ b/src/asarUtil.ts @@ -1,13 +1,21 @@ import { AsarFileInfo, listPackage, statFile, AsarOptions } from "asar-electron-builder" -import { statOrNull, debug } from "./util/util" +import { statOrNull, debug, isCi } from "./util/util" import { - lstat, readdir, readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson, - writeFile, realpath + lstat, + readdir, + readFile, + Stats, + createWriteStream, + ensureDir, + createReadStream, + readJson, + writeFile, + realpath, + link } from "fs-extra-p" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { log } from "./util/log" -import { Minimatch } from "minimatch" import { deepAssign } from "./util/deepAssign" import { Filter } from "./util/filter" @@ -71,10 +79,6 @@ export async function createAsarArchive(src: string, resourcesPath: string, opti await new AsarPackager(src, resourcesPath, options, unpackPattern).pack(filter) } -function isUnpackDir(path: string, pattern: Minimatch, rawPattern: string): boolean { - return path.startsWith(rawPattern) || pattern.match(path) -} - function addValue(map: Map>, key: string, value: string) { let list = map.get(key) if (list == null) { @@ -193,19 +197,15 @@ class AsarPackager { async createPackageFromFiles(files: Array, metadata: Map) { // search auto unpacked dir - const autoUnpackDirs = new Set() + const unpackedDirs = new Set() const unpackedDest = `${this.outFile}.unpacked` const fileIndexToModulePackageData = new Map>() + await ensureDir(path.dirname(this.outFile)) + if (this.options.smartUnpack !== false) { - await this.detectUnpackedDirs(files, metadata, autoUnpackDirs, unpackedDest, fileIndexToModulePackageData) + await this.detectUnpackedDirs(files, metadata, unpackedDirs, unpackedDest, fileIndexToModulePackageData) } - const unpackDir = this.options.unpackDir == null ? null : new Minimatch(this.options.unpackDir) - const unpack = this.options.unpack == null ? null : new Minimatch(this.options.unpack, { - matchBase: true - }) - - const createDirPromises: Array> = [ensureDir(path.dirname(this.outFile))] const copyPromises: Array> = [] const mainPackageJson = path.join(this.src, "package.json") for (let i = 0, n = files.length; i < n; i++) { @@ -214,12 +214,6 @@ class AsarPackager { if (stat.isFile()) { const fileParent = path.dirname(file) const dirNode = this.fs.searchNodeFromPath(fileParent) - - if (dirNode.unpacked && createDirPromises.length > 0) { - await BluebirdPromise.all(createDirPromises) - createDirPromises.length = 0 - } - const packageDataPromise = fileIndexToModulePackageData.get(i) let newData: any | null = null if (packageDataPromise == null) { @@ -234,19 +228,12 @@ class AsarPackager { const fileSize = newData == null ? stat.size : Buffer.byteLength(newData) const node = this.fs.searchNodeFromPath(file) node.size = fileSize - if (dirNode.unpacked || (this.unpackPattern != null && this.unpackPattern(file, stat)) || (unpack != null && unpack.match(file))) { + if (dirNode.unpacked || (this.unpackPattern != null && this.unpackPattern(file, stat))) { node.unpacked = true - if (!dirNode.unpacked) { - const promise = ensureDir(path.join(unpackedDest, path.relative(this.src, fileParent))) - if (createDirPromises.length === 0) { - await createDirPromises - } - else { - createDirPromises.push(promise) - await BluebirdPromise.all(createDirPromises) - createDirPromises.length = 0 - } + if (!dirNode.unpacked && !unpackedDirs.has(fileParent)) { + unpackedDirs.add(fileParent) + await ensureDir(path.join(unpackedDest, path.relative(this.src, fileParent))) } const unpackedFile = path.join(unpackedDest, path.relative(this.src, file)) @@ -276,24 +263,18 @@ class AsarPackager { } else if (stat.isDirectory()) { let unpacked = false - if (autoUnpackDirs.has(file)) { + if (unpackedDirs.has(file)) { unpacked = true } else { - unpacked = unpackDir != null && isUnpackDir(path.relative(this.src, file), unpackDir, this.options.unpackDir!) - if (unpacked) { - createDirPromises.push(ensureDir(path.join(unpackedDest, path.relative(this.src, file)))) - } - else { - for (let d of autoUnpackDirs) { - if (file.length > (d.length + 2) && file[d.length] === path.sep && file.startsWith(d)) { - unpacked = true - autoUnpackDirs.add(file) - // not all dirs marked as unpacked after first iteration - because node module dir can be marked as unpacked after processing node module dir content - // e.g. node-notifier/example/advanced.js processed, but only on process vendor/terminal-notifier.app module will be marked as unpacked - createDirPromises.push(ensureDir(path.join(unpackedDest, path.relative(this.src, file)))) - break - } + for (const dir of unpackedDirs) { + if (file.length > (dir.length + 2) && file[dir.length] === path.sep && file.startsWith(dir)) { + unpacked = true + unpackedDirs.add(file) + // not all dirs marked as unpacked after first iteration - because node module dir can be marked as unpacked after processing node module dir content + // e.g. node-notifier/example/advanced.js processed, but only on process vendor/terminal-notifier.app module will be marked as unpacked + await ensureDir(path.join(unpackedDest, path.relative(this.src, file))) + break } } } @@ -362,7 +343,7 @@ class AsarPackager { }) } - async order(filenames: Array) { + private async order(filenames: Array) { const orderingFiles = (await readFile(this.options.ordering!, "utf8")).split("\n").map(line => { if (line.indexOf(":") !== -1) { line = line.split(":").pop()! @@ -375,7 +356,7 @@ class AsarPackager { }) const ordering: Array = [] - for (let file of orderingFiles) { + for (const file of orderingFiles) { let pathComponents = file.split(path.sep) let str = this.src for (let pathComponent of pathComponents) { @@ -387,12 +368,12 @@ class AsarPackager { const filenamesSorted: Array = [] let missing = 0 const total = filenames.length - for (let file of ordering) { + for (const file of ordering) { if (!filenamesSorted.includes(file) && filenames.includes(file)) { filenamesSorted.push(file) } } - for (let file of filenames) { + for (const file of filenames) { if (!filenamesSorted.includes(file)) { filenamesSorted.push(file) missing += 1 @@ -406,7 +387,7 @@ class AsarPackager { function cleanupPackageJson(data: any): any { try { let changed = false - for (let prop of Object.getOwnPropertyNames(data)) { + for (const prop of Object.getOwnPropertyNames(data)) { if (prop[0] === "_" || prop === "dist" || prop === "gitHead" || prop === "keywords") { delete data[prop] changed = true @@ -459,6 +440,10 @@ export async function checkFileInArchive(asarFile: string, relativeFile: string, } function copyFile(src: string, dest: string, stats: Stats) { + if (process.platform != "win32" && (isCi || process.env.USE_HARD_LINKS === "true")) { + return link(src, dest) + } + return new BluebirdPromise(function (resolve, reject) { const readStream = createReadStream(src) const writeStream = createWriteStream(dest, {mode: stats.mode}) diff --git a/src/fileMatcher.ts b/src/fileMatcher.ts index 7774f44fd51..f4219614e48 100644 --- a/src/fileMatcher.ts +++ b/src/fileMatcher.ts @@ -30,6 +30,11 @@ export class FileMatcher { this.patterns.push(pattern) } + addAllPattern() { + // must be first, see minimatchAll implementation + this.patterns.unshift("**/*") + } + isEmpty() { return this.patterns.length === 0 } diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 7254487d9aa..9ca68483242 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -18,6 +18,7 @@ import { PublishConfiguration, GithubOptions, BintrayOptions, GenericServerOptio import { getRepositoryInfo } from "./repositoryInfo" import { dependencies } from "./yarn" import { Target } from "./targets/targetFactory" +import { deepAssign } from "./util/deepAssign" import EventEmitter = NodeJS.EventEmitter export interface PackagerOptions { @@ -198,7 +199,7 @@ export abstract class PlatformPackager const patterns = this.getFileMatchers("files", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions) let defaultMatcher = patterns == null ? new FileMatcher(appDir, path.join(resourcesPath, "app"), fileMatchOptions) : patterns[0] if (defaultMatcher.isEmpty() || defaultMatcher.containsOnlyIgnore()) { - defaultMatcher.addPattern("**/*") + defaultMatcher.addAllPattern() } else { defaultMatcher.addPattern("package.json") @@ -288,34 +289,41 @@ export abstract class PlatformPackager } private computeAsarOptions(customBuildOptions: DC): AsarOptions | null { - let result = this.devMetadata.build.asar - let platformSpecific = customBuildOptions.asar - if (platformSpecific != null) { - result = platformSpecific - } - - if (result === false) { - return null + function errorMessage(name: string) { + return `${name} is deprecated is deprecated and not supported — please use build.asarUnpack` } const buildMetadata = this.devMetadata.build if (buildMetadata["asar-unpack"] != null) { - warn("asar-unpack is deprecated, please set as asar.unpack") + throw new Error(errorMessage("asar-unpack")) } if (buildMetadata["asar-unpack-dir"] != null) { - warn("asar-unpack-dir is deprecated, please set as asar.unpackDir") + throw new Error(errorMessage("asar-unpack-dir")) + } + + const platformSpecific = customBuildOptions.asar + const result = platformSpecific == null ? this.devMetadata.build.asar : platformSpecific + + if (result === false) { + return null + } + + const defaultOptions = { + extraMetadata: this.options.extraMetadata, } if (result == null || result === true) { - result = { - unpack: buildMetadata["asar-unpack"], - unpackDir: buildMetadata["asar-unpack-dir"] - } + return defaultOptions } - return Object.assign(result, { - extraMetadata: this.options.extraMetadata - }) + if ((result).unpackDir != null) { + throw new Error(errorMessage("asar.unpackDir")) + } + if ((result).unpack != null) { + throw new Error(errorMessage("asar.unpack")) + } + + return deepAssign({}, result, defaultOptions) } private doCopyExtraFiles(patterns: Array | null): Promise { @@ -324,11 +332,11 @@ export abstract class PlatformPackager } else { const promises: Array> = [] - for (let i = 0; i < patterns.length; i++) { - if (patterns[i].isEmpty()) { - patterns[i].addPattern("**/*") + for (const pattern of patterns) { + if (pattern.isEmpty() || pattern.containsOnlyIgnore()) { + pattern.addAllPattern() } - promises.push(copyFiltered(patterns[i].from, patterns[i].to, patterns[i].createFilter(), this.platform === Platform.WINDOWS)) + promises.push(copyFiltered(pattern.from, pattern.to, pattern.createFilter(), this.platform === Platform.WINDOWS)) } return BluebirdPromise.all(promises) } diff --git a/test/jestSetup.js b/test/jestSetup.js index d62a3e52f89..90dfc7fca17 100644 --- a/test/jestSetup.js +++ b/test/jestSetup.js @@ -36,4 +36,5 @@ test.ifDevOrLinuxCi = !isCi || process.platform === "linux" ? test : skip test.ifWinCi = isCi && isWindows ? test : skip delete process.env.CSC_NAME -process.env.CSC_IDENTITY_AUTO_DISCOVERY = "false" \ No newline at end of file +process.env.CSC_IDENTITY_AUTO_DISCOVERY = "false" +process.env.USE_HARD_LINKS = "true" \ No newline at end of file diff --git a/test/src/extraMetadataTest.ts b/test/src/extraMetadataTest.ts index f2a50e446c1..ef981dee3bc 100644 --- a/test/src/extraMetadataTest.ts +++ b/test/src/extraMetadataTest.ts @@ -1,11 +1,12 @@ -import { assertPack, modifyPackageJson, appTwoThrows } from "./helpers/packTester" +import { modifyPackageJson, appTwoThrows, app, appTwo } from "./helpers/packTester" import { Platform, DIR_TARGET } from "out" import { assertThat } from "./helpers/fileAssert" import * as path from "path" import { extractFile } from "asar-electron-builder" -test.ifDevOrLinuxCi("extra metadata", () => { - const extraMetadata = { +test.ifDevOrLinuxCi("extra metadata", app({ + targets: Platform.LINUX.createTarget(DIR_TARGET), + extraMetadata: { foo: { bar: 12, }, @@ -14,46 +15,39 @@ test.ifDevOrLinuxCi("extra metadata", () => { executableName: "new-name" } } - } - return assertPack("test-app-one", { - targets: Platform.LINUX.createTarget(DIR_TARGET), - extraMetadata: extraMetadata, - }, { - projectDirCreated: projectDir => modifyPackageJson(projectDir, data => { - data.foo = { - bar: 42, + }, +}, { + projectDirCreated: projectDir => modifyPackageJson(projectDir, data => { + data.foo = { + bar: 42, + existingProp: 22, + } + }), + packed: async context => { + await assertThat(path.join(context.getContent(Platform.LINUX), "new-name")).isFile() + assertThat(JSON.parse(extractFile(path.join(context.getResources(Platform.LINUX), "app.asar"), "package.json").toString())).hasProperties({ + foo: { + bar: 12, existingProp: 22, } - }), - packed: async context => { - await assertThat(path.join(context.getContent(Platform.LINUX), "new-name")).isFile() - assertThat(JSON.parse(extractFile(path.join(context.getResources(Platform.LINUX), "app.asar"), "package.json").toString())).hasProperties({ - foo: { - bar: 12, - existingProp: 22, - } - }) - } - }) -}) + }) + } +})) -test.ifDevOrLinuxCi("extra metadata - two", () => { - const extraMetadata = { +test.ifDevOrLinuxCi("extra metadata - two", appTwo({ + targets: Platform.LINUX.createTarget(DIR_TARGET), + extraMetadata: { build: { linux: { executableName: "new-name" } } - } - return assertPack("test-app", { - targets: Platform.LINUX.createTarget(DIR_TARGET), - extraMetadata: extraMetadata, + }, }, { packed: async context => { await assertThat(path.join(context.getContent(Platform.LINUX), "new-name")).isFile() } - }) -}) +})) test.ifMac("extra metadata - override icon", appTwoThrows(/ENOENT: no such file or directory/, { targets: Platform.MAC.createTarget(DIR_TARGET), diff --git a/test/src/globTest.ts b/test/src/globTest.ts index d85afcd7311..3440aa2a34c 100644 --- a/test/src/globTest.ts +++ b/test/src/globTest.ts @@ -10,9 +10,7 @@ test.ifDevOrLinuxCi("unpackDir one", app({ targets: Platform.LINUX.createTarget(DIR_TARGET), devMetadata: { build: { - asar: { - unpackDir: "{assets,b2}" - }, + asarUnpack: ["assets", "b2"], } } }, { @@ -35,9 +33,7 @@ test.ifDevOrLinuxCi("unpackDir", () => { targets: Platform.LINUX.createTarget(DIR_TARGET), devMetadata: { build: { - asar: { - unpackDir: "{assets,b2}" - }, + asarUnpack: ["assets", "b2"], } } }, { diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 5c53151c953..39318754348 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -78,6 +78,10 @@ export function app(packagerOptions: PackagerOptions, checkOptions: AssertPackOp return () => assertPack("test-app-one", packagerOptions, checkOptions) } +export function appTwo(packagerOptions: PackagerOptions, checkOptions: AssertPackOptions = {}) { + return () => assertPack("test-app", packagerOptions, checkOptions) +} + export async function assertPack(fixtureName: string, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions = {}): Promise { if (checkOptions.signed) { packagerOptions = signed(packagerOptions) diff --git a/typings/asar.d.ts b/typings/asar.d.ts index b5efe8cc6e3..3f01aa5f4d8 100644 --- a/typings/asar.d.ts +++ b/typings/asar.d.ts @@ -5,8 +5,6 @@ declare module "asar-electron-builder" { } interface AsarOptions { - unpack?: string - unpackDir?: string dot?: boolean smartUnpack?: boolean From 71aa6207c3d5d6322459b642d0f208453d23f123 Mon Sep 17 00:00:00 2001 From: Daniel Nieto Date: Sat, 26 Nov 2016 10:47:42 -0600 Subject: [PATCH 03/43] docs: remove unnecesary comma (#938) text editors "complain" when using this snippet because this extra comma --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c14aa725562..1686f053dcd 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ For an app that will be shipped to production, you should sign your application. "build": { "appId": "your.id", "mac": { - "category": "your.app.category.type", + "category": "your.app.category.type" }, "win": { "iconUrl": "(windows-only) https link to icon" From b91c1d1072f7736ca11e49fd8f223aea6ba2c083 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sat, 26 Nov 2016 17:07:07 +0100 Subject: [PATCH 04/43] refactor: extract fs util --- .idea/codeStyleSettings.xml | 1 + src/asarUtil.ts | 67 ++---------------------- src/cli/create-self-signed-cert.ts | 5 +- src/codeSign.ts | 3 +- src/metadata.ts | 2 - src/platformPackager.ts | 20 ++++--- src/targets/appImage.ts | 3 +- src/targets/dmg.ts | 3 +- src/util/binDownload.ts | 3 +- src/util/filter.ts | 1 - src/util/fs.ts | 84 ++++++++++++++++++++++++++++++ src/util/util.ts | 34 +----------- src/yarn.ts | 3 +- test/src/helpers/wine.ts | 2 +- test/src/windows/nsisTest.ts | 2 +- 15 files changed, 115 insertions(+), 118 deletions(-) create mode 100644 src/util/fs.ts diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index d52229a2c3d..f9db70b81e0 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -22,6 +22,7 @@ diff --git a/src/asarUtil.ts b/src/asarUtil.ts index 6417a302750..69c9d02d309 100644 --- a/src/asarUtil.ts +++ b/src/asarUtil.ts @@ -1,79 +1,20 @@ import { AsarFileInfo, listPackage, statFile, AsarOptions } from "asar-electron-builder" -import { statOrNull, debug, isCi } from "./util/util" -import { - lstat, - readdir, - readFile, - Stats, - createWriteStream, - ensureDir, - createReadStream, - readJson, - writeFile, - realpath, - link -} from "fs-extra-p" +import { debug, isCi } from "./util/util" +import { readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson, writeFile, realpath, link } from "fs-extra-p" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { log } from "./util/log" import { deepAssign } from "./util/deepAssign" import { Filter } from "./util/filter" +import { walk, statOrNull, CONCURRENCY, MAX_FILE_REQUESTS } from "./util/fs" 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 -const MAX_FILE_REQUESTS = 8 -const concurrency = {concurrency: MAX_FILE_REQUESTS} const NODE_MODULES_PATTERN = path.sep + "node_modules" + path.sep -export async function walk(initialDirPath: string, consumer?: (file: string, stat: Stats) => void, filter?: Filter): Promise> { - const result: Array = [] - const queue: Array = [initialDirPath] - let addDirToResult = false - while (queue.length > 0) { - const dirPath = queue.pop()! - if (addDirToResult) { - result.push(dirPath) - } - else { - addDirToResult = true - } - - const childNames = await readdir(dirPath) - childNames.sort() - - const dirs: Array = [] - await BluebirdPromise.map(childNames, name => { - const filePath = dirPath + path.sep + name - return lstat(filePath) - .then(stat => { - if (filter != null && !filter(filePath, stat)) { - return - } - - if (consumer != null) { - consumer(filePath, stat) - } - - if (stat.isDirectory()) { - dirs.push(filePath) - } - else { - result.push(filePath) - } - }) - }, concurrency) - - for (let i = dirs.length - 1; i > -1; i--) { - queue.push(dirs[i]) - } - } - - return result -} - export async function createAsarArchive(src: string, resourcesPath: string, options: AsarOptions, filter: Filter, unpackPattern: Filter | null): Promise { // sort files to minimize file change (i.e. asar file is not changed dramatically on small change) await new AsarPackager(src, resourcesPath, options, unpackPattern).pack(filter) @@ -191,7 +132,7 @@ class AsarPackager { const base = path.join(unpackedDest, it) await ensureDir(base) await BluebirdPromise.each(dirToCreate.get(it)!, it => ensureDir(path.join(base, it))) - }, concurrency) + }, CONCURRENCY) } } diff --git a/src/cli/create-self-signed-cert.ts b/src/cli/create-self-signed-cert.ts index cc263358828..3bfe53b09c8 100644 --- a/src/cli/create-self-signed-cert.ts +++ b/src/cli/create-self-signed-cert.ts @@ -1,11 +1,12 @@ import yargs from "yargs" import { printErrorAndExit } from "../util/promise" -import { exec, spawn, unlinkIfExists } from "../util/util" +import { exec, spawn } from "../util/util" import { getSignVendorPath } from "../windowsCodeSign" import * as path from "path" import sanitizeFileName from "sanitize-filename" import { log } from "../util/log" import { TmpDir } from "../util/tmp" +import { unlinkIfExists } from "../util/fs" async function main() { const args: any = yargs @@ -31,7 +32,7 @@ async function main() { log(`${pfx} created. Please see https://github.com/electron-userland/electron-builder/wiki/Code-Signing how do use it to sign.`) const certLocation = "Cert:\\LocalMachine\\TrustedPeople" - log(`${pfx} will be imported into ${certLocation} Operation will be succeded only if runned from root. Otherwise import file manually.`) + log(`${pfx} will be imported into ${certLocation} Operation will be succeed only if runned from root. Otherwise import file manually.`) await spawn("powershell.exe", ["Import-PfxCertificate", "-FilePath", `"${pfx}"`, "-CertStoreLocation", ""]) tmpDir.cleanup() } diff --git a/src/codeSign.ts b/src/codeSign.ts index e787c02ffb0..d181229f151 100644 --- a/src/codeSign.ts +++ b/src/codeSign.ts @@ -1,4 +1,4 @@ -import { exec, getTempName, isEmptyOrSpaces, isCi, getCacheDirectory, statOrNull } from "./util/util" +import { exec, getTempName, isEmptyOrSpaces, isCi, getCacheDirectory } from "./util/util" import { deleteFile, outputFile, copy, rename } from "fs-extra-p" import { download } from "./util/httpRequest" import * as path from "path" @@ -7,6 +7,7 @@ import BluebirdPromise from "bluebird-lst-c" import { randomBytes } from "crypto" import { TmpDir } from "./util/tmp" import { homedir } from "os" +import { statOrNull } from "./util/fs" export const appleCertificatePrefixes = ["Developer ID Application:", "Developer ID Installer:", "3rd Party Mac Developer Application:", "3rd Party Mac Developer Installer:"] diff --git a/src/metadata.ts b/src/metadata.ts index 830538172b2..0334abed5bc 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -239,8 +239,6 @@ export interface BuildMetadata { // deprecated readonly "app-bundle-id"?: string | null - readonly dereference?: boolean - /* See [.build.publish](#PublishConfiguration). */ diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 9ca68483242..2667ea20bf8 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -2,7 +2,7 @@ import { AppMetadata, DevMetadata, Platform, PlatformSpecificBuildOptions, Arch, import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { readdir, remove, rename } from "fs-extra-p" -import { statOrNull, use, unlinkIfExists, isEmptyOrSpaces, asArray, debug } from "./util/util" +import { use, isEmptyOrSpaces, asArray, debug } from "./util/util" import { Packager } from "./packager" import { AsarOptions } from "asar-electron-builder" import { Minimatch } from "minimatch" @@ -19,6 +19,7 @@ import { getRepositoryInfo } from "./repositoryInfo" import { dependencies } from "./yarn" import { Target } from "./targets/targetFactory" import { deepAssign } from "./util/deepAssign" +import { statOrNull, unlinkIfExists } from "./util/fs" import EventEmitter = NodeJS.EventEmitter export interface PackagerOptions { @@ -242,7 +243,7 @@ export abstract class PlatformPackager const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length ? excludePatterns : null) let promise if (asarOptions == null) { - promise = copyFiltered(appDir, path.join(resourcesPath, "app"), filter, this.info.devMetadata.build.dereference || this.platform === Platform.WINDOWS) + promise = copyFiltered(appDir, path.join(resourcesPath, "app"), filter, this.platform === Platform.WINDOWS) } else { const unpackPattern = this.getFileMatchers("asarUnpack", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions) @@ -330,16 +331,13 @@ export abstract class PlatformPackager if (patterns == null || patterns.length === 0) { return BluebirdPromise.resolve() } - else { - const promises: Array> = [] - for (const pattern of patterns) { - if (pattern.isEmpty() || pattern.containsOnlyIgnore()) { - pattern.addAllPattern() - } - promises.push(copyFiltered(pattern.from, pattern.to, pattern.createFilter(), this.platform === Platform.WINDOWS)) + + return BluebirdPromise.map(patterns, pattern => { + if (pattern.isEmpty() || pattern.containsOnlyIgnore()) { + pattern.addAllPattern() } - return BluebirdPromise.all(promises) - } + return copyFiltered(pattern.from, pattern.to, pattern.createFilter(), this.platform === Platform.WINDOWS) + }) } private getFileMatchers(name: "files" | "extraFiles" | "extraResources" | "asarUnpack", defaultSrc: string, defaultDest: string, allowAdvancedMatching: boolean, fileMatchOptions: FileMatchOptions, customBuildOptions: DC): Array | null { diff --git a/src/targets/appImage.ts b/src/targets/appImage.ts index a2bfe255ef4..ecd2d734e92 100644 --- a/src/targets/appImage.ts +++ b/src/targets/appImage.ts @@ -1,6 +1,6 @@ import { Arch } from "../metadata" import * as path from "path" -import { exec, unlinkIfExists } from "../util/util" +import { exec } from "../util/util" import { open, write, createReadStream, createWriteStream, close, chmod } from "fs-extra-p" import { LinuxTargetHelper } from "./LinuxTargetHelper" import { getBin } from "../util/binDownload" @@ -9,6 +9,7 @@ import { v1 as uuid1 } from "uuid-1345" import { LinuxPackager } from "../linuxPackager" import { log } from "../util/log" import { Target } from "./targetFactory" +import { unlinkIfExists } from "../util/fs" const appImageVersion = process.platform === "darwin" ? "AppImage-09-07-16-mac" : "AppImage-09-07-16-linux" //noinspection SpellCheckingInspection diff --git a/src/targets/dmg.ts b/src/targets/dmg.ts index eb9346a015c..188474ed5c6 100644 --- a/src/targets/dmg.ts +++ b/src/targets/dmg.ts @@ -4,12 +4,13 @@ import { log, warn } from "../util/log" import { PlatformPackager } from "../platformPackager" import { MacOptions, DmgOptions, DmgContent } from "../options/macOptions" import BluebirdPromise from "bluebird-lst-c" -import { debug, use, exec, statOrNull, isEmptyOrSpaces, spawn, exists } from "../util/util" +import { debug, use, exec, isEmptyOrSpaces, spawn } from "../util/util" import { copy, unlink, outputFile, remove } from "fs-extra-p" import { executeFinally } from "../util/promise" import sanitizeFileName from "sanitize-filename" import { Arch } from "../metadata" import { Target } from "./targetFactory" +import { exists, statOrNull } from "../util/fs" export class DmgTarget extends Target { private helperDir = path.join(__dirname, "..", "..", "templates", "dmg") diff --git a/src/util/binDownload.ts b/src/util/binDownload.ts index 531ad8b20b2..617f7d8ff05 100644 --- a/src/util/binDownload.ts +++ b/src/util/binDownload.ts @@ -1,9 +1,10 @@ -import { statOrNull, spawn, debug, debug7zArgs, getTempName, getCacheDirectory } from "./util" +import { spawn, debug, debug7zArgs, getTempName, getCacheDirectory } from "./util" import { rename, unlink, emptyDir } from "fs-extra-p" import { download } from "./httpRequest" import { path7za } from "7zip-bin" import * as path from "path" import BluebirdPromise from "bluebird-lst-c" +import { statOrNull } from "./fs" const versionToPromise = new Map>() diff --git a/src/util/filter.ts b/src/util/filter.ts index 07e97c66fcc..82d66fcdb35 100644 --- a/src/util/filter.ts +++ b/src/util/filter.ts @@ -2,7 +2,6 @@ import { copy, Stats } from "fs-extra-p" import { Minimatch } from "minimatch" import * as path from "path" -// we use relative path to avoid canonical path issue - e.g. /tmp vs /private/tmp export function copyFiltered(src: string, destination: string, filter: Filter, dereference: boolean): Promise { return copy(src, destination, { dereference: dereference, diff --git a/src/util/fs.ts b/src/util/fs.ts new file mode 100644 index 00000000000..a34c95f236a --- /dev/null +++ b/src/util/fs.ts @@ -0,0 +1,84 @@ +import { unlink, access, stat, Stats, lstat, readdir } from "fs-extra-p" +import { Filter } from "./filter" +import BluebirdPromise from "bluebird-lst-c" +import * as path from "path" + +export const MAX_FILE_REQUESTS = 8 +export const CONCURRENCY = {concurrency: MAX_FILE_REQUESTS} + +export function unlinkIfExists(file: string) { + return unlink(file) + .catch(() => { + // ignore + }) +} + +export async function statOrNull(file: string): Promise { + try { + return await stat(file) + } + catch (e) { + if (e.code === "ENOENT") { + return null + } + else { + throw e + } + } +} + +export async function exists(file: string): Promise { + try { + await access(file) + return true + } + catch (e) { + return false + } +} + +export async function walk(initialDirPath: string, consumer?: (file: string, stat: Stats) => void, filter?: Filter): Promise> { + const result: Array = [] + const queue: Array = [initialDirPath] + let addDirToResult = false + while (queue.length > 0) { + const dirPath = queue.pop()! + if (addDirToResult) { + result.push(dirPath) + } + else { + addDirToResult = true + } + + const childNames = await readdir(dirPath) + childNames.sort() + + const dirs: Array = [] + await BluebirdPromise.map(childNames, name => { + const filePath = dirPath + path.sep + name + return lstat(filePath) + .then(stat => { + if (filter != null && !filter(filePath, stat)) { + return + } + + if (consumer != null) { + consumer(filePath, stat) + } + + if (stat.isDirectory()) { + dirs.push(filePath) + } + else { + result.push(filePath) + } + }) + }, CONCURRENCY) + + for (let i = dirs.length - 1; i > -1; i--) { + queue.push(dirs[i]) + } + } + + return result +} \ No newline at end of file diff --git a/src/util/util.ts b/src/util/util.ts index a49233cca16..817b5d20fe4 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -2,12 +2,13 @@ import { execFile, spawn as _spawn, ChildProcess, SpawnOptions } from "child_pro import BluebirdPromise from "bluebird-lst-c" import { homedir } from "os" import * as path from "path" -import { readJson, stat, Stats, unlink, access } from "fs-extra-p" +import { readJson } from "fs-extra-p" import { yellow, red } from "chalk" import _debug from "debug" import { warn, log } from "./log" import { createHash } from "crypto" import "source-map-support/register" +import { statOrNull } from "./fs" import Debugger = debug.Debugger export const debug: Debugger = _debug("electron-builder") @@ -182,30 +183,6 @@ function findFromElectronPrebuilt(packageData: any): any { return null } -export async function statOrNull(file: string): Promise { - try { - return await stat(file) - } - catch (e) { - if (e.code === "ENOENT") { - return null - } - else { - throw e - } - } -} - -export async function exists(file: string): Promise { - try { - await access(file) - return true - } - catch (e) { - return false - } -} - export async function computeDefaultAppDirectory(projectDir: string, userAppDir: string | null | undefined): Promise { if (userAppDir != null) { const absolutePath = path.resolve(projectDir, userAppDir) @@ -260,13 +237,6 @@ export function isEmptyOrSpaces(s: string | n) { return s == null || s.trim().length === 0 } -export function unlinkIfExists(file: string) { - return unlink(file) - .catch(() => { - // ignore - }) -} - export function asArray(v: n | T | Array): Array { if (v == null) { return [] diff --git a/src/yarn.ts b/src/yarn.ts index 38152eef119..00a50d572a3 100644 --- a/src/yarn.ts +++ b/src/yarn.ts @@ -2,8 +2,9 @@ import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { log } from "./util/log" import { homedir } from "os" -import { spawn, exists, asArray } from "./util/util" +import { spawn, asArray } from "./util/util" import { BuildMetadata } from "./metadata" +import { exists } from "./util/fs" export async function installOrRebuild(options: BuildMetadata, appDir: string, electronVersion: string, arch: string, forceInstall: boolean = false) { const args = computeExtraArgs(options) diff --git a/test/src/helpers/wine.ts b/test/src/helpers/wine.ts index 9e0781f4659..ec6fc5af70c 100644 --- a/test/src/helpers/wine.ts +++ b/test/src/helpers/wine.ts @@ -4,7 +4,7 @@ import { emptyDir, readFile, writeFile, ensureDir } from "fs-extra-p" import * as path from "path" import BluebirdPromise from "bluebird-lst-c" import pathSorter from "path-sort" -import { unlinkIfExists } from "out/util/util" +import { unlinkIfExists } from "out/util/fs" export class WineManager { wineDir: string diff --git a/test/src/windows/nsisTest.ts b/test/src/windows/nsisTest.ts index fbab37bac93..f60686a34a3 100644 --- a/test/src/windows/nsisTest.ts +++ b/test/src/windows/nsisTest.ts @@ -5,7 +5,7 @@ import * as path from "path" import BluebirdPromise from "bluebird-lst-c" import { assertThat } from "../helpers/fileAssert" import { extractFile } from "asar-electron-builder" -import { walk } from "out/asarUtil" +import { walk } from "out/util/fs" import { nsisPerMachineInstall } from "../helpers/expectedContents" import { WineManager, diff } from "../helpers/wine" import { safeLoad } from "js-yaml" From 3f97b86993d4ea5172e562b182230a194de0f621 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sun, 27 Nov 2016 09:03:00 +0100 Subject: [PATCH 05/43] feat: use hardlinks if possible, do not create empty dirs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extra files — dirs always created with 755 permissions detect a lot of CI — is-ci now is used. Closes #732 --- docs/Options.md | 2 +- nsis-auto-updater/package.json | 6 +- package.json | 37 ++++-- src/asarUtil.ts | 80 ++++++------ src/builder.ts | 7 +- src/cli/build-cli.ts | 3 +- src/codeSign.ts | 5 +- src/fileMatcher.ts | 3 +- src/metadata.ts | 14 +-- src/packager/dirPackager.ts | 5 +- src/packager/mac.ts | 4 +- src/platformPackager.ts | 20 ++- src/targets/LinuxTargetHelper.ts | 4 +- src/targets/appx.ts | 3 +- src/util/filter.ts | 13 +- src/util/fs.ts | 129 ++++++++++++++++++-- src/util/tmp.ts | 6 +- src/util/util.ts | 11 +- src/windowsCodeSign.ts | 3 +- test/jestSetup.js | 2 +- test/lint.js | 23 ++-- test/src/ArtifactPublisherTest.ts | 3 +- test/src/BuildTest.ts | 3 +- test/src/filesTest.ts | 72 ++++++++--- test/src/helpers/fileAssert.ts | 12 +- test/src/helpers/packTester.ts | 44 +++---- test/src/helpers/runTests.ts | 5 +- test/src/nsisUpdaterTest.ts | 7 +- test/src/windows/nsisBoring.ts | 9 +- test/src/windows/nsisTest.ts | 16 +-- test/src/windows/squirrelWindowsTest.ts | 5 +- test/src/windows/winPackagerTest.ts | 9 +- typings/isCi.d.ts | 4 + typings/stat-mode.d.ts | 19 +++ yarn.lock | 155 ++++++++++++------------ 35 files changed, 462 insertions(+), 281 deletions(-) create mode 100644 typings/isCi.d.ts create mode 100644 typings/stat-mode.d.ts diff --git a/docs/Options.md b/docs/Options.md index 93774232d3d..c8e1d263059 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -70,7 +70,7 @@ Don't customize paths to background and icon, — just follow conventions. | files |

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.

See [File Patterns](#multiple-glob-patterns).

| extraResources |

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 (Contents/Resources for MacOS, resources for Linux/Windows).

Glob rules the same as for [files](#multiple-glob-patterns).

| extraFiles | The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for MacOS, root directory for Linux/Windows). -| asar |

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. 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/tutorial/application-packaging/#limitations-of-the-node-api).

Or you can pass object of any asar options.

Node modules, that must be unpacked, will be detected automatically, you don’t need to explicitly set asarUnpack - please file issue if this doesn’t work.

+| asar |

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. Node modules, that must be unpacked, will be detected automatically, you don’t need to explicitly set [asarUnpack](#BuildMetadata-asarUnpack) - please file issue if this doesn’t work.

Or you can pass object of asar options.

| asarUnpack | A [glob patterns](https://www.npmjs.com/package/glob#glob-primer) relative to the [app directory](#MetadataDirectories-app), which specifies which files to unpack when creating the [asar](http://electron.atom.io/docs/tutorial/application-packaging/) archive. | fileAssociations | The file associations. See [.build.fileAssociations](#FileAssociation). | protocols | The URL protocol scheme(s) to associate the app with. See [.build.protocol](#Protocol). diff --git a/nsis-auto-updater/package.json b/nsis-auto-updater/package.json index f365102bbc3..d806ab18260 100644 --- a/nsis-auto-updater/package.json +++ b/nsis-auto-updater/package.json @@ -1,6 +1,6 @@ { "name": "electron-auto-updater", - "version": "0.6.0", + "version": "0.6.2", "description": "NSIS Auto Updater", "main": "out/nsis-auto-updater/src/main.js", "author": "Vladimir Krivosheev", @@ -13,8 +13,8 @@ ], "dependencies": { "bluebird-lst-c": "^1.0.5", - "debug": "^2.3.2", - "fs-extra-p": "^2.0.7", + "debug": "^2.3.3", + "fs-extra-p": "^3.0.2", "ini": "^1.3.4", "js-yaml": "^3.7.0", "semver": "^5.3.0", diff --git a/package.json b/package.json index 94cfe1464b5..80e5195de3b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint": "node ./test/lint.js", "pretest": "yarn run compile && yarn run lint", "check-deps": "node ./test/out/helpers/checkDeps.js", - "test": "node ./test/out/helpers/runTests.js", + "test": "node --trace-warnings ./test/out/helpers/runTests.js", "test-linux": "docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine /test.sh", "pack-updater": "cd nsis-auto-updater && yarn --production && cd ..", "semantic-release": "semantic-release pre && npm publish && semantic-release post", @@ -70,9 +70,10 @@ "debug": "^2.3.3", "electron-download-tf": "3.1.0", "electron-macos-sign": "^1.3.4", - "fs-extra-p": "^2.0.7", + "fs-extra-p": "^3.0.2", "hosted-git-info": "^2.1.5", "ini": "^1.3.4", + "is-ci": "^1.0.10", "isbinaryfile": "^3.0.1", "js-yaml": "^3.7.0", "lodash.template": "^4.4.0", @@ -89,13 +90,14 @@ "sanitize-filename": "^1.6.1", "semver": "^5.3.0", "source-map-support": "^0.4.6", + "stat-mode": "^0.2.2", "tunnel-agent": "^0.4.3", "update-notifier": "^1.0.2", "uuid-1345": "^0.99.6", "yargs": "^6.4.0" }, "devDependencies": { - "@develar/semantic-release": "^6.3.21", + "@develar/semantic-release": "^6.3.24", "@types/ini": "^1.3.29", "@types/jest": "^16.0.0", "@types/js-yaml": "^3.5.28", @@ -108,13 +110,13 @@ "babel-plugin-transform-inline-imports-commonjs": "^1.2.0", "decompress-zip": "^0.3.0", "depcheck": "^0.6.5", - "diff": "^3.0.1", + "diff": "^3.1.0", "husky": "^0.11.9", "jest-cli": "^17.0.3", "json8": "^0.9.2", "path-sort": "^0.1.0", - "ts-babel": "^1.1.4", - "tslint": "4.0.0-dev.1", + "ts-babel": "^1.1.5", + "tslint": "^4.0.2", "typescript": "^2.1.1", "validate-commit-msg": "^2.8.2", "whitespace": "^2.1.0" @@ -140,7 +142,28 @@ ] } ] - ] + ], + "env": { + "test": { + "plugins": [ + [ + "transform-async-to-module-method", + { + "module": "bluebird-lst-c", + "method": "coroutine" + } + ], + [ + "transform-inline-imports-commonjs", + { + "excludeModules": [ + "path" + ] + } + ] + ] + } + } }, "jest": { "testEnvironment": "node", diff --git a/src/asarUtil.ts b/src/asarUtil.ts index 69c9d02d309..3895da7803e 100644 --- a/src/asarUtil.ts +++ b/src/asarUtil.ts @@ -1,12 +1,11 @@ import { AsarFileInfo, listPackage, statFile, AsarOptions } from "asar-electron-builder" -import { debug, isCi } from "./util/util" -import { readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson, writeFile, realpath, link } from "fs-extra-p" +import { debug } from "./util/util" +import { readFile, Stats, createWriteStream, ensureDir, createReadStream, readJson, writeFile, realpath } from "fs-extra-p" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { log } from "./util/log" import { deepAssign } from "./util/deepAssign" -import { Filter } from "./util/filter" -import { walk, statOrNull, CONCURRENCY, MAX_FILE_REQUESTS } from "./util/fs" +import { walk, statOrNull, CONCURRENCY, MAX_FILE_REQUESTS, Filter, FileCopier } from "./util/fs" const isBinaryFile: any = BluebirdPromise.promisify(require("isbinaryfile")) const pickle = require ("chromium-pickle-js") @@ -31,6 +30,24 @@ function addValue(map: Map>, key: string, value: string) { } } +interface UnpackedFileTask { + stats: Stats + src?: string + data?: string + destination: string +} + +function writeUnpackedFiles(filesToUnpack: Array, fileCopier: FileCopier): Promise { + return BluebirdPromise.map(filesToUnpack, it => { + if (it.data == null) { + return fileCopier.copy(it.src!, it.destination, it.stats) + } + else { + return writeFile(it.destination, it.data) + } + }) +} + class AsarPackager { private readonly toPack: Array = [] private readonly fs = new Filesystem(this.src) @@ -39,16 +56,13 @@ class AsarPackager { private srcRealPath: Promise - constructor(private readonly src: string, private readonly resourcesPath: string, private readonly options: AsarOptions, private readonly unpackPattern: Filter | null) { - this.outFile = path.join(this.resourcesPath, "app.asar") + constructor(private readonly src: string, destination: string, private readonly options: AsarOptions, private readonly unpackPattern: Filter | null) { + this.outFile = path.join(destination, "app.asar") } async pack(filter: Filter) { const metadata = new Map() - const files = await walk(this.src, (it, stat) => { - metadata.set(it, stat) - }, filter) - + const files = await walk(this.src, filter, (it, stat) => metadata.set(it, stat)) await this.createPackageFromFiles(this.options.ordering == null ? files : await this.order(files), metadata) await this.writeAsarFile() } @@ -83,7 +97,7 @@ class AsarPackager { const nodeModuleDir = file.substring(0, nextSlashIndex) if (file.length === (nodeModuleDir.length + 1 + packageJsonStringLength) && file.endsWith("package.json")) { - fileIndexToModulePackageData.set(i, readJson(file).then(it => cleanupPackageJson(it))) + fileIndexToModulePackageData.set(i, >readJson(file).then(it => cleanupPackageJson(it))) } if (autoUnpackDirs.has(nodeModuleDir)) { @@ -147,8 +161,9 @@ class AsarPackager { await this.detectUnpackedDirs(files, metadata, unpackedDirs, unpackedDest, fileIndexToModulePackageData) } - const copyPromises: Array> = [] + const filesToUnpack: Array = [] const mainPackageJson = path.join(this.src, "package.json") + const fileCopier = new FileCopier() for (let i = 0, n = files.length; i < n; i++) { const file = files[i] const stat = metadata.get(file)! @@ -174,14 +189,14 @@ class AsarPackager { if (!dirNode.unpacked && !unpackedDirs.has(fileParent)) { unpackedDirs.add(fileParent) - await ensureDir(path.join(unpackedDest, path.relative(this.src, fileParent))) + await ensureDir(fileParent.replace(this.src, unpackedDest)) } - const unpackedFile = path.join(unpackedDest, path.relative(this.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 + const unpackedFile = file.replace(this.src, unpackedDest) + filesToUnpack.push(newData == null ? {src: file, destination: unpackedFile, stats: stat} : {destination: unpackedFile, data: newData, stats: stat}) + if (filesToUnpack.length > MAX_FILE_REQUESTS) { + await writeUnpackedFiles(filesToUnpack, fileCopier) + filesToUnpack.length = 0 } } else { @@ -214,7 +229,7 @@ class AsarPackager { unpackedDirs.add(file) // not all dirs marked as unpacked after first iteration - because node module dir can be marked as unpacked after processing node module dir content // e.g. node-notifier/example/advanced.js processed, but only on process vendor/terminal-notifier.app module will be marked as unpacked - await ensureDir(path.join(unpackedDest, path.relative(this.src, file))) + await ensureDir(file.replace(this.src, unpackedDest)) break } } @@ -226,8 +241,9 @@ class AsarPackager { } } - if (copyPromises.length > 0) { - await BluebirdPromise.all(copyPromises) + if (filesToUnpack.length > MAX_FILE_REQUESTS) { + await writeUnpackedFiles(filesToUnpack, fileCopier) + filesToUnpack.length = 0 } } @@ -300,7 +316,7 @@ class AsarPackager { for (const file of orderingFiles) { let pathComponents = file.split(path.sep) let str = this.src - for (let pathComponent of pathComponents) { + for (const pathComponent of pathComponents) { str = path.join(str, pathComponent) ordering.push(str) } @@ -378,24 +394,4 @@ export async function checkFileInArchive(asarFile: string, relativeFile: string, if (stat.size === 0) { throw error(`is corrupted: size 0`) } -} - -function copyFile(src: string, dest: string, stats: Stats) { - if (process.platform != "win32" && (isCi || process.env.USE_HARD_LINKS === "true")) { - return link(src, dest) - } - - return new BluebirdPromise(function (resolve, reject) { - const readStream = createReadStream(src) - const writeStream = createWriteStream(dest, {mode: stats.mode}) - - readStream.on("error", reject) - writeStream.on("error", reject) - - writeStream.on("open", function () { - readStream.pipe(writeStream) - }) - - writeStream.once("finish", resolve) - }) } \ No newline at end of file diff --git a/src/builder.ts b/src/builder.ts index 0f0f0c4ea17..ecad4beaa07 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -4,12 +4,13 @@ import { PublishOptions, Publisher } from "./publish/publisher" import { GitHubPublisher } from "./publish/gitHubPublisher" import { executeFinally } from "./util/promise" import BluebirdPromise from "bluebird-lst-c" -import { isEmptyOrSpaces, isCi, debug } from "./util/util" +import { isEmptyOrSpaces, debug } from "./util/util" import { log } from "./util/log" import { Platform, Arch, archFromString } from "./metadata" import { DIR_TARGET } from "./targets/targetFactory" import { BintrayPublisher } from "./publish/BintrayPublisher" import { PublishConfiguration, GithubOptions, BintrayOptions } from "./options/publishOptions" +import isCi from "is-ci" export interface BuildOptions extends PackagerOptions, PublishOptions { } @@ -209,7 +210,7 @@ export async function build(rawOptions?: CliOptions): Promise> { options.publish = "onTag" isPublishOptionGuessed = true } - else if (isCi()) { + else if (isCi) { log("CI detected, so artifacts will be published if draft release exists") options.publish = "onTagOrDraft" isPublishOptionGuessed = true @@ -225,7 +226,7 @@ export async function build(rawOptions?: CliOptions): Promise> { if (isAuthTokenSet()) { publishManager(packager, publishTasks, options, isPublishOptionGuessed) } - else if (isCi()) { + else if (isCi) { log(`CI detected, publish is set to ${options.publish}, but neither GH_TOKEN nor BT_TOKEN is not set, so artifacts will be not published`) } } diff --git a/src/cli/build-cli.ts b/src/cli/build-cli.ts index c94633697e7..f1bc037422a 100644 --- a/src/cli/build-cli.ts +++ b/src/cli/build-cli.ts @@ -7,8 +7,9 @@ import * as path from "path" import { dim, reset, green, cyan } from "chalk" import updateNotifier from "update-notifier" import { warn } from "../util/log" +import isCi from "is-ci" -if (process.env.CI == null && process.env.NO_UPDATE_NOTIFIER == null) { +if (!isCi && process.env.NO_UPDATE_NOTIFIER == null) { readJson(path.join(__dirname, "..", "..", "package.json")) .then(it => { const notifier = updateNotifier({pkg: it}) diff --git a/src/codeSign.ts b/src/codeSign.ts index d181229f151..a0987426e07 100644 --- a/src/codeSign.ts +++ b/src/codeSign.ts @@ -1,4 +1,4 @@ -import { exec, getTempName, isEmptyOrSpaces, isCi, getCacheDirectory } from "./util/util" +import { exec, getTempName, isEmptyOrSpaces, getCacheDirectory } from "./util/util" import { deleteFile, outputFile, copy, rename } from "fs-extra-p" import { download } from "./util/httpRequest" import * as path from "path" @@ -8,6 +8,7 @@ import { randomBytes } from "crypto" import { TmpDir } from "./util/tmp" import { homedir } from "os" import { statOrNull } from "./util/fs" +import isCi from "is-ci" export const appleCertificatePrefixes = ["Developer ID Application:", "Developer ID Installer:", "3rd Party Mac Developer Application:", "3rd Party Mac Developer Installer:"] @@ -208,7 +209,7 @@ async function _findIdentity(type: CertType, qualifier?: string | null, keychain export async function findIdentity(certType: CertType, qualifier?: string | null, keychain?: string | null): Promise { let identity = process.env.CSC_NAME || qualifier if (isEmptyOrSpaces(identity)) { - if (keychain == null && !isCi() && process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") { + if (keychain == null && !isCi && process.env.CSC_IDENTITY_AUTO_DISCOVERY === "false") { return null } else { diff --git a/src/fileMatcher.ts b/src/fileMatcher.ts index f4219614e48..a061e04c46d 100644 --- a/src/fileMatcher.ts +++ b/src/fileMatcher.ts @@ -1,7 +1,8 @@ import * as path from "path" -import { createFilter, hasMagic, Filter } from "./util/filter" +import { createFilter, hasMagic } from "./util/filter" import { Minimatch } from "minimatch" import { asArray } from "./util/util" +import { Filter } from "./util/fs" export interface FilePattern { from?: string diff --git a/src/metadata.ts b/src/metadata.ts index 0334abed5bc..713c2c4e840 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -115,26 +115,24 @@ export interface BuildMetadata { */ readonly files?: Array | string | null - /** + /* 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 (`Contents/Resources` for MacOS, `resources` for Linux/Windows). Glob rules the same as for [files](#multiple-glob-patterns). */ readonly extraResources?: Array | string | null - /** + /* The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for MacOS, root directory for Linux/Windows). */ readonly extraFiles?: Array | 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`. - 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/tutorial/application-packaging/#limitations-of-the-node-api). + 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`. + Node modules, that must be unpacked, will be detected automatically, you don't need to explicitly set [asarUnpack](#BuildMetadata-asarUnpack) - please file issue if this doesn't work. - Or you can pass object of any asar options. - - Node modules, that must be unpacked, will be detected automatically, you don't need to explicitly set `asarUnpack` - please file issue if this doesn't work. - */ + Or you can pass object of asar options. + */ readonly asar?: AsarOptions | boolean | null /** diff --git a/src/packager/dirPackager.ts b/src/packager/dirPackager.ts index a7e1423524b..30c86f54edc 100644 --- a/src/packager/dirPackager.ts +++ b/src/packager/dirPackager.ts @@ -1,10 +1,11 @@ import BluebirdPromise from "bluebird-lst-c" -import { emptyDir, copy, chmod } from "fs-extra-p" +import { emptyDir, chmod } from "fs-extra-p" import { warn } from "../util/log" import { PlatformPackager } from "../platformPackager" import { debug7zArgs, spawn } from "../util/util" import { path7za } from "7zip-bin" import * as path from "path" +import { copyDir } from "../util/fs" const downloadElectron: (options: any) => Promise = BluebirdPromise.promisify(require("electron-download-tf")) @@ -43,7 +44,7 @@ export async function unpackElectron(packager: PlatformPackager, out: strin } else { await emptyDir(out) - await copy(path.resolve(packager.info.projectDir, electronDist, "Electron.app"), path.join(out, "Electron.app")) + await copyDir(path.resolve(packager.info.projectDir, electronDist, "Electron.app"), path.join(out, "Electron.app")) } if (platform === "linux") { diff --git a/src/packager/mac.ts b/src/packager/mac.ts index 47c5b82d40e..2314006758f 100644 --- a/src/packager/mac.ts +++ b/src/packager/mac.ts @@ -1,4 +1,4 @@ -import { rename, readFile, writeFile, copy, unlink, utimes } from "fs-extra-p" +import { rename, readFile, writeFile, unlink, utimes, copy } from "fs-extra-p" import * as path from "path" import { parse as parsePlist, build as buildPlist } from "plist" import BluebirdPromise from "bluebird-lst-c" @@ -115,7 +115,7 @@ export async function createApp(packager: PlatformPackager, appOutDir: stri use(packager.platformSpecificBuildOptions.category || (buildMetadata).category, it => appPlist.LSApplicationCategoryType = it) appPlist.NSHumanReadableCopyright = appInfo.copyright - const promises: Array> = [ + const promises: Array> = [ writeFile(appPlistFilename, buildPlist(appPlist)), writeFile(helperPlistFilename, buildPlist(helperPlist)), writeFile(helperEHPlistFilename, buildPlist(helperEHPlist)), diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 2667ea20bf8..852fa5c3d55 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -9,7 +9,6 @@ import { Minimatch } from "minimatch" import { checkFileInArchive, createAsarArchive } from "./asarUtil" import { warn, log } from "./util/log" import { AppInfo } from "./appInfo" -import { copyFiltered } from "./util/filter" import { unpackElectron } from "./packager/dirPackager" import { TmpDir } from "./util/tmp" import { FileMatchOptions, FileMatcher, FilePattern, deprecatedUserIgnoreFilter } from "./fileMatcher" @@ -19,7 +18,7 @@ import { getRepositoryInfo } from "./repositoryInfo" import { dependencies } from "./yarn" import { Target } from "./targets/targetFactory" import { deepAssign } from "./util/deepAssign" -import { statOrNull, unlinkIfExists } from "./util/fs" +import { statOrNull, unlinkIfExists, copyDir } from "./util/fs" import EventEmitter = NodeJS.EventEmitter export interface PackagerOptions { @@ -243,7 +242,7 @@ export abstract class PlatformPackager const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length ? excludePatterns : null) let promise if (asarOptions == null) { - promise = copyFiltered(appDir, path.join(resourcesPath, "app"), filter, this.platform === Platform.WINDOWS) + promise = copyDir(appDir, path.join(resourcesPath, "app"), filter) } else { const unpackPattern = this.getFileMatchers("asarUnpack", appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions) @@ -304,8 +303,9 @@ export abstract class PlatformPackager const platformSpecific = customBuildOptions.asar const result = platformSpecific == null ? this.devMetadata.build.asar : platformSpecific - if (result === false) { + warn("Packaging using asar archive is disabled — it is strongly not recommended.\n" + + "Please enable asar and use asarUnpack to unpack files that must be externally available.") return null } @@ -317,13 +317,11 @@ export abstract class PlatformPackager return defaultOptions } - if ((result).unpackDir != null) { - throw new Error(errorMessage("asar.unpackDir")) - } - if ((result).unpack != null) { - throw new Error(errorMessage("asar.unpack")) + for (const name of ["unpackDir", "unpack"]) { + if ((result)[name] != null) { + throw new Error(errorMessage(`asar.${name}`)) + } } - return deepAssign({}, result, defaultOptions) } @@ -336,7 +334,7 @@ export abstract class PlatformPackager if (pattern.isEmpty() || pattern.containsOnlyIgnore()) { pattern.addAllPattern() } - return copyFiltered(pattern.from, pattern.to, pattern.createFilter(), this.platform === Platform.WINDOWS) + return copyDir(pattern.from, pattern.to, pattern.createFilter()) }) } diff --git a/src/targets/LinuxTargetHelper.ts b/src/targets/LinuxTargetHelper.ts index ed1ae6bfbf3..e261e155ae9 100644 --- a/src/targets/LinuxTargetHelper.ts +++ b/src/targets/LinuxTargetHelper.ts @@ -23,7 +23,9 @@ export class LinuxTargetHelper { return await this.iconsFromDir(path.join(this.packager.buildResourcesDir, "icons")) } else { - return await this.createFromIcns(await this.packager.getTempFile("electron-builder-linux.iconset").then(it => ensureDir(it).thenReturn(it))) + const iconDir = await this.packager.getTempFile("linux.iconset") + ensureDir(iconDir) + return await this.createFromIcns(iconDir) } } diff --git a/src/targets/appx.ts b/src/targets/appx.ts index dc5f9c5027c..a37c0e98a41 100644 --- a/src/targets/appx.ts +++ b/src/targets/appx.ts @@ -10,6 +10,7 @@ import { Target } from "./targetFactory" import { getSignVendorPath } from "../windowsCodeSign" import sanitizeFileName from "sanitize-filename" import { release } from "os" +import { copyDir } from "../util/fs" export default class AppXTarget extends Target { private readonly options: AppXOptions = Object.assign({}, this.packager.platformSpecificBuildOptions, this.packager.devMetadata.build.appx) @@ -53,7 +54,7 @@ export default class AppXTarget extends Target { } return copy(path.join(templatePath, "assets", `SampleAppx.${size}.png`), target) }), - copy(appOutDir, path.join(preAppx, "app")), + copyDir(appOutDir, path.join(preAppx, "app")), this.writeManifest(templatePath, preAppx, safeName) ]) diff --git a/src/util/filter.ts b/src/util/filter.ts index 82d66fcdb35..414edb1f90d 100644 --- a/src/util/filter.ts +++ b/src/util/filter.ts @@ -1,14 +1,7 @@ -import { copy, Stats } from "fs-extra-p" +import { Stats } from "fs-extra-p" import { Minimatch } from "minimatch" import * as path from "path" - -export function copyFiltered(src: string, destination: string, filter: Filter, dereference: boolean): Promise { - return copy(src, destination, { - dereference: dereference, - filter: filter, - passStats: true, - }) -} +import { Filter } from "./fs" export function hasMagic(pattern: Minimatch) { const set = pattern.set @@ -25,8 +18,6 @@ export function hasMagic(pattern: Minimatch) { return false } -export type Filter = (file: string, stat: Stats) => boolean - export function createFilter(src: string, patterns: Array, ignoreFiles?: Set, rawFilter?: (file: string) => boolean, excludePatterns?: Array | null): Filter { return function (it, stat) { if (src === it) { diff --git a/src/util/fs.ts b/src/util/fs.ts index a34c95f236a..4d7350c7a0f 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -1,16 +1,18 @@ -import { unlink, access, stat, Stats, lstat, readdir } from "fs-extra-p" -import { Filter } from "./filter" +import { unlink, access, stat, Stats, lstat, readdir, createReadStream, createWriteStream, link, mkdirs, readlink, symlink } from "fs-extra-p" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" +import { debug } from "./util" +import isCi from "is-ci" +import Mode from "stat-mode" export const MAX_FILE_REQUESTS = 8 export const CONCURRENCY = {concurrency: MAX_FILE_REQUESTS} +export type Filter = (file: string, stat: Stats) => boolean + export function unlinkIfExists(file: string) { return unlink(file) - .catch(() => { - // ignore - }) + .catch(() => {/* ignore */}) } export async function statOrNull(file: string): Promise { @@ -37,7 +39,7 @@ export async function exists(file: string): Promise { } } -export async function walk(initialDirPath: string, consumer?: (file: string, stat: Stats) => void, filter?: Filter): Promise> { +export async function walk(initialDirPath: string, filter?: Filter, consumer?: (file: string, stat: Stats, parent: string) => any): Promise> { const result: Array = [] const queue: Array = [initialDirPath] let addDirToResult = false @@ -62,16 +64,14 @@ export async function walk(initialDirPath: string, consumer?: (file: string, sta return } - if (consumer != null) { - consumer(filePath, stat) - } - if (stat.isDirectory()) { dirs.push(filePath) } else { result.push(filePath) } + + return consumer == null ? null : consumer(filePath, stat, dirPath) }) }, CONCURRENCY) @@ -81,4 +81,113 @@ export async function walk(initialDirPath: string, consumer?: (file: string, sta } return result +} + +const _isUseHardLink = process.platform != "win32" && (isCi || process.env.USE_HARD_LINKS === "true") + +/** + * Hard links is used if supported and allowed. + * File permission is fixed — allow execute for all if owner can, allow read for all if owner can. + */ +export function copyFile(src: string, dest: string, stats?: Stats, isUseHardLink = _isUseHardLink): Promise { + if (stats != null) { + const originalModeNumber = stats.mode + const mode = new Mode(stats) + if (mode.owner.execute) { + mode.group.execute = true + mode.others.execute = true + } + + mode.group.read = true + mode.others.read = true + + if (originalModeNumber !== stats.mode) { + if (debug.enabled) { + const oldMode = new Mode(Object.assign({}, stats, {mode: originalModeNumber})) + debug(`${dest} permissions fixed from ${oldMode.toOctal()} (${oldMode.toString()}) to ${mode.toOctal()} (${mode.toString()})`) + } + + // https://helgeklein.com/blog/2009/05/hard-links-and-permissions-acls/ + // Permissions on all hard links to the same data on disk are always identical. The same applies to attributes. + // That means if you change the permissions/owner/attributes on one hard link, you will immediately see the changes on all other hard links. + if (isUseHardLink) { + isUseHardLink = false + debug(`${dest} will be copied, but not linked, because file permissions need to be fixed`) + } + } + } + + if (isUseHardLink) { + return link(src, dest) + } + + return new BluebirdPromise(function (resolve, reject) { + const readStream = createReadStream(src) + const writeStream = createWriteStream(dest, stats == null ? undefined : {mode: stats.mode}) + + readStream.on("error", reject) + writeStream.on("error", reject) + + writeStream.on("open", function () { + readStream.pipe(writeStream) + }) + + writeStream.once("finish", resolve) + }) +} + +export class FileCopier { + constructor(private isUseHardLinkFunction?: (file: string) => boolean, private isUseHardLink = _isUseHardLink) { + } + + async copy(src: string, dest: string, stat: Stats | undefined) { + try { + await copyFile(src, dest, stat, (!this.isUseHardLink || this.isUseHardLinkFunction == null) ? this.isUseHardLink : this.isUseHardLinkFunction(dest)) + } + catch (e) { + // files are copied concurrently, so, we must not check here currentIsUseHardLink — our code can be executed after that other handler will set currentIsUseHardLink to false + if (e.code === "EXDEV") { + // ...but here we want to avoid excess debug log message + if (this.isUseHardLink) { + debug(`Cannot copy using hard link: ${e}`) + this.isUseHardLink = false + } + + await copyFile(src, dest, stat, false) + } + else { + throw e + } + } + } +} + +/** + * Empty directories is never created. + * Hard links is used if supported and allowed. + */ +export function copyDir(src: string, destination: string, filter?: Filter, isUseHardLink?: (file: string) => boolean): Promise { + if (debug.enabled) { + debug(`Copying ${src} to ${destination}${_isUseHardLink ? " using hard links" : ""}`) + } + + const createdSourceDirs = new Set() + const fileCopier = new FileCopier(isUseHardLink) + return walk(src, filter, async (file, stat, parent) => { + if (stat.isSymbolicLink()) { + await symlink(await readlink(file), file.replace(src, destination)) + return + } + + if (!stat.isFile()) { + return + } + + if (!createdSourceDirs.has(parent)) { + await mkdirs(parent.replace(src, destination)) + createdSourceDirs.add(parent) + } + + await fileCopier.copy(file, file.replace(src, destination), stat) + }) } \ No newline at end of file diff --git a/src/util/tmp.ts b/src/util/tmp.ts index a09fce256df..ff6410ff4cb 100644 --- a/src/util/tmp.ts +++ b/src/util/tmp.ts @@ -11,16 +11,16 @@ process.setMaxListeners(30) export class TmpDir { private tmpFileCounter = 0 - private tempDirectoryPromise: BluebirdPromise + private tempDirectoryPromise: Promise private dir: string | null getTempFile(suffix: string | null): Promise { if (this.tempDirectoryPromise == null) { - let promise: BluebirdPromise + let promise: Promise if (mkdtemp == null) { const dir = path.join(tmpdir(), getTempName("electron-builder")) - promise = mkdirs(dir, {mode: 448}).thenReturn(dir) + promise = mkdirs(dir, {mode: 448}).then(() => dir) } else { promise = mkdtemp(`${path.join(process.env.TEST_DIR || tmpdir(), "electron-builder")}-`) diff --git a/src/util/util.ts b/src/util/util.ts index 817b5d20fe4..2986513e8f7 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -9,10 +9,9 @@ import { warn, log } from "./log" import { createHash } from "crypto" import "source-map-support/register" import { statOrNull } from "./fs" -import Debugger = debug.Debugger -export const debug: Debugger = _debug("electron-builder") -export const debug7z: Debugger = _debug("electron-builder:7z") +export const debug = _debug("electron-builder") +export const debug7z = _debug("electron-builder:7z") const DEFAULT_APP_DIR_NAMES = ["app", "www"] @@ -30,7 +29,7 @@ export interface ExecOptions extends BaseExecOptions { killSignal?: string } -export function removePassword(input: string): string { +export function removePassword(input: string) { return input.replace(/(-P |pass:|\/p|-pass )([^ ]+)/, function (match, p1, p2) { return `${p1}${createHash("sha256").update(p2).digest("hex")} (sha256 hash)` }) @@ -248,10 +247,6 @@ export function asArray(v: n | T | Array): Array { return [v] } } -export function isCi(): boolean { - return (process.env.CI || "").toLowerCase() === "true" -} - export function getCacheDirectory(): string { if (process.platform === "darwin") { return path.join(homedir(), "Library", "Caches", "electron-builder") diff --git a/src/windowsCodeSign.ts b/src/windowsCodeSign.ts index 11c0940ca54..b4705e7386f 100644 --- a/src/windowsCodeSign.ts +++ b/src/windowsCodeSign.ts @@ -3,6 +3,7 @@ import { rename } from "fs-extra-p" import * as path from "path" import { release } from "os" import { getBinFromBintray } from "./util/binDownload" +import isCi from "is-ci" const TOOLS_VERSION = "1.5.0" @@ -158,7 +159,7 @@ async function getToolPath(): Promise { return path.join(vendorPath, "windows-10", process.arch, "signtool.exe") } } - else if (process.platform === "darwin" && process.env.CI) { + else if (process.platform === "darwin" && isCi) { return path.join(vendorPath, process.platform, "ci", "osslsigncode") } else { diff --git a/test/jestSetup.js b/test/jestSetup.js index 90dfc7fca17..d8fc5b7d13d 100644 --- a/test/jestSetup.js +++ b/test/jestSetup.js @@ -1,9 +1,9 @@ "use strict" require('source-map-support').install() +const isCi = require("is-ci") const isWindows = process.platform === "win32" -const isCi = (process.env.CI || "").toLowerCase() === "true" // Squirrel.Windows msi is very slow jasmine.DEFAULT_TIMEOUT_INTERVAL = (isWindows ? 30 : 10) * 1000 * 60 diff --git a/test/lint.js b/test/lint.js index 2794cb9b780..574eab4f31a 100644 --- a/test/lint.js +++ b/test/lint.js @@ -1,6 +1,6 @@ "use strict" -const Linter = require("tslint") +const Linter = require("tslint").Linter const path = require("path") const configuration = { @@ -61,17 +61,24 @@ const configuration = { } } const options = { - configuration: configuration, + formatter: "stylish", } -for (let projectDir of [path.join(__dirname, ".."), path.join(__dirname, "..", "nsis-auto-updater"), __dirname]) { +let hasErrors = false +for (const projectDir of [path.join(__dirname, ".."), path.join(__dirname, "..", "nsis-auto-updater"), __dirname]) { const program = Linter.createProgram("tsconfig.json", projectDir) - for (let file of Linter.getFileNames(program)) { + for (const file of Linter.getFileNames(program)) { const fileContents = program.getSourceFile(file).getFullText() - const linter = new Linter(file, fileContents, options, program) - const out = linter.lint().output - if (out.length > 0) { - process.stdout.write(out) + const linter = new Linter(options, program) + linter.lint(file, fileContents, configuration) + const result = linter.getResult() + if (result.failures.length > 0) { + hasErrors = true + process.stdout.write(result.output) } } +} + +if (hasErrors) { + process.exit(1) } \ No newline at end of file diff --git a/test/src/ArtifactPublisherTest.ts b/test/src/ArtifactPublisherTest.ts index f5f10d31d5a..3b64e5e3976 100644 --- a/test/src/ArtifactPublisherTest.ts +++ b/test/src/ArtifactPublisherTest.ts @@ -4,8 +4,9 @@ import { join } from "path" import { assertThat } from "./helpers/fileAssert" import { BintrayPublisher } from "out/publish/BintrayPublisher" import { createPublisher } from "out/builder" +import isCi from "is-ci" -if (process.env.CI && process.platform === "win32") { +if (isCi && process.platform === "win32") { fit("Skip ArtifactPublisherTest suite on Windows CI", () => { console.warn("[SKIP] Skip ArtifactPublisherTest suite on Windows CI") }) diff --git a/test/src/BuildTest.ts b/test/src/BuildTest.ts index b16b8ef1f19..9abc4ba5812 100644 --- a/test/src/BuildTest.ts +++ b/test/src/BuildTest.ts @@ -17,6 +17,7 @@ import { normalizeOptions } from "out/builder" import { createYargs } from "out/cli/cliOptions" import { extractFile } from "asar-electron-builder" import { ELECTRON_VERSION } from "./helpers/config" +import isCi from "is-ci" test("cli", async () => { const yargs = createYargs() @@ -156,7 +157,7 @@ test("www as default dir", () => assertPack("test-app", currentPlatform(), { })) test("afterPack", () => { - const targets = process.env.CI ? Platform.fromString(process.platform).createTarget(DIR_TARGET) : getPossiblePlatforms(DIR_TARGET) + const targets = isCi ? Platform.fromString(process.platform).createTarget(DIR_TARGET) : getPossiblePlatforms(DIR_TARGET) let called = 0 return assertPack("test-app-one", { targets: targets, diff --git a/test/src/filesTest.ts b/test/src/filesTest.ts index 8e469dfb36c..fe6e3d805be 100644 --- a/test/src/filesTest.ts +++ b/test/src/filesTest.ts @@ -1,33 +1,39 @@ import { expectedWinContents } from "./helpers/expectedContents" -import { outputFile } from "fs-extra-p" +import { outputFile, stat } from "fs-extra-p" import { assertPack, modifyPackageJson, getPossiblePlatforms, app } from "./helpers/packTester" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" import { assertThat } from "./helpers/fileAssert" import { Platform, DIR_TARGET } from "out" import pathSorter from "path-sort" +import Mode from "stat-mode" +import { Permissions } from "stat-mode" test.ifDevOrLinuxCi("files", app({ targets: Platform.LINUX.createTarget(DIR_TARGET), devMetadata: { build: { asar: false, - files: ["!ignoreMe${/*}"] + files: ["!ignoreMe${/*}", "!**/bar"], } } }, { - projectDirCreated: projectDir => { - return outputFile(path.join(projectDir, "ignoreMe", "foo"), "data") - }, + projectDirCreated: projectDir => BluebirdPromise.all([ + outputFile(path.join(projectDir, "ignoreMe", "foo"), "data"), + outputFile(path.join(projectDir, "ignoreEmptyDir", "bar"), "data"), + ]), packed: context => { - return assertThat(path.join(context.getResources(Platform.LINUX), "app", "ignoreMe")).doesNotExist() + const resources = path.join(context.getResources(Platform.LINUX), "app") + return BluebirdPromise.all([ + assertThat(path.join(resources, "ignoreMe")).doesNotExist(), + assertThat(path.join(resources, "ignoreEmptyDir")).doesNotExist(), + ]) }, })) test("extraResources", async () => { for (const platform of getPossiblePlatforms().keys()) { const osName = platform.buildConfigurationKey - const winDirPrefix = "lib/net45/resources/" //noinspection SpellCheckingInspection @@ -114,6 +120,7 @@ test("extraResources - one-package", () => { "bar/hello.txt", "bar/${arch}.txt", "${os}/${arch}.txt", + "executable*", ] data.build[osName] = { @@ -126,11 +133,13 @@ test("extraResources - one-package", () => { } }), outputFile(path.join(projectDir, "foo/nameWithoutDot"), "nameWithoutDot"), - outputFile(path.join(projectDir, "bar/hello.txt"), "data"), + outputFile(path.join(projectDir, "bar/hello.txt"), "data", {mode: "400"}), outputFile(path.join(projectDir, `bar/${process.arch}.txt`), "data"), outputFile(path.join(projectDir, `${osName}/${process.arch}.txt`), "data"), outputFile(path.join(projectDir, "platformSpecificR"), "platformSpecificR"), outputFile(path.join(projectDir, "ignoreMe.txt"), "ignoreMe"), + outputFile(path.join(projectDir, "executable"), "executable", {mode: "755"}), + outputFile(path.join(projectDir, "executableOnlyOwner"), "executable", {mode: "740"}), ]) }, packed: async context => { @@ -141,20 +150,25 @@ test("extraResources - one-package", () => { } const appDir = path.join(resourcesDir, "app") - await assertThat(path.join(resourcesDir, "foo")).isDirectory() - await assertThat(path.join(appDir, "foo")).doesNotExist() + await BluebirdPromise.all([ + assertThat(path.join(resourcesDir, "foo")).isDirectory(), + assertThat(path.join(appDir, "foo")).doesNotExist(), - await assertThat(path.join(resourcesDir, "foo", "nameWithoutDot")).isFile() - await assertThat(path.join(appDir, "foo", "nameWithoutDot")).doesNotExist() + assertThat(path.join(resourcesDir, "foo", "nameWithoutDot")).isFile(), + assertThat(path.join(appDir, "foo", "nameWithoutDot")).doesNotExist(), - await assertThat(path.join(resourcesDir, "bar", "hello.txt")).isFile() - await assertThat(path.join(resourcesDir, "bar", `${process.arch}.txt`)).isFile() - await assertThat(path.join(appDir, "bar", `${process.arch}.txt`)).doesNotExist() + assertThat(path.join(resourcesDir, "bar", "hello.txt")).isFile(), + assertThat(path.join(resourcesDir, "bar", `${process.arch}.txt`)).isFile(), + assertThat(path.join(appDir, "bar", `${process.arch}.txt`)).doesNotExist(), - await BluebirdPromise.all([ assertThat(path.join(resourcesDir, osName, `${process.arch}.txt`)).isFile(), assertThat(path.join(resourcesDir, "platformSpecificR")).isFile(), assertThat(path.join(resourcesDir, "ignoreMe.txt")).doesNotExist(), + + allCan(path.join(resourcesDir, "executable"), true), + allCan(path.join(resourcesDir, "executableOnlyOwner"), true), + + allCan(path.join(resourcesDir, "bar", "hello.txt"), false), ]) }, expectedContents: platform === Platform.WINDOWS ? pathSorter(expectedWinContents.concat( @@ -165,4 +179,28 @@ test("extraResources - one-package", () => { winDirPrefix + "win/x64.txt" )) : null, }) -}) \ No newline at end of file +}) + +async function allCan(file: string, execute: Boolean) { + const mode = new Mode(await stat(file)) + + function checkExecute(value: Permissions) { + if (value.execute !== execute) { + throw new Error(`${file} is ${execute ? "not " : ""}executable`) + } + } + + function checkRead(value: Permissions) { + if (!value.read) { + throw new Error(`${file} is not readable`) + } + } + + checkExecute(mode.owner) + checkExecute(mode.group) + checkExecute(mode.others) + + checkRead(mode.owner) + checkRead(mode.group) + checkRead(mode.others) +} \ No newline at end of file diff --git a/test/src/helpers/fileAssert.ts b/test/src/helpers/fileAssert.ts index 728f1bc2b56..11b33c51dd2 100644 --- a/test/src/helpers/fileAssert.ts +++ b/test/src/helpers/fileAssert.ts @@ -1,9 +1,10 @@ -import { stat, Stats, access } from "fs-extra-p" +import { stat, Stats } from "fs-extra-p" import * as json8 from "json8" import { green, red, gray } from "chalk" import { diffJson } from "diff" import { AssertionError } from "assert" import * as path from "path" +import { exists } from "out/util/fs" // http://joel-costigliola.github.io/assertj/ export function assertThat(actual: any): Assertions { @@ -65,14 +66,9 @@ class Assertions { } async doesNotExist() { - try { - await access(this.actual) - } - catch (e) { - return + if (await exists(this.actual)) { + throw new Error(`Path ${this.actual} must not exist`) } - - throw new Error(`Path ${this.actual} must not exist`) } async throws(error: string | RegExp) { diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 39318754348..d83c398d1ff 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -1,24 +1,10 @@ -import { copy, emptyDir, remove, writeJson, readJson, readFile, mkdir } from "fs-extra-p" +import { emptyDir, remove, writeJson, readJson, readFile, mkdir } from "fs-extra-p" import { assertThat } from "./fileAssert" import * as path from "path" import { parse as parsePlist } from "plist" import { CSC_LINK } from "./codeSignData" import { expectedLinuxContents, expectedWinContents } from "./expectedContents" -import { - Packager, - PackagerOptions, - Platform, - ArtifactCreated, - Arch, - DIR_TARGET, - createTargets, - getArchSuffix, - MacOsTargetName, - Target, - MacOptions, - BuildInfo, - SquirrelWindowsOptions -} from "out" +import { Packager, PackagerOptions, Platform, ArtifactCreated, Arch, DIR_TARGET, createTargets, getArchSuffix, MacOsTargetName, Target, MacOptions, BuildInfo, SquirrelWindowsOptions } from "out" import { exec, spawn, getTempName } from "out/util/util" import { log, warn } from "out/util/log" import pathSorter from "path-sort" @@ -32,6 +18,8 @@ import SquirrelWindowsTarget from "out/targets/squirrelWindows" import { DmgTarget } from "out/targets/dmg" import OsXPackager from "out/macPackager" import { SignOptions as MacSignOptions } from "electron-macos-sign" +import { copyDir, FileCopier } from "out/util/fs" +import isCi from "is-ci" if (process.env.TRAVIS !== "true") { process.env.CIRCLE_BUILD_NUM = 42 @@ -104,12 +92,10 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO log(`Custom temp dir used: ${customTmpDir}`) } await emptyDir(dir) - await copy(projectDir, dir, { - filter: it => { - const basename = path.basename(it) - return basename !== OUT_DIR_NAME && basename !== "node_modules" && basename[0] !== "." - } - }) + await copyDir(projectDir, dir, it => { + const basename = path.basename(it) + return basename !== OUT_DIR_NAME && basename !== "node_modules" && !basename.startsWith(".") + }, it => path.basename(it) != "package.json") projectDir = dir } @@ -164,8 +150,10 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO } } -export function getTestAsset(file: string) { - return path.join(__dirname, "..", "..", "fixtures", file) +const fileCopier = new FileCopier() + +export function copyTestAsset(name: string, destination: string): Promise { + return fileCopier.copy(path.join(__dirname, "..", "..", "fixtures", name), destination, undefined) } async function packAndCheck(outDir: string, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions): Promise { @@ -412,11 +400,11 @@ async function getContents(path: string) { ) } -export function packageJson(task: (data: any) => void, isApp: boolean = false) { +export function packageJson(task: (data: any) => void, isApp = false) { return (projectDir: string) => modifyPackageJson(projectDir, task, isApp) } -export async function modifyPackageJson(projectDir: string, task: (data: any) => void, isApp: boolean = false): Promise { +export async function modifyPackageJson(projectDir: string, task: (data: any) => void, isApp = false): Promise { const file = isApp ? path.join(projectDir, "app", "package.json") : path.join(projectDir, "package.json") const data = await readJson(file) task(data) @@ -443,7 +431,7 @@ export function getPossiblePlatforms(type?: string): Map, expectedCont }) } -export function allPlatforms(dist: boolean = true): PackagerOptions { +export function allPlatforms(dist = true): PackagerOptions { return { targets: getPossiblePlatforms(dist ? null : DIR_TARGET), } diff --git a/test/src/helpers/runTests.ts b/test/src/helpers/runTests.ts index 0c0ae20a3d9..20129cd283d 100755 --- a/test/src/helpers/runTests.ts +++ b/test/src/helpers/runTests.ts @@ -3,6 +3,7 @@ import BluebirdPromise from "bluebird-lst-c" import { emptyDir, readdir, unlink, removeSync, readJson } from "fs-extra-p" import { homedir } from "os" import { TEST_DIR, ELECTRON_VERSION } from "./config" +import isCi from "is-ci" // we set NODE_PATH in this file, so, we cannot use 'out/awaiter' path here const util = require("../../../out/util/util") @@ -33,7 +34,7 @@ main() }) async function deleteOldElectronVersion(): Promise { - if (!process.env.CI) { + if (!isCi) { return } @@ -97,7 +98,7 @@ async function runTests() { const circleNodeIndex = parseInt(process.env.CIRCLE_NODE_INDEX, 10) if (circleNodeIndex === 0 || circleNodeIndex === 2) { skipWin = true - args.push("linux.*", "BuildTest.js", "extraMetadataTest.js", "mainEntryTest.js", "globTest.js", "filesTest.js", "ignoreTest.js") + args.push("linux.*", "BuildTest.js", "extraMetadataTest.js", "mainEntryTest.js", "globTest.js", "filesTest.js", "ignoreTest.js", "nsisUpdaterTest") } else { args.push("windows.*", "mac.*") diff --git a/test/src/nsisUpdaterTest.ts b/test/src/nsisUpdaterTest.ts index 0d1e612e0dc..692aaac0bb0 100644 --- a/test/src/nsisUpdaterTest.ts +++ b/test/src/nsisUpdaterTest.ts @@ -86,7 +86,7 @@ test("file url generic", async () => { const actualEvents: Array = [] const expectedEvents = ["checking-for-update", "update-available", "update-downloaded"] - for (let eventName of expectedEvents) { + for (const eventName of expectedEvents) { updater.addListener(eventName, () => { actualEvents.push(eventName) }) @@ -114,7 +114,7 @@ test("file url github", async () => { const actualEvents: Array = [] const expectedEvents = ["checking-for-update", "update-available", "update-downloaded"] - for (let eventName of expectedEvents) { + for (const eventName of expectedEvents) { updater.addListener(eventName, () => { actualEvents.push(eventName) }) @@ -130,11 +130,12 @@ test("file url github", async () => { }) test("test error", async () => { + g.__test_resourcesPath = null const updater: NsisUpdater = new NsisUpdaterClass() const actualEvents: Array = [] const expectedEvents = ["checking-for-update", "error"] - for (let eventName of expectedEvents) { + for (const eventName of expectedEvents) { updater.addListener(eventName, () => { actualEvents.push(eventName) }) diff --git a/test/src/windows/nsisBoring.ts b/test/src/windows/nsisBoring.ts index b712b4ec04a..ebe48a07418 100644 --- a/test/src/windows/nsisBoring.ts +++ b/test/src/windows/nsisBoring.ts @@ -1,6 +1,5 @@ import { Platform, Arch } from "out" -import { assertPack, getTestAsset, app } from "../helpers/packTester" -import { copy } from "fs-extra-p" +import { assertPack, app, copyTestAsset } from "../helpers/packTester" import * as path from "path" const nsisTarget = Platform.WINDOWS.createTarget(["nsis"]) @@ -27,7 +26,7 @@ test.ifNotCiMac("boring, MUI_HEADER", () => { }, { projectDirCreated: projectDir => { installerHeaderPath = path.join(projectDir, "build", "installerHeader.bmp") - return copy(getTestAsset("installerHeader.bmp"), installerHeaderPath) + return copyTestAsset("installerHeader.bmp", installerHeaderPath) } } ) @@ -56,7 +55,7 @@ test.ifNotCiMac("boring, MUI_HEADER as option", () => { }, { projectDirCreated: projectDir => { installerHeaderPath = path.join(projectDir, "foo.bmp") - return copy(getTestAsset("installerHeader.bmp"), installerHeaderPath) + return copyTestAsset("installerHeader.bmp", installerHeaderPath) }, } ) @@ -90,6 +89,6 @@ test.ifNotCiMac("boring", app({ }, { signed: true, projectDirCreated: projectDir => { - return copy(getTestAsset("license.txt"), path.join(projectDir, "build", "license.txt")) + return copyTestAsset("license.txt", path.join(projectDir, "build", "license.txt")) }, })) diff --git a/test/src/windows/nsisTest.ts b/test/src/windows/nsisTest.ts index f60686a34a3..24c08083b01 100644 --- a/test/src/windows/nsisTest.ts +++ b/test/src/windows/nsisTest.ts @@ -1,6 +1,6 @@ import { Platform, Arch } from "out" -import { assertPack, getTestAsset, app } from "../helpers/packTester" -import { copy, outputFile, readFile } from "fs-extra-p" +import { assertPack, app, copyTestAsset } from "../helpers/packTester" +import { outputFile, readFile } from "fs-extra-p" import * as path from "path" import BluebirdPromise from "bluebird-lst-c" import { assertThat } from "../helpers/fileAssert" @@ -63,8 +63,10 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", app({ }, }, { projectDirCreated: projectDir => { - let headerIconPath = path.join(projectDir, "build", "foo.ico") - return BluebirdPromise.all([copy(getTestAsset("headerIcon.ico"), headerIconPath), copy(getTestAsset("license.txt"), path.join(projectDir, "build", "license.txt"))]) + return BluebirdPromise.all([ + copyTestAsset("headerIcon.ico", path.join(projectDir, "build", "foo.ico")), + copyTestAsset("license.txt", path.join(projectDir, "build", "license.txt"), + )]) }, packed: async(context) => { assertThat(safeLoad(await readFile(path.join(context.getResources(Platform.WINDOWS, Arch.ia32), "app-update.yml"), "utf-8"))).hasProperties({ @@ -144,14 +146,14 @@ test.ifNotCiMac("installerHeaderIcon", () => { }, { projectDirCreated: projectDir => { headerIconPath = path.join(projectDir, "build", "installerHeaderIcon.ico") - return copy(getTestAsset("headerIcon.ico"), headerIconPath) + return copyTestAsset("headerIcon.ico", headerIconPath) } } ) }) test.ifDevOrLinuxCi("custom include", () => assertPack("test-app-one", {targets: nsisTarget}, { - projectDirCreated: projectDir => copy(getTestAsset("installer.nsh"), path.join(projectDir, "build", "installer.nsh")), + projectDirCreated: projectDir => copyTestAsset("installer.nsh", path.join(projectDir, "build", "installer.nsh")), packed: context => BluebirdPromise.all([ assertThat(path.join(context.projectDir, "build", "customHeader")).isFile(), assertThat(path.join(context.projectDir, "build", "customInit")).isFile(), @@ -160,6 +162,6 @@ test.ifDevOrLinuxCi("custom include", () => assertPack("test-app-one", {targets: })) test.ifDevOrLinuxCi("custom script", app({targets: nsisTarget}, { - projectDirCreated: projectDir => copy(getTestAsset("installer.nsi"), path.join(projectDir, "build", "installer.nsi")), + projectDirCreated: projectDir => copyTestAsset("installer.nsi", path.join(projectDir, "build", "installer.nsi")), packed: context => assertThat(path.join(context.projectDir, "build", "customInstallerScript")).isFile(), })) \ No newline at end of file diff --git a/test/src/windows/squirrelWindowsTest.ts b/test/src/windows/squirrelWindowsTest.ts index a6967a77c0a..b810962b957 100644 --- a/test/src/windows/squirrelWindowsTest.ts +++ b/test/src/windows/squirrelWindowsTest.ts @@ -1,8 +1,7 @@ import { Platform, Arch } from "out" -import { app, modifyPackageJson, appThrows, getTestAsset, assertPack, CheckingWinPackager } from "../helpers/packTester" +import { app, modifyPackageJson, appThrows, assertPack, CheckingWinPackager, copyTestAsset } from "../helpers/packTester" import * as path from "path" import BluebirdPromise from "bluebird-lst-c" -import { copy } from "fs-extra-p" test.ifNotCiMac("Squirrel.Windows", app({targets: Platform.WINDOWS.createTarget(["squirrel", "zip"])}, {signed: true})) @@ -45,7 +44,7 @@ test("detect install-spinner, certificateFile/password", () => { projectDirCreated: it => { loadingGifPath = path.join(it, "build", "install-spinner.gif") return BluebirdPromise.all([ - copy(getTestAsset("install-spinner.gif"), loadingGifPath), + copyTestAsset("install-spinner.gif", loadingGifPath), modifyPackageJson(it, data => { data.build.win = { certificateFile: "secretFile", diff --git a/test/src/windows/winPackagerTest.ts b/test/src/windows/winPackagerTest.ts index e54040e1ad1..8d33acb98df 100755 --- a/test/src/windows/winPackagerTest.ts +++ b/test/src/windows/winPackagerTest.ts @@ -1,6 +1,6 @@ import { Platform } from "out" import { assertPack, platform, modifyPackageJson, app, appThrows, CheckingWinPackager } from "../helpers/packTester" -import { outputFile, rename } from "fs-extra-p" +import { writeFile, rename, unlink } from "fs-extra-p" import * as path from "path" import BluebirdPromise from "bluebird-lst-c" @@ -16,7 +16,12 @@ test.ifNotCiMac("icon < 256", appThrows(/Windows icon size must be at least 256x })) test.ifNotCiMac("icon not an image", appThrows(/Windows icon is not valid ico file, please fix ".+/, platform(Platform.WINDOWS), { - projectDirCreated: projectDir => outputFile(path.join(projectDir, "build", "icon.ico"), "foo") + projectDirCreated: async (projectDir) => { + const file = path.join(projectDir, "build", "icon.ico") + // because we use hardlinks + await unlink(file) + await writeFile(file, "foo") + } })) test.ifMac("custom icon", () => { diff --git a/typings/isCi.d.ts b/typings/isCi.d.ts new file mode 100644 index 00000000000..4fe046c5a8b --- /dev/null +++ b/typings/isCi.d.ts @@ -0,0 +1,4 @@ +declare module "is-ci" { + const isCI: boolean + export default isCI +} \ No newline at end of file diff --git a/typings/stat-mode.d.ts b/typings/stat-mode.d.ts new file mode 100644 index 00000000000..89302b65a39 --- /dev/null +++ b/typings/stat-mode.d.ts @@ -0,0 +1,19 @@ +declare module "stat-mode" { + import { Stats } from "fs" + + interface Permissions { + read: boolean + write: boolean + execute: boolean + } + + export default class Mode { + constructor(stats: Stats) + + owner: Permissions + group: Permissions + others: Permissions + + toOctal(): string + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 57e6ff7b8d1..f817d054cce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,27 +22,27 @@ "7zip-bin-mac" "^1.0.1" "7zip-bin-win" "^2.0.2" -"@develar/semantic-release@^6.3.21": - version "6.3.23" - resolved "https://registry.yarnpkg.com/@develar/semantic-release/-/semantic-release-6.3.23.tgz#80c8ecd7369cf4a5c442d59d24bd614c051dd004" +"@develar/semantic-release@^6.3.24": + version "6.3.24" + resolved "https://registry.yarnpkg.com/@develar/semantic-release/-/semantic-release-6.3.24.tgz#79c7fdd4a2f5de879a8eb1cebde369fdb3a5784c" dependencies: "@semantic-release/commit-analyzer" "^2.0.0" "@semantic-release/error" "^1.0.0" - bluebird-lst-c "^1.0.2" + bluebird-lst-c "^1.0.5" conventional-changelog "^1.1.0" conventional-commits-parser "^1.3.0" - debug "^2.2.0" - fs-extra-p "^2.0.3" - git-head "^1.14.0" - github "^5.3.3" + debug "^2.3.3" + fs-extra-p "^3.0.2" + git-head "^1.15.0" + github "^6.1.0" hosted-git-info "^2.1.5" normalize-package-data "^2.3.5" - npm-registry-client "^7.3.0" + npm-registry-client "^7.4.1" require-relative "^0.8.7" run-auto "^2.0.0" run-series "^1.1.4" semver "^5.3.0" - through2 "^2.0.1" + through2 "^2.0.2" "@semantic-release/commit-analyzer@^2.0.0": version "2.0.0" @@ -67,8 +67,8 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.5.28.tgz#efd7614f8eb1b924c41235ff653b7370da467fac" "@types/node@*": - version "6.0.48" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.48.tgz#86ccc15f66b73cbbc5eb3483398936c585122b3c" + version "6.0.51" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.51.tgz#84cbf25111516ec9304d0b61469dc0fa9d12ba32" "@types/source-map-support@^0.2.28": version "0.2.28" @@ -262,8 +262,8 @@ async@1.x, async@^1.4.0, async@^1.4.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/async/-/async-2.1.2.tgz#612a4ab45ef42a70cde806bad86ee6db047e8385" + version "2.1.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: lodash "^4.14.0" @@ -547,7 +547,7 @@ bl@^1.0.0: dependencies: readable-stream "~2.0.5" -bluebird-lst-c@^1.0.2, bluebird-lst-c@^1.0.3, bluebird-lst-c@^1.0.4, bluebird-lst-c@^1.0.5: +bluebird-lst-c@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/bluebird-lst-c/-/bluebird-lst-c-1.0.5.tgz#50da657dcde337a0e29d9e080ba714c5a52016e2" dependencies: @@ -605,8 +605,8 @@ bser@^1.0.2: node-int64 "^0.4.0" buffer-crc32@^0.2.1: - version "0.2.6" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.6.tgz#612b318074fc6c4c30504b297247a1f91641253b" + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" buffer-shims@^1.0.0: version "1.0.0" @@ -1018,8 +1018,8 @@ dargs@^4.0.1: number-is-nan "^1.0.0" dashdash@^1.12.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141" + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" dependencies: assert-plus "^1.0.0" @@ -1030,9 +1030,9 @@ dateformat@^1.0.11, dateformat@^1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -debug@2, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.2.tgz#94cb466ef7d6d2c7e5245cdd6e4104f2d0d70d30" +debug@2, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.2, debug@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" dependencies: ms "0.7.2" @@ -1116,9 +1116,9 @@ dezalgo@^1.0.0, dezalgo@^1.0.1: asap "^2.0.0" wrappy "1" -diff@^3.0.0, diff@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.0.1.tgz#a52d90cc08956994be00877bff97110062582c35" +diff@^3.0.0, diff@^3.0.1, diff@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.1.0.tgz#9406c73a401e6c2b3ba901c5e2c44eb6a60c5385" dot-prop@^3.0.0: version "3.0.0" @@ -1363,20 +1363,12 @@ from@~0: version "0.1.3" resolved "https://registry.yarnpkg.com/from/-/from-0.1.3.tgz#ef63ac2062ac32acf7862e0d40b44b896f22f3bc" -fs-extra-p@^2.0.3, fs-extra-p@^2.0.5, fs-extra-p@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-2.0.7.tgz#525a86ee1250510210defe1ea39f473c23ecfa96" - dependencies: - bluebird-lst-c "^1.0.4" - fs-extra-tf "^1.0.0" - -fs-extra-tf@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-extra-tf/-/fs-extra-tf-1.0.0.tgz#77d3d7dc2199fbc618a42fe5ea81137db95eeb2e" +fs-extra-p@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-3.0.2.tgz#074e44ca8ae535fe6999458458ff2eb7c956d0c8" dependencies: - graceful-fs "^4.1.10" - jsonfile "^2.4.0" - klaw "^1.3.1" + bluebird-lst-c "^1.0.5" + fs-extra "^1.0.0" fs-extra@^1.0.0: version "1.0.0" @@ -1438,7 +1430,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -git-head@^1.14.0: +git-head@^1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/git-head/-/git-head-1.15.0.tgz#e29cb415f7c9462cd3d69f8e2050544777ab263f" dependencies: @@ -1483,16 +1475,16 @@ gitconfiglocal@^1.0.0: ini "^1.3.2" github-url-from-git@^1.3.0, github-url-from-git@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.4.0.tgz#285e6b520819001bde128674704379e4ff03e0de" + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.5.0.tgz#f985fedcc0a9aa579dc88d7aff068d55cc6251a0" github-url-from-username-repo@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz#7dd79330d2abe69c10c2cef79714c97215791dfa" -github@^5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/github/-/github-5.3.3.tgz#8d0d587b3d4556ea7be90d1c5a20fce96f3826ad" +github@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/github/-/github-6.1.0.tgz#664cbd232c59ce9d050749a188bd0f57ec8abcd8" dependencies: follow-redirects "0.0.7" https-proxy-agent "^1.0.0" @@ -1543,8 +1535,8 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.1: path-is-absolute "^1.0.0" globals@^9.0.0: - version "9.13.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.13.0.tgz#d97706b61600d8dbe94708c367d3fdcf48470b8f" + version "9.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" got@^5.0.0: version "5.7.1" @@ -1566,9 +1558,9 @@ got@^5.0.0: unzip-response "^1.0.2" url-parse-lax "^1.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: - version "4.1.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.10.tgz#f2d720c22092f743228775c75e3612632501f131" +graceful-fs@^4.1.0, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -1668,10 +1660,14 @@ husky@^0.11.9: is-ci "^1.0.9" normalize-path "^1.0.0" -iconv-lite@0.4.13, iconv-lite@^0.4.13: +iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" +iconv-lite@^0.4.13: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -1721,7 +1717,7 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-ci@^1.0.9: +is-ci@^1.0.10, is-ci@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" dependencies: @@ -2188,14 +2184,14 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" json5@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.0.tgz#9b20715b026cbe3778fd769edccd822d8332a5b2" + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" json8@^0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/json8/-/json8-0.9.2.tgz#dced62a24c8ed457702d45c71068081925c3011f" -jsonfile@^2.1.0, jsonfile@^2.4.0: +jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" optionalDependencies: @@ -2227,7 +2223,7 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.0.2" -klaw@^1.0.0, klaw@^1.3.1: +klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" optionalDependencies: @@ -2639,7 +2635,7 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: hosted-git-info "^2.1.5" semver "^5.1.0" -npm-registry-client@^7.3.0: +npm-registry-client@^7.4.1: version "7.4.1" resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-7.4.1.tgz#e9e5de15bb498fe49fff87f7cf9aece21e071e42" dependencies: @@ -2936,8 +2932,8 @@ qs@~6.3.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" randomatic@^1.1.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.5.tgz#5e9ef5f2d573c67bd2b8124ae90b5156e457840b" + version "1.1.6" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" dependencies: is-number "^2.0.2" kind-of "^3.0.2" @@ -3005,7 +3001,7 @@ readable-stream@^1.1.8, readable-stream@~1.1.9: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5: +readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -3317,6 +3313,10 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" +stat-mode@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -3421,12 +3421,12 @@ throttleit@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" -through2@^2.0.0, through2@^2.0.1, through2@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" +through2@^2.0.0, through2@^2.0.2, through2@~2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.2.tgz#316d3a4f444af641496aa7f45a713be72576baf4" dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" + readable-stream "^2.1.5" + xtend "~4.0.1" through2@~0.2.3: version "0.2.3" @@ -3485,20 +3485,20 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -ts-babel@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/ts-babel/-/ts-babel-1.1.4.tgz#69339a1cc69e57c07bd4202eec01ca55092c9f4e" +ts-babel@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ts-babel/-/ts-babel-1.1.5.tgz#5a83460bb66eeb6c5af727ad18afb4f3c6e189b8" dependencies: babel-core "^6.18.2" - bluebird-lst-c "^1.0.3" + bluebird-lst-c "^1.0.5" chalk "^1.1.3" - fs-extra-p "^2.0.5" + fs-extra-p "^3.0.2" markdown-it "^8.1.0" source-map-support "^0.4.6" -tslint@4.0.0-dev.1: - version "4.0.0-dev.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.0.0-dev.1.tgz#10146c7ff47e18ce41315c146ff03dbdb5eb8abc" +tslint@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.0.2.tgz#d43f24c0c1f826de7f3a097bb7808a8b4325feac" dependencies: colors "^1.1.2" diff "^3.0.1" @@ -3507,6 +3507,7 @@ tslint@4.0.0-dev.1: optimist "~0.6.0" resolve "^1.1.7" underscore.string "^3.3.4" + update-notifier "^1.0.2" tunnel-agent@^0.4.3, tunnel-agent@~0.4.1: version "0.4.3" @@ -3655,8 +3656,8 @@ whatwg-encoding@^1.0.1: iconv-lite "0.4.13" whatwg-url@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-3.0.0.tgz#b9033c50c7ce763e91d78777ce825a6d7f56dac5" + version "3.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-3.1.0.tgz#7bdcae490f921aef6451fb6739ec6bbd8e907bf6" dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" @@ -3751,10 +3752,10 @@ xmlbuilder@8.2.2: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773" xmldom@0.1.x: - version "0.1.22" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.22.tgz#10de4e5e964981f03c8cc72fadc08d14b6c3aa26" + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 640fea03260af61dc2b939fdf731877218ce0217 Mon Sep 17 00:00:00 2001 From: develar Date: Tue, 29 Nov 2016 08:28:57 +0100 Subject: [PATCH 06/43] feat(nsis): use name instead of product name as inst dir Do not use version in the inst dir path. Close #926, #941, #810, #928 --- docker/7/Dockerfile | 2 +- docker/base/Dockerfile | 10 +-- docker/wine/Dockerfile | 2 +- nsis-auto-updater/package.json | 2 +- package.json | 4 +- src/cli/create-self-signed-cert.ts | 2 +- src/targets/nsis.ts | 1 + src/util/tmp.ts | 113 ++++++++++++++++------------- src/util/util.ts | 2 +- templates/nsis/multiUser.nsh | 6 +- templates/nsis/uninstaller.nsh | 2 +- yarn.lock | 10 +-- 12 files changed, 86 insertions(+), 70 deletions(-) diff --git a/docker/7/Dockerfile b/docker/7/Dockerfile index 71aa8514924..96a6466c4d6 100644 --- a/docker/7/Dockerfile +++ b/docker/7/Dockerfile @@ -1,6 +1,6 @@ FROM electronuserland/electron-builder:base -ENV NODE_VERSION 7.1.0 +ENV NODE_VERSION 7.2.0 # https://github.com/npm/npm/issues/4531 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \ diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 1eb54385db5..1f9fa2ffd8b 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,4 +1,4 @@ -FROM buildpack-deps:yakkety-curl +FROM buildpack-deps:xenial-curl # rpm is required for FPM to build rpm package # yasm is required to build p7zip @@ -15,9 +15,8 @@ ENV USE_SYSTEM_7ZA true ENV DEBUG_COLORS true ENV FORCE_COLOR true -RUN apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg && echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ -apt-get update -y && \ - apt-get install --no-install-recommends -y git snapcraft yarn xorriso bsdtar build-essential autoconf libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm yasm && \ +RUN curl -L https://yarnpkg.com/latest.tar.gz | tar xvz && mv dist yarn && ln -s /yarn/bin/yarn /usr/local/bin/yarn && apt-get update -y && \ + apt-get install --no-install-recommends -y git snapcraft xorriso bsdtar build-essential autoconf libssl-dev icnsutils graphicsmagick gcc-multilib g++-multilib libgnome-keyring-dev lzip rpm yasm && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ curl -L http://tukaani.org/xz/xz-$XZ_VERSION.tar.xz | tar -xJ && cd xz-$XZ_VERSION && ./configure && make && make install && cd .. && rm -rf xz-$XZ_VERSION && ldconfig && \ @@ -48,4 +47,5 @@ WORKDIR /project RUN locale-gen en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 \ No newline at end of file +ENV LC_ALL en_US.UTF-8 +ENV PATH "$HOME/.yarn/bin:$PATH" \ No newline at end of file diff --git a/docker/wine/Dockerfile b/docker/wine/Dockerfile index df96a42f3a5..6967f5c7796 100644 --- a/docker/wine/Dockerfile +++ b/docker/wine/Dockerfile @@ -2,7 +2,7 @@ FROM electronuserland/electron-builder:latest # libgnome-keyring-dev — to build keytar RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F9CB8DB0 && \ -echo "deb http://ppa.launchpad.net/ubuntu-wine/ppa/ubuntu yakkety main " | tee /etc/apt/sources.list.d/wine.list && \ +echo "deb http://ppa.launchpad.net/ubuntu-wine/ppa/ubuntu xenial main " | tee /etc/apt/sources.list.d/wine.list && \ dpkg --add-architecture i386 && \ apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \ echo "deb http://download.mono-project.com/repo/debian wheezy main" | tee /etc/apt/sources.list.d/mono-xamarin.list && \ diff --git a/nsis-auto-updater/package.json b/nsis-auto-updater/package.json index d806ab18260..22950c61d44 100644 --- a/nsis-auto-updater/package.json +++ b/nsis-auto-updater/package.json @@ -14,7 +14,7 @@ "dependencies": { "bluebird-lst-c": "^1.0.5", "debug": "^2.3.3", - "fs-extra-p": "^3.0.2", + "fs-extra-p": "^3.0.3", "ini": "^1.3.4", "js-yaml": "^3.7.0", "semver": "^5.3.0", diff --git a/package.json b/package.json index 80e5195de3b..8f19849470f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint": "node ./test/lint.js", "pretest": "yarn run compile && yarn run lint", "check-deps": "node ./test/out/helpers/checkDeps.js", - "test": "node --trace-warnings ./test/out/helpers/runTests.js", + "test": "node ./test/out/helpers/runTests.js", "test-linux": "docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron electronuserland/electron-builder:wine /test.sh", "pack-updater": "cd nsis-auto-updater && yarn --production && cd ..", "semantic-release": "semantic-release pre && npm publish && semantic-release post", @@ -70,7 +70,7 @@ "debug": "^2.3.3", "electron-download-tf": "3.1.0", "electron-macos-sign": "^1.3.4", - "fs-extra-p": "^3.0.2", + "fs-extra-p": "^3.0.3", "hosted-git-info": "^2.1.5", "ini": "^1.3.4", "is-ci": "^1.0.10", diff --git a/src/cli/create-self-signed-cert.ts b/src/cli/create-self-signed-cert.ts index 3bfe53b09c8..4cf33fc69fe 100644 --- a/src/cli/create-self-signed-cert.ts +++ b/src/cli/create-self-signed-cert.ts @@ -16,7 +16,7 @@ async function main() { const tmpDir = new TmpDir() const targetDir = process.cwd() - const tempPrefix = path.join(await tmpDir.getTempFile(null), sanitizeFileName(args.publisher)) + const tempPrefix = path.join(await tmpDir.getTempFile(""), sanitizeFileName(args.publisher)) const cer = `${tempPrefix}.cer` const pvk = `${tempPrefix}.pvk` diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index bf8b7d783ef..f0156ccd250 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -94,6 +94,7 @@ export default class NsisTarget extends Target { APP_GUID: guid, PRODUCT_NAME: appInfo.productName, PRODUCT_FILENAME: appInfo.productFilename, + APP_FILENAME: appInfo.name, APP_DESCRIPTION: appInfo.description, VERSION: version, diff --git a/src/util/tmp.ts b/src/util/tmp.ts index ff6410ff4cb..61aa319213a 100644 --- a/src/util/tmp.ts +++ b/src/util/tmp.ts @@ -1,75 +1,90 @@ import { tmpdir } from "os" -import { remove, mkdirs, removeSync } from "fs-extra-p" +import { remove, mkdirs, removeSync, mkdtemp } from "fs-extra-p" import * as path from "path" import { getTempName } from "./util" import BluebirdPromise from "bluebird-lst-c" import { warn } from "./log" - -const mkdtemp: any | null = require("fs-extra-p").mkdtemp +import { all } from "./promise" process.setMaxListeners(30) -export class TmpDir { - private tmpFileCounter = 0 - private tempDirectoryPromise: Promise - - private dir: string | null +let tempDirPromise: Promise | null +let tempDir: string | null - getTempFile(suffix: string | null): Promise { - if (this.tempDirectoryPromise == null) { - let promise: Promise - if (mkdtemp == null) { - const dir = path.join(tmpdir(), getTempName("electron-builder")) - promise = mkdirs(dir, {mode: 448}).then(() => dir) - } - else { - promise = mkdtemp(`${path.join(process.env.TEST_DIR || tmpdir(), "electron-builder")}-`) - } +function getTempDir() { + if (tempDirPromise == null) { + let promise: Promise + const systemTmpDir = process.env.TEST_DIR || tmpdir() + if (mkdtemp == null) { + const dir = path.join(systemTmpDir, getTempName("electron-builder")) + promise = mkdirs(dir, {mode: 448}).then(() => dir) + } + else { + promise = mkdtemp(`${path.join(systemTmpDir, "electron-builder")}-`) + } - this.tempDirectoryPromise = promise - .then(dir => { - this.dir = dir - const cleanup = () => { - if (this.dir == null) { - return - } + tempDirPromise = promise + .then(dir => { + tempDir = dir + const cleanup = () => { + if (tempDir == null) { + return + } - this.dir = null - try { - removeSync(dir) - } - catch (e) { - if (e.code !== "EPERM") { - warn(`Cannot delete temporary dir "${dir}": ${(e.stack || e).toString()}`) - } + tempDir = null + try { + removeSync(dir) + } + catch (e) { + if (e.code !== "EPERM") { + warn(`Cannot delete temporary dir "${dir}": ${(e.stack || e).toString()}`) } } - process.on("exit", cleanup) - process.on("uncaughtException", cleanup) - process.on("SIGINT", cleanup) - return dir - }) + } + process.on("exit", cleanup) + process.on("uncaughtException", cleanup) + process.on("SIGINT", cleanup) + return dir + }) + } + + return tempDirPromise +} + +let tmpFileCounter = 0 + +export class TmpDir { + private tempPrefixPromise: Promise | null + private tempFiles: Array = [] + + getTempFile(suffix: string): Promise { + if (this.tempPrefixPromise == null) { + this.tempPrefixPromise = getTempDir().then(it => path.join(it, (tmpFileCounter++).toString(16))) } - return this.tempDirectoryPromise - .then(it => suffix == null ? it : path.join(it, `t-${process.pid.toString(16)}-${(this.tmpFileCounter++).toString(16)}${suffix.startsWith(".") ? suffix : `-${suffix}`}`)) + return this.tempPrefixPromise + .then(it => { + const result = `${it}-${(tmpFileCounter++).toString(16)}${suffix.length === 0 || suffix.startsWith(".") ? suffix : `-${suffix}`}` + this.tempFiles.push(result) + return result + }) } cleanup(): Promise { - const dir = this.dir - if (dir == null) { + const tempFiles = this.tempFiles + if (tempFiles.length === 0) { return BluebirdPromise.resolve() } - this.dir = null - return remove(dir) + this.tempFiles = [] + this.tempPrefixPromise = null + + return all(tempFiles.map(it => remove(it) .catch(e => { - if (e.code === "EPERM") { - this.dir = dir - } - else { - warn(`Cannot delete temporary dir "${dir}": ${(e.stack || e).toString()}`) + if (e.code !== "EPERM") { + warn(`Cannot delete temporary dir "${it}": ${(e.stack || e).toString()}`) } }) + )) } } diff --git a/src/util/util.ts b/src/util/util.ts index 2986513e8f7..2ee98b2095e 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -224,7 +224,7 @@ export function debug7zArgs(command: "a" | "x"): Array { return args } -let tmpDirCounter = 0 +export let tmpDirCounter = 0 // add date to avoid use stale temp dir const tempDirPrefix = `${process.pid.toString(16)}-${Date.now().toString(16)}` diff --git a/templates/nsis/multiUser.nsh b/templates/nsis/multiUser.nsh index 01df12146d2..9b59f4db435 100644 --- a/templates/nsis/multiUser.nsh +++ b/templates/nsis/multiUser.nsh @@ -29,7 +29,7 @@ Var installMode StrCpy $0 $1 System::Call 'Ole32::CoTaskMemFree(ir2)' ${endif} - StrCpy $INSTDIR "$0\${PRODUCT_FILENAME}\${VERSION}" + StrCpy $INSTDIR "$0\${APP_FILENAME}" !endif # сhecks registry for previous installation path — for uninstall only, currently, installation path is not customizable @@ -49,11 +49,11 @@ Var installMode StrCpy $installMode all SetShellVarContext all - StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCT_FILENAME}\${VERSION}" + StrCpy $INSTDIR "$PROGRAMFILES\${APP_FILENAME}" !ifdef APP_64 ${if} ${RunningX64} - StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCT_FILENAME}\${VERSION}" + StrCpy $INSTDIR "$PROGRAMFILES64\${APP_FILENAME}" ${endif} !endif diff --git a/templates/nsis/uninstaller.nsh b/templates/nsis/uninstaller.nsh index 09717a144c7..8c890b80986 100644 --- a/templates/nsis/uninstaller.nsh +++ b/templates/nsis/uninstaller.nsh @@ -50,7 +50,7 @@ Section "un.install" ${if} $installMode == "all" SetShellVarContext current ${endif} - RMDir /r "$APPDATA\${PRODUCT_FILENAME}" + RMDir /r "$APPDATA\${APP_FILENAME}" ${if} $installMode == "all" SetShellVarContext all ${endif} diff --git a/yarn.lock b/yarn.lock index f817d054cce..78b2722dc69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1363,9 +1363,9 @@ from@~0: version "0.1.3" resolved "https://registry.yarnpkg.com/from/-/from-0.1.3.tgz#ef63ac2062ac32acf7862e0d40b44b896f22f3bc" -fs-extra-p@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-3.0.2.tgz#074e44ca8ae535fe6999458458ff2eb7c956d0c8" +fs-extra-p@^3.0.2, fs-extra-p@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-3.0.3.tgz#2e48b1e6c196feb75ac6f0c6419760ce8b847f33" dependencies: bluebird-lst-c "^1.0.5" fs-extra "^1.0.0" @@ -3422,8 +3422,8 @@ throttleit@0.0.2: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" through2@^2.0.0, through2@^2.0.2, through2@~2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.2.tgz#316d3a4f444af641496aa7f45a713be72576baf4" + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" dependencies: readable-stream "^2.1.5" xtend "~4.0.1" From 6c90f30680a09662f76323470019253534c773bd Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 30 Nov 2016 08:56:15 +0100 Subject: [PATCH 07/43] feat: exclude yarn-error.log and .jshintrc, remove LICENSE outside of .app --- package.json | 2 +- src/packager/mac.ts | 5 ++++- src/platformPackager.ts | 11 ++++++++--- yarn.lock | 19 ++++++++++--------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8f19849470f..0c347302839 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "babel-plugin-transform-es2015-spread": "^6.8.0", "babel-plugin-transform-inline-imports-commonjs": "^1.2.0", "decompress-zip": "^0.3.0", - "depcheck": "^0.6.5", + "depcheck": "^0.6.6", "diff": "^3.1.0", "husky": "^0.11.9", "jest-cli": "^17.0.3", diff --git a/src/packager/mac.ts b/src/packager/mac.ts index 2314006758f..db825ebeb17 100644 --- a/src/packager/mac.ts +++ b/src/packager/mac.ts @@ -5,6 +5,7 @@ import BluebirdPromise from "bluebird-lst-c" import { use, asArray } from "../util/util" import { normalizeExt, PlatformPackager } from "../platformPackager" import { warn } from "../util/log" +import { unlinkIfExists } from "../util/fs" function doRename (basePath: string, oldName: string, newName: string) { return rename(path.join(basePath, oldName), path.join(basePath, newName)) @@ -120,7 +121,9 @@ export async function createApp(packager: PlatformPackager, appOutDir: stri writeFile(helperPlistFilename, buildPlist(helperPlist)), writeFile(helperEHPlistFilename, buildPlist(helperEHPlist)), writeFile(helperNPPlistFilename, buildPlist(helperNPPlist)), - doRename(path.join(contentsPath, "MacOS"), "Electron", appPlist.CFBundleExecutable) + doRename(path.join(contentsPath, "MacOS"), "Electron", appPlist.CFBundleExecutable), + unlinkIfExists(path.join(appOutDir, "LICENSE")), + unlinkIfExists(path.join(appOutDir, "LICENSES.chromium.html")), ] if (icon != null) { diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 852fa5c3d55..7829a6893c8 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -193,7 +193,7 @@ export abstract class PlatformPackager if (debug.enabled) { const nodeModulesDir = path.join(appDir, "node_modules") - debug(`Pruned dev or extraneous dependencies: ${Array.from(ignoreFiles).slice(2).map(it => path.relative(nodeModulesDir, it)).join(", ")}`) + 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) @@ -208,10 +208,15 @@ export abstract class PlatformPackager defaultMatcher.addPattern("!**/node_modules/.bin") defaultMatcher.addPattern("!**/*.{o,hprof,orig,pyc,pyo,rbc,swp}") defaultMatcher.addPattern("!**/._*") - defaultMatcher.addPattern("!.idea") defaultMatcher.addPattern("!*.iml") //noinspection SpellCheckingInspection - defaultMatcher.addPattern("!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.editorconfig,.flowconfig,.yarn-metadata.json,.idea,appveyor.yml,.travis.yml,circle.yml,npm-debug.log,.nyc_output,yarn.lock,.yarn-integrity}") + defaultMatcher.addPattern("!**/{.git,.hg,.svn,CVS,RCS,SCCS," + + "__pycache__,.DS_Store,thumbs.db,.gitignore,.gitattributes," + + ".editorconfig,.flowconfig,.jshintrc," + + ".yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,npm-debug.log," + + ".idea," + + "appveyor.yml,.travis.yml,circle.yml," + + ".nyc_output}") let rawFilter: any = null const deprecatedIgnore = (this.devMetadata.build).ignore diff --git a/yarn.lock b/yarn.lock index 78b2722dc69..2e97672a01b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1072,9 +1072,9 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depcheck@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/depcheck/-/depcheck-0.6.5.tgz#baee2148e83f1295d372d7c50fd6f7205ccdb8fa" +depcheck@^0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/depcheck/-/depcheck-0.6.6.tgz#e01b8c5883c102c28b7df6127fe53c3d85ffb6e1" dependencies: babel-traverse "^6.7.3" babylon "^6.1.21" @@ -3536,8 +3536,8 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" uglify-js@^2.6: - version "2.7.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.4.tgz#a295a0de12b6a650c031c40deb0dc40b14568bd2" + version "2.7.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" dependencies: async "~0.2.6" source-map "~0.5.1" @@ -3601,8 +3601,8 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" uuid@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" validate-commit-msg@^2.8.2: version "2.8.2" @@ -3720,10 +3720,11 @@ worker-farm@^1.3.1: xtend ">=4.0.0 <4.1.0-0" wrap-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.0.0.tgz#7d30f8f873f9a5bbc3a64dabc8d177e071ae426f" + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" dependencies: string-width "^1.0.1" + strip-ansi "^3.0.1" wrappy@1: version "1.0.2" From 5d4b747866c639a9cc54d20be9277a551e045153 Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 30 Nov 2016 10:34:54 +0100 Subject: [PATCH 08/43] fix: Not a valid Win32 application Closes #844 --- package.json | 5 +---- src/cli/node-gyp-rebuild.ts | 2 +- src/packager.ts | 2 +- src/yarn.ts | 37 +++++++++++-------------------------- 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 0c347302839..e2dc77bc5ae 100644 --- a/package.json +++ b/package.json @@ -182,8 +182,5 @@ "release": { "verifyConditions": [] }, - "typings": "./out/electron-builder.d.ts", - "publishConfig": { - "tag": "next" - } + "typings": "./out/electron-builder.d.ts" } diff --git a/src/cli/node-gyp-rebuild.ts b/src/cli/node-gyp-rebuild.ts index 7640db6aa53..32ef496f74b 100644 --- a/src/cli/node-gyp-rebuild.ts +++ b/src/cli/node-gyp-rebuild.ts @@ -19,7 +19,7 @@ async function main() { const arch = args.arch || process.arch log(`Execute node-gyp rebuild for arch ${arch}`) await exec(process.platform === "win32" ? "node-gyp.cmd" : "node-gyp", ["rebuild"], { - env: getGypEnv(await getElectronVersion(await readPackageJson(devPackageFile), devPackageFile), arch), + env: getGypEnv(await getElectronVersion(await readPackageJson(devPackageFile), devPackageFile), arch, true), }) } diff --git a/src/packager.ts b/src/packager.ts index 57ed2e3173b..9f213a34f98 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -235,7 +235,7 @@ export class Packager implements BuildInfo { if (options.nodeGypRebuild === true) { log(`Executing node-gyp rebuild for arch ${Arch[arch]}`) await exec(process.platform === "win32" ? "node-gyp.cmd" : "node-gyp", ["rebuild"], { - env: getGypEnv(this.electronVersion, Arch[arch]), + env: getGypEnv(this.electronVersion, Arch[arch], true), }) } diff --git a/src/yarn.ts b/src/yarn.ts index 00a50d572a3..8675dd358db 100644 --- a/src/yarn.ts +++ b/src/yarn.ts @@ -7,36 +7,30 @@ import { BuildMetadata } from "./metadata" import { exists } from "./util/fs" export async function installOrRebuild(options: BuildMetadata, appDir: string, electronVersion: string, arch: string, forceInstall: boolean = false) { - const args = computeExtraArgs(options) + const args = asArray(options.npmArgs) if (forceInstall || !(await exists(path.join(appDir, "node_modules")))) { - await installDependencies(appDir, electronVersion, arch, args) + await installDependencies(appDir, electronVersion, arch, args, !options.npmSkipBuildFromSource) } else { - await rebuild(appDir, electronVersion, arch, args) + await rebuild(appDir, electronVersion, arch, args, !options.npmSkipBuildFromSource) } } -export function getGypEnv(electronVersion: string, arch: string) { +export function getGypEnv(electronVersion: string, arch: string, buildFromSource: boolean) { const gypHome = path.join(homedir(), ".electron-gyp") return Object.assign({}, process.env, { npm_config_disturl: "https://atom.io/download/electron", npm_config_target: electronVersion, npm_config_runtime: "electron", npm_config_arch: arch, + npm_config_target_arch: arch, + npm_config_build_from_source: buildFromSource, HOME: gypHome, USERPROFILE: gypHome, }) } -function computeExtraArgs(options: BuildMetadata) { - const args = asArray(options.npmArgs) - if (options.npmSkipBuildFromSource !== true) { - args.push("--build-from-source") - } - return args -} - -export function installDependencies(appDir: string, electronVersion: string, arch: string = process.arch, additionalArgs: Array): Promise { +export function installDependencies(appDir: string, electronVersion: string, arch: string = process.arch, additionalArgs: Array, buildFromSource: boolean): Promise { log(`Installing app dependencies for arch ${arch} to ${appDir}`) let execPath = process.env.npm_execpath || process.env.NPM_CLI_JS const execArgs = ["install", "--production"] @@ -57,15 +51,10 @@ export function installDependencies(appDir: string, electronVersion: string, arc execPath = process.env.npm_node_execpath || process.env.NODE_EXE || "node" } - for (let a of additionalArgs) { - if (!isYarn || a !== "--build-from-source") { - execArgs.push(a) - } - } - + execArgs.push(...additionalArgs) return spawn(execPath, execArgs, { cwd: appDir, - env: getGypEnv(electronVersion, arch), + env: getGypEnv(electronVersion, arch, buildFromSource), }) } @@ -120,7 +109,7 @@ function isYarnPath(execPath: string | null) { return execPath != null && path.basename(execPath).startsWith("yarn") } -export async function rebuild(appDir: string, electronVersion: string, arch: string = process.arch, additionalArgs: Array) { +export async function rebuild(appDir: string, electronVersion: string, arch: string = process.arch, additionalArgs: Array, buildFromSource: boolean) { const deps = new Set() await dependencies(appDir, false, deps) const nativeDeps = await BluebirdPromise.filter(deps, it => exists(path.join(it, "binding.gyp")), {concurrency: 8}) @@ -141,13 +130,9 @@ export async function rebuild(appDir: string, electronVersion: string, arch: str execPath = process.env.npm_node_execpath || process.env.NODE_EXE || "node" } - const env = getGypEnv(electronVersion, arch) + const env = getGypEnv(electronVersion, arch, buildFromSource) if (isYarnPath(execPath)) { execArgs.push("run", "install", "--") - execArgs.push("--disturl=https://atom.io/download/electron") - execArgs.push(`--target=${electronVersion}`) - execArgs.push("--runtime=electron") - execArgs.push(`--arch=${arch}`) execArgs.push(...additionalArgs) await BluebirdPromise.each(nativeDeps, it => spawn(execPath, execArgs, {cwd: it, env: env})) } From 92af26228ea046f6d1f49163a9d0b98a80e3685e Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 30 Nov 2016 10:56:01 +0100 Subject: [PATCH 09/43] feat: ability to disable hard links on a CI server --- src/util/fs.ts | 2 +- test/jestSetup.js | 4 +++- test/src/filesTest.ts | 4 +++- test/src/helpers/runTests.ts | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/util/fs.ts b/src/util/fs.ts index 4d7350c7a0f..78e0eddd64b 100644 --- a/src/util/fs.ts +++ b/src/util/fs.ts @@ -83,7 +83,7 @@ export async function walk(initialDirPath: string, filter?: Filter, consumer?: ( return result } -const _isUseHardLink = process.platform != "win32" && (isCi || process.env.USE_HARD_LINKS === "true") +const _isUseHardLink = process.platform != "win32" && process.env.USE_HARD_LINKS !== "false" && (isCi || process.env.USE_HARD_LINKS === "true") /** * Hard links is used if supported and allowed. diff --git a/test/jestSetup.js b/test/jestSetup.js index d8fc5b7d13d..c9f18698091 100644 --- a/test/jestSetup.js +++ b/test/jestSetup.js @@ -37,4 +37,6 @@ test.ifWinCi = isCi && isWindows ? test : skip delete process.env.CSC_NAME process.env.CSC_IDENTITY_AUTO_DISCOVERY = "false" -process.env.USE_HARD_LINKS = "true" \ No newline at end of file +if (!process.env.USE_HARD_LINKS) { + process.env.USE_HARD_LINKS = "true" +} \ No newline at end of file diff --git a/test/src/filesTest.ts b/test/src/filesTest.ts index fe6e3d805be..f3cc26c5eac 100644 --- a/test/src/filesTest.ts +++ b/test/src/filesTest.ts @@ -1,5 +1,5 @@ import { expectedWinContents } from "./helpers/expectedContents" -import { outputFile, stat } from "fs-extra-p" +import { outputFile, stat, readFile } from "fs-extra-p" import { assertPack, modifyPackageJson, getPossiblePlatforms, app } from "./helpers/packTester" import BluebirdPromise from "bluebird-lst-c" import * as path from "path" @@ -170,6 +170,8 @@ test("extraResources - one-package", () => { allCan(path.join(resourcesDir, "bar", "hello.txt"), false), ]) + + expect(await readFile(path.join(resourcesDir, "bar", "hello.txt"), "utf-8")).toEqual("data") }, expectedContents: platform === Platform.WINDOWS ? pathSorter(expectedWinContents.concat( winDirPrefix + "bar/hello.txt", diff --git a/test/src/helpers/runTests.ts b/test/src/helpers/runTests.ts index 20129cd283d..c00969f2f3b 100755 --- a/test/src/helpers/runTests.ts +++ b/test/src/helpers/runTests.ts @@ -98,7 +98,10 @@ async function runTests() { const circleNodeIndex = parseInt(process.env.CIRCLE_NODE_INDEX, 10) if (circleNodeIndex === 0 || circleNodeIndex === 2) { skipWin = true - args.push("linux.*", "BuildTest.js", "extraMetadataTest.js", "mainEntryTest.js", "globTest.js", "filesTest.js", "ignoreTest.js", "nsisUpdaterTest") + args.push("linux.*", "BuildTest.js", "extraMetadataTest.js", "mainEntryTest.js", "globTest.js", "filesTest.js", "ignoreTest.js") + if (circleNodeIndex === 0) { + args.push("nsisUpdaterTest") + } } else { args.push("windows.*", "mac.*") From da16181cba34aee7aa4c1e213b555e7e74b0596a Mon Sep 17 00:00:00 2001 From: Marcel Link Date: Wed, 30 Nov 2016 17:27:38 +0100 Subject: [PATCH 10/43] fix: wine version processing (#955) --- src/packager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/packager.ts b/src/packager.ts index 9f213a34f98..a9e4ea0b7d6 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -305,6 +305,10 @@ async function checkWineVersion(checkPromise: Promise) { wineVersion = wineVersion.substring("wine-".length) } + if (wineVersion.split(" ").length > 1) { + wineVersion = wineVersion.split(" ")[0] + } + if (wineVersion.split(".").length === 2) { wineVersion += ".0" } From c2eb8c225c366966cd1aaad0a46a2a16b117700d Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 30 Nov 2016 19:25:07 +0100 Subject: [PATCH 11/43] feat: win code sign timestamp server option Closes #951 --- docs/Multi Platform Build.md | 2 +- docs/Options.md | 1 + src/options/winOptions.ts | 5 +++++ src/packager.ts | 7 ++++--- src/winPackager.ts | 3 +-- src/windowsCodeSign.ts | 13 +++++++------ 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/Multi Platform Build.md b/docs/Multi Platform Build.md index be04daa33ec..766caf8b83c 100755 --- a/docs/Multi Platform Build.md +++ b/docs/Multi Platform Build.md @@ -81,4 +81,4 @@ dist: trusty ## Windows -Use [Docker](https://github.com/electron-userland/electron-builder/wiki/Docker). +Please use [Docker](https://github.com/electron-userland/electron-builder/wiki/Docker). diff --git a/docs/Options.md b/docs/Options.md index c8e1d263059..9b62d1e9e39 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -278,6 +278,7 @@ Windows specific build options. | certificatePassword |

The password to the certificate provided in certificateFile. Please use it only if you cannot use env variable CSC_KEY_PASSWORD (WIN_CSC_KEY_PASSWORD) for some reason. Please see [Code Signing](https://github.com/electron-userland/electron-builder/wiki/Code-Signing).

| certificateSubjectName | The name of the subject of the signing certificate. Required only for EV Code Signing and works only on Windows. | rfc3161TimeStampServer | The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`. +| timeStampServer | The URL of the time stamp server. Defaults to `http://timestamp.verisign.com/scripts/timstamp.dll`. ## `.directories` diff --git a/src/options/winOptions.ts b/src/options/winOptions.ts index b5f1f7e17e2..da1b34d1f76 100644 --- a/src/options/winOptions.ts +++ b/src/options/winOptions.ts @@ -51,6 +51,11 @@ export interface WinBuildOptions extends PlatformSpecificBuildOptions { The URL of the RFC 3161 time stamp server. Defaults to `http://timestamp.comodoca.com/rfc3161`. */ readonly rfc3161TimeStampServer?: string + + /* + The URL of the time stamp server. Defaults to `http://timestamp.verisign.com/scripts/timstamp.dll`. + */ + readonly timeStampServer?: string } /* diff --git a/src/packager.ts b/src/packager.ts index a9e4ea0b7d6..a734aa9277d 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -285,7 +285,7 @@ function checkConflictingOptions(options: any) { async function checkWineVersion(checkPromise: Promise) { function wineError(prefix: string): string { - return `${prefix}, please see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build#${(process.platform === "linux" ? "linux" : "os-x")}` + return `${prefix}, please see https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build#${(process.platform === "linux" ? "linux" : "macos")}` } let wineVersion: string @@ -305,8 +305,9 @@ async function checkWineVersion(checkPromise: Promise) { wineVersion = wineVersion.substring("wine-".length) } - if (wineVersion.split(" ").length > 1) { - wineVersion = wineVersion.split(" ")[0] + const spaceIndex = wineVersion.indexOf(" ") + if (spaceIndex > 0) { + wineVersion = wineVersion.substring(0, spaceIndex) } if (wineVersion.split(".").length === 2) { diff --git a/src/winPackager.ts b/src/winPackager.ts index fd1f38d130e..2a5043cf7e4 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -134,8 +134,7 @@ export class WinPackager extends PlatformPackager { password: cscInfo.password, name: this.appInfo.productName, site: await this.appInfo.computePackageUrl(), - hash: this.platformSpecificBuildOptions.signingHashAlgorithms, - tr: this.platformSpecificBuildOptions.rfc3161TimeStampServer, + options: this.platformSpecificBuildOptions, }) } diff --git a/src/windowsCodeSign.ts b/src/windowsCodeSign.ts index b4705e7386f..8764d3ed601 100644 --- a/src/windowsCodeSign.ts +++ b/src/windowsCodeSign.ts @@ -4,10 +4,12 @@ import * as path from "path" import { release } from "os" import { getBinFromBintray } from "./util/binDownload" import isCi from "is-ci" +import { WinBuildOptions } from "./options/winOptions" const TOOLS_VERSION = "1.5.0" export function getSignVendorPath() { + //noinspection SpellCheckingInspection return getBinFromBintray("winCodeSign", TOOLS_VERSION, "5febefb4494f0f62f0f5c0cd6408f0930caf5943ccfeea2bbf90d2eeb34c571d") } @@ -20,13 +22,12 @@ export interface SignOptions { readonly name?: string | null readonly password?: string | null readonly site?: string | null - readonly hash?: Array | null - readonly tr?: string | null + readonly options: WinBuildOptions } export async function sign(options: SignOptions) { - let hashes = options.hash + let hashes = options.options.signingHashAlgorithms // msi does not support dual-signing if (options.path.endsWith(".msi")) { hashes = [hashes != null && !hashes.includes("sha1") ? "sha256" : "sha1"] @@ -47,7 +48,7 @@ export async function sign(options: SignOptions) { let nest = false //noinspection JSUnusedAssignment let outputPath = "" - for (let hash of hashes) { + for (const hash of hashes) { outputPath = isWin ? options.path : getOutputPath(options.path, hash) await spawnSign(options, options.path, outputPath, hash, nest) nest = true @@ -63,9 +64,9 @@ async function spawnSign(options: SignOptions, inputPath: string, outputPath: st const args = isWin ? ["sign"] : ["-in", inputPath, "-out", outputPath] if (process.env.ELECTRON_BUILDER_OFFLINE !== "true") { - const timestampingServiceUrl = "http://timestamp.verisign.com/scripts/timstamp.dll" + const timestampingServiceUrl = options.options.timeStampServer || "http://timestamp.verisign.com/scripts/timstamp.dll" if (isWin) { - args.push(nest || hash === "sha256" ? "/tr" : "/t", nest || hash === "sha256" ? (options.tr || "http://timestamp.comodoca.com/rfc3161") : timestampingServiceUrl) + args.push(nest || hash === "sha256" ? "/tr" : "/t", nest || hash === "sha256" ? (options.options.rfc3161TimeStampServer || "http://timestamp.comodoca.com/rfc3161") : timestampingServiceUrl) } else { args.push("-t", timestampingServiceUrl) From d289f4b0f09d090bf6883ada661fcf8e1c0559f2 Mon Sep 17 00:00:00 2001 From: develar Date: Thu, 1 Dec 2016 08:57:12 +0100 Subject: [PATCH 12/43] fix(mac): build.fileAssociations icon links to original file in output Closes #954 --- .gitignore | 23 +++++++++--- .idea/inspectionProfiles/Project_Default.xml | 5 +++ package.json | 4 +-- src/packager/mac.ts | 16 ++++++--- .../mac/__snapshots__/macPackagerTest.js.snap | 35 +++++++++++++++++++ test/src/helpers/packTester.ts | 9 +++-- test/src/mac/macPackagerTest.ts | 29 +++++++++++++-- yarn.lock | 22 ++++++------ 8 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 test/out/mac/__snapshots__/macPackagerTest.js.snap diff --git a/.gitignore b/.gitignore index 6cd89979bf8..8f131770e95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,31 @@ node_modules/ *.log -out/ + +#**/out/**/* +#!**/*js.snap + dist/ + /.idea/compiler.xml /.idea/encodings.xml -/.idea/copyright/profiles_settings.xml /.idea/workspace.xml +/.idea/copyright/ /.idea/deployment.xml +/.idea/shelf/ + +/docs/.idea/ + /typings/browser/ /typings/browser.d.ts /typings/main.d.ts + .DS_Store -.idea/shelf/ + /test/typings/electron-builder.d.ts -/test/typings/electron-auto-updater.d.ts \ No newline at end of file +/test/typings/electron-auto-updater.d.ts + +/nsis-auto-updater/out/ +/out/ +# to not exclude .js.snap (jest snapshots) +/test/out/**/*.js +/test/out/**/*.map \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index a4ab52713ac..3d4082833f6 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -3,8 +3,13 @@