diff --git a/README.md b/README.md index 0f55ef3..9059515 100644 --- a/README.md +++ b/README.md @@ -162,5 +162,5 @@ function handleSquirrelEvent() { You can get debug messages from this package by running with the environment variable `DEBUG=electron-windows-installer:main` e.g. ``` -DEBUG=electron-windows-installer:main node tasks/electron-winstaller.js +DEBUG=electron-windows-installer node tasks/electron-winstaller.js ``` diff --git a/package.json b/package.json index b508a5c..6e925e5 100644 --- a/package.json +++ b/package.json @@ -16,23 +16,23 @@ "tdd": "ava --watch" }, "dependencies": { - "asar": "~0.10.0", - "bluebird": "^3.3.4", + "archiver": "^1.0.0", + "asar": "~0.11.0", + "bluebird": "^3.3.5", "debug": "^2.2.0", - "fs-extra": "^0.26.7", - "lodash.template": "^4.2.2", - "temp": "^0.8.3" + "fs-extra": "^0.28.0" }, "devDependencies": { - "ava": "^0.13.0", - "babel-cli": "^6.6.5", + "ava": "^0.14.0", + "babel-cli": "^6.7.5", "babel-eslint": "^6.0.2", "babel-plugin-transform-async-to-module-method": "^6.7.0", - "babel-plugin-transform-runtime": "^6.6.0", - "babel-preset-es2015-node4": "^2.0.3", + "babel-plugin-transform-runtime": "^6.7.5", + "babel-preset-es2015-node4": "^2.1.0", "babel-preset-stage-0": "^6.5.0", "babel-register": "^6.7.2", - "eslint": "^2.4.0" + "eslint": "^2.8.0", + "temp": "^0.8.3" }, "engines": { "node": ">=0.4.0" diff --git a/spec/installer-spec.js b/spec/installer-spec.js index c818a8c..5a4aee5 100644 --- a/spec/installer-spec.js +++ b/spec/installer-spec.js @@ -1,7 +1,13 @@ import test from 'ava'; import path from 'path'; -import { createTempDir, fileExists, unlink, readDir } from '../src/fs-utils'; +import { fileExists, unlink, readDir } from '../src/fs-utils'; import { createWindowsInstaller } from '../src/index.js'; +import { Promise } from 'bluebird'; +import temp from 'temp'; + +temp.track(); + +const createTempDir = Promise.promisify(temp.mkdir); const log = require('debug')('electron-windows-installer:spec'); diff --git a/src/fs-utils.js b/src/fs-utils.js index 7dbf93a..b3806e1 100644 --- a/src/fs-utils.js +++ b/src/fs-utils.js @@ -1,19 +1,15 @@ -import { copy as extraCopy } from 'fs-extra'; import { Promise } from 'bluebird'; -import temp from 'temp'; -import fs from 'fs'; +import * as fs from 'fs-extra'; const log = require('debug')('electron-windows-installer:fs-utils'); -temp.track(); - -export const copy = Promise.promisify(extraCopy); -export const createTempDir = Promise.promisify(temp.mkdir); +export const copy = Promise.promisify(fs.copy); export const readFile = Promise.promisify(fs.readFile); export const readDir = Promise.promisify(fs.readdir); export const unlink = Promise.promisify(fs.unlink); -export const writeFile = Promise.promisify(fs.writeFile); export const rename = Promise.promisify(fs.rename); +export const mkdirs = Promise.promisify(fs.mkdirs); +export const remove = Promise.promisify(fs.remove); const inspect = Promise.promisify(fs.stat); export async function fileExists(file) { diff --git a/src/index.js b/src/index.js index ba6ba7f..2011a39 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,12 @@ -import template from 'lodash.template'; import spawn from './spawn-promise'; import asar from 'asar'; import path from 'path'; import * as fsUtils from './fs-utils'; +import archiver from 'archiver'; +import * as fs from 'fs-extra'; +import { Promise } from 'bluebird'; -const log = require('debug')('electron-windows-installer:main'); +const log = require('debug')('electron-windows-installer'); export function convertVersion(version) { const parts = version.split('-'); @@ -18,29 +20,13 @@ export function convertVersion(version) { } export async function createWindowsInstaller(options) { - let useMono = false; - - const monoExe = 'mono'; - const wineExe = 'wine'; - - if (process.platform !== 'win32') { - useMono = true; - if (!wineExe || !monoExe) { - throw new Error('You must install both Mono and Wine on non-Windows'); - } - - log(`Using Mono: '${monoExe}'`); - log(`Using Wine: '${wineExe}'`); - } - - let { appDirectory, outputDirectory, loadingGif } = options; - outputDirectory = path.resolve(outputDirectory || 'installer'); + const useMono = process.platform !== 'win32'; + const { appDirectory } = options; const vendorPath = path.join(__dirname, '..', 'vendor'); - const vendorUpdate = path.join(vendorPath, 'Update.exe'); const appUpdate = path.join(appDirectory, 'Update.exe'); - await fsUtils.copy(vendorUpdate, appUpdate); + await fsUtils.copy(path.join(vendorPath, 'Update.exe'), appUpdate); if (options.setupIcon && (options.skipUpdateIcon !== true)) { let cmd = path.join(vendorPath, 'rcedit.exe'); let args = [ @@ -50,17 +36,12 @@ export async function createWindowsInstaller(options) { if (useMono) { args.unshift(cmd); - cmd = wineExe; + cmd = 'wine'; } await spawn(cmd, args); } - const defaultLoadingGif = path.join(__dirname, '..', 'resources', 'install-spinner.gif'); - loadingGif = loadingGif ? path.resolve(loadingGif) : defaultLoadingGif; - - let {certificateFile, certificatePassword, remoteReleases, signWithParams, remoteToken} = options; - const metadata = { description: '', iconUrl: 'https://raw.githubusercontent.com/atom/electron/master/atom/browser/resources/win/atom.ico' @@ -93,69 +74,139 @@ export async function createWindowsInstaller(options) { } } - metadata.owners = metadata.owners || metadata.authors; - metadata.version = convertVersion(metadata.version); - metadata.copyright = metadata.copyright || - `Copyright © ${new Date().getFullYear()} ${metadata.authors || metadata.owners}`; - - let templateData = await fsUtils.readFile(path.join(__dirname, '..', 'template.nuspec'), 'utf8'); - if (path.sep === '/') { - templateData = templateData.replace(/\\/g, '/'); - } - const nuspecContent = template(templateData)(metadata); - - log(`Created NuSpec file:\n${nuspecContent}`); + const outputDirectory = path.resolve(options.outputDirectory || 'installer'); + if (options.remoteReleases) { + let cmd = path.join(vendorPath, 'SyncReleases.exe'); + let args = ['-u', options.remoteReleases, '-r', outputDirectory]; - const nugetOutput = await fsUtils.createTempDir('si-'); - const targetNuspecPath = path.join(nugetOutput, metadata.name + '.nuspec'); - - await fsUtils.writeFile(targetNuspecPath, nuspecContent); + if (useMono) { + args.unshift(cmd); + cmd = 'mono'; + } - let cmd = path.join(vendorPath, 'nuget.exe'); - let args = [ - 'pack', targetNuspecPath, - '-BasePath', appDirectory, - '-OutputDirectory', nugetOutput, - '-NoDefaultExcludes' - ]; + if (options.remoteToken) { + args.push('-t', options.remoteToken); + } - if (useMono) { - args.unshift(cmd); - cmd = monoExe; + await spawn(cmd, args); } - // Call NuGet to create our package - log(await spawn(cmd, args)); - const nupkgPath = path.join(nugetOutput, `${metadata.name}.${metadata.version}.nupkg`); + // todo fix Squirrel.windows "Sharing violation on path" (avoid copy, use file directly) + const squirrelWorkaroundDir = path.join(outputDirectory, '.tmp'); + await fsUtils.mkdirs(squirrelWorkaroundDir); + try { + const nupkgPath = path.join(squirrelWorkaroundDir, 'in.nupkg'); + await pack(metadata, appDirectory, nupkgPath); + await releasify(nupkgPath, outputDirectory, options, vendorPath); + } + finally { + await fsUtils.remove(squirrelWorkaroundDir); + } - if (remoteReleases) { - cmd = path.join(vendorPath, 'SyncReleases.exe'); - args = ['-u', remoteReleases, '-r', outputDirectory]; + if (options.fixUpPaths !== false) { + log('Fixing up paths'); - if (useMono) { - args.unshift(cmd); - cmd = monoExe; + if (metadata.productName || options.setupExe) { + const setupPath = path.join(outputDirectory, options.setupExe || `${metadata.productName}Setup.exe`); + const unfixedSetupPath = path.join(outputDirectory, 'Setup.exe'); + log(`Renaming ${unfixedSetupPath} => ${setupPath}`); + await fsUtils.rename(unfixedSetupPath, setupPath); } - if (remoteToken) { - args.push('-t', remoteToken); + if (metadata.productName) { + const msiPath = path.join(outputDirectory, `${metadata.productName}Setup.msi`); + const unfixedMsiPath = path.join(outputDirectory, 'Setup.msi'); + if (await fsUtils.fileExists(unfixedMsiPath)) { + log(`Renaming ${unfixedMsiPath} => ${msiPath}`); + await fsUtils.rename(unfixedMsiPath, msiPath); + } } - - log(await spawn(cmd, args)); } +} + +function pack(metadata, appDirectory, outFile) { + return new Promise(function (resolve, reject) { + const archive = archiver('zip', {store: true}); + const out = fs.createWriteStream(outFile); + out.on('close', function () { + resolve(outFile); + }); + archive.on('error', reject); + archive.pipe(out); + + archive.directory(appDirectory, 'lib/net45'); + + archive.append(` + + + +`, {name: '.rels', prefix: '_rels'}); + + const author = metadata.authors || metadata.owners; + const copyright = metadata.copyright || + `Copyright © ${new Date().getFullYear()} ${author}`; + const version = convertVersion(metadata.version); + const nuspecContent = ` + + + ${metadata.name} + ${metadata.title} + ${version} + ${author} + ${metadata.owners || metadata.authors} + ${metadata.iconUrl} + false + ${metadata.description} + ${copyright}${metadata.extraMetadataSpecs || ''} + +`; + log(`Created NuSpec file:\n${nuspecContent}`); + + archive.append(nuspecContent, {name: metadata.name + '.nuspec'}); + + archive.append(` + + + + + + + + + + +`, {name: '[Content_Types].xml'}); + + archive.append(` + + ${author} + ${metadata.description} + ${metadata.name} + + NuGet, Version=3.4.0.653, Culture=neutral, PublicKeyToken=31bf3856ad364e35;Unix 15.4.0.0;.NET Framework 4.5 + ${metadata.title} + ${version} +`, {name: '1.psmdcp', prefix: 'package/services/metadata/core-properties'}); + + archive.finalize(); + }); +} - cmd = path.join(vendorPath, 'Update.com'); - args = [ +async function releasify(nupkgPath, outputDirectory, options, vendorPath) { + let cmd = path.join(vendorPath, 'Update.com'); + const args = [ '--releasify', nupkgPath, '--releaseDir', outputDirectory, - '--loadingGif', loadingGif + '--loadingGif', options.loadingGif ? path.resolve(options.loadingGif) : path.join(__dirname, '..', 'resources', 'install-spinner.gif') ]; - if (useMono) { + if (process.platform !== 'win32') { args.unshift(path.join(vendorPath, 'Update-Mono.exe')); - cmd = monoExe; + cmd = 'mono'; } + const {certificateFile, certificatePassword, signWithParams} = options; if (signWithParams) { args.push('--signWithParams'); args.push(signWithParams); @@ -173,25 +224,5 @@ export async function createWindowsInstaller(options) { args.push('--no-msi'); } - log(await spawn(cmd, args)); - - if (options.fixUpPaths !== false) { - log('Fixing up paths'); - - if (metadata.productName || options.setupExe) { - const setupPath = path.join(outputDirectory, options.setupExe || `${metadata.productName}Setup.exe`); - const unfixedSetupPath = path.join(outputDirectory, 'Setup.exe'); - log(`Renaming ${unfixedSetupPath} => ${setupPath}`); - await fsUtils.rename(unfixedSetupPath, setupPath); - } - - if (metadata.productName) { - const msiPath = path.join(outputDirectory, `${metadata.productName}Setup.msi`); - const unfixedMsiPath = path.join(outputDirectory, 'Setup.msi'); - if (await fsUtils.fileExists(unfixedMsiPath)) { - log(`Renaming ${unfixedMsiPath} => ${msiPath}`); - await fsUtils.rename(unfixedMsiPath, msiPath); - } - } - } -} + await spawn(cmd, args); +} \ No newline at end of file diff --git a/src/spawn-promise.js b/src/spawn-promise.js index 7647967..01fc878 100644 --- a/src/spawn-promise.js +++ b/src/spawn-promise.js @@ -1,7 +1,7 @@ import { spawn as spawnOg } from 'child_process'; import { Promise } from 'bluebird'; -const d = require('debug')('electron-windows-installer:spawn'); +const d = require('debug')('electron-windows-installer'); // Public: Maps a process's output into an {Observable} // diff --git a/vendor/nuget.exe b/vendor/nuget.exe deleted file mode 100644 index 8dd7e45..0000000 Binary files a/vendor/nuget.exe and /dev/null differ