Skip to content

Commit 6d4ab11

Browse files
committed
feat: Development dependencies are never copied in any case
You don't need to ignore it explicitly anymore
1 parent fc1587f commit 6d4ab11

File tree

9 files changed

+160
-113
lines changed

9 files changed

+160
-113
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
osx_image: xcode7
1+
osx_image: xcode7.3
22

33
matrix:
44
include:
@@ -13,7 +13,6 @@ language: c
1313
cache:
1414
directories:
1515
- node_modules
16-
- test/testApp/node_modules
1716
- $HOME/.electron
1817
- $HOME/.cache/fpm
1918

@@ -23,6 +22,7 @@ before_install:
2322

2423
install:
2524
- nvm install $NODE_VERSION
25+
- nvm use --delete-prefix $NODE_VERSION
2626
- if [[ "$TRAVIS_OS_NAME" == "osx" && "$NODE_VERSION" == "4" ]]; then npm install npm -g ; fi
2727
- npm install
2828
- npm prune

docs/Options.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Here documented only `electron-builder` specific options:
5454
| app-category-type | <a name="BuildMetadata-app-category-type"></a><p>*OS X-only.* The application category type, as shown in the Finder via *View -&gt; Arrange by Application Category* when viewing the Applications directory.</p> <p>For example, <code>app-category-type=public.app-category.developer-tools</code> will set the application category to *Developer Tools*.</p> <p>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).</p>
5555
| asar | <a name="BuildMetadata-asar"></a><p>Whether to package the application’s source code into an archive, using [Electron’s archive format](https://github.com/electron/asar). Defaults to <code>true</code>. 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/).</p> <p>Or you can pass object of any asar options.</p>
5656
| productName | <a name="BuildMetadata-productName"></a>See [AppMetadata.productName](#AppMetadata-productName).
57-
| files | <a name="BuildMetadata-files"></a><p>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 <code>\*\*\/\*</code> (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).</p> <p>[Multiple patterns](#multiple-glob-patterns) are supported. You can use <code>${os}</code> (expanded to osx, linux or win according to current platform) and <code>${arch}</code> in the pattern.</p> <p>If directory matched, all contents are copied. So, you can just specify <code>foo</code> to copy <code>foo</code> directory.</p> <p>Remember that default pattern <code>\*\*\/\*</code> is not added to your custom, so, you have to add it explicitly — e.g. <code>[&quot;\*\*\/\*&quot;, &quot;!ignoreMe${/\*}&quot;]</code>.</p> <p>May be specified in the platform options (e.g. in the <code>build.osx</code>).</p>
57+
| files | <a name="BuildMetadata-files"></a><p>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 <code>\*\*\/\*</code> (i.e. [hidden files are ignored by default](https://www.npmjs.com/package/glob#dots)).</p> <p>Development dependencies are never copied in any case. You don’t need to ignore it explicitly.</p> <p>[Multiple patterns](#multiple-glob-patterns) are supported. You can use <code>${os}</code> (expanded to osx, linux or win according to current platform) and <code>${arch}</code> in the pattern. If directory matched, all contents are copied. So, you can just specify <code>foo</code> to copy <code>foo</code> directory.</p> <p>Remember that default pattern <code>\*\*\/\*</code> is not added to your custom, so, you have to add it explicitly — e.g. <code>[&quot;\*\*\/\*&quot;, &quot;!ignoreMe${/\*}&quot;]</code>.</p> <p>May be specified in the platform options (e.g. in the <code>build.osx</code>).</p>
5858
| extraResources | <a name="BuildMetadata-extraResources"></a><p>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 (<code>Contents/Resources</code> for OS X, <code>resources</code> for Linux/Windows).</p> <p>Glob rules the same as for [files](#BuildMetadata-files).</p>
5959
| extraFiles | <a name="BuildMetadata-extraFiles"></a>The same as [extraResources](#BuildMetadata-extraResources) but copy into the app's content directory (`Contents` for OS X, root directory for Linux/Windows).
6060
| osx | <a name="BuildMetadata-osx"></a>See [.build.osx](#OsXBuildOptions).
@@ -63,8 +63,6 @@ Here documented only `electron-builder` specific options:
6363
| linux | <a name="BuildMetadata-linux"></a>See [.build.linux](#LinuxBuildOptions).
6464
| compression | <a name="BuildMetadata-compression"></a>The compression level, one of `store`, `normal`, `maximum` (default: `normal`). If you want to rapidly test build, `store` can reduce build time significantly.
6565
| afterPack | <a name="BuildMetadata-afterPack"></a>*programmatic API only* The function to be run after pack (but before pack into distributable format and sign). Promise must be returned.
66-
| npmPrune | <a name="BuildMetadata-npmPrune"></a><p>Whether to [prune](https://docs.npmjs.com/cli/prune) native dependencies (<code>npm prune --production</code>) before starting to package the app. Defaults to <code>true</code> if [two package.json structure](https://github.com/electron-userland/electron-builder#two-packagejson-structure) is not used.</p>
67-
| npmRebuild | <a name="BuildMetadata-npmRebuild"></a>Whether to [rebuild](https://docs.npmjs.com/cli/rebuild) native dependencies (`npm rebuild`) before starting to package the app. Defaults to `true`.
6866

6967
<a name="OsXBuildOptions"></a>
7068
### `.build.osx`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"pre-git": "^3.8.4",
108108
"semantic-release": "^6.3.0",
109109
"should": "^9.0.0",
110-
"ts-babel": "^1.0.0",
110+
"ts-babel": "^1.0.2",
111111
"tsconfig-glob": "^0.4.3",
112112
"tslint": "3.10.0-dev.2",
113113
"typescript": "1.9.0-dev.20160607-1.0",

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ export { Packager } from "./packager"
22
export { PackagerOptions, ArtifactCreated, DIR_TARGET, BuildInfo } from "./platformPackager"
33
export { BuildOptions, build, createPublisher, CliOptions, createTargets } from "./builder"
44
export { PublishOptions, Publisher } from "./gitHubPublisher"
5-
export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions } from "./metadata"
5+
export { AppMetadata, DevMetadata, Platform, Arch, archFromString, getProductName, BuildMetadata, OsXBuildOptions, WinBuildOptions, LinuxBuildOptions, CompressionLevel } from "./metadata"

src/metadata.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export interface AuthorMetadata {
7474
readonly email: string
7575
}
7676

77+
export type CompressionLevel = "store" | "normal" | "maximum"
78+
7779
/*
7880
## `.build`
7981
*/
@@ -156,7 +158,7 @@ export interface BuildMetadata {
156158
/*
157159
The compression level, one of `store`, `normal`, `maximum` (default: `normal`). If you want to rapidly test build, `store` can reduce build time significantly.
158160
*/
159-
readonly compression?: "store" | "normal" | "maximum" | null
161+
readonly compression?: CompressionLevel | null
160162

161163
readonly "build-version"?: string | null
162164

@@ -171,7 +173,7 @@ export interface BuildMetadata {
171173
// */
172174
// readonly npmPrune?: boolean
173175
// deprecated
174-
readonly prune?: boolean
176+
// readonly prune?: boolean
175177

176178
/*
177179
Whether to [rebuild](https://docs.npmjs.com/cli/rebuild) native dependencies (`npm rebuild`) before starting to package the app. Defaults to `true`.

src/platformPackager.ts

Lines changed: 61 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,18 @@ import EventEmitter = NodeJS.EventEmitter
44
import { Promise as BluebirdPromise } from "bluebird"
55
import * as path from "path"
66
import { pack, ElectronPackagerOptions, userIgnoreFilter } from "electron-packager-tf"
7-
import { readdir, copy, unlink, lstat, remove } from "fs-extra-p"
8-
import { statOrNull, use, spawn, debug7zArgs, debug, warn, log, spawnNpmProduction } from "./util"
7+
import { readdir, copy, unlink, lstat, remove, realpath } from "fs-extra-p"
8+
import { statOrNull, use, warn, log, exec } from "./util"
99
import { Packager } from "./packager"
1010
import { listPackage, statFile, AsarFileMetadata, createPackageFromFiles, AsarOptions } from "asar"
11-
import { path7za } from "7zip-bin"
11+
import { archiveApp } from "./targets/archive"
1212
import { Glob } from "glob"
1313
import { Minimatch } from "minimatch"
1414
import deepAssign = require("deep-assign")
1515

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

19-
class CompressionDescriptor {
20-
constructor(public flag: string, public env: string, public minLevel: string, public maxLevel: string = "-9") {
21-
}
22-
}
23-
24-
const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = {
25-
"tar.xz": new CompressionDescriptor("--xz", "XZ_OPT", "-0", "-9e"),
26-
"tar.lz": new CompressionDescriptor("--lzip", "LZOP", "-0"),
27-
"tar.gz": new CompressionDescriptor("--gz", "GZIP", "-1"),
28-
"tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"),
29-
}
30-
3119
export const commonTargets = ["dir", "zip", "7z", "tar.xz", "tar.lz", "tar.gz", "tar.bz2"]
3220

3321
export const DIR_TARGET = "dir"
@@ -168,18 +156,32 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
168156
promise = copy(this.info.appDir, appPath, {filter: userIgnoreFilter(opts), dereference: true})
169157
}
170158
else {
159+
const ignoreFiles = new Set([path.relative(this.info.appDir, opts.out!), path.relative(this.info.appDir, this.buildResourcesDir)])
160+
if (!this.info.isTwoPackageJsonProjectLayoutUsed) {
161+
const result = await BluebirdPromise.all([listDependencies(this.info.appDir, false), listDependencies(this.info.appDir, true)])
162+
const productionDepsSet = new Set(result[1])
163+
164+
// npm returns real path, so, we should use relative path to avoid any mismatch
165+
const realAppDirPath = await realpath(this.info.appDir)
166+
167+
for (let it of result[0]) {
168+
if (!productionDepsSet.has(it)) {
169+
if (it.startsWith(realAppDirPath)) {
170+
it = it.substring(realAppDirPath.length + 1)
171+
}
172+
else if (it.startsWith(this.info.appDir)) {
173+
it = it.substring(this.info.appDir.length + 1)
174+
}
175+
ignoreFiles.add(it)
176+
}
177+
}
178+
}
179+
171180
let patterns = this.getFilePatterns("files", customBuildOptions)
172181
if (patterns == null || patterns.length === 0) {
173182
patterns = ["**/*"]
174183
}
175-
176-
const parsedPatterns = this.getParsedPatterns(patterns, arch)
177-
if (!this.info.isTwoPackageJsonProjectLayoutUsed) {
178-
const dotOptions = {dot: true}
179-
parsedPatterns.push(new Minimatch("!node_modules/@(appdmg|electron-download|electron-builder|electron-prebuilt|electron-packager-tf|electron-winstaller-fixed|electron-osx-sign-tf|electron-osx-sign){,/**/*}", dotOptions))
180-
parsedPatterns.push(new Minimatch(`!@(${path.relative(this.info.appDir, this.buildResourcesDir)}|${path.relative(this.info.appDir, opts.out!)}){,/**/*}`, dotOptions))
181-
}
182-
promise = copyFiltered(this.info.appDir, appPath, parsedPatterns, true)
184+
promise = copyFiltered(this.info.appDir, appPath, this.getParsedPatterns(patterns, arch), true, ignoreFiles)
183185
}
184186

185187
const promises = [promise]
@@ -193,24 +195,8 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
193195

194196
await BluebirdPromise.all(promises)
195197

196-
let npmPrune = this.devMetadata.build.npmPrune
197-
if (npmPrune == null) {
198-
npmPrune = this.devMetadata.build.prune
199-
if (npmPrune != null) {
200-
warn("prune is deprecated and renamed to npmPrune, please specify as npmPrune")
201-
}
202-
}
203-
204-
if (npmPrune == null) {
205-
npmPrune = !this.info.isTwoPackageJsonProjectLayoutUsed
206-
}
207-
else if (typeof npmPrune !== "boolean") {
208-
throw new Error(`npmPrune expected to be boolean value, but string '"${npmPrune}"' was specified`)
209-
}
210-
211-
if (npmPrune) {
212-
log("Pruning app dependencies")
213-
await spawnNpmProduction("prune", appPath)
198+
if (opts.prune != null) {
199+
warn("prune is deprecated — development dependencies are never copied in any case")
214200
}
215201

216202
if (asarOptions != null) {
@@ -469,66 +455,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
469455
}
470456

471457
protected async archiveApp(format: string, appOutDir: string, outFile: string): Promise<any> {
472-
const compression = this.devMetadata.build.compression
473-
const storeOnly = compression === "store"
474-
475-
const dirToArchive = this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`) : appOutDir
476-
if (format.startsWith("tar.")) {
477-
// 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)
478-
// 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).
479-
const info = extToCompressionDescriptor[format]
480-
let tarEnv = process.env
481-
if (compression != null && compression !== "normal") {
482-
tarEnv = Object.assign({}, process.env)
483-
tarEnv[info.env] = storeOnly ? info.minLevel : info.maxLevel
484-
}
485-
486-
await spawn(process.platform === "darwin" || process.platform === "freebsd" ? "gtar" : "tar", [info.flag, "--transform", `s,^\.,${path.basename(outFile, "." + format)},`, "-cf", outFile, "."], {
487-
cwd: dirToArchive,
488-
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
489-
env: tarEnv
490-
})
491-
return
492-
}
493-
494-
const args = debug7zArgs("a")
495-
if (compression === "maximum") {
496-
if (format === "7z" || format.endsWith(".7z")) {
497-
args.push("-mx=9", "-mfb=64", "-md=32m", "-ms=on")
498-
}
499-
else if (format === "zip") {
500-
// http://superuser.com/a/742034
501-
//noinspection SpellCheckingInspection
502-
args.push("-mfb=258", "-mpass=15")
503-
}
504-
else {
505-
args.push("-mx=9")
506-
}
507-
}
508-
else if (storeOnly) {
509-
if (format !== "zip") {
510-
args.push("-mx=1")
511-
}
512-
}
513-
514-
// remove file before - 7z doesn't overwrite file, but update
515-
try {
516-
await unlink(outFile)
517-
}
518-
catch (e) {
519-
// ignore
520-
}
521-
522-
if (format === "zip" || storeOnly) {
523-
args.push("-mm=" + (storeOnly ? "Copy" : "Deflate"))
524-
}
525-
526-
args.push(outFile, dirToArchive)
527-
528-
await spawn(path7za, args, {
529-
cwd: path.dirname(dirToArchive),
530-
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
531-
})
458+
return archiveApp(this.devMetadata.build.compression, format, outFile, this.platform === Platform.OSX ? path.join(appOutDir, `${this.appName}.app`) : appOutDir)
532459
}
533460
}
534461

@@ -592,15 +519,21 @@ function minimatchAll(path: string, patterns: Array<Minimatch>): boolean {
592519
return match
593520
}
594521

595-
function copyFiltered(src: string, destination: string, patterns: Array<Minimatch>, dereference: boolean = false): Promise<any> {
522+
// we use relative path to avoid canonical path issue - e.g. /tmp vs /private/tmp
523+
function copyFiltered(src: string, destination: string, patterns: Array<Minimatch>, dereference: boolean = false, ignoreFiles?: Set<string>): Promise<any> {
596524
return copy(src, destination, {
597525
dereference: dereference,
598526
filter: it => {
599527
if (src === it) {
600528
return true
601529
}
602-
603530
let relative = it.substring(src.length + 1)
531+
532+
// yes, check before path sep normalization
533+
if (ignoreFiles != null && ignoreFiles.has(relative)) {
534+
return false
535+
}
536+
604537
if (path.sep === "\\") {
605538
relative = relative.replace(/\\/g, "/")
606539
}
@@ -612,4 +545,29 @@ function copyFiltered(src: string, destination: string, patterns: Array<Minimatc
612545
export function computeEffectiveTargets(rawList: Array<string>, targetsFromMetadata: Array<string> | n): Array<string> {
613546
let targets = normalizeTargets(rawList.length === 0 ? targetsFromMetadata : rawList)
614547
return targets == null ? ["default"] : targets
548+
}
549+
550+
async function listDependencies(appDir: string, production: boolean): Promise<Array<string>> {
551+
let npmExecPath = process.env.npm_execpath || process.env.NPM_CLI_JS
552+
const npmExecArgs = ["ls", production ? "--production" : "--dev", "--parseable"]
553+
if (npmExecPath == null) {
554+
npmExecPath = process.platform === "win32" ? "npm.cmd" : "npm"
555+
}
556+
else {
557+
npmExecArgs.unshift(npmExecPath)
558+
npmExecPath = process.env.npm_node_execpath || process.env.NODE_EXE || "node"
559+
}
560+
561+
const result = (await exec(npmExecPath, npmExecArgs, {
562+
cwd: appDir,
563+
stdio: "inherit",
564+
maxBuffer: 1024 * 1024,
565+
})).trim().split("\n")
566+
if (result.length > 0 && !result[0].includes("/node_modules/")) {
567+
// first line is a project dir
568+
const lastIndex = result.length - 1
569+
result[0] = result[lastIndex]
570+
result.length = result.length - 1
571+
}
572+
return result
615573
}

src/targets/archive.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { spawn, debug, debug7zArgs } from "../util"
2+
import { CompressionLevel } from "../metadata"
3+
import * as path from "path"
4+
import { unlink } from "fs-extra-p"
5+
import { path7za } from "7zip-bin"
6+
7+
//noinspection JSUnusedLocalSymbols
8+
const __awaiter = require("../awaiter")
9+
10+
class CompressionDescriptor {
11+
constructor(public flag: string, public env: string, public minLevel: string, public maxLevel: string = "-9") {
12+
}
13+
}
14+
15+
const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = {
16+
"tar.xz": new CompressionDescriptor("--xz", "XZ_OPT", "-0", "-9e"),
17+
"tar.lz": new CompressionDescriptor("--lzip", "LZOP", "-0"),
18+
"tar.gz": new CompressionDescriptor("--gz", "GZIP", "-1"),
19+
"tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"),
20+
}
21+
22+
export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string): Promise<any> {
23+
const storeOnly = compression === "store"
24+
25+
if (format.startsWith("tar.")) {
26+
// 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)
27+
// 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).
28+
const info = extToCompressionDescriptor[format]
29+
let tarEnv = process.env
30+
if (compression != null && compression !== "normal") {
31+
tarEnv = Object.assign({}, process.env)
32+
tarEnv[info.env] = storeOnly ? info.minLevel : info.maxLevel
33+
}
34+
35+
await spawn(process.platform === "darwin" || process.platform === "freebsd" ? "gtar" : "tar", [info.flag, "--transform", `s,^\.,${path.basename(outFile, "." + format)},`, "-cf", outFile, "."], {
36+
cwd: dirToArchive,
37+
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
38+
env: tarEnv
39+
})
40+
return
41+
}
42+
43+
const args = debug7zArgs("a")
44+
if (compression === "maximum") {
45+
if (format === "7z" || format.endsWith(".7z")) {
46+
args.push("-mx=9", "-mfb=64", "-md=32m", "-ms=on")
47+
}
48+
else if (format === "zip") {
49+
// http://superuser.com/a/742034
50+
//noinspection SpellCheckingInspection
51+
args.push("-mfb=258", "-mpass=15")
52+
}
53+
else {
54+
args.push("-mx=9")
55+
}
56+
}
57+
else if (storeOnly) {
58+
if (format !== "zip") {
59+
args.push("-mx=1")
60+
}
61+
}
62+
63+
// remove file before - 7z doesn't overwrite file, but update
64+
try {
65+
await unlink(outFile)
66+
}
67+
catch (e) {
68+
// ignore
69+
}
70+
71+
if (format === "zip" || storeOnly) {
72+
args.push("-mm=" + (storeOnly ? "Copy" : "Deflate"))
73+
}
74+
75+
args.push(outFile, dirToArchive)
76+
77+
await spawn(path7za, args, {
78+
cwd: path.dirname(dirToArchive),
79+
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
80+
})
81+
}

0 commit comments

Comments
 (0)