907 lines
32 KiB
JavaScript
907 lines
32 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs-extra');
|
|
const zlib = require('zlib');
|
|
const asar = require('asar');
|
|
const https = require('https');
|
|
const eol = require('os').EOL;
|
|
const exec = require('child_process').exec;
|
|
const config = require('./build-app.config');
|
|
|
|
/**
|
|
* Base class for platform dependent electron packagers
|
|
*/
|
|
class ElectronPackager {
|
|
|
|
/**
|
|
*
|
|
*/
|
|
constructor(configuration) {
|
|
this._configuration = configuration;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
build(architecture) {
|
|
throw new Error('Not implemented!');
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} folder
|
|
*/
|
|
async _getSize(folder) {
|
|
if(process.platform !== 'win32') {
|
|
let size = await this._executeCommand(`du -k -c "${folder}" | grep total | cut -f1`);
|
|
return size.trim();
|
|
} else {
|
|
throw new Error('Not implemented!');
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} archive
|
|
* @param {string} target
|
|
*/
|
|
async _extractArchive(archive, target) {
|
|
if(process.platform === 'win32') {
|
|
await this._executeCommand(`7z x "${archive}" -o"${target}"`);
|
|
} else {
|
|
await this._executeCommand(`unzip "${archive}" -d "${target}"`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} source
|
|
* @param {string} archive
|
|
*/
|
|
async _compressArchive(source, archive) {
|
|
if(process.platform === 'win32') {
|
|
await this._executeCommand(`7z a "${archive}" "${source}"`);
|
|
} else {
|
|
throw new Error('Not implemented!');
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} file
|
|
* @param {*} data
|
|
* @param {*} gzip
|
|
*/
|
|
_saveFile(file, data, gzip) {
|
|
fs.ensureDirSync(path.dirname(file));
|
|
let content = gzip ? zlib.gzipSync(data, { level: 9 }) : data;
|
|
fs.writeFileSync(file, content, typeof content === 'string' ? { encoding: 'utf8' } : undefined);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} uri
|
|
* @param {*} file
|
|
*/
|
|
_download(uri, file) {
|
|
return new Promise( (resolve, reject) => {
|
|
https.get(uri, response => {
|
|
if(response.headers['location']) {
|
|
this._download(response.headers['location'], file)
|
|
.then(() => resolve())
|
|
.catch(error => reject(error));
|
|
} else {
|
|
if(response.statusCode === 200) {
|
|
console.log('Downloading:', file);
|
|
let stream = fs.createWriteStream(file);
|
|
response.pipe(stream);
|
|
stream.on('finish', () => resolve());
|
|
stream.on('error', error => reject(error));
|
|
} else {
|
|
console.error('Download Failed!');
|
|
reject(new Error('Failed to download electron client!'));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} version
|
|
* @param {*} platform
|
|
*/
|
|
async _downloadElectron(version, platform, directory) {
|
|
let file = `electron-v${version}-${platform}.zip`;
|
|
let uri = `https://github.com/electron/electron/releases/download/v${version}/${file}`;
|
|
file = path.join('redist', file);
|
|
|
|
if(!fs.existsSync(file)) {
|
|
await this._download(uri, file);
|
|
}
|
|
await fs.ensureDir(directory);
|
|
await this._extractArchive(file, directory);
|
|
await fs.remove(path.join(directory, 'version'));
|
|
await fs.remove(path.join(directory, 'LICENSE'));
|
|
await fs.remove(path.join(directory, 'LICENSES.chromium.html'));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} moduleName
|
|
* @param {string} imageName Name of the binary image (wihtout any extension)
|
|
* @param {string} platform
|
|
* @param {bool} is64
|
|
*/
|
|
async _bundleStaticBinary(moduleName, imageName, platform, architecture) {
|
|
let binary = imageName + (process.platform === 'win32' ? '.exe' : '');
|
|
let source = path.join('node_modules', '@hakuneko', moduleName, 'bin', platform, architecture, binary);
|
|
let target = path.join(this._stagingExecutableDirectory, binary);
|
|
console.log(`Bundle '${source}' ...`);
|
|
if(await fs.exists(source)) {
|
|
await fs.copy(source, target);
|
|
await fs.chmod(target, '0755');
|
|
console.log(` => File bundled: '${target}'`);
|
|
} else {
|
|
console.log(' => File not found: skipped');
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture
|
|
*/
|
|
async _bundleFFMPEG(architecture) {
|
|
await this._bundleStaticBinary('ffmpeg-binaries', 'ffmpeg', process.platform, architecture);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture
|
|
*/
|
|
async _bundleImageMagick(architecture) {
|
|
await this._bundleStaticBinary('imagemagick-binaries', 'convert', process.platform, architecture);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture
|
|
*/
|
|
async _bundleKindleGenerate(architecture) {
|
|
await this._bundleStaticBinary('kindlegen-binaries', 'kindlegen', process.platform, architecture);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} command
|
|
* @param {bool} silent
|
|
*/
|
|
_executeCommand(command, silent) {
|
|
if(!silent) {
|
|
console.log('>', command);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
exec(command, (error, stdout, stderr) => {
|
|
if(!silent) {
|
|
console.log(stdout);
|
|
console.log(stderr);
|
|
}
|
|
if(error) {
|
|
reject(error);
|
|
} else {
|
|
resolve(stdout);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {...string} commands
|
|
*/
|
|
async _validateCommands(...commands) {
|
|
for(let command of commands) {
|
|
try {
|
|
await this._executeCommand(command, true);
|
|
} catch(error) {
|
|
throw new Error(`Failed to run command '${command}', make sure it is correctly installed!`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Packager for linux platform
|
|
*/
|
|
class ElectronPackagerLinux extends ElectronPackager {
|
|
|
|
/**
|
|
*
|
|
*/
|
|
constructor(configuration) {
|
|
super(configuration);
|
|
this.architectures = {
|
|
'32': {
|
|
name: 'i386',
|
|
suffix: 'linux_i386',
|
|
platform: 'linux-ia32'
|
|
},
|
|
'64': {
|
|
name: 'amd64',
|
|
suffix: 'linux_amd64',
|
|
platform: 'linux-x64'
|
|
},
|
|
'ARMv7': {
|
|
name: 'armv7l',
|
|
suffix: 'linux_armv7l',
|
|
platform: 'linux-armv7l'
|
|
},
|
|
'ARMHF': {
|
|
name: 'armhf',
|
|
suffix: 'linux_armhf',
|
|
platform: 'linux-armv7l'
|
|
},
|
|
'ARMv8': {
|
|
name: 'arm64',
|
|
suffix: 'linux_arm64',
|
|
platform: 'linux-arm64'
|
|
}
|
|
};
|
|
this._architecture = this.architectures[0];
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _dirBuildRoot() {
|
|
return path.join('build', `${this._configuration.name.package}_${this._configuration.version}_${this._architecture.suffix}`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _stagingExecutableDirectory() {
|
|
return path.join(this._dirBuildRoot, 'usr', 'lib', this._configuration.name.package);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture '32' or '64'
|
|
*/
|
|
async build(architecture) {
|
|
await this.buildDEB(architecture);
|
|
await this.buildRPM(architecture);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async buildDEB(architecture) {
|
|
this._architecture = this.architectures[architecture];
|
|
|
|
await this._validateCommands('unzip --help', 'asar --version', 'fakeroot --version', 'dpkg --version', 'lintian --version');
|
|
|
|
await fs.remove(this._dirBuildRoot);
|
|
await this._copySkeletonDEB();
|
|
await this._bundleElectron();
|
|
await this._bundleFFMPEG(this._architecture.name);
|
|
await this._bundleImageMagick(this._architecture.name);
|
|
await this._bundleKindleGenerate(this._architecture.name);
|
|
this._createManpage();
|
|
this._createChangelog();
|
|
//this._createMenuEntry(); // => only menu or desktop is recommend
|
|
this._createDesktopShortcut();
|
|
await this._createControlDEB();
|
|
this._createPostScript('postrm');
|
|
this._createPostScript('postinst');
|
|
await this._createChecksumsDEB();
|
|
|
|
let deb = this._dirBuildRoot + '.deb';
|
|
await fs.remove(deb);
|
|
await this._executeCommand(`fakeroot dpkg-deb -v -b "${this._dirBuildRoot}" "${deb}"`);
|
|
await this._executeCommand(`lintian --profile debian "${deb}" || true`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async buildRPM(architecture) {
|
|
this._architecture = this.architectures[architecture];
|
|
|
|
await this._validateCommands('unzip --help', 'asar --version', 'fakeroot --version', 'rpm --version');
|
|
|
|
await fs.remove(this._dirBuildRoot);
|
|
await this._copySkeletonRPM();
|
|
await this._bundleElectron();
|
|
await this._bundleFFMPEG(this._architecture.name);
|
|
await this._bundleImageMagick(this._architecture.name);
|
|
await this._bundleKindleGenerate(this._architecture.name);
|
|
this._createManpage();
|
|
this._createChangelog();
|
|
//this._createMenuEntry(); // => only menu or desktop is recommend
|
|
this._createDesktopShortcut();
|
|
let specs = await this._createSpecsRPM();
|
|
|
|
let rpm = this._dirBuildRoot + '.rpm';
|
|
await fs.remove(rpm);
|
|
await this._executeCommand(`rpmbuild -bb --noclean --define "_topdir $(pwd)/${this._dirBuildRoot}" --define "buildroot %{_topdir}" "${specs}"`);
|
|
await this._executeCommand(`mv -f ${this._dirBuildRoot}/RPMS/*/*.rpm ${rpm}`);
|
|
await fs.remove(specs);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _bundleElectron() {
|
|
console.log('Bundle electron ...');
|
|
let folder = this._stagingExecutableDirectory;
|
|
await this._downloadElectron(this._configuration.version, this._architecture.platform, folder);
|
|
await fs.remove(path.join(folder, 'resources', 'default_app.asar'));
|
|
await asar.createPackage(config.src, path.join(folder, 'resources', 'app.asar'));
|
|
await fs.move(path.join(folder, 'electron'), path.join(folder, this._configuration.binary.linux));
|
|
// chmod 4755 fixes https://github.com/electron/electron/issues/17972
|
|
await fs.chmod(path.join(folder, 'chrome-sandbox'), '4755');
|
|
// remove executable flag from libraries => avoid lintian errors
|
|
await this._executeCommand(`find "${folder}" -type f -iname "*.so" -exec chmod -x {} \\;`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _copySkeletonDEB() {
|
|
console.log('Copy DEB Skeleton ...');
|
|
//await this._executeCommand(`cp -r "redist/deb" "${this._dirBuildRoot}"`);
|
|
await fs.copy(path.join('redist', 'deb'), this._dirBuildRoot);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _copySkeletonRPM() {
|
|
console.log('Copy RPM Skeleton ...');
|
|
//await this._executeCommand(`cp -r "redist/rpm" "${this._dirBuildRoot}"`);
|
|
await fs.copy(path.join('redist', 'rpm'), this._dirBuildRoot);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createManpage() {
|
|
console.log('Creating Manpage ...');
|
|
let file = path.join(this._dirBuildRoot, 'usr', 'share', 'man', 'man1', this._configuration.name.package + '.1.gz');
|
|
let content = [
|
|
`.TH ${this._configuration.name.package} 1 "" ""`,
|
|
'',
|
|
'.SH NAME',
|
|
`${this._configuration.name.package} - ${this._configuration.description.short}`,
|
|
'',
|
|
'.SH SYNOPSIS',
|
|
this._configuration.name.package,
|
|
'',
|
|
'.SH DESCRIPTION',
|
|
this._configuration.description.long
|
|
];
|
|
this._saveFile(file, content.join(eol), true);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createChangelog() {
|
|
console.log('Creating Changelog ...');
|
|
let file = path.join(this._dirBuildRoot, 'usr', 'share', 'doc', this._configuration.name.package, 'changelog.gz');
|
|
this._saveFile(file, '-', true);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createDesktopShortcut() {
|
|
console.log('Creating Desktop Shortcut ...');
|
|
let file = path.join(this._dirBuildRoot, 'usr', 'share', 'applications', this._configuration.name.package + '.desktop');
|
|
let content = [
|
|
'[Desktop Entry]',
|
|
'Version=1.0',
|
|
'Type=' + this._configuration.meta.type,
|
|
'Name=' + this._configuration.name.product,
|
|
'GenericName=' + this._configuration.description.short,
|
|
'Exec=' + path.join('/usr', 'lib', this._configuration.name.package, this._configuration.binary.linux),
|
|
'Icon=' + this._configuration.name.package,
|
|
'Categories=' + this._configuration.meta.categories
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createMenuEntry() {
|
|
console.log('Creatng Menu Entry');
|
|
let file = path.join(this._dirBuildRoot, 'usr', 'share', 'menu', this._configuration.name.package);
|
|
let content = [
|
|
`?package(${this._configuration.name.package}):needs="X11" \\`,
|
|
` section="${this._configuration.meta.menu}" \\`,
|
|
` title="${this._configuration.name.product}" \\`,
|
|
` icon="/usr/share/pixmaps/${this._configuration.name.package}.xpm" \\`,
|
|
` command="${path.join('/usr/lib', this._configuration.name.package, this._configuration.binary.linux)}"`
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _createControlDEB() {
|
|
console.log('Creating DEB Control File ...');
|
|
let file = path.join(this._dirBuildRoot, 'DEBIAN', 'control');
|
|
let content = [
|
|
'Package: ' + this._configuration.name.package,
|
|
'Version: ' + this._configuration.version,
|
|
'Section: ' + this._configuration.meta.section,
|
|
'Architecture: ' + this._architecture.name,
|
|
'Installed-Size: ' + await this._getSize(path.join(this._dirBuildRoot, 'usr')),
|
|
'Depends: ' + this._configuration.meta.dependencies.deb,
|
|
'Maintainer: ' + this._configuration.author,
|
|
'Priority: optional',
|
|
'Homepage: ' + this._configuration.url,
|
|
'Description: ' + this._configuration.description.short,
|
|
' ' + this._configuration.description.long,
|
|
''
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createPostScript(name) {
|
|
console.log(`Creating PostScript '${name}' ...`);
|
|
let file = path.join(this._dirBuildRoot, 'DEBIAN', name);
|
|
let symbolic = path.join('/usr', 'bin', this._configuration.name.package);
|
|
let binary = path.join('/usr', 'lib', this._configuration.name.package, this._configuration.binary.linux);
|
|
let content = [
|
|
'#!/bin/sh',
|
|
'set -e',
|
|
'#if [ -x /usr/bin/update-mime ] ; then update-mime ; fi',
|
|
'#if [ -x /usr/bin/update-menus ] ; then update-menus ; fi'
|
|
];
|
|
if(name === 'postinst') {
|
|
content.push(`if [ ! -f ${symbolic} ] ; then ln -s ${binary} ${symbolic} ; fi`);
|
|
}
|
|
if(name === 'postrm') {
|
|
content.push(`if [ -f ${symbolic} ] ; then rm -f ${symbolic} ; fi`);
|
|
}
|
|
this._saveFile(file, content.join(eol), false);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _createChecksumsDEB() {
|
|
console.log('Creating Checksums ...');
|
|
await this._executeCommand(`cd "${this._dirBuildRoot}" && find usr -type f -print0 | xargs -0 md5sum > "DEBIAN/md5sums"`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _createSpecsRPM() {
|
|
console.log('Creating RPM Specification File ...');
|
|
let file = path.join('build', 'specfile.spec');
|
|
let symbolic = path.join('/usr', 'bin', this._configuration.name.package);
|
|
let binary = path.join('/usr', 'lib', this._configuration.name.package, this._configuration.binary.linux);
|
|
let content = [
|
|
'Name: ' + this._configuration.name.package,
|
|
'Version: ' + this._configuration.version,
|
|
'Release: 0',
|
|
'License: ' + this._configuration.license,
|
|
'URL: ' + this._configuration.url,
|
|
'Requires: ' + this._configuration.meta.dependencies.rpm,
|
|
'Summary: ' + this._configuration.description.short,
|
|
'',
|
|
'Autoreq: no',
|
|
'AutoReqProv: no',
|
|
'',
|
|
'%description',
|
|
this._configuration.description.long,
|
|
'',
|
|
'%files',
|
|
`%dir /usr/lib/${this._configuration.name.package}/`,
|
|
await this._executeCommand(`cd "${this._dirBuildRoot}" && find usr -type f -exec echo /{} \\;`),
|
|
'%post',
|
|
`if [ ! -f ${symbolic} ] ; then ln -s ${binary} ${symbolic} ; fi`,
|
|
'',
|
|
'%postun',
|
|
`if [ -f ${symbolic} ] ; then rm -f ${symbolic} ; fi`
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
return file;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Packager for windows platform
|
|
*/
|
|
class ElectronPackagerWindows extends ElectronPackager {
|
|
|
|
/**
|
|
*
|
|
*/
|
|
constructor(configuration) {
|
|
super(configuration);
|
|
this.architectures = {
|
|
'32': {
|
|
is: {
|
|
name: 'i386',
|
|
suffix: 'windows-setup_i386',
|
|
platform: 'win32-ia32'
|
|
},
|
|
zip: {
|
|
name: 'i386',
|
|
suffix: 'windows-portable_i386',
|
|
platform: 'win32-ia32'
|
|
}
|
|
},
|
|
'64': {
|
|
is: {
|
|
name: 'amd64',
|
|
suffix: 'windows-setup_amd64',
|
|
platform: 'win32-x64'
|
|
},
|
|
zip: {
|
|
name: 'amd64',
|
|
suffix: 'windows-portable_amd64',
|
|
platform: 'win32-x64'
|
|
}
|
|
}
|
|
};
|
|
this._architecture = this.architectures[0];
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _dirBuildRoot() {
|
|
return path.join('build', `${this._configuration.name.package}_${this._configuration.version}_${this._architecture.suffix}`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _stagingExecutableDirectory() {
|
|
return this._dirBuildRoot;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture '32' or '64'
|
|
*/
|
|
async build(architecture) {
|
|
await this.buildIS(architecture);
|
|
await this.buildZIP(architecture);
|
|
}
|
|
|
|
/**
|
|
* Create InnoSetup installer
|
|
* @param {string} architecture
|
|
*/
|
|
async buildIS(architecture) {
|
|
this._architecture = this.architectures[architecture].is;
|
|
|
|
await this._validateCommands('7z --help', 'asar --version', 'innosetup-compiler /?');
|
|
|
|
await fs.remove(this._dirBuildRoot);
|
|
await this._bundleElectron(false);
|
|
await this._bundleFFMPEG(this._architecture.name);
|
|
await this._bundleImageMagick(this._architecture.name);
|
|
await this._bundleKindleGenerate(this._architecture.name);
|
|
await this._editResource();
|
|
let setup = this._createScriptIS(architecture === '64');
|
|
|
|
await this._executeCommand(`innosetup-compiler "${setup}"`);
|
|
await fs.remove(setup);
|
|
}
|
|
|
|
/**
|
|
* Create portable archive
|
|
* @param {string} architecture
|
|
*/
|
|
async buildZIP(architecture) {
|
|
this._architecture = this.architectures[architecture].zip;
|
|
|
|
await this._validateCommands('7z --help', 'asar --version');
|
|
|
|
await fs.remove(this._dirBuildRoot);
|
|
await this._bundleElectron(true);
|
|
await this._bundleFFMPEG(this._architecture.name);
|
|
await this._bundleImageMagick(this._architecture.name);
|
|
await this._bundleKindleGenerate(this._architecture.name);
|
|
await this._editResource();
|
|
|
|
let zip = this._dirBuildRoot + '.zip';
|
|
await fs.remove(zip);
|
|
await this._compressArchive('.\\' + this._dirBuildRoot, zip);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {bool} portable
|
|
*/
|
|
async _bundleElectron(portable) {
|
|
console.log('Bundle electron ...');
|
|
let folder = this._stagingExecutableDirectory;
|
|
await this._downloadElectron(this._configuration.version, this._architecture.platform, folder);
|
|
await fs.remove(path.join(folder, 'resources', 'default_app.asar'));
|
|
if(portable) {
|
|
this._saveFile(path.join(folder, this._configuration.binary.windows + '.portable'), 'Delete this File to disable Portable Mode');
|
|
}
|
|
await asar.createPackage(config.src, path.join(folder, 'resources', 'app.asar'));
|
|
await fs.move(path.join(folder, 'electron.exe'), path.join(folder, this._configuration.binary.windows));
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _editResource() {
|
|
let command = [
|
|
path.join('node_modules', 'rcedit', 'bin', 'rcedit.exe'),
|
|
`"${path.join(this._dirBuildRoot, this._configuration.binary.windows)}"`,
|
|
`--set-version-string "ProductName" "${this._configuration.name.product}"`,
|
|
`--set-version-string "CompanyName" ""`,
|
|
`--set-version-string "LegalCopyright" "${(new Date()).getFullYear()}"`,
|
|
`--set-version-string "FileDescription" "${this._configuration.description.short}"`,
|
|
`--set-version-string "InternalName" ""`,
|
|
`--set-version-string "OriginalFilename" "${this._configuration.binary.windows}"`,
|
|
`--set-file-version "${this._configuration.version}"`,
|
|
`--set-product-version "${this._configuration.version}"`,
|
|
`--set-icon "redist\\iss\\app.ico"`
|
|
].join(' ');
|
|
await this._executeCommand(command);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {bool} is64
|
|
*/
|
|
_createScriptIS(is64) {
|
|
console.log('Creating InnoSetup Script ...');
|
|
let file = path.join('build', 'setup.iss');
|
|
let content = [
|
|
'[Setup]',
|
|
'AppName=' + this._configuration.name.product,
|
|
'AppVerName=' + this._configuration.name.product,
|
|
'AppVersion=' + this._configuration.version,
|
|
'VersionInfoVersion=' + this._configuration.version,
|
|
'AppPublisher=' + this._configuration.author,
|
|
'AppPublisherURL=' + this._configuration.url,
|
|
(is64 ? '' : ';') + 'ArchitecturesInstallIn64BitMode=x64',
|
|
'DisableWelcomePage=yes',
|
|
'DefaultDirName=' + path.join('{pf}', this._configuration.name.product),
|
|
'DisableProgramGroupPage=yes',
|
|
//'DefaultGroupName=' + this._configuration.name.product,
|
|
'DisableReadyPage=yes',
|
|
'UninstallDisplayIcon=' + path.join('{app}', this._configuration.binary.windows),
|
|
//'WizardImageFile=compiler:wizmodernimage.bmp',
|
|
//'WizardSmallImageFile=compiler:wizmodernsmallimage.bmp',
|
|
'WizardImageFile=' + path.join('..', 'redist', 'iss', 'wizard.bmp'),
|
|
'WizardSmallImageFile=' + path.join('..', 'redist', 'iss', 'wizard-small.bmp'),
|
|
'OutputDir=.',
|
|
'OutputBaseFilename=' + path.basename(this._dirBuildRoot),
|
|
'ChangesEnvironment=yes',
|
|
'',
|
|
'[Tasks]',
|
|
'Name: shortcuts; Description: "All"; GroupDescription: "Create Shortcuts:";',
|
|
'Name: shortcuts\\desktop; Description: "Desktop"; GroupDescription: "Create Shortcuts:";',
|
|
'Name: shortcuts\\startmenu; Description: "Startmenu Programs"; GroupDescription: "Create Shortcuts:"; Flags: unchecked',
|
|
'',
|
|
'[Files]',
|
|
`Source: ${path.basename(this._dirBuildRoot)}\\*; DestDir: {app}; Flags: recursesubdirs`,
|
|
'',
|
|
'[UninstallDelete]',
|
|
'Name: {app}; Type: filesandordirs',
|
|
'',
|
|
'[Icons]',
|
|
`Name: "{commondesktop}\\${this._configuration.name.product}"; Tasks: shortcuts\\desktop; Filename: "{app}\\${this._configuration.binary.windows}";`,
|
|
`Name: "{commonstartmenu}\\${this._configuration.name.product}"; Tasks: shortcuts\\startmenu; Filename: "{app}\\${this._configuration.binary.windows}";`
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
return file;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Packager for windows platform
|
|
*/
|
|
class ElectronPackagerDarwin extends ElectronPackager {
|
|
|
|
/**
|
|
*
|
|
*/
|
|
constructor(configuration) {
|
|
super(configuration);
|
|
this.architectures = {
|
|
'64': {
|
|
dmg: {
|
|
name: 'amd64',
|
|
suffix: 'macos_amd64',
|
|
platform: 'darwin-x64'
|
|
}
|
|
}
|
|
};
|
|
this._architecture = this.architectures[0];
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _dirBuildRoot() {
|
|
return path.join('build', `${this._configuration.name.package}_${this._configuration.version}_${this._architecture.suffix}`);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
get _stagingExecutableDirectory() {
|
|
return path.join(this._dirBuildRoot, this._configuration.name.product + '.app', 'Contents', 'MacOS');
|
|
}
|
|
|
|
_wait(milliseconds) {
|
|
return new Promise(resolve => {
|
|
setTimeout(() => resolve(), milliseconds);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} architecture '64'
|
|
*/
|
|
async build(architecture) {
|
|
await this.buildDMG(architecture);
|
|
}
|
|
|
|
/**
|
|
* Create InnoSetup installer
|
|
* @param {string} architecture
|
|
*/
|
|
async buildDMG(architecture) {
|
|
this._architecture = this.architectures[architecture].dmg;
|
|
|
|
await this._validateCommands('hdiutil info');
|
|
|
|
await fs.remove(this._dirBuildRoot);
|
|
await this._bundleElectron(false);
|
|
await this._bundleFFMPEG(this._architecture.name);
|
|
await this._bundleImageMagick(this._architecture.name);
|
|
await this._bundleKindleGenerate(this._architecture.name);
|
|
await this._createPList();
|
|
|
|
let dmg = this._dirBuildRoot + '.dmg';
|
|
await fs.remove(dmg);
|
|
let tmp = this._dirBuildRoot + '.tmp';
|
|
await fs.remove(tmp + '.dmg');
|
|
await this._executeCommand(`hdiutil create -volname "${this._configuration.name.product}" -srcfolder "${this._dirBuildRoot}" -fs "HFS+" -fsargs "-c c=64,a=16,e=16" -format "UDRW" "${tmp}"`);
|
|
let device = (await this._executeCommand(`hdiutil attach -readwrite -noverify -noautoopen "${tmp}.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}'`)).trim();
|
|
await this._wait(5000);
|
|
await this._executeCommand(`echo '${this._appleScript}' | osascript`);
|
|
await this._executeCommand(`chmod -Rf go-w "/Volumes/${this._configuration.name.product}"`);
|
|
await this._executeCommand(`sync`);
|
|
await this._wait(5000);
|
|
await this._executeCommand(`hdiutil detach "${device}"`);
|
|
await this._wait(5000);
|
|
await this._executeCommand(`hdiutil convert "${tmp}.dmg" -format "UDZO" -imagekey zlib-level=9 -o "${dmg}"`);
|
|
await fs.remove(tmp + '.dmg');
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async _bundleElectron() {
|
|
console.log('Bundle electron ...');
|
|
let folder = path.join(this._dirBuildRoot, 'Electron.app', 'Contents');
|
|
await this._downloadElectron(this._configuration.version, this._architecture.platform, this._dirBuildRoot);
|
|
await fs.remove(path.join(folder, 'Resources', 'default_app.asar'));
|
|
await asar.createPackage(config.src, path.join(folder, 'Resources', 'app.asar'));
|
|
await fs.move(path.join(folder, 'MacOS', 'Electron'), path.join(folder, 'MacOS', this._configuration.binary.darwin));
|
|
await fs.remove(path.join(folder, 'Resources', 'electron.icns'));
|
|
await fs.copy('res/icon.icns', path.join(folder, 'Resources', this._configuration.binary.darwin + '.icns'));
|
|
await fs.ensureDir(path.join(this._dirBuildRoot, '.images'));
|
|
await fs.copy('res/OSXSetup.png', path.join(this._dirBuildRoot, '.images', 'OSXSetup.png'));
|
|
await fs.move(path.join(this._dirBuildRoot, 'Electron.app'), path.join(this._dirBuildRoot, this._configuration.name.product + '.app'));
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
_createPList() {
|
|
console.log('Creating P-List Info ...');
|
|
let file = path.join(this._dirBuildRoot, this._configuration.name.product + '.app', 'Contents', 'Info.plist');
|
|
let content = [
|
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
'<plist version="1.0">',
|
|
'<dict>',
|
|
' <key>CFBundleDisplayName</key>',
|
|
` <string>${this._configuration.name.product}</string>`,
|
|
// execytable is required
|
|
' <key>CFBundleExecutable</key>',
|
|
` <string>${this._configuration.binary.darwin}</string>`,
|
|
// icon file is required
|
|
' <key>CFBundleIconFile</key>',
|
|
` <string>${this._configuration.binary.darwin}.icns</string>`,
|
|
' <key>CFBundleIdentifier</key>',
|
|
` <string>${this._configuration.url}</string>`,
|
|
' <key>CFBundleName</key>',
|
|
` <string>${this._configuration.name.product}</string>`,
|
|
' <key>CFBundlePackageType</key>',
|
|
' <string>APPL</string>',
|
|
' <key>CFBundleShortVersionString</key>',
|
|
` <string>${this._configuration.version}</string>`,
|
|
' <key>CFBundleVersion</key>',
|
|
` <string>${this._configuration.version}</string>`,
|
|
' <key>LSMinimumSystemVersion</key>',
|
|
' <string>10.10.0</string>',
|
|
' <key>NSHighResolutionCapable</key>',
|
|
' <true/>',
|
|
'</dict>',
|
|
'</plist>'
|
|
];
|
|
this._saveFile(file, content.join(eol), false);
|
|
return file;
|
|
}
|
|
|
|
get _appleScript() {
|
|
return `
|
|
tell application "Finder"
|
|
tell disk "${this._configuration.name.product}"
|
|
open
|
|
set current view of container window to icon view
|
|
set toolbar visible of container window to false
|
|
set statusbar visible of container window to false
|
|
set the bounds of container window to {100, 100, 560, 620}
|
|
set theViewOptions to the icon view options of container window
|
|
set arrangement of theViewOptions to not arranged
|
|
set icon size of theViewOptions to 64
|
|
set background picture of theViewOptions to file ".images:OSXSetup.png"
|
|
make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
|
|
set position of item "'${this._configuration.name.product}'" of container window to {360, 180}
|
|
set position of item "Applications" of container window to {360, 390}
|
|
set position of item ".fseventsd" of container window to {180, 620}
|
|
set position of item ".images" of container window to {280, 620}
|
|
update without registering applications
|
|
delay 5
|
|
close
|
|
end tell
|
|
end tell
|
|
`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
async function main() {
|
|
if(process.platform === 'win32') {
|
|
let packager = new ElectronPackagerWindows(config);
|
|
await packager.buildIS('64');
|
|
await packager.buildIS('32');
|
|
await packager.buildZIP('64');
|
|
await packager.buildZIP('32');
|
|
}
|
|
if(process.platform === 'linux') {
|
|
let packager = new ElectronPackagerLinux(config);
|
|
await packager.buildDEB('64');
|
|
await packager.buildDEB('32');
|
|
await packager.buildDEB('ARMv8');
|
|
await packager.buildDEB('ARMHF');
|
|
await packager.buildDEB('ARMv7');
|
|
await packager.buildRPM('64');
|
|
await packager.buildRPM('32');
|
|
}
|
|
if(process.platform === 'darwin') {
|
|
let packager = new ElectronPackagerDarwin(config);
|
|
await packager.buildDMG('64');
|
|
}
|
|
}
|
|
|
|
// exit application as soon as any uncaught exception is thrown
|
|
process.on('unhandledRejection', error => { throw error; });
|
|
main(); |