From 3f97b86993d4ea5172e562b182230a194de0f621 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sun, 27 Nov 2016 09:03:00 +0100 Subject: [PATCH] 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"