'use strict'; const assert = require('assert'); const childProcess = require('child_process'); const electronPackager = require('electron-packager'); const fs = require('fs-extra'); const hostArch = require('@electron/get').getHostArch; const includePathInPackagedApp = require('./include-path-in-packaged-app'); const getLicenseText = require('./get-license-text'); const path = require('path'); const spawnSync = require('./spawn-sync'); const template = require('lodash.template'); const CONFIG = require('../config'); const HOST_ARCH = hostArch(); module.exports = function() { const appName = getAppName(); console.log( `Running electron-packager on ${ CONFIG.intermediateAppPath } with app name "${appName}"` ); return runPackager({ appBundleId: 'com.github.atom', appCopyright: `Copyright © 2014-${new Date().getFullYear()} GitHub, Inc. All rights reserved.`, appVersion: CONFIG.appMetadata.version, arch: process.platform === 'darwin' ? 'x64' : HOST_ARCH, // OS X is 64-bit only asar: { unpack: buildAsarUnpackGlobExpression() }, buildVersion: CONFIG.appMetadata.version, derefSymlinks: false, download: { cache: CONFIG.electronDownloadPath }, dir: CONFIG.intermediateAppPath, electronVersion: CONFIG.appMetadata.electronVersion, extendInfo: path.join( CONFIG.repositoryRootPath, 'resources', 'mac', 'atom-Info.plist' ), helperBundleId: 'com.github.atom.helper', icon: path.join( CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'atom' ), name: appName, out: CONFIG.buildOutputPath, overwrite: true, platform: process.platform, // Atom doesn't have devDependencies, but if prune is true, it will delete the non-standard packageDependencies. prune: false, win32metadata: { CompanyName: 'GitHub, Inc.', FileDescription: 'Atom', ProductName: CONFIG.appName } }).then(packagedAppPath => { let bundledResourcesPath; if (process.platform === 'darwin') { bundledResourcesPath = path.join( packagedAppPath, 'Contents', 'Resources' ); setAtomHelperVersion(packagedAppPath); } else if (process.platform === 'linux') { bundledResourcesPath = path.join(packagedAppPath, 'resources'); chmodNodeFiles(packagedAppPath); } else { bundledResourcesPath = path.join(packagedAppPath, 'resources'); } return copyNonASARResources(packagedAppPath, bundledResourcesPath).then( () => { console.log(`Application bundle created at ${packagedAppPath}`); return packagedAppPath; } ); }); }; function copyNonASARResources(packagedAppPath, bundledResourcesPath) { console.log(`Copying non-ASAR resources to ${bundledResourcesPath}`); fs.copySync( path.join( CONFIG.repositoryRootPath, 'apm', 'node_modules', 'atom-package-manager' ), path.join(bundledResourcesPath, 'app', 'apm'), { filter: includePathInPackagedApp } ); if (process.platform !== 'win32') { // Existing symlinks on user systems point to an outdated path, so just symlink it to the real location of the apm binary. // TODO: Change command installer to point to appropriate path and remove this fallback after a few releases. fs.symlinkSync( path.join('..', '..', 'bin', 'apm'), path.join( bundledResourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm' ) ); fs.copySync( path.join(CONFIG.repositoryRootPath, 'atom.sh'), path.join(bundledResourcesPath, 'app', 'atom.sh') ); } if (process.platform === 'darwin') { fs.copySync( path.join(CONFIG.repositoryRootPath, 'resources', 'mac', 'file.icns'), path.join(bundledResourcesPath, 'file.icns') ); } else if (process.platform === 'linux') { fs.copySync( path.join( CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png', '1024.png' ), path.join(packagedAppPath, 'atom.png') ); } else if (process.platform === 'win32') { [ 'atom.sh', 'atom.js', 'apm.cmd', 'apm.sh', 'file.ico', 'folder.ico' ].forEach(file => fs.copySync( path.join(CONFIG.repositoryRootPath, 'resources', 'win', file), path.join(bundledResourcesPath, 'cli', file) ) ); // Customize atom.cmd for the channel-specific atom.exe name (e.g. atom-beta.exe) generateAtomCmdForChannel(bundledResourcesPath); } console.log(`Writing LICENSE.md to ${bundledResourcesPath}`); return getLicenseText().then(licenseText => { fs.writeFileSync( path.join(bundledResourcesPath, 'LICENSE.md'), licenseText ); }); } function setAtomHelperVersion(packagedAppPath) { const frameworksPath = path.join(packagedAppPath, 'Contents', 'Frameworks'); const helperPListPath = path.join( frameworksPath, 'Atom Helper.app', 'Contents', 'Info.plist' ); console.log(`Setting Atom Helper Version for ${helperPListPath}`); spawnSync('/usr/libexec/PlistBuddy', [ '-c', `Add CFBundleVersion string ${CONFIG.appMetadata.version}`, helperPListPath ]); spawnSync('/usr/libexec/PlistBuddy', [ '-c', `Add CFBundleShortVersionString string ${CONFIG.appMetadata.version}`, helperPListPath ]); } function chmodNodeFiles(packagedAppPath) { console.log(`Changing permissions for node files in ${packagedAppPath}`); childProcess.execSync( `find "${packagedAppPath}" -type f -name *.node -exec chmod a-x {} \\;` ); } function buildAsarUnpackGlobExpression() { const unpack = [ '*.node', 'ctags-config', 'ctags-darwin', 'ctags-linux', 'ctags-win32.exe', path.join('**', 'node_modules', 'spellchecker', '**'), path.join('**', 'node_modules', 'dugite', 'git', '**'), path.join('**', 'node_modules', 'github', 'bin', '**'), path.join('**', 'node_modules', 'vscode-ripgrep', 'bin', '**'), path.join('**', 'resources', 'atom.png') ]; return `{${unpack.join(',')}}`; } function getAppName() { if (process.platform === 'darwin') { return CONFIG.appName; } else if (process.platform === 'win32') { return CONFIG.channel === 'stable' ? 'atom' : `atom-${CONFIG.channel}`; } else { return 'atom'; } } async function runPackager(options) { const packageOutputDirPaths = await electronPackager(options); assert( packageOutputDirPaths.length === 1, 'Generated more than one electron application!' ); return renamePackagedAppDir(packageOutputDirPaths[0]); } function renamePackagedAppDir(packageOutputDirPath) { let packagedAppPath; if (process.platform === 'darwin') { const appBundleName = getAppName() + '.app'; packagedAppPath = path.join(CONFIG.buildOutputPath, appBundleName); if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); fs.renameSync( path.join(packageOutputDirPath, appBundleName), packagedAppPath ); } else if (process.platform === 'linux') { const appName = CONFIG.channel !== 'stable' ? `atom-${CONFIG.channel}` : 'atom'; let architecture; if (HOST_ARCH === 'ia32') { architecture = 'i386'; } else if (HOST_ARCH === 'x64') { architecture = 'amd64'; } else { architecture = HOST_ARCH; } packagedAppPath = path.join( CONFIG.buildOutputPath, `${appName}-${CONFIG.appMetadata.version}-${architecture}` ); if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); fs.renameSync(packageOutputDirPath, packagedAppPath); } else { packagedAppPath = path.join(CONFIG.buildOutputPath, CONFIG.appName); if (process.platform === 'win32' && HOST_ARCH !== 'ia32') { packagedAppPath += ` ${process.arch}`; } if (fs.existsSync(packagedAppPath)) fs.removeSync(packagedAppPath); fs.renameSync(packageOutputDirPath, packagedAppPath); } return packagedAppPath; } function generateAtomCmdForChannel(bundledResourcesPath) { const atomCmdTemplate = fs.readFileSync( path.join(CONFIG.repositoryRootPath, 'resources', 'win', 'atom.cmd') ); const atomCmdContents = template(atomCmdTemplate)({ atomExeName: CONFIG.executableName }); fs.writeFileSync( path.join(bundledResourcesPath, 'cli', 'atom.cmd'), atomCmdContents ); }