Builder.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. import { dirname, basename, resolve } from 'path';
  2. import * as semver from 'semver';
  3. import { ensureDir, emptyDir, readFile, readJson, writeFile, copy, remove, createReadStream, createWriteStream, rename } from 'fs-extra';
  4. import * as Bluebird from 'bluebird';
  5. const debug = require('debug')('build:builder');
  6. const globby = require('globby');
  7. const rcedit = require('rcedit');
  8. const plist = require('plist');
  9. import { Downloader } from './Downloader';
  10. import { FFmpegDownloader } from './FFmpegDownloader';
  11. import { BuildConfig } from './config';
  12. import { NsisVersionInfo } from './common';
  13. import { NsisComposer, NsisDiffer, Nsis7Zipper, nsisBuild } from './nsis-gen';
  14. import { mergeOptions, findExecutable, findFFmpeg, findRuntimeRoot, findExcludableDependencies, tmpName, tmpFile, tmpDir, fixWindowsVersion, copyFileAsync, extractGeneric, compress } from './util';
  15. interface IBuilderOptions {
  16. win?: boolean;
  17. mac?: boolean;
  18. linux?: boolean;
  19. x86?: boolean;
  20. x64?: boolean;
  21. chromeApp?: boolean;
  22. mirror?: string;
  23. concurrent?: boolean;
  24. mute?: boolean;
  25. }
  26. export class Builder {
  27. public static DEFAULT_OPTIONS: IBuilderOptions = {
  28. win: false,
  29. mac: false,
  30. linux: false,
  31. x86: false,
  32. x64: false,
  33. chromeApp: false,
  34. mirror: Downloader.DEFAULT_OPTIONS.mirror,
  35. concurrent: false,
  36. mute: true,
  37. };
  38. public options: IBuilderOptions;
  39. constructor(options: IBuilderOptions = {}, public dir: string) {
  40. this.options = mergeOptions(Builder.DEFAULT_OPTIONS, options);
  41. debug('in constructor', 'dir', dir);
  42. debug('in constructor', 'options', this.options);
  43. }
  44. public async build() {
  45. const tasks: string[][] = [];
  46. [ 'win', 'mac', 'linux' ].map((platform) => {
  47. [ 'x86', 'x64' ].map((arch) => {
  48. if((<any>this.options)[platform] && (<any>this.options)[arch]) {
  49. tasks.push([ platform, arch ]);
  50. }
  51. });
  52. });
  53. if(!this.options.mute) {
  54. console.info('Starting building tasks...', {
  55. tasks,
  56. concurrent: this.options.concurrent,
  57. });
  58. }
  59. if(tasks.length == 0) {
  60. throw new Error('ERROR_NO_TASK');
  61. }
  62. if(this.options.concurrent) {
  63. await Bluebird.map(tasks, async ([ platform, arch ]) => {
  64. const options: any = {};
  65. options[platform] = true;
  66. options[arch] = true;
  67. options.mirror = this.options.mirror;
  68. options.concurrent = false;
  69. options.mute = true;
  70. const builder = new Builder(options, this.dir);
  71. const started = Date.now();
  72. if(!this.options.mute) {
  73. console.info(`Building for ${ platform }, ${ arch } starts...`);
  74. }
  75. await builder.build();
  76. if(!this.options.mute) {
  77. console.info(`Building for ${ platform }, ${ arch } ends within ${ this.getTimeDiff(started) }s.`);
  78. }
  79. });
  80. }
  81. else {
  82. const pkg: any = await readJson(resolve(this.dir, this.options.chromeApp ? 'manifest.json' : 'package.json'));
  83. const config = new BuildConfig(pkg);
  84. debug('in build', 'config', config);
  85. for(const [ platform, arch ] of tasks) {
  86. const started = Date.now();
  87. if(!this.options.mute) {
  88. console.info(`Building for ${ platform }, ${ arch } starts...`);
  89. }
  90. try {
  91. await this.buildTask(platform, arch, pkg, config);
  92. }
  93. catch(err) {
  94. console.warn(err);
  95. }
  96. if(!this.options.mute) {
  97. console.info(`Building for ${ platform }, ${ arch } ends within ${ this.getTimeDiff(started) }s.`);
  98. }
  99. }
  100. }
  101. }
  102. protected getTimeDiff(started: number) {
  103. return ((Date.now() - started) / 1000).toFixed(2);
  104. }
  105. protected async writeStrippedManifest(path: string, pkg: any, config: BuildConfig) {
  106. const json: any = {};
  107. for(const key in pkg) {
  108. if(pkg.hasOwnProperty(key) && config.strippedProperties.indexOf(key) === -1) {
  109. json[key] = pkg[key];
  110. }
  111. }
  112. await writeFile(path, JSON.stringify(json));
  113. }
  114. protected combineExecutable(executable: string, nwFile: string) {
  115. return new Promise((resolve, reject) => {
  116. const nwStream = createReadStream(nwFile);
  117. const stream = createWriteStream(executable, {
  118. flags: 'a',
  119. });
  120. nwStream.on('error', reject);
  121. stream.on('error', reject);
  122. stream.on('finish', resolve);
  123. nwStream.pipe(stream);
  124. });
  125. }
  126. protected readPlist(path: string): Promise<any> {
  127. return readFile(path, {
  128. encoding: 'utf-8',
  129. })
  130. .then(data => plist.parse(data));
  131. }
  132. protected writePlist(path: string, p: any) {
  133. return writeFile(path, plist.build(p));
  134. }
  135. protected updateWinResources(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  136. const pathResolve = resolve;
  137. return new Promise((resolve, reject) => {
  138. const path = pathResolve(targetDir, 'nw.exe');
  139. const rc = {
  140. 'product-version': fixWindowsVersion(config.win.productVersion),
  141. 'file-version': fixWindowsVersion(config.win.fileVersion),
  142. 'version-string': config.win.versionStrings,
  143. 'icon': config.win.icon ? pathResolve(this.dir, config.win.icon) : undefined,
  144. };
  145. rcedit(path, rc, (err: Error) => err ? reject(err) : resolve());
  146. });
  147. }
  148. protected renameWinApp(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  149. const src = resolve(targetDir, 'nw.exe');
  150. const dest = resolve(targetDir, `${ config.win.versionStrings.ProductName }.exe`);
  151. return rename(src, dest);
  152. }
  153. protected async updatePlist(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  154. const path = resolve(targetDir, './nwjs.app/Contents/Info.plist');
  155. const plist = await this.readPlist(path);
  156. plist.CFBundleIdentifier = config.appId;
  157. plist.CFBundleName = config.mac.name;
  158. plist.CFBundleDisplayName = config.mac.displayName;
  159. plist.CFBundleVersion = config.mac.version;
  160. plist.CFBundleShortVersionString = config.mac.version;
  161. for(const key in config.mac.plistStrings) {
  162. if(config.mac.plistStrings.hasOwnProperty(key)) {
  163. plist[key] = config.mac.plistStrings[key];
  164. }
  165. }
  166. await this.writePlist(path, plist);
  167. }
  168. protected async updateMacIcon(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  169. const path = resolve(targetDir, './nwjs.app/Contents/Resources/app.icns');
  170. if(!config.mac.icon) {
  171. return;
  172. }
  173. await copy(resolve(this.dir, config.mac.icon), path);
  174. }
  175. protected async fixMacMeta(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  176. const files = await globby([ '**/InfoPlist.strings' ], {
  177. cwd: targetDir,
  178. });
  179. for(const file of files) {
  180. const path = resolve(targetDir, file);
  181. // Different versions has different encodings for `InforPlist.strings`.
  182. // We determine encoding by evaluating bytes of `CF` here.
  183. const data = await readFile(path);
  184. const encoding = data.indexOf(Buffer.from('43004600', 'hex')) >= 0
  185. ? 'ucs2' : 'utf-8';
  186. const strings = data.toString(encoding);
  187. const newStrings = strings.replace(/([A-Za-z]+)\s+=\s+"(.+?)";/g, (match: string, key: string, value: string) => {
  188. switch(key) {
  189. case 'CFBundleName':
  190. return `${ key } = "${ config.mac.name }";`;
  191. case 'CFBundleDisplayName':
  192. return `${ key } = "${ config.mac.displayName }";`;
  193. case 'CFBundleGetInfoString':
  194. return `${ key } = "${ config.mac.version }";`;
  195. case 'NSContactsUsageDescription':
  196. return `${ key } = "${ config.mac.description }";`;
  197. case 'NSHumanReadableCopyright':
  198. return `${ key } = "${ config.mac.copyright }";`;
  199. default:
  200. return `${ key } = "${ value }";`;
  201. }
  202. });
  203. await writeFile(path, Buffer.from(newStrings, encoding));
  204. }
  205. }
  206. protected renameMacApp(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  207. const src = resolve(targetDir, 'nwjs.app');
  208. const dest = resolve(targetDir, `${ config.mac.displayName }.app`);
  209. return rename(src, dest);
  210. }
  211. protected renameLinuxApp(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  212. const src = resolve(targetDir, 'nw');
  213. const dest = resolve(targetDir, `${ pkg.name }`);
  214. return rename(src, dest);
  215. }
  216. protected async prepareWinBuild(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  217. await this.updateWinResources(targetDir, appRoot, pkg, config);
  218. }
  219. protected async prepareMacBuild(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  220. await this.updatePlist(targetDir, appRoot, pkg, config);
  221. await this.updateMacIcon(targetDir, appRoot, pkg, config);
  222. await this.fixMacMeta(targetDir, appRoot, pkg, config);
  223. }
  224. protected async prepareLinuxBuild(targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  225. }
  226. protected async copyFiles(platform: string, targetDir: string, appRoot: string, pkg: any, config: BuildConfig) {
  227. const generalExcludes = [
  228. '**/node_modules/.bin',
  229. '**/node_modules/*/{ example, examples, test, tests }',
  230. '**/{ .DS_Store, .git, .hg, .svn, *.log }',
  231. ];
  232. const dependenciesExcludes = await findExcludableDependencies(this.dir, pkg)
  233. .then((excludable) => {
  234. return excludable.map(excludable => [ excludable, `${ excludable }/**/*` ]);
  235. })
  236. .then((excludes) => {
  237. return Array.prototype.concat.apply([], excludes);
  238. });
  239. debug('in copyFiles', 'dependenciesExcludes', dependenciesExcludes);
  240. const ignore = [
  241. ...config.excludes,
  242. ...generalExcludes,
  243. ...dependenciesExcludes,
  244. ...[ config.output, `${ config.output }/**/*` ]
  245. ];
  246. debug('in copyFiles', 'ignore', ignore);
  247. const files = await globby(config.files, {
  248. cwd: this.dir,
  249. // TODO: https://github.com/isaacs/node-glob#options, warn for cyclic links.
  250. follow: true,
  251. mark: true,
  252. ignore,
  253. });
  254. debug('in copyFiles', 'config.files', config.files);
  255. debug('in copyFiles', 'files', files);
  256. if(config.packed) {
  257. switch(platform) {
  258. case 'win32':
  259. case 'win':
  260. case 'linux':
  261. const nwFile = await tmpName({
  262. postfix: '.zip',
  263. });
  264. await compress(this.dir, files, 'zip', nwFile);
  265. const { path: tempDir } = await tmpDir();
  266. await this.writeStrippedManifest(resolve(tempDir, 'package.json'), pkg, config);
  267. await compress(tempDir, [ './package.json' ], 'zip', nwFile);
  268. await remove(tempDir);
  269. const executable = await findExecutable(platform, targetDir);
  270. await this.combineExecutable(executable, nwFile);
  271. await remove(nwFile);
  272. break;
  273. case 'darwin':
  274. case 'osx':
  275. case 'mac':
  276. for(const file of files) {
  277. await copyFileAsync(resolve(this.dir, file), resolve(appRoot, file));
  278. }
  279. await this.writeStrippedManifest(resolve(appRoot, 'package.json'), pkg, config);
  280. break;
  281. default:
  282. throw new Error('ERROR_UNKNOWN_PLATFORM');
  283. }
  284. }
  285. else {
  286. for(const file of files) {
  287. await copyFileAsync(resolve(this.dir, file), resolve(appRoot, file));
  288. }
  289. await this.writeStrippedManifest(resolve(appRoot, 'package.json'), pkg, config);
  290. }
  291. }
  292. protected async integrateFFmpeg(platform: string, arch: string, targetDir: string, pkg: any, config: BuildConfig) {
  293. const downloader = new FFmpegDownloader({
  294. platform, arch,
  295. version: config.nwVersion,
  296. useCaches: true,
  297. showProgress: this.options.mute ? false : true,
  298. });
  299. if(!this.options.mute) {
  300. console.info('Fetching FFmpeg prebuilt...', {
  301. platform: downloader.options.platform,
  302. arch: downloader.options.arch,
  303. version: downloader.options.version,
  304. });
  305. }
  306. const ffmpegDir = await downloader.fetchAndExtract();
  307. const src = await findFFmpeg(platform, ffmpegDir);
  308. const dest = await findFFmpeg(platform, targetDir);
  309. await copy(src, dest);
  310. }
  311. protected async buildNsisDiffUpdater(platform: string, arch: string, versionInfo: NsisVersionInfo, fromVersion: string, toVersion: string, pkg: any, config: BuildConfig) {
  312. const diffNsis = resolve(this.dir, config.output, `${ pkg.name }-${ toVersion }-from-${ fromVersion }-${ platform }-${ arch }-Update.exe`);
  313. const fromDir = resolve(this.dir, config.output, (await versionInfo.getVersion(fromVersion)).source);
  314. const toDir = resolve(this.dir, config.output, (await versionInfo.getVersion(toVersion)).source);
  315. const data = await (new NsisDiffer(fromDir, toDir, {
  316. // Basic.
  317. appName: config.win.versionStrings.ProductName,
  318. companyName: config.win.versionStrings.CompanyName,
  319. description: config.win.versionStrings.FileDescription,
  320. version: fixWindowsVersion(config.win.productVersion),
  321. copyright: config.win.versionStrings.LegalCopyright,
  322. icon: config.nsis.icon ? resolve(this.dir, config.nsis.icon) : undefined,
  323. unIcon: config.nsis.unIcon ? resolve(this.dir, config.nsis.unIcon) : undefined,
  324. // Compression.
  325. compression: 'lzma',
  326. solid: true,
  327. languages: config.nsis.languages,
  328. installDirectory: config.nsis.installDirectory,
  329. // Output.
  330. output: diffNsis,
  331. })).make();
  332. const script = await tmpName();
  333. await writeFile(script, data);
  334. await nsisBuild(toDir, script, {
  335. mute: this.options.mute,
  336. });
  337. await remove(script);
  338. await versionInfo.addUpdater(toVersion, fromVersion, arch, diffNsis);
  339. }
  340. protected async buildDirTarget(platform: string, arch: string, runtimeDir: string, pkg: any, config: BuildConfig): Promise<string> {
  341. const targetDir = resolve(this.dir, config.output, `${ pkg.name }-${ pkg.version }-${ platform }-${ arch }`);
  342. const runtimeRoot = await findRuntimeRoot(platform, runtimeDir);
  343. const appRoot = resolve(targetDir, (() => {
  344. switch(platform) {
  345. case 'win32':
  346. case 'win':
  347. case 'linux':
  348. return './';
  349. case 'darwin':
  350. case 'osx':
  351. case 'mac':
  352. return './nwjs.app/Contents/Resources/app.nw/';
  353. default:
  354. throw new Error('ERROR_UNKNOWN_PLATFORM');
  355. }
  356. })());
  357. await emptyDir(targetDir);
  358. await copy(runtimeRoot, targetDir, {
  359. //dereference: true,
  360. });
  361. if(config.ffmpegIntegration) {
  362. await this.integrateFFmpeg(platform, arch, targetDir, pkg, config);
  363. }
  364. await ensureDir(appRoot);
  365. // Copy before refining might void the effort.
  366. switch(platform) {
  367. case 'win32':
  368. case 'win':
  369. await this.prepareWinBuild(targetDir, appRoot, pkg, config);
  370. await this.copyFiles(platform, targetDir, appRoot, pkg, config);
  371. await this.renameWinApp(targetDir, appRoot, pkg, config);
  372. break;
  373. case 'darwin':
  374. case 'osx':
  375. case 'mac':
  376. await this.prepareMacBuild(targetDir, appRoot, pkg, config);
  377. await this.copyFiles(platform, targetDir, appRoot, pkg, config);
  378. await this.renameMacApp(targetDir, appRoot, pkg, config);
  379. break;
  380. case 'linux':
  381. await this.prepareLinuxBuild(targetDir, appRoot, pkg, config);
  382. await this.copyFiles(platform, targetDir, appRoot, pkg, config);
  383. await this.renameLinuxApp(targetDir, appRoot, pkg, config);
  384. break;
  385. default:
  386. throw new Error('ERROR_UNKNOWN_PLATFORM');
  387. }
  388. return targetDir;
  389. }
  390. protected async buildArchiveTarget(type: string, sourceDir: string) {
  391. const targetArchive = resolve(dirname(sourceDir), `${ basename(sourceDir) }.${ type }`);
  392. await remove(targetArchive);
  393. const files = await globby([ '**/*' ], {
  394. cwd: sourceDir,
  395. });
  396. await compress(sourceDir, files, type, targetArchive);
  397. return targetArchive;
  398. }
  399. protected async buildNsisTarget(platform: string, arch: string, sourceDir: string, pkg: any, config: BuildConfig) {
  400. if(platform != 'win') {
  401. if(!this.options.mute) {
  402. console.info(`Skip building nsis target for ${ platform }.`);
  403. }
  404. return;
  405. }
  406. const versionInfo = new NsisVersionInfo(resolve(this.dir, config.output, 'versions.nsis.json'));
  407. const targetNsis = resolve(dirname(sourceDir), `${ basename(sourceDir) }-Setup.exe`);
  408. const data = await (new NsisComposer({
  409. // Basic.
  410. appName: config.win.versionStrings.ProductName,
  411. companyName: config.win.versionStrings.CompanyName,
  412. description: config.win.versionStrings.FileDescription,
  413. version: fixWindowsVersion(config.win.productVersion),
  414. copyright: config.win.versionStrings.LegalCopyright,
  415. icon: config.nsis.icon ? resolve(this.dir, config.nsis.icon) : undefined,
  416. unIcon: config.nsis.unIcon ? resolve(this.dir, config.nsis.unIcon) : undefined,
  417. // Compression.
  418. compression: 'lzma',
  419. solid: true,
  420. languages: config.nsis.languages,
  421. installDirectory: config.nsis.installDirectory,
  422. // Output.
  423. output: targetNsis,
  424. })).make();
  425. const script = await tmpName();
  426. await writeFile(script, data);
  427. await nsisBuild(sourceDir, script, {
  428. mute: this.options.mute,
  429. });
  430. await remove(script);
  431. await versionInfo.addVersion(pkg.version, '', sourceDir);
  432. await versionInfo.addInstaller(pkg.version, arch, targetNsis);
  433. if(config.nsis.diffUpdaters) {
  434. for(const version of await versionInfo.getVersions()) {
  435. if(semver.gt(pkg.version, version)) {
  436. await this.buildNsisDiffUpdater(platform, arch, versionInfo, version, pkg.version, pkg, config);
  437. }
  438. }
  439. }
  440. await versionInfo.save();
  441. }
  442. protected async buildNsis7zTarget(platform: string, arch: string, sourceDir: string, pkg: any, config: BuildConfig) {
  443. if(platform != 'win') {
  444. if(!this.options.mute) {
  445. console.info(`Skip building nsis7z target for ${ platform }.`);
  446. }
  447. return;
  448. }
  449. const sourceArchive = await this.buildArchiveTarget('7z', sourceDir);
  450. const versionInfo = new NsisVersionInfo(resolve(this.dir, config.output, 'versions.nsis.json'));
  451. const targetNsis = resolve(dirname(sourceDir), `${ basename(sourceDir) }-Setup.exe`);
  452. const data = await (new Nsis7Zipper(sourceArchive, {
  453. // Basic.
  454. appName: config.win.versionStrings.ProductName,
  455. companyName: config.win.versionStrings.CompanyName,
  456. description: config.win.versionStrings.FileDescription,
  457. version: fixWindowsVersion(config.win.productVersion),
  458. copyright: config.win.versionStrings.LegalCopyright,
  459. icon: config.nsis.icon ? resolve(this.dir, config.nsis.icon) : undefined,
  460. unIcon: config.nsis.unIcon ? resolve(this.dir, config.nsis.unIcon) : undefined,
  461. // Compression.
  462. compression: 'lzma',
  463. solid: true,
  464. languages: config.nsis.languages,
  465. installDirectory: config.nsis.installDirectory,
  466. // Output.
  467. output: targetNsis,
  468. })).make();
  469. const script = await tmpName();
  470. await writeFile(script, data);
  471. await nsisBuild(sourceDir, script, {
  472. mute: this.options.mute,
  473. });
  474. await remove(script);
  475. await versionInfo.addVersion(pkg.version, '', sourceDir);
  476. await versionInfo.addInstaller(pkg.version, arch, targetNsis);
  477. if(config.nsis.diffUpdaters) {
  478. for(const version of await versionInfo.getVersions()) {
  479. if(semver.gt(pkg.version, version)) {
  480. await this.buildNsisDiffUpdater(platform, arch, versionInfo, version, pkg.version, pkg, config);
  481. }
  482. }
  483. }
  484. await versionInfo.save();
  485. }
  486. protected async buildTask(platform: string, arch: string, pkg: any, config: BuildConfig) {
  487. if(platform === 'mac' && arch === 'x86' && !config.nwVersion.includes('0.12.3')) {
  488. if(!this.options.mute) {
  489. console.info(`The NW.js binary for ${ platform }, ${ arch } isn't available for ${ config.nwVersion }, skipped.`);
  490. }
  491. throw new Error('ERROR_TASK_MAC_X86_SKIPPED');
  492. }
  493. const downloader = new Downloader({
  494. platform, arch,
  495. version: config.nwVersion,
  496. flavor: config.nwFlavor,
  497. mirror: this.options.mirror,
  498. useCaches: true,
  499. showProgress: this.options.mute ? false : true,
  500. });
  501. if(!this.options.mute) {
  502. console.info('Fetching NW.js binary...', {
  503. platform: downloader.options.platform,
  504. arch: downloader.options.arch,
  505. version: downloader.options.version,
  506. flavor: downloader.options.flavor,
  507. });
  508. }
  509. const runtimeDir = await downloader.fetchAndExtract();
  510. if(!this.options.mute) {
  511. console.info('Building targets...');
  512. }
  513. const started = Date.now();
  514. if(!this.options.mute) {
  515. console.info(`Building directory target starts...`);
  516. }
  517. const targetDir = await this.buildDirTarget(platform, arch, runtimeDir, pkg, config);
  518. if(!this.options.mute) {
  519. console.info(`Building directory target ends within ${ this.getTimeDiff(started) }s.`);
  520. }
  521. // TODO: Consider using `Bluebird.map` to enable concurrent target building.
  522. for(const target of config.targets) {
  523. const started = Date.now();
  524. switch(target) {
  525. case 'zip':
  526. case '7z':
  527. if(!this.options.mute) {
  528. console.info(`Building ${ target } archive target starts...`);
  529. }
  530. await this.buildArchiveTarget(target, targetDir);
  531. if(!this.options.mute) {
  532. console.info(`Building ${ target } archive target ends within ${ this.getTimeDiff(started) }s.`);
  533. }
  534. break;
  535. case 'nsis':
  536. if(!this.options.mute) {
  537. console.info(`Building nsis target starts...`);
  538. }
  539. await this.buildNsisTarget(platform, arch, targetDir, pkg, config);
  540. if(!this.options.mute) {
  541. console.info(`Building nsis target ends within ${ this.getTimeDiff(started) }s.`);
  542. }
  543. break;
  544. case 'nsis7z':
  545. if(!this.options.mute) {
  546. console.info(`Building nsis7z target starts...`);
  547. }
  548. await this.buildNsis7zTarget(platform, arch, targetDir, pkg, config);
  549. if(!this.options.mute) {
  550. console.info(`Building nsis7z target ends within ${ this.getTimeDiff(started) }s.`);
  551. }
  552. break;
  553. default:
  554. throw new Error('ERROR_UNKNOWN_TARGET');
  555. }
  556. }
  557. }
  558. }