diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index fcc6c3bbde9..9dd44fdb82c 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -37,6 +37,7 @@ lzma lzop makedeb + minimatch mkdirp mpass multilib diff --git a/.idea/runConfigurations/BuildTest.xml b/.idea/runConfigurations/BuildTest.xml index 0af9c0cc35b..a8e2d4f3070 100644 --- a/.idea/runConfigurations/BuildTest.xml +++ b/.idea/runConfigurations/BuildTest.xml @@ -1,5 +1,5 @@ - + diff --git a/.travis.yml b/.travis.yml index b67443c5f67..06a77544fe6 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -osx_image: xcode7 +osx_image: xcode7.3 matrix: include: @@ -13,7 +13,6 @@ language: c cache: directories: - node_modules - - test/testApp/node_modules - $HOME/.electron - $HOME/.cache/fpm @@ -23,6 +22,7 @@ before_install: install: - nvm install $NODE_VERSION +- nvm use --delete-prefix $NODE_VERSION - if [[ "$TRAVIS_OS_NAME" == "osx" && "$NODE_VERSION" == "4" ]]; then npm install npm -g ; fi - npm install - npm prune diff --git a/docs/Options.md b/docs/Options.md index 2d13a61bf04..c79669d28e4 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -54,15 +54,15 @@ Here documented only `electron-builder` specific options: | app-category-type |

*OS X-only.* The application category type, as shown in the Finder via *View -> Arrange by Application Category* when viewing the Applications directory.

For example, app-category-type=public.app-category.developer-tools will set the application category to *Developer Tools*.

Valid values are listed in [Apple’s documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8).

| asar |

Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). 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/latest/tutorial/application-packaging/#limitations-on-node-api/).

Or you can pass object of any asar options.

| productName | See [AppMetadata.productName](#AppMetadata-productName). -| extraResources |

A [glob expression](https://www.npmjs.com/package/glob#glob-primer), when specified, copy the file or directory with matching names directly into the app’s resources directory (Contents/Resources for OS X, resources for Linux/Windows).

You can use ${os} (expanded to osx, linux or win according to current platform) and ${arch} in the pattern.

If directory matched, all contents are copied. So, you can just specify foo to copy <project_dir>/foo directory.

May be specified in the platform options (i.e. in the build.osx).

-| extraFiles | The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for OS X, `` for Linux/Windows). +| 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. Defaults to \*\*\/\* (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).

Development dependencies are never copied in any case. You don’t need to ignore it explicitly.

[Multiple patterns](#multiple-glob-patterns) are supported. You can use ${os} (expanded to osx, linux or win according to current platform) and ${arch} in the pattern. If directory matched, all contents are copied. So, you can just specify foo to copy foo directory.

Remember that default pattern \*\*\/\* is not added to your custom, so, you have to add it explicitly — e.g. ["\*\*\/\*", "!ignoreMe${/\*}"].

May be specified in the platform options (e.g. in the build.osx).

+| 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 OS X, resources for Linux/Windows).

Glob rules the same as for [files](#BuildMetadata-files).

+| extraFiles | The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for OS X, root directory for Linux/Windows). | osx | See [.build.osx](#OsXBuildOptions). | mas | See [.build.mas](#MasBuildOptions). | win | See [.build.win](#LinuxBuildOptions). | linux | See [.build.linux](#LinuxBuildOptions). | compression | The compression level, one of `store`, `normal`, `maximum` (default: `normal`). If you want to rapidly test build, `store` can reduce build time significantly. | afterPack | *programmatic API only* The function to be run after pack (but before pack into distributable format and sign). Promise must be returned. -| npmRebuild | Whether to rebuild native dependencies (`npm rebuild`) before starting to package the app. Defaults to `true`. ### `.build.osx` @@ -107,7 +107,7 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`). | synopsis | *deb-only.* The [short description](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description). | maintainer | The maintainer. Defaults to [author](#AppMetadata-author). | vendor | The vendor. Defaults to [author](#AppMetadata-author). -| compression | *deb-only.* The compression type, one of `gz`, `bzip2`, `xz` (default: `xz`). +| compression | *deb-only.* The compression type, one of `gz`, `bzip2`, `xz`. Defaults to `xz`. | depends | Package dependencies. Defaults to `["libappindicator1", "libnotify-bin"]`. | target |

Target package type: list of default, deb, rpm, freebsd, pacman, p5p, apk, 7z, zip, tar.xz, tar.lz, tar.gz, tar.bz2. Defaults to default (deb).

Only deb is tested. Feel free to file issues for rpm and other package formats.

@@ -115,8 +115,28 @@ MAS (Mac Application Store) specific options (in addition to `build.osx`). ## `.directories` | Name | Description | --- | --- -| buildResources | The path to build resources, default `build`. -| output | The output directory, default `dist`. -| app | The application directory (containing the application package.json), default `app`, `www` or working directory. +| buildResources | The path to build resources, defaults to `build`. +| output | The output directory, defaults to `dist`. +| app | The application directory (containing the application package.json), defaults to `app`, `www` or working directory. + + +# Multiple Glob Patterns + ```js + [ + // match all files + "**/*", + + // except for js files in the foo/ directory + "!foo/*.js", + + // unless it's foo/bar.js + "foo/bar.js", + ] + ``` + +## Excluding directories + +Remember that `!doNotCopyMe/**/*` would match the files *in* the `doNotCopyMe` directory, but not the directory itself, so the [empty directory](https://github.com/gulpjs/gulp/issues/165#issuecomment-32613179) would be created. +Solution — use macro `${/*}`, e.g. `!doNotCopyMe${/*}`. \ No newline at end of file diff --git a/package.json b/package.json index a9a6aaf37ff..7c458049527 100644 --- a/package.json +++ b/package.json @@ -61,18 +61,19 @@ "asar": "^0.11.0", "bluebird": "^3.4.0", "chalk": "^1.1.3", - "compare-versions": "^2.0.1", + "compare-versions": "^2.0.2", "debug": "^2.2.0", "deep-assign": "^2.0.0", "electron-osx-sign-tf": "0.6.0", - "electron-packager-tf": "~7.3.0", + "electron-packager-tf": "~7.3.2", "electron-winstaller-fixed": "~2.9.6", - "fs-extra-p": "^1.0.1", + "fs-extra-p": "^1.0.2", "glob": "^7.0.3", "hosted-git-info": "^2.1.5", "image-size": "^0.5.0", "lodash.template": "^4.2.5", "mime": "^1.3.4", + "minimatch": "^3.0.0", "progress": "^1.1.8", "progress-stream": "^1.2.0", "read-package-json": "^2.0.4", @@ -106,10 +107,10 @@ "pre-git": "^3.8.4", "semantic-release": "^6.3.0", "should": "^9.0.0", - "ts-babel": "^0.8.6", + "ts-babel": "^1.0.2", "tsconfig-glob": "^0.4.3", "tslint": "3.10.0-dev.2", - "typescript": "1.9.0-dev.20160520-1.0", + "typescript": "1.9.0-dev.20160607-1.0", "whitespace": "^2.0.0" }, "babel": { diff --git a/src/globby.ts b/src/globby.ts deleted file mode 100644 index aa0025796b0..00000000000 --- a/src/globby.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Promise as BluebirdPromise } from "bluebird" -import { Glob, Options } from "glob" - -//noinspection JSUnusedLocalSymbols -const __awaiter = require("./awaiter") - -function isNegative(pattern: string): boolean { - return pattern[0] === "!" -} - -function generateGlobTasks(patterns: Array, opts: Options): Array { - opts = Object.assign({ignore: []}, opts) - - const globTasks: Array = [] - patterns.forEach(function (pattern, i) { - if (isNegative(pattern)) { - return - } - - const ignore = patterns.slice(i).filter(isNegative).map(it => it.slice(1)) - globTasks.push({ - pattern: pattern, - opts: Object.assign({}, opts, { - ignore: (>opts.ignore).concat(ignore) - }) - }) - }) - return globTasks -} - -export function globby(patterns: Array, opts: Options): Promise> { - let firstGlob: Glob | null = null - return BluebirdPromise - .map(generateGlobTasks(patterns, opts), task => new BluebirdPromise((resolve, reject) => { - let glob = new Glob(task.pattern, task.opts, (error, matches) => { - if (error == null) { - resolve(matches) - } - else { - reject(error) - } - }) - - if (firstGlob == null) { - firstGlob = glob - } - else { - glob.statCache = firstGlob.statCache - glob.symlinks = firstGlob.symlinks - glob.realpathCache = firstGlob.realpathCache - glob.cache = firstGlob.cache - } - })) - .then(it => new Set([].concat(...it))) -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0ff481389b9..3779277901f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ export { Packager } from "./packager" export { PackagerOptions, ArtifactCreated, DIR_TARGET, BuildInfo } from "./platformPackager" export { BuildOptions, build, createPublisher, CliOptions, createTargets } from "./builder" export { PublishOptions, Publisher } from "./gitHubPublisher" -export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions } from "./metadata" \ No newline at end of file +export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions, CompressionLevel } from "./metadata" \ No newline at end of file diff --git a/src/linuxPackager.ts b/src/linuxPackager.ts index 9db79b78140..8fcf596bb5f 100755 --- a/src/linuxPackager.ts +++ b/src/linuxPackager.ts @@ -48,7 +48,7 @@ export class LinuxPackager extends PlatformPackager { } } - protected get supportedTargets(): Array { + get supportedTargets(): Array { return ["deb", "rpm", "sh", "freebsd", "pacman", "apk", "p5p"] } @@ -66,7 +66,7 @@ export class LinuxPackager extends PlatformPackager { promises.push(this.computeDesktop(tempDir)) - return [].concat(...await BluebirdPromise.all(promises)) + return Array.prototype.concat.apply([], await BluebirdPromise.all(promises)) } async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { diff --git a/src/metadata.ts b/src/metadata.ts index 6d7afb12815..06c7fa912a3 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -74,6 +74,8 @@ export interface AuthorMetadata { readonly email: string } +export type CompressionLevel = "store" | "normal" | "maximum" + /* ## `.build` */ @@ -108,20 +110,30 @@ export interface BuildMetadata { readonly productName?: string | null /** - A [glob expression](https://www.npmjs.com/package/glob#glob-primer), when specified, copy the file or directory with matching names directly into the app's resources directory (`Contents/Resources` for OS X, `resources` for Linux/Windows). + A [glob patterns](https://www.npmjs.com/package/glob#glob-primer) relative to the [app directory](#MetadataDirectories-app), which specifies which files to include when copying files to create the package. Defaults to `\*\*\/\*` (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)). + + Development dependencies are never copied in any case. You don't need to ignore it explicitly. - You can use `${os}` (expanded to osx, linux or win according to current platform) and `${arch}` in the pattern. + [Multiple patterns](#multiple-glob-patterns) are supported. You can use `${os}` (expanded to osx, linux or win according to current platform) and `${arch}` in the pattern. + If directory matched, all contents are copied. So, you can just specify `foo` to copy `foo` directory. - 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${/\*}"]`. - May be specified in the platform options (i.e. in the `build.osx`). + May be specified in the platform options (e.g. in the `build.osx`). */ - readonly extraResources?: Array | null + readonly files?: Array | string | null /** - The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for OS X, `` for Linux/Windows). + 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 OS X, `resources` for Linux/Windows). + + Glob rules the same as for [files](#BuildMetadata-files). */ - readonly extraFiles?: Array | null + readonly extraResources?: Array | string | null + + /** + The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for OS X, root directory for Linux/Windows). + */ + readonly extraFiles?: Array | string | null /* See [.build.osx](#OsXBuildOptions). @@ -146,7 +158,7 @@ export interface BuildMetadata { /* The compression level, one of `store`, `normal`, `maximum` (default: `normal`). If you want to rapidly test build, `store` can reduce build time significantly. */ - readonly compression?: "store" | "normal" | "maximum" | null + readonly compression?: CompressionLevel | null readonly "build-version"?: string | null @@ -155,8 +167,16 @@ export interface BuildMetadata { */ readonly afterPack?: (context: AfterPackContext) => Promise | null + // /* + // Whether to [prune](https://docs.npmjs.com/cli/prune) dependencies (`npm prune --production`) before starting to package the app. + // Defaults to `false`. + // */ + // readonly npmPrune?: boolean + // deprecated + // readonly prune?: boolean + /* - Whether to rebuild native dependencies (`npm rebuild`) before starting to package the app. Defaults to `true`. + Whether to [rebuild](https://docs.npmjs.com/cli/rebuild) native dependencies (`npm rebuild`) before starting to package the app. Defaults to `true`. */ readonly npmRebuild?: boolean } @@ -307,7 +327,7 @@ export interface LinuxBuildOptions extends PlatformSpecificBuildOptions { afterRemove?: string | null /* - *deb-only.* The compression type, one of `gz`, `bzip2`, `xz` (default: `xz`). + *deb-only.* The compression type, one of `gz`, `bzip2`, `xz`. Defaults to `xz`. */ readonly compression?: string | null @@ -329,22 +349,23 @@ export interface LinuxBuildOptions extends PlatformSpecificBuildOptions { */ export interface MetadataDirectories { /* - The path to build resources, default `build`. + The path to build resources, defaults to `build`. */ readonly buildResources?: string | null /* - The output directory, default `dist`. + The output directory, defaults to `dist`. */ readonly output?: string | null /* - The application directory (containing the application package.json), default `app`, `www` or working directory. + The application directory (containing the application package.json), defaults to `app`, `www` or working directory. */ readonly app?: string | null } export interface PlatformSpecificBuildOptions { + readonly files?: Array | null readonly extraFiles?: Array | null readonly extraResources?: Array | null @@ -354,9 +375,9 @@ export interface PlatformSpecificBuildOptions { } export class Platform { - public static OSX = new Platform("osx", "osx", "darwin") - public static LINUX = new Platform("linux", "linux", "linux") - public static WINDOWS = new Platform("windows", "win", "win32") + static OSX = new Platform("osx", "osx", "darwin") + static LINUX = new Platform("linux", "linux", "linux") + static WINDOWS = new Platform("windows", "win", "win32") constructor(public name: string, public buildConfigurationKey: string, public nodeName: string) { } @@ -369,19 +390,19 @@ export class Platform { return this.name } - public createTarget(type?: string | null, ...archs: Array): Map>> { + createTarget(type?: string | Array | null, ...archs: Array): Map>> { const archToType = new Map() for (let arch of (archs == null || archs.length === 0 ? [archFromString(process.arch)] : archs)) { - archToType.set(arch, type == null ? [] : [type]) + archToType.set(arch, type == null ? [] : (Array.isArray(type) ? type : [type])) } return new Map([[this, archToType]]) } - public static current(): Platform { + static current(): Platform { return Platform.fromString(process.platform) } - public static fromString(name: string): Platform { + static fromString(name: string): Platform { switch (name) { case Platform.OSX.nodeName: case Platform.OSX.name: diff --git a/src/osxPackager.ts b/src/osxPackager.ts index a60531ba9bc..05717c1661b 100644 --- a/src/osxPackager.ts +++ b/src/osxPackager.ts @@ -30,7 +30,7 @@ export default class OsXPackager extends PlatformPackager { return Platform.OSX } - protected get supportedTargets(): Array { + get supportedTargets(): Array { return ["dmg", "mas"] } @@ -42,7 +42,7 @@ export default class OsXPackager extends PlatformPackager { nonMasPromise = this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions) .then(() => this.sign(appOutDir, null)) .then(() => { - postAsyncTasks.push(this.packageInDistributableFormat(outDir, appOutDir, targets)) + this.packageInDistributableFormat(appOutDir, targets, postAsyncTasks) }) } @@ -215,8 +215,7 @@ export default class OsXPackager extends PlatformPackager { return specification } - protected packageInDistributableFormat(outDir: string, appOutDir: string, targets: Array): Promise { - const promises: Array> = [] + protected packageInDistributableFormat(appOutDir: string, targets: Array, promises: Array>): void { for (let target of targets) { if (target === "dmg" || target === "default") { promises.push(this.createDmg(appOutDir)) @@ -233,7 +232,6 @@ export default class OsXPackager extends PlatformPackager { .then(() => this.dispatchArtifactCreated(outFile, `${this.metadata.name}-${this.metadata.version}-${classifier}.${format}`))) } } - return BluebirdPromise.all(promises) } private async createDmg(appOutDir: string) { diff --git a/src/packager.ts b/src/packager.ts index 05b8b5c5d51..32a11814b25 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -8,7 +8,7 @@ import { EventEmitter } from "events" import { Promise as BluebirdPromise } from "bluebird" import { InfoRetriever } from "./repositoryInfo" import { AppMetadata, DevMetadata, Platform, Arch } from "./metadata" -import { PackagerOptions, PlatformPackager, BuildInfo, ArtifactCreated } from "./platformPackager" +import { PackagerOptions, PlatformPackager, BuildInfo, ArtifactCreated, computeEffectiveTargets, commonTargets } from "./platformPackager" import OsXPackager from "./osxPackager" import { WinPackager } from "./winPackager" import * as errorMessages from "./errorMessages" @@ -96,7 +96,15 @@ export class Packager implements BuildInfo { } // electron-packager uses productName in the directory name - await helper.pack(outDir, arch, helper.computeEffectiveTargets(targets), distTasks)} + const effectiveTargets = computeEffectiveTargets(targets, helper.customBuildOptions.target) + const supportedTargets = helper.supportedTargets.concat(commonTargets) + for (let target of effectiveTargets) { + if (target !== "default" && !supportedTargets.includes(target)) { + throw new Error(`Unknown target: ${target}`) + } + } + await helper.pack(outDir, arch, effectiveTargets, distTasks) + } } return await BluebirdPromise.all(distTasks) diff --git a/src/platformPackager.ts b/src/platformPackager.ts index d391927f25c..262bf124c6c 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -3,31 +3,19 @@ import { AppMetadata, DevMetadata, Platform, PlatformSpecificBuildOptions, getPr import EventEmitter = NodeJS.EventEmitter import { Promise as BluebirdPromise } from "bluebird" import * as path from "path" -import { pack, ElectronPackagerOptions } from "electron-packager-tf" -import { globby } from "./globby" -import { readdir, copy, unlink, lstat, remove } from "fs-extra-p" -import { statOrNull, use, spawn, debug7zArgs, debug, warn, log } from "./util" +import { pack, ElectronPackagerOptions, userIgnoreFilter } from "electron-packager-tf" +import { readdir, copy, unlink, lstat, remove, realpath } from "fs-extra-p" +import { statOrNull, use, warn, log, exec } from "./util" import { Packager } from "./packager" import { listPackage, statFile, AsarFileMetadata, createPackageFromFiles, AsarOptions } from "asar" -import { path7za } from "7zip-bin" -import deepAssign = require("deep-assign") +import { archiveApp } from "./targets/archive" import { Glob } from "glob" +import { Minimatch } from "minimatch" +import deepAssign = require("deep-assign") //noinspection JSUnusedLocalSymbols const __awaiter = require("./awaiter") -class CompressionDescriptor { - constructor(public flag: string, public env: string, public minLevel: string, public maxLevel: string = "-9") { - } -} - -const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = { - "tar.xz": new CompressionDescriptor("--xz", "XZ_OPT", "-0", "-9e"), - "tar.lz": new CompressionDescriptor("--lzip", "LZOP", "-0"), - "tar.gz": new CompressionDescriptor("--gz", "GZIP", "-1"), - "tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"), -} - export const commonTargets = ["dir", "zip", "7z", "tar.xz", "tar.lz", "tar.gz", "tar.bz2"] export const DIR_TARGET = "dir" @@ -73,7 +61,7 @@ export abstract class PlatformPackager protected readonly options: PackagerOptions protected readonly projectDir: string - protected readonly buildResourcesDir: string + readonly buildResourcesDir: string readonly metadata: AppMetadata readonly devMetadata: DevMetadata @@ -86,7 +74,7 @@ export abstract class PlatformPackager public abstract get platform(): Platform - constructor(protected info: BuildInfo) { + constructor(public info: BuildInfo) { this.options = info.options this.projectDir = info.projectDir this.metadata = info.metadata @@ -116,19 +104,6 @@ export abstract class PlatformPackager } } - public computeEffectiveTargets(rawList: Array): Array { - let targets = normalizeTargets(rawList.length === 0 ? this.customBuildOptions.target : rawList) - if (targets != null) { - const supportedTargets = this.supportedTargets.concat(commonTargets) - for (let target of targets) { - if (target !== "default" && !supportedTargets.includes(target)) { - throw new Error(`Unknown target: ${target}`) - } - } - } - return targets == null ? ["default"] : targets - } - protected hasOnlyDirTarget(): boolean { for (let targets of this.options.targets!.get(this.platform)!.values()) { for (let t of targets) { @@ -142,17 +117,17 @@ export abstract class PlatformPackager return targets != null && targets.length === 1 && targets[0] === "dir" } - protected get relativeBuildResourcesDirname() { + get relativeBuildResourcesDirname() { return use(this.devMetadata.directories, it => it!.buildResources) || "build" } - protected abstract get supportedTargets(): Array + abstract get supportedTargets(): Array protected computeAppOutDir(outDir: string, arch: Arch): string { return path.join(outDir, `${this.platform.buildConfigurationKey}${arch === Arch.x64 ? "" : `-${Arch[arch]}`}`) } - protected dispatchArtifactCreated(file: string, artifactName?: string) { + dispatchArtifactCreated(file: string, artifactName?: string) { this.info.eventEmitter.emit("artifactCreated", { file: file, artifactName: artifactName, @@ -163,17 +138,75 @@ export abstract class PlatformPackager abstract pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: Arch, customBuildOptions: DC) { - const asar = options.asar - options.asar = false - await pack(options) - options.asar = asar - const asarOptions = this.computeAsarOptions(customBuildOptions) - if (asarOptions != null) { - await this.createAsarArchive(appOutDir, asarOptions) + options.initializeApp = async (opts, buildDir, appRelativePath) => { + const appPath = path.join(buildDir, appRelativePath) + const resourcesPath = path.dirname(appPath) + + let promise: Promise | null = null + const deprecatedIgnore = (this.devMetadata.build).ignore + if (deprecatedIgnore) { + if (typeof deprecatedIgnore === "function") { + log(`"ignore is specified as function, may be new "files" option will be suit your needs? Please see https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-files`) + } + else { + warn(`"ignore is deprecated, please use "files", see https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-files`) + } + + promise = copy(this.info.appDir, appPath, {filter: userIgnoreFilter(opts), dereference: true}) + } + else { + const ignoreFiles = new Set([path.relative(this.info.appDir, opts.out!), path.relative(this.info.appDir, this.buildResourcesDir)]) + if (!this.info.isTwoPackageJsonProjectLayoutUsed) { + const result = await BluebirdPromise.all([listDependencies(this.info.appDir, false), listDependencies(this.info.appDir, true)]) + const productionDepsSet = new Set(result[1]) + + // npm returns real path, so, we should use relative path to avoid any mismatch + const realAppDirPath = await realpath(this.info.appDir) + + for (let it of result[0]) { + if (!productionDepsSet.has(it)) { + if (it.startsWith(realAppDirPath)) { + it = it.substring(realAppDirPath.length + 1) + } + else if (it.startsWith(this.info.appDir)) { + it = it.substring(this.info.appDir.length + 1) + } + ignoreFiles.add(it) + } + } + } + + let patterns = this.getFilePatterns("files", customBuildOptions) + if (patterns == null || patterns.length === 0) { + patterns = ["**/*"] + } + promise = copyFiltered(this.info.appDir, appPath, this.getParsedPatterns(patterns, arch), true, ignoreFiles) + } + + const promises = [promise] + if (this.info.electronVersion[0] === "0") { + // electron release >= 0.37.4 - the default_app/ folder is a default_app.asar file + promises.push(remove(path.join(resourcesPath, "default_app.asar")), remove(path.join(resourcesPath, "default_app"))) + } + else { + promises.push(unlink(path.join(resourcesPath, "default_app.asar"))) + } + + await BluebirdPromise.all(promises) + + if (opts.prune != null) { + warn("prune is deprecated — development dependencies are never copied in any case") + } + + if (asarOptions != null) { + await this.createAsarArchive(appPath, resourcesPath, asarOptions) + } } + await pack(options) - await this.copyExtraFiles(appOutDir, arch, customBuildOptions) + await this.doCopyExtraFiles(true, appOutDir, arch, customBuildOptions) + await this.doCopyExtraFiles(false, appOutDir, arch, customBuildOptions) const afterPack = this.devMetadata.build.afterPack if (afterPack != null) { @@ -218,14 +251,6 @@ export abstract class PlatformPackager } }, this.devMetadata.build) - if (!this.info.isTwoPackageJsonProjectLayoutUsed && typeof options.ignore !== "function") { - const defaultIgnores = ["/node_modules/electron-builder($|/)", "^/" + path.relative(this.projectDir, this.buildResourcesDir) + "($|/)"] - if (options.ignore != null && !Array.isArray(options.ignore)) { - options.ignore = [options.ignore] - } - options.ignore = options.ignore == null ? defaultIgnores : options.ignore.concat(defaultIgnores) - } - delete options.osx delete options.win delete options.linux @@ -234,15 +259,6 @@ export abstract class PlatformPackager return options } - private getExtraResources(isResources: boolean, arch: Arch, customBuildOptions: DC): Promise> { - let patterns: Array | n = (this.devMetadata.build)[isResources ? "extraResources" : "extraFiles"] - const platformSpecificPatterns = isResources ? customBuildOptions.extraResources : customBuildOptions.extraFiles - if (platformSpecificPatterns != null) { - patterns = patterns == null ? platformSpecificPatterns : patterns.concat(platformSpecificPatterns) - } - return patterns == null ? BluebirdPromise.resolve(new Set()) : globby(this.expandPatterns(patterns, arch), {cwd: this.projectDir}); - } - private computeAsarOptions(customBuildOptions: DC): AsarOptions | null { let result = this.devMetadata.build.asar let platformSpecific = customBuildOptions.asar @@ -273,16 +289,12 @@ export abstract class PlatformPackager } } - private async createAsarArchive(appOutDir: string, options: AsarOptions): Promise { - const src = path.join(this.getResourcesDir(appOutDir), "app") - + private async createAsarArchive(src: string, resourcesPath: string, options: AsarOptions): Promise { + // dot: true as in the asar by default by we use glob default - do not copy hidden files let glob: Glob | null = null const files = (await new BluebirdPromise>((resolve, reject) => { glob = new Glob("**/*", { cwd: src, - // dot: true as in the asar by default - dot: true, - ignore: "**/.DS_Store", }, (error, matches) => { if (error == null) { resolve(matches) @@ -293,43 +305,81 @@ export abstract class PlatformPackager }) })).map(it => path.join(src, it)) + const metadata: { [key: string]: AsarFileMetadata; } = {} + const stats = await BluebirdPromise.map(files, it => { - // const stat = glob!.statCache[it] - // return stat == null ? lstat(it) : stat - // todo check is it safe to reuse glob stat - return lstat(it) + if (glob!.symlinks[it]) { + // asar doesn't use stat for link + metadata[it] = { + type: "link", + } + } + else if (glob!.cache[it] === "FILE") { + const stat = glob!.statCache[it] + return stat == null ? lstat(it) : stat + } + else { + // asar doesn't use stat for dir + metadata[it] = { + type: "directory", + } + } + return null }) - const metadata: { [key: string]: AsarFileMetadata; } = {} for (let i = 0, n = files.length; i < n; i++) { const stat = stats[i] - metadata[files[i]] = { - type: stat.isFile() ? "file" : (stat.isDirectory() ? "directory" : "link"), - stat: stat, + if (stat != null) { + metadata[files[i]] = { + type: "file", + stat: stat, + } } } - await BluebirdPromise.promisify(createPackageFromFiles)(src, path.join(this.getResourcesDir(appOutDir), "app.asar"), files, metadata, options) + await BluebirdPromise.promisify(createPackageFromFiles)(src, path.join(resourcesPath, "app.asar"), files, metadata, options) await remove(src) } - private expandPatterns(list: Array, arch: Arch): Array { - return list.map(it => it + private expandPattern(pattern: string, arch: Arch): string { + return pattern .replace(/\$\{arch}/g, Arch[arch]) - .replace(/\$\{os}/g, this.platform.buildConfigurationKey)) + .replace(/\$\{os}/g, this.platform.buildConfigurationKey) + .replace(/\$\{\/\*}/g, "{,/**/*,/**/.*}") } - protected async copyExtraFiles(appOutDir: string, arch: Arch, customBuildOptions: DC): Promise { - await this.doCopyExtraFiles(true, appOutDir, arch, customBuildOptions) - await this.doCopyExtraFiles(false, appOutDir, arch, customBuildOptions) + private async doCopyExtraFiles(isResources: boolean, appOutDir: string, arch: Arch, customBuildOptions: DC): Promise { + const base = isResources ? this.getResourcesDir(appOutDir) : this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`, "Contents") : appOutDir + const patterns = this.getFilePatterns(isResources ? "extraResources" : "extraFiles", customBuildOptions) + return patterns == null || patterns.length === 0 ? null : copyFiltered(this.projectDir, base, this.getParsedPatterns(patterns, arch)) } - private async doCopyExtraFiles(isResources: boolean, appOutDir: string, arch: Arch, customBuildOptions: DC): Promise> { - const base = isResources ? this.getResourcesDir(appOutDir) : this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`, "Contents") : appOutDir - return await BluebirdPromise.map(await this.getExtraResources(isResources, arch, customBuildOptions), it => copy(path.join(this.projectDir, it), path.join(base, it))) + private getParsedPatterns(patterns: Array, arch: Arch): Array { + const minimatchOptions = {} + const parsedPatterns: Array = [] + for (let i = 0; i < patterns.length; i++) { + parsedPatterns[i] = new Minimatch(this.expandPattern(patterns[i], arch), minimatchOptions) + } + return parsedPatterns + } + + private getFilePatterns(name: "files" | "extraFiles" | "extraResources", customBuildOptions: DC): Array | n { + let patterns: Array | string | n = (this.devMetadata.build)[name] + if (patterns != null && !Array.isArray(patterns)) { + patterns = [patterns] + } + + let platformSpecificPatterns: Array | string | n = (customBuildOptions)[name] + if (platformSpecificPatterns != null) { + if (!Array.isArray(platformSpecificPatterns)) { + platformSpecificPatterns = [platformSpecificPatterns] + } + return patterns == null ? platformSpecificPatterns : Array.from(new Set(patterns.concat(platformSpecificPatterns))) + } + return patterns } - protected async computePackageUrl(): Promise { + async computePackageUrl(): Promise { const url = this.metadata.homepage || this.devMetadata.homepage if (url != null) { return url @@ -405,66 +455,7 @@ export abstract class PlatformPackager } protected async archiveApp(format: string, appOutDir: string, outFile: string): Promise { - const compression = this.devMetadata.build.compression - const storeOnly = compression === "store" - - const dirToArchive = this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`) : appOutDir - if (format.startsWith("tar.")) { - // we don't use 7z here - develar: I spent a lot of time making pipe working - but it works on OS X and often hangs on Linux (even if use pipe-io lib) - // and in any case it is better to use system tools (in the light of docker - it is not problem for user because we provide complete docker image). - const info = extToCompressionDescriptor[format] - let tarEnv = process.env - if (compression != null && compression !== "normal") { - tarEnv = Object.assign({}, process.env) - tarEnv[info.env] = storeOnly ? info.minLevel : info.maxLevel - } - - await spawn(process.platform === "darwin" || process.platform === "freebsd" ? "gtar" : "tar", [info.flag, "--transform", `s,^\.,${path.basename(outFile, "." + format)},`, "-cf", outFile, "."], { - cwd: dirToArchive, - stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], - env: tarEnv - }) - return - } - - const args = debug7zArgs("a") - if (compression === "maximum") { - if (format === "7z" || format.endsWith(".7z")) { - args.push("-mx=9", "-mfb=64", "-md=32m", "-ms=on") - } - else if (format === "zip") { - // http://superuser.com/a/742034 - //noinspection SpellCheckingInspection - args.push("-mfb=258", "-mpass=15") - } - else { - args.push("-mx=9") - } - } - else if (storeOnly) { - if (format !== "zip") { - args.push("-mx=1") - } - } - - // remove file before - 7z doesn't overwrite file, but update - try { - await unlink(outFile) - } - catch (e) { - // ignore - } - - if (format === "zip" || storeOnly) { - args.push("-mm=" + (storeOnly ? "Copy" : "Deflate")) - } - - args.push(outFile, dirToArchive) - - await spawn(path7za, args, { - cwd: path.dirname(dirToArchive), - stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], - }) + return archiveApp(this.devMetadata.build.compression, format, outFile, this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`) : appOutDir) } } @@ -501,3 +492,82 @@ export function smarten(s: string): string { s = s.replace(/"/g, "\u201d") return s } + +// https://github.com/joshwnj/minimatch-all/blob/master/index.js +function minimatchAll(path: string, patterns: Array): boolean { + let match = false + for (let 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) { + continue + } + + // partial match — pattern: foo/bar.txt path: foo — we must allow foo + // use it only for non-negate patterns: const m = new Minimatch("!node_modules/@(electron-download|electron-prebuilt)/**/*", {dot: true }); m.match("node_modules", true) will return false, but must be true + match = pattern.match(path, !pattern.negate) + if (!match && !pattern.negate) { + const rawPattern = pattern.pattern + // 1 - slash + const patternLengthPlusSlash = rawPattern.length + 1 + if (path.length > patternLengthPlusSlash) { + // foo: include all directory content + match = path[rawPattern.length] === "/" && path.startsWith(rawPattern) + } + } + } + return match +} + +// we use relative path to avoid canonical path issue - e.g. /tmp vs /private/tmp +function copyFiltered(src: string, destination: string, patterns: Array, dereference: boolean = false, ignoreFiles?: Set): Promise { + return copy(src, destination, { + dereference: dereference, + filter: it => { + if (src === it) { + return true + } + let relative = it.substring(src.length + 1) + + // yes, check before path sep normalization + if (ignoreFiles != null && ignoreFiles.has(relative)) { + return false + } + + if (path.sep === "\\") { + relative = relative.replace(/\\/g, "/") + } + return minimatchAll(relative, patterns) + } + }) +} + +export function computeEffectiveTargets(rawList: Array, targetsFromMetadata: Array | n): Array { + let targets = normalizeTargets(rawList.length === 0 ? targetsFromMetadata : rawList) + return targets == null ? ["default"] : targets +} + +async function listDependencies(appDir: string, production: boolean): Promise> { + let npmExecPath = process.env.npm_execpath || process.env.NPM_CLI_JS + const npmExecArgs = ["ls", production ? "--production" : "--dev", "--parseable"] + if (npmExecPath == null) { + npmExecPath = process.platform === "win32" ? "npm.cmd" : "npm" + } + else { + npmExecArgs.unshift(npmExecPath) + npmExecPath = process.env.npm_node_execpath || process.env.NODE_EXE || "node" + } + + const result = (await exec(npmExecPath, npmExecArgs, { + cwd: appDir, + stdio: "inherit", + maxBuffer: 1024 * 1024, + })).trim().split("\n") + if (result.length > 0 && !result[0].includes("/node_modules/")) { + // first line is a project dir + const lastIndex = result.length - 1 + result[0] = result[lastIndex] + result.length = result.length - 1 + } + return result +} \ No newline at end of file diff --git a/src/targets/archive.ts b/src/targets/archive.ts new file mode 100644 index 00000000000..e3bec2a4b3b --- /dev/null +++ b/src/targets/archive.ts @@ -0,0 +1,81 @@ +import { spawn, debug, debug7zArgs } from "../util" +import { CompressionLevel } from "../metadata" +import * as path from "path" +import { unlink } from "fs-extra-p" +import { path7za } from "7zip-bin" + +//noinspection JSUnusedLocalSymbols +const __awaiter = require("../awaiter") + +class CompressionDescriptor { + constructor(public flag: string, public env: string, public minLevel: string, public maxLevel: string = "-9") { + } +} + +const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = { + "tar.xz": new CompressionDescriptor("--xz", "XZ_OPT", "-0", "-9e"), + "tar.lz": new CompressionDescriptor("--lzip", "LZOP", "-0"), + "tar.gz": new CompressionDescriptor("--gz", "GZIP", "-1"), + "tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"), +} + +export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string): Promise { + const storeOnly = compression === "store" + + if (format.startsWith("tar.")) { + // we don't use 7z here - develar: I spent a lot of time making pipe working - but it works on OS X and often hangs on Linux (even if use pipe-io lib) + // and in any case it is better to use system tools (in the light of docker - it is not problem for user because we provide complete docker image). + const info = extToCompressionDescriptor[format] + let tarEnv = process.env + if (compression != null && compression !== "normal") { + tarEnv = Object.assign({}, process.env) + tarEnv[info.env] = storeOnly ? info.minLevel : info.maxLevel + } + + await spawn(process.platform === "darwin" || process.platform === "freebsd" ? "gtar" : "tar", [info.flag, "--transform", `s,^\.,${path.basename(outFile, "." + format)},`, "-cf", outFile, "."], { + cwd: dirToArchive, + stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], + env: tarEnv + }) + return + } + + const args = debug7zArgs("a") + if (compression === "maximum") { + if (format === "7z" || format.endsWith(".7z")) { + args.push("-mx=9", "-mfb=64", "-md=32m", "-ms=on") + } + else if (format === "zip") { + // http://superuser.com/a/742034 + //noinspection SpellCheckingInspection + args.push("-mfb=258", "-mpass=15") + } + else { + args.push("-mx=9") + } + } + else if (storeOnly) { + if (format !== "zip") { + args.push("-mx=1") + } + } + + // remove file before - 7z doesn't overwrite file, but update + try { + await unlink(outFile) + } + catch (e) { + // ignore + } + + if (format === "zip" || storeOnly) { + args.push("-mm=" + (storeOnly ? "Copy" : "Deflate")) + } + + args.push(outFile, dirToArchive) + + await spawn(path7za, args, { + cwd: path.dirname(dirToArchive), + stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], + }) +} \ No newline at end of file diff --git a/src/targets/squirrelWindows.ts b/src/targets/squirrelWindows.ts new file mode 100644 index 00000000000..70b56714618 --- /dev/null +++ b/src/targets/squirrelWindows.ts @@ -0,0 +1,122 @@ +import { WinPackager } from "../winPackager" +import { getArchSuffix, smarten } from "../platformPackager" +import { ElectronPackagerOptions } from "electron-packager-tf" +import { Arch, WinBuildOptions } from "../metadata" +import { createWindowsInstaller, convertVersion } from "electron-winstaller-fixed" +import * as path from "path" +import { warn } from "../util" +import { emptyDir } from "fs-extra-p" + +//noinspection JSUnusedLocalSymbols +const __awaiter = require("../awaiter") + +export default class SquirrelWindowsTarget { + constructor(private packager: WinPackager, private appOutDir: string, private arch: Arch) { + } + + async build(packOptions: ElectronPackagerOptions) { + const version = this.packager.metadata.version + const archSuffix = getArchSuffix(this.arch) + const setupExeName = `${this.packager.appName} Setup ${version}${archSuffix}.exe` + + const installerOutDir = path.join(this.appOutDir, "..", `win${getArchSuffix(this.arch)}`) + await emptyDir(installerOutDir) + + const distOptions = await this.computeEffectiveDistOptions(installerOutDir, packOptions, setupExeName) + await createWindowsInstaller(distOptions) + this.packager.dispatchArtifactCreated(path.join(installerOutDir, setupExeName), `${this.packager.metadata.name}-Setup-${version}${archSuffix}.exe`) + + const packagePrefix = `${this.packager.metadata.name}-${convertVersion(version)}-` + this.packager.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}full.nupkg`)) + if (distOptions.remoteReleases != null) { + this.packager.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}delta.nupkg`)) + } + + this.packager.dispatchArtifactCreated(path.join(installerOutDir, "RELEASES")) + } + + async computeEffectiveDistOptions(installerOutDir: string, packOptions: ElectronPackagerOptions, setupExeName: string): Promise { + const packager = this.packager + let iconUrl = packager.customBuildOptions.iconUrl || packager.devMetadata.build.iconUrl + if (iconUrl == null) { + if (packager.info.repositoryInfo != null) { + const info = await packager.info.repositoryInfo.getInfo(packager) + if (info != null) { + iconUrl = `https://github.com/${info.user}/${info.project}/blob/master/${packager.relativeBuildResourcesDirname}/icon.ico?raw=true` + } + } + + if (iconUrl == null) { + throw new Error("iconUrl is not specified, please see https://github.com/electron-userland/electron-builder/wiki/Options#WinBuildOptions-iconUrl") + } + } + + checkConflictingOptions(packager.customBuildOptions) + + const projectUrl = await packager.computePackageUrl() + const rceditOptions = { + "version-string": packOptions["version-string"], + "file-version": packOptions["build-version"], + "product-version": packOptions["app-version"], + } + rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"] + + const cscInfo = await packager.cscInfo + const options: any = Object.assign({ + name: packager.metadata.name, + productName: packager.appName, + exe: packager.appName + ".exe", + setupExe: setupExeName, + title: packager.appName, + appDirectory: this.appOutDir, + outputDirectory: installerOutDir, + version: packager.metadata.version, + description: smarten(packager.metadata.description), + authors: packager.metadata.author.name, + iconUrl: iconUrl, + setupIcon: await packager.iconPath, + certificateFile: cscInfo == null ? null : cscInfo.file, + certificatePassword: cscInfo == null ? null : cscInfo.password, + fixUpPaths: false, + skipUpdateIcon: true, + usePackageJson: false, + extraMetadataSpecs: projectUrl == null ? null : `\n ${projectUrl}`, + copyright: packOptions["app-copyright"], + packageCompressionLevel: packager.devMetadata.build.compression === "store" ? 0 : 9, + sign: { + name: packager.appName, + site: projectUrl, + overwrite: true, + hash: packager.customBuildOptions.signingHashAlgorithms, + }, + rcedit: rceditOptions, + }, packager.customBuildOptions) + + if (!("loadingGif" in options)) { + const resourceList = await packager.resourceList + if (resourceList.includes("install-spinner.gif")) { + options.loadingGif = path.join(packager.buildResourcesDir, "install-spinner.gif") + } + } + + return options + } +} + +function checkConflictingOptions(options: any) { + for (let name of ["outputDirectory", "appDirectory", "exe", "fixUpPaths", "usePackageJson", "extraFileSpecs", "extraMetadataSpecs", "skipUpdateIcon", "setupExe"]) { + if (name in options) { + throw new Error(`Option ${name} is ignored, do not specify it.`) + } + } + + if ("noMsi" in options) { + warn(`noMsi is deprecated, please specify as "msi": true if you want to create an MSI installer`) + options.msi = !options.noMsi + } + + const msi = options.msi + if (msi != null && typeof msi !== "boolean") { + throw new Error(`msi expected to be boolean value, but string '"${msi}"' was specified`) + } +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index 87cd7f9b039..9fa01925a4c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -25,17 +25,20 @@ const DEFAULT_APP_DIR_NAMES = ["app", "www"] export const readPackageJson = BluebirdPromise.promisify(readPackageJsonAsync) export function installDependencies(appDir: string, electronVersion: string, arch: string = process.arch, command: string = "install"): BluebirdPromise { - log((command === "install" ? "Installing" : "Rebuilding") + " app dependencies for arch %s to %s", arch, appDir) + log(`${(command === "install" ? "Installing" : "Rebuilding")} app dependencies for arch ${arch} to ${appDir}`) const gypHome = path.join(os.homedir(), ".electron-gyp") - const env = Object.assign({}, process.env, { - npm_config_disturl: "https://atom.io/download/atom-shell", - npm_config_target: electronVersion, - npm_config_runtime: "electron", - npm_config_arch: arch, - HOME: gypHome, - USERPROFILE: gypHome, - }) + return spawnNpmProduction(command, appDir, Object.assign({}, process.env, { + npm_config_disturl: "https://atom.io/download/atom-shell", + npm_config_target: electronVersion, + npm_config_runtime: "electron", + npm_config_arch: arch, + HOME: gypHome, + USERPROFILE: gypHome, + }) + ) +} +export function spawnNpmProduction(command: string, appDir: string, env?: any): BluebirdPromise { let npmExecPath = process.env.npm_execpath || process.env.NPM_CLI_JS const npmExecArgs = [command, "--production"] if (npmExecPath == null) { @@ -49,7 +52,7 @@ export function installDependencies(appDir: string, electronVersion: string, arc return spawn(npmExecPath, npmExecArgs, { cwd: appDir, stdio: "inherit", - env: env + env: env || process.env }) } diff --git a/src/winPackager.ts b/src/winPackager.ts index 9ac2ce19ee6..b5d3b94294b 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -1,25 +1,26 @@ import { downloadCertificate } from "./codeSign" import { Promise as BluebirdPromise } from "bluebird" -import { PlatformPackager, BuildInfo, smarten, getArchSuffix } from "./platformPackager" +import { PlatformPackager, BuildInfo, getArchSuffix } from "./platformPackager" import { Platform, WinBuildOptions, Arch } from "./metadata" import * as path from "path" import { log, warn } from "./util" -import { deleteFile, emptyDir, open, close, read } from "fs-extra-p" +import { deleteFile, open, close, read } from "fs-extra-p" import { sign, SignOptions } from "signcode-tf" import { ElectronPackagerOptions } from "electron-packager-tf" +import SquirrelWindowsTarget from "./targets/squirrelWindows" //noinspection JSUnusedLocalSymbols const __awaiter = require("./awaiter") -interface FileCodeSigningInfo { +export interface FileCodeSigningInfo { readonly file: string readonly password?: string | null } export class WinPackager extends PlatformPackager { - private readonly cscInfo: Promise + readonly cscInfo: Promise - private readonly iconPath: Promise + readonly iconPath: Promise constructor(info: BuildInfo, cleanupTasks: Array<() => Promise>) { super(info) @@ -53,8 +54,8 @@ export class WinPackager extends PlatformPackager { return Platform.WINDOWS } - protected get supportedTargets(): Array { - return [] + get supportedTargets(): Array { + return ["squirrel"] } private async getValidIconPath(): Promise { @@ -74,29 +75,15 @@ export class WinPackager extends PlatformPackager { const appOutDir = this.computeAppOutDir(outDir, arch) const packOptions = this.computePackOptions(outDir, appOutDir, arch) - if (!targets.includes("default")) { - await this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions) - return - } - - const installerOut = computeDistOut(outDir, arch) - await BluebirdPromise.all([ - this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions), - emptyDir(installerOut) - ]) - - postAsyncTasks.push(this.packageInDistributableFormat(appOutDir, installerOut, arch, packOptions)) + await this.doPack(packOptions, outDir, appOutDir, arch, this.customBuildOptions) + await this.sign(appOutDir) + this.packageInDistributableFormat(outDir, appOutDir, arch, packOptions, targets, postAsyncTasks) } protected computeAppOutDir(outDir: string, arch: Arch): string { return path.join(outDir, `win${getArchSuffix(arch)}-unpacked`) } - protected async doPack(options: ElectronPackagerOptions, outDir: string, appOutDir: string, arch: Arch, customBuildOptions: WinBuildOptions) { - await super.doPack(options, outDir, appOutDir, arch, customBuildOptions) - await this.sign(appOutDir) - } - protected async sign(appOutDir: string) { const cscInfo = await this.cscInfo if (cscInfo != null) { @@ -118,89 +105,20 @@ export class WinPackager extends PlatformPackager { return BluebirdPromise.promisify(sign)(opts) } - protected async computeEffectiveDistOptions(appOutDir: string, installerOutDir: string, packOptions: ElectronPackagerOptions, setupExeName: string): Promise { - let iconUrl = this.customBuildOptions.iconUrl || this.devMetadata.build.iconUrl - if (iconUrl == null) { - if (this.info.repositoryInfo != null) { - const info = await this.info.repositoryInfo.getInfo(this) - if (info != null) { - iconUrl = `https://github.com/${info.user}/${info.project}/blob/master/${this.relativeBuildResourcesDirname}/icon.ico?raw=true` - } + protected packageInDistributableFormat(outDir: string, appOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions, targets: Array, promises: Array>): void { + for (let target of targets) { + if (target === "squirrel.windows" || target === "default") { + const helperClass: typeof SquirrelWindowsTarget = require("./targets/squirrelWindows").default + promises.push(new helperClass(this, appOutDir, arch).build(packOptions)) } - - if (iconUrl == null) { - throw new Error("iconUrl is not specified, please see https://github.com/electron-userland/electron-builder/wiki/Options#WinBuildOptions-iconUrl") + else { + log(`Creating Windows ${target}`) + // we use app name here - see https://github.com/electron-userland/electron-builder/pull/204 + const outFile = path.join(outDir, `${this.appName}-${this.metadata.version}${getArchSuffix(arch)}-win.${target}`) + promises.push(this.archiveApp(target, appOutDir, outFile) + .then(() => this.dispatchArtifactCreated(outFile, `${this.metadata.name}-${this.metadata.version}${getArchSuffix(arch)}-win.${target}`))) } } - - checkConflictingOptions(this.customBuildOptions) - - const projectUrl = await this.computePackageUrl() - const rceditOptions = { - "version-string": packOptions["version-string"], - "file-version": packOptions["build-version"], - "product-version": packOptions["app-version"], - } - rceditOptions["version-string"]!.LegalCopyright = packOptions["app-copyright"] - - const cscInfo = await this.cscInfo - const options: any = Object.assign({ - name: this.metadata.name, - productName: this.appName, - exe: this.appName + ".exe", - setupExe: setupExeName, - title: this.appName, - appDirectory: appOutDir, - outputDirectory: installerOutDir, - version: this.metadata.version, - description: smarten(this.metadata.description), - authors: this.metadata.author.name, - iconUrl: iconUrl, - setupIcon: await this.iconPath, - certificateFile: cscInfo == null ? null : cscInfo.file, - certificatePassword: cscInfo == null ? null : cscInfo.password, - fixUpPaths: false, - skipUpdateIcon: true, - usePackageJson: false, - extraMetadataSpecs: projectUrl == null ? null : `\n ${projectUrl}`, - copyright: packOptions["app-copyright"], - packageCompressionLevel: this.devMetadata.build.compression === "store" ? 0 : 9, - sign: { - name: this.appName, - site: projectUrl, - overwrite: true, - hash: this.customBuildOptions.signingHashAlgorithms, - }, - rcedit: rceditOptions, - }, this.customBuildOptions) - - if (!("loadingGif" in options)) { - const resourceList = await this.resourceList - if (resourceList.includes("install-spinner.gif")) { - options.loadingGif = path.join(this.buildResourcesDir, "install-spinner.gif") - } - } - - return options - } - - protected async packageInDistributableFormat(appOutDir: string, installerOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions): Promise { - const winstaller = require("electron-winstaller-fixed") - const version = this.metadata.version - const archSuffix = getArchSuffix(arch) - const setupExeName = `${this.appName} Setup ${version}${archSuffix}.exe` - - const distOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, packOptions, setupExeName) - await winstaller.createWindowsInstaller(distOptions) - this.dispatchArtifactCreated(path.join(installerOutDir, setupExeName), `${this.metadata.name}-Setup-${version}${archSuffix}.exe`) - - const packagePrefix = `${this.metadata.name}-${winstaller.convertVersion(version)}-` - this.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}full.nupkg`)) - if (distOptions.remoteReleases != null) { - this.dispatchArtifactCreated(path.join(installerOutDir, `${packagePrefix}delta.nupkg`)) - } - - this.dispatchArtifactCreated(path.join(installerOutDir, "RELEASES")) } } @@ -251,26 +169,4 @@ function parseIco(buffer: Buffer): Array { function isIco(buffer: Buffer): boolean { return buffer.readUInt16LE(0) === 0 && buffer.readUInt16LE(2) === 1 -} - -export function computeDistOut(outDir: string, arch: Arch): string { - return path.join(outDir, `win${getArchSuffix(arch)}`) -} - -function checkConflictingOptions(options: any) { - for (let name of ["outputDirectory", "appDirectory", "exe", "fixUpPaths", "usePackageJson", "extraFileSpecs", "extraMetadataSpecs", "skipUpdateIcon", "setupExe"]) { - if (name in options) { - throw new Error(`Option ${name} is ignored, do not specify it.`) - } - } - - if ("noMsi" in options) { - warn(`noMsi is deprecated, please specify as "msi": true if you want to create an MSI installer`) - options.msi = !options.noMsi - } - - const msi = options.msi - if (msi != null && typeof msi !== "boolean") { - throw new Error(`msi expected to be boolean value, but string '"${msi}"' was specified`) - } } \ No newline at end of file diff --git a/test/src/globTest.ts b/test/src/globTest.ts index 56fbc96e218..12774726fa1 100644 --- a/test/src/globTest.ts +++ b/test/src/globTest.ts @@ -29,7 +29,51 @@ test.ifDevOrLinuxCi("ignore build resources", () => { }) }) -test("copy extra content", async () => { +test.ifDevOrLinuxCi("files", () => { + return assertPack("test-app-one", { + targets: Platform.LINUX.createTarget(DIR_TARGET), + devMetadata: { + build: { + asar: false, + files: ["**/*", "!ignoreMe${/*}"] + } + } + }, { + tempDirCreated: projectDir => { + return outputFile(path.join(projectDir, "ignoreMe", "foo"), "data") + }, + packed: projectDir => { + return assertThat(path.join(projectDir, outDirName, "linux", "resources", "app", "ignoreMe")).doesNotExist() + }, + }) +}) + +test.ifDevOrLinuxCi("ignore node_modules known dev dep", () => { + return assertPack("test-app-one", { + targets: Platform.LINUX.createTarget(DIR_TARGET), + devMetadata: { + build: { + asar: false, + } + } + }, { + tempDirCreated: projectDir => { + return BluebirdPromise.all([ + modifyPackageJson(projectDir, data => { + data.devDependencies = Object.assign({ + "electron-osx-sign": "*", + }, data.devDependencies) + }), + outputFile(path.join(projectDir, "node_modules", "electron-osx-sign", "package.json"), "{}"), + ]) + }, + packed: projectDir => { + return assertThat(path.join(projectDir, outDirName, "linux", "resources", "app", "node_modules", "electron-osx-sign")).doesNotExist() + }, + }) +}) + +test("extraResources", async () => { for (let platform of getPossiblePlatforms().keys()) { const osName = platform.buildConfigurationKey diff --git a/test/src/helpers/fileAssert.ts b/test/src/helpers/fileAssert.ts index 697f3cd4e83..1a4a27a1067 100644 --- a/test/src/helpers/fileAssert.ts +++ b/test/src/helpers/fileAssert.ts @@ -25,15 +25,11 @@ class Assertions { } isEqualTo(expected: any) { - if (!json8.equal(this.actual, expected)) { - const actualJson = JSON.stringify(this.actual, jsonReplacer, 2) - const expectedJson = JSON.stringify(expected, jsonReplacer, 2) - throw new AssertionError({ - message: `Expected \n${expectedJson}\n\nis not equal to\n\n${actualJson}\n\n${prettyDiff(JSON.parse(actualJson), JSON.parse(expectedJson))}`, - actual: this.actual, - expected: expected, - }) - } + compare(this.actual, expected) + } + + containsAll(expected: Iterable) { + compare(this.actual.slice().sort(), Array.from(expected).slice().sort()) } isAbsolute() { @@ -76,4 +72,17 @@ function prettyDiff(actual: any, expected: any): string { return gray(part.value.replace(/.+/g, ' | $&')) }).join('') return `\n${diff}\n` +} + +function compare(actual: any, expected: any) { + if (!json8.equal(actual, expected)) { + const actualJson = JSON.stringify(actual, jsonReplacer, 2) + const expectedJson = JSON.stringify(expected, jsonReplacer, 2) + const stack = new Error().stack + throw new AssertionError({ + message: `Expected \n${expectedJson}\n\nis not equal to\n\n${actualJson}\n\n${prettyDiff(JSON.parse(actualJson), JSON.parse(expectedJson))}\n${stack.split("\n")[3].trim()}`, + actual: actual, + expected: expected, + }) + } } \ No newline at end of file diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 2ee8dac9269..b9779aa6a68 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -9,7 +9,7 @@ import { Packager, PackagerOptions, Platform, getProductName, ArtifactCreated, A import { exec, getTempName } from "out/util" import { createTargets } from "out" import { tmpdir } from "os" -import { getArchSuffix } from "out/platformPackager" +import { getArchSuffix, computeEffectiveTargets } from "out/platformPackager" import pathSorter = require("path-sort") import DecompressZip = require("decompress-zip") @@ -43,7 +43,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO // non-osx test uses the same dir as osx test, but we cannot share node_modules (because tests executed in parallel) const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}`) : path.resolve(customTmpDir) if (customTmpDir != null) { - console.log("Custom temp dir used: %s", customTmpDir) + console.log(`Custom temp dir used: ${customTmpDir}`) } await emptyDir(dir) await copy(projectDir, dir, { @@ -113,7 +113,7 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions await checkLinuxResult(projectDir, packager, checkOptions, artifacts.get(Platform.LINUX), arch) } else if (platform === Platform.WINDOWS) { - await checkWindowsResult(packager, checkOptions, artifacts.get(Platform.WINDOWS), arch) + await checkWindowsResult(packager, targets, checkOptions, artifacts.get(Platform.WINDOWS), arch) } } } @@ -131,7 +131,7 @@ async function checkLinuxResult(projectDir: string, packager: Packager, checkOpt return result } - assertThat(getFileNames(artifacts)).isEqualTo((checkOptions == null || checkOptions.expectedArtifacts == null ? getExpected() : checkOptions.expectedArtifacts.slice()).sort()) + assertThat(getFileNames(artifacts)).containsAll(checkOptions == null || checkOptions.expectedArtifacts == null ? getExpected() : checkOptions.expectedArtifacts) if (!targets.includes("deb") || !targets.includes("default")) { return @@ -223,35 +223,44 @@ async function checkOsXResult(packager: Packager, packagerOptions: PackagerOptio } function getFileNames(list: Array): Array { - return list.map(it => path.basename(it.file)).sort() + return list.map(it => path.basename(it.file)) } -async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOptions, artifacts: Array, arch: Arch) { +async function checkWindowsResult(packager: Packager, targets: Array, checkOptions: AssertPackOptions, artifacts: Array, arch: Arch) { const productName = getProductName(packager.metadata, packager.devMetadata) + let squirrel = false - function getExpectedFileNames(archSuffix: string) { - const result = [ - `RELEASES`, - `${productName} Setup 1.1.0${archSuffix}.exe`, - `TestApp-1.1.0-full.nupkg`, - ] - const buildOptions = packager.devMetadata.build.win - if (buildOptions != null && buildOptions.remoteReleases != null) { - result.push(`${productName}-1.1.0-delta.nupkg`) + const artifactNames: Array = [] + const expectedFileNames: Array = [] + const archSuffix = getArchSuffix(arch) + const buildOptions = packager.devMetadata.build.win + for (let target of computeEffectiveTargets(targets, buildOptions == null ? null : buildOptions.target)) { + if (target === "default" || target === "squirrel") { + squirrel = true + expectedFileNames.push("RELEASES", `${productName} Setup 1.1.0${archSuffix}.exe`, `TestApp-1.1.0-full.nupkg`) + + if (buildOptions != null && buildOptions.remoteReleases != null) { + expectedFileNames.push(`${productName}-1.1.0-delta.nupkg`) + } + + artifactNames.push(`TestApp-Setup-1.1.0${archSuffix}.exe`) + } + else { + expectedFileNames.push(`${productName}-1.1.0${archSuffix}-win.${target}`) + + artifactNames.push(`TestApp-1.1.0${archSuffix}-win.${target}`) } - return result } - const archSuffix = getArchSuffix(arch) - assertThat(getFileNames(artifacts)).isEqualTo((checkOptions == null || checkOptions.expectedArtifacts == null ? getExpectedFileNames(archSuffix) : checkOptions.expectedArtifacts.slice()).sort()) + assertThat(getFileNames(artifacts)).containsAll(checkOptions == null || checkOptions.expectedArtifacts == null ? expectedFileNames : checkOptions.expectedArtifacts) - if (checkOptions != null && checkOptions.expectedArtifacts != null) { + if (!squirrel || (checkOptions != null && checkOptions.expectedArtifacts != null)) { return } - assertThat(artifacts.map(it => it.artifactName).filter(it => it != null)).isEqualTo([`TestApp-Setup-1.1.0${archSuffix}.exe`]) + assertThat(artifacts.map(it => it.artifactName).filter(it => it != null)).containsAll(artifactNames) - const packageFile = path.join(path.dirname(artifacts[0].file), `TestApp-1.1.0-full.nupkg`) + const packageFile = artifacts.find(it => it.file.endsWith("-full.nupkg"))!.file const unZipper = new DecompressZip(packageFile) const fileDescriptors = await unZipper.getFiles() diff --git a/test/src/osxPackagerTest.ts b/test/src/osxPackagerTest.ts index 7ef7677d222..255c49de673 100644 --- a/test/src/osxPackagerTest.ts +++ b/test/src/osxPackagerTest.ts @@ -174,7 +174,7 @@ class CheckingOsXPackager extends OsXPackager { this.effectiveFlatOptions = opts } - async packageInDistributableFormat(outDir: string, appOutDir: string, targets: Array): Promise { + packageInDistributableFormat(appOutDir: string, targets: Array, promises: Array>): void { // skip } } \ No newline at end of file diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index a7a1e4c9ede..4d655e208bf 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -3,17 +3,18 @@ import test from "./helpers/avaEx" import { assertPack, platform, modifyPackageJson, signed } from "./helpers/packTester" import { move, outputFile } from "fs-extra-p" import * as path from "path" -import { WinPackager, computeDistOut } from "out/winPackager" +import { WinPackager } from "out/winPackager" import { Promise as BluebirdPromise } from "bluebird" import { ElectronPackagerOptions } from "electron-packager-tf" import { assertThat } from "./helpers/fileAssert" import { SignOptions } from "signcode-tf" +import SquirrelWindowsTarget from "out/targets/squirrelWindows" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/awaiter") test.ifDevOrWinCi("win", () => assertPack("test-app-one", signed({ - targets: Platform.WINDOWS.createTarget(), + targets: Platform.WINDOWS.createTarget(["default", "zip"]), }) )) @@ -115,15 +116,16 @@ class CheckingWinPackager extends WinPackager { async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { // skip pack - const installerOutDir = computeDistOut(outDir, arch) const appOutDir = this.computeAppOutDir(outDir, arch) const packOptions = this.computePackOptions(outDir, appOutDir, arch) - this.effectiveDistOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, packOptions, "Foo.exe") + + const helperClass: typeof SquirrelWindowsTarget = require("out/targets/squirrelWindows").default + this.effectiveDistOptions = await (new helperClass(this, appOutDir, arch).computeEffectiveDistOptions("foo", packOptions, "Foo.exe")) await this.sign(appOutDir) } - async packageInDistributableFormat(appOutDir: string, installerOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions): Promise { + packageInDistributableFormat(outDir: string, appOutDir: string, arch: Arch, packOptions: ElectronPackagerOptions, targets: Array, promises: Array>): void { // skip } diff --git a/test/tsconfig.json b/test/tsconfig.json index c98400d64c0..b910be012e8 100755 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -33,6 +33,7 @@ "../typings/compareVersions.d.ts", "../typings/deep-assign.d.ts", "../typings/electron-packager.d.ts", + "../typings/electron-winstaller-fixed.d.ts", "../typings/gh-api.d.ts", "../typings/hosted-git-info.d.ts", "../typings/lib.es2016.array.include.d.ts", @@ -42,6 +43,7 @@ "../typings/main/definitions/debug/index.d.ts", "../typings/main/definitions/source-map-support/source-map-support.d.ts", "../typings/modules/glob/index.d.ts", + "../typings/modules/minimatch/index.d.ts", "../typings/node.d.ts", "../typings/progress-stream.d.ts", "../typings/read-package-json.d.ts", diff --git a/tsconfig.json b/tsconfig.json index acb8a3b8b6a..ab9d4e96589 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ }, "docs": "docs/Options.md", "filesGlob": [ - "src/*.ts", + "src/**/*.ts", "lib/*.d.ts", "typings/**/*.d.ts", "!typings/browser/**/*.d.ts", @@ -37,6 +37,7 @@ "typings/compareVersions.d.ts", "typings/deep-assign.d.ts", "typings/electron-packager.d.ts", + "typings/electron-winstaller-fixed.d.ts", "typings/gh-api.d.ts", "typings/hosted-git-info.d.ts", "typings/lib.es2016.array.include.d.ts", @@ -46,6 +47,7 @@ "typings/main/definitions/debug/index.d.ts", "typings/main/definitions/source-map-support/source-map-support.d.ts", "typings/modules/glob/index.d.ts", + "typings/modules/minimatch/index.d.ts", "typings/node.d.ts", "typings/progress-stream.d.ts", "typings/read-package-json.d.ts", @@ -65,7 +67,6 @@ "src/fpmDownload.ts", "src/gitHubPublisher.ts", "src/gitHubRequest.ts", - "src/globby.ts", "src/httpRequest.ts", "src/index.ts", "src/install-app-deps.ts", @@ -76,6 +77,8 @@ "src/platformPackager.ts", "src/promise.ts", "src/repositoryInfo.ts", + "src/targets/archive.ts", + "src/targets/squirrelWindows.ts", "src/util.ts", "src/winPackager.ts" ] diff --git a/typings.json b/typings.json index ee848027787..621a51e7b24 100755 --- a/typings.json +++ b/typings.json @@ -5,6 +5,7 @@ }, "dependencies": { "glob": "registry:npm/glob#6.0.0+20160211003958", + "minimatch": "registry:npm/minimatch#3.0.0+20160211003958", "source-map-support": "github:typed-typings/npm-source-map-support#900ed4180a22285bce4bbabc0760427e71a59eca" } } diff --git a/typings/asar.d.ts b/typings/asar.d.ts index 41ab7a713e4..024fce4d474 100644 --- a/typings/asar.d.ts +++ b/typings/asar.d.ts @@ -8,7 +8,7 @@ declare module "asar" { interface AsarFileMetadata { type: "file" | "directory" | "link" - stat: Stats + stat?: Stats } interface AsarOptions { diff --git a/typings/electron-packager.d.ts b/typings/electron-packager.d.ts index fd87b0609fb..f16b806f0b0 100644 --- a/typings/electron-packager.d.ts +++ b/typings/electron-packager.d.ts @@ -62,6 +62,8 @@ declare module "electron-packager-tf" { "app-copyright"?: string generateFinalBasename?: (context: any) => void + + initializeApp?: (opts: ElectronPackagerOptions, buildDir: string, appRelativePath: string) => Promise } /** Object hash of application metadata to embed into the executable (Windows only). */ @@ -76,5 +78,7 @@ declare module "electron-packager-tf" { InternalName?: string; } + export function userIgnoreFilter(opts: ElectronPackagerOptions): any + export function pack(opts: ElectronPackagerOptions): Promise } \ No newline at end of file diff --git a/typings/electron-winstaller-fixed.d.ts b/typings/electron-winstaller-fixed.d.ts new file mode 100644 index 00000000000..6739ffc9ad8 --- /dev/null +++ b/typings/electron-winstaller-fixed.d.ts @@ -0,0 +1,5 @@ +declare module "electron-winstaller-fixed" { + export function createWindowsInstaller(options: any): Promise + + export function convertVersion(version: string): string +} \ No newline at end of file diff --git a/typings/modules/minimatch/index.d.ts b/typings/modules/minimatch/index.d.ts new file mode 100644 index 00000000000..30a5fbae834 --- /dev/null +++ b/typings/modules/minimatch/index.d.ts @@ -0,0 +1,116 @@ +// Generated by typings +// Source: https://raw.githubusercontent.com/typed-typings/npm-minimatch/74f47de8acb42d668491987fc6bc144e7d9aa891/minimatch.d.ts +declare module '~minimatch/minimatch' { +function minimatch (target: string, pattern: string, options?: minimatch.Options): boolean; + +namespace minimatch { + export function match (list: string[], pattern: string, options?: Options): string[]; + export function filter (pattern: string, options?: Options): (element: string, indexed: number, array: string[]) => boolean; + export function makeRe (pattern: string, options?: Options): RegExp; + + /** + * All options are `false` by default. + */ + export interface Options { + /** + * Dump a ton of stuff to stderr. + */ + debug?: boolean; + /** + * Do not expand `{a,b}` and `{1..3}` brace sets. + */ + nobrace?: boolean; + /** + * Disable `**` matching against multiple folder names. + */ + noglobstar?: boolean; + /** + * Allow patterns to match filenames starting with a period, even if the pattern does not explicitly have a period in that spot. + * + * Note that by default, `a\/**\/b` will not match `a/.d/b`, unless `dot` is set. + */ + dot?: boolean; + /** + * Disable "extglob" style patterns like `+(a|b)`. + */ + noext?: boolean; + /** + * Perform a case-insensitive match. + */ + nocase?: boolean; + /** + * When a match is not found by `minimatch.match`, return a list containing the pattern itself if this option is set. When not set, an empty list is returned if there are no matches. + */ + nonull?: boolean; + /** + * If set, then patterns without slashes will be matched against the basename of the path if it contains slashes. For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. + */ + matchBase?: boolean; + /** + * Suppress the behavior of treating `#` at the start of a pattern as a comment. + */ + nocomment?: boolean; + /** + * Suppress the behavior of treating a leading `!` character as negation. + */ + nonegate?: boolean; + /** + * Returns from negate expressions the same as if they were not negated. (Ie, true on a hit, false on a miss.) + */ + flipNegate?: boolean; + } + + export class Minimatch { + constructor (pattern: string, options?: Options); + + /** + * The original pattern the minimatch object represents. + */ + pattern: string; + /** + * The options supplied to the constructor. + */ + options: Options; + + /** + * Created by the `makeRe` method. A single regular expression expressing the entire pattern. This is useful in cases where you wish to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled. + */ + regexp: RegExp; + /** + * True if the pattern is negated. + */ + negate: boolean; + /** + * True if the pattern is a comment. + */ + comment: boolean; + /** + * True if the pattern is `""`. + */ + empty: boolean; + + /** + * Generate the regexp member if necessary, and return it. Will return false if the pattern is invalid. + */ + makeRe (): RegExp | boolean; + /** + * Return true if the filename matches the pattern, or false otherwise. + */ + match (fname: string, partial?: boolean): boolean; + /** + * Take a `/-`split filename, and match it against a single row in the `regExpSet`. This method is mainly for internal use, but is exposed so that it can be used by a glob-walker that needs to avoid excessive filesystem calls. + */ + matchOne (fileArray: string[], patternArray: string[], partial: boolean): boolean; + } +} + +export = minimatch; +} +declare module 'minimatch/minimatch' { +import alias = require('~minimatch/minimatch'); +export = alias; +} +declare module 'minimatch' { +import alias = require('~minimatch/minimatch'); +export = alias; +} diff --git a/typings/modules/minimatch/typings.json b/typings/modules/minimatch/typings.json new file mode 100644 index 00000000000..22ff949ed9f --- /dev/null +++ b/typings/modules/minimatch/typings.json @@ -0,0 +1,11 @@ +{ + "resolution": "main", + "tree": { + "src": "https://raw.githubusercontent.com/typed-typings/npm-minimatch/74f47de8acb42d668491987fc6bc144e7d9aa891/typings.json", + "raw": "registry:npm/minimatch#3.0.0+20160211003958", + "main": "minimatch.d.ts", + "global": false, + "name": "minimatch", + "type": "typings" + } +}