const { promisify } = require('util'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const zlib = require('zlib'); const makeDir = require('make-dir'); const { pipeline } = require('stream'); const fse = require('fs-extra'); const electronBuilder = require('electron-builder'); const dotenv = require('dotenv'); dotenv.config(); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); const type = process.argv[2]; const getFiles = async dir => { const subdirs = await readdir(dir); const files = await Promise.all( subdirs.map(async subdir => { const res = path.resolve(dir, subdir); return (await stat(res)).isDirectory() ? getFiles(res) : res; }) ); return files.reduce((a, f) => a.concat(f), []); }; const getSha1 = async filePath => { // Calculate sha1 on original file const algorithm = 'sha1'; const shasum = crypto.createHash(algorithm); const s = fs.ReadStream(filePath); s.on('data', data => { shasum.update(data); }); const hash = await new Promise(resolve => { s.on('end', () => { resolve(shasum.digest('hex')); }); }); return hash; }; const winReleaseFolder = path.resolve( __dirname, '../', './release', `win-unpacked` ); const deployFolder = path.resolve(__dirname, '../', 'deploy'); const createDeployFiles = async () => { const files = await getFiles(winReleaseFolder); const mappedFiles = await Promise.all( files.map(async v => { // Compress const hash = await getSha1(v); const gzip = zlib.createGzip(); const source = fs.createReadStream(v); const isAppAsar = path.basename(v) === 'app.asar'; const destinationPath = path.join( deployFolder, `win_${path.relative(winReleaseFolder, v).replace(path.sep, '-')}.gz` ); await makeDir(path.dirname(destinationPath)); const destination = fs.createWriteStream(destinationPath); await new Promise((resolve, reject) => { pipeline(source, gzip, destination, err => { if (err) { reject(); } destination.close(); resolve(); }); }); const compressedSha1 = await getSha1(destinationPath); return { file: path.relative(winReleaseFolder, v).split(path.sep), sha1: hash, compressedFile: path.basename(destinationPath), compressedSha1, ...(isAppAsar && { isAppAsar: true }) }; }) ); await fs.promises.writeFile( path.join(deployFolder, `win32_latest.json`), JSON.stringify(mappedFiles) ); }; const extraFiles = []; let sevenZipPath = null; if (process.platform === 'win32') { sevenZipPath = 'node_modules/7zip-bin/win/x64/7za.exe'; extraFiles.push({ from: 'vcredist/', to: './', filter: '**/*' }); } else if (process.platform === 'linux') { if (process.arch === 'arm64') { sevenZipPath = 'node_modules/7zip-bin/linux/arm64/7za'; } else if (process.arch === 'arm') { sevenZipPath = 'node_modules/7zip-bin/linux/arm/7za'; } else { sevenZipPath = 'node_modules/7zip-bin/linux/x64/7za'; } } else if (process.platform === 'darwin') { sevenZipPath = 'node_modules/7zip-bin/mac/x64/7za'; } extraFiles.push({ from: sevenZipPath, to: './' }); const commonConfig = { publish: 'never', config: { generateUpdatesFilesForAllChannels: true, npmRebuild: false, productName: 'GDLauncher', appId: 'org.gorilladevs.GDLauncher', files: [ '!node_modules/**/*', 'build/**/*', 'package.json', 'public/icon.png' ], extraFiles, asar: { smartUnpack: false }, dmg: { contents: [ { x: 130, y: 220 }, { x: 410, y: 220, type: 'link', path: '/Applications' } ] }, nsisWeb: { oneClick: true, installerIcon: './public/icon.ico', uninstallerIcon: './public/icon.ico', installerHeader: './public/installerHeader.bmp', installerSidebar: './public/installerSidebar.bmp', installerHeaderIcon: './public/icon.ico', deleteAppDataOnUninstall: true, allowToChangeInstallationDirectory: false, perMachine: false, differentialPackage: true, include: './public/installer.nsh' }, mac: { entitlements: './entitlements.mac.plist', entitlementsInherit: './entitlements.mac.plist' }, /* eslint-disable */ artifactName: `${'${productName}'}-${'${os}'}-${ process.argv[2] }.${'${ext}'}`, /* eslint-enable */ linux: { category: 'Game', icon: 'public/linux-icons/' }, directories: { buildResources: 'public', output: 'release' }, protocols: [ { name: 'gdlauncher', role: 'Viewer', schemes: ['gdlauncher'] } ] }, ...(process.platform === 'linux' && { linux: type === 'setup' ? ['appimage', 'zip', 'deb', 'rpm'].map(x => `${x}:${process.arch}`) : [`snap:${process.arch}`] }), ...(process.platform === 'win32' && { win: [type === 'setup' ? 'nsis:x64' : 'zip:x64'] }), ...(process.platform === 'darwin' && { mac: type === 'setup' ? ['dmg:x64'] : [] }) }; const main = async () => { const releasesFolder = path.resolve(__dirname, '../', './release'); await fse.remove(releasesFolder); await makeDir(deployFolder); await electronBuilder.build(commonConfig); if (type === 'portable' && process.platform === 'win32') { await createDeployFiles(); } // Copy all other files to deploy folder const { productName } = commonConfig.config; let linuxyml = ''; if (process.arch === 'x64') { linuxyml = 'latest-linux.yml'; } else { linuxyml = `latest-linux-${process.arch}.yml`; } const allFiles = { setup: { darwin: [ `${productName}-mac-${type}.dmg`, `${productName}-mac-${type}.dmg.blockmap`, 'latest-mac.yml' ], win32: [ path.join(`${productName}-win-${type}.exe`), path.join(`${productName}-win-${type}.exe.blockmap`), path.join('latest.yml') ], linux: [ `${productName}-linux-${type}.zip`, `${productName}-linux-${type}.AppImage`, `${productName}-linux-${type}.deb`, `${productName}-linux-${type}.rpm`, `${linuxyml}` ] }, portable: { darwin: [], win32: [`${productName}-win-${type}.zip`], linux: [`${productName}-linux-${type}.snap`] } }; const filesToMove = allFiles[type][process.platform]; await Promise.all( filesToMove.map(async file => { const stats = await fs.promises.stat(path.join(releasesFolder, file)); if (stats.isFile()) { await fse.move( path.join(releasesFolder, file), path.join(deployFolder, file.replace('nsis-web', '')) ); } }) ); await fse.remove(releasesFolder); }; main().catch(err => { console.error(err); process.exit(1); });