'use strict' const fs = require('fs-extra') const handleTilde = require('./handle-tilde') const path = require('path') const template = require('lodash.template') const startCase = require('lodash.startcase') const execSync = require('child_process').execSync const CONFIG = require('../config') function install (installationDirPath, packagedAppFileName, packagedAppPath) { if (fs.existsSync(installationDirPath)) { console.log(`Removing previously installed "${packagedAppFileName}" at "${installationDirPath}"`) fs.removeSync(installationDirPath) } console.log(`Installing "${packagedAppFileName}" at "${installationDirPath}"`) fs.copySync(packagedAppPath, installationDirPath) } /** * Finds the path to the base directory of the icon default icon theme * This follows the freedesktop Icon Theme Specification: * https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#install_icons * and the XDG Base Directory Specification: * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables */ function findBaseIconThemeDirPath () { const defaultBaseIconThemeDir = '/usr/share/icons/hicolor' const dataDirsString = process.env.XDG_DATA_DIRS if (dataDirsString) { const dataDirs = dataDirsString.split(path.delimiter) if (dataDirs.includes('/usr/share/') || dataDirs.includes('/usr/share')) { return defaultBaseIconThemeDir } else { return path.join(dataDirs[0], 'icons', 'hicolor') } } else { return defaultBaseIconThemeDir } } module.exports = function (packagedAppPath, installDir) { const packagedAppFileName = path.basename(packagedAppPath) if (process.platform === 'darwin') { const installPrefix = installDir !== '' ? handleTilde(installDir) : path.join(path.sep, 'Applications') const installationDirPath = path.join(installPrefix, packagedAppFileName) install(installationDirPath, packagedAppFileName, packagedAppPath) } else if (process.platform === 'win32') { const installPrefix = installDir !== '' ? installDir : process.env.LOCALAPPDATA const installationDirPath = path.join(installPrefix, packagedAppFileName, 'app-dev') try { install(installationDirPath, packagedAppFileName, packagedAppPath) } catch (e) { console.log(`Administrator elevation required to install into "${installationDirPath}"`) const fsAdmin = require('fs-admin') return new Promise((resolve, reject) => { fsAdmin.recursiveCopy(packagedAppPath, installationDirPath, (error) => { error ? reject(error) : resolve() }) }) } } else { const atomExecutableName = CONFIG.channel === 'stable' ? 'atom' : 'atom-' + CONFIG.channel const apmExecutableName = CONFIG.channel === 'stable' ? 'apm' : 'apm-' + CONFIG.channel const appName = CONFIG.channel === 'stable' ? 'Atom' : startCase('Atom ' + CONFIG.channel) const appDescription = CONFIG.appMetadata.description const prefixDirPath = installDir !== '' ? handleTilde(installDir) : path.join('/usr', 'local') const shareDirPath = path.join(prefixDirPath, 'share') const installationDirPath = path.join(shareDirPath, atomExecutableName) const applicationsDirPath = path.join(shareDirPath, 'applications') const binDirPath = path.join(prefixDirPath, 'bin') fs.mkdirpSync(applicationsDirPath) fs.mkdirpSync(binDirPath) install(installationDirPath, packagedAppFileName, packagedAppPath) { // Install icons const baseIconThemeDirPath = findBaseIconThemeDirPath() const fullIconName = atomExecutableName + '.png' let existingIconsFound = false fs.readdirSync(baseIconThemeDirPath).forEach(size => { const iconPath = path.join(baseIconThemeDirPath, size, 'apps', fullIconName) if (fs.existsSync(iconPath)) { if (!existingIconsFound) { console.log(`Removing existing icons from "${baseIconThemeDirPath}"`) } existingIconsFound = true fs.removeSync(iconPath) } }) console.log(`Installing icons at "${baseIconThemeDirPath}"`) const appIconsPath = path.join(CONFIG.repositoryRootPath, 'resources', 'app-icons', CONFIG.channel, 'png') fs.readdirSync(appIconsPath).forEach(imageName => { if (/\.png$/.test(imageName)) { const size = path.basename(imageName, '.png') const iconPath = path.join(appIconsPath, imageName) fs.copySync(iconPath, path.join(baseIconThemeDirPath, `${size}x${size}`, 'apps', fullIconName)) } }) console.log(`Updating icon cache for "${baseIconThemeDirPath}"`) try { execSync(`gtk-update-icon-cache ${baseIconThemeDirPath} --force`) } catch (e) {} } { // Install xdg desktop file const desktopEntryPath = path.join(applicationsDirPath, `${atomExecutableName}.desktop`) if (fs.existsSync(desktopEntryPath)) { console.log(`Removing existing desktop entry file at "${desktopEntryPath}"`) fs.removeSync(desktopEntryPath) } console.log(`Writing desktop entry file at "${desktopEntryPath}"`) const desktopEntryTemplate = fs.readFileSync(path.join(CONFIG.repositoryRootPath, 'resources', 'linux', 'atom.desktop.in')) const desktopEntryContents = template(desktopEntryTemplate)({ appName, appFileName: atomExecutableName, description: appDescription, installDir: prefixDirPath, iconPath: atomExecutableName }) fs.writeFileSync(desktopEntryPath, desktopEntryContents) } { // Add atom executable to the PATH const atomBinDestinationPath = path.join(binDirPath, atomExecutableName) if (fs.existsSync(atomBinDestinationPath)) { console.log(`Removing existing executable at "${atomBinDestinationPath}"`) fs.removeSync(atomBinDestinationPath) } console.log(`Copying atom.sh to "${atomBinDestinationPath}"`) fs.copySync(path.join(CONFIG.repositoryRootPath, 'atom.sh'), atomBinDestinationPath) } { // Link apm executable to the PATH const apmBinDestinationPath = path.join(binDirPath, apmExecutableName) try { fs.lstatSync(apmBinDestinationPath) console.log(`Removing existing executable at "${apmBinDestinationPath}"`) fs.removeSync(apmBinDestinationPath) } catch (e) { } console.log(`Symlinking apm to "${apmBinDestinationPath}"`) fs.symlinkSync(path.join('..', 'share', atomExecutableName, 'resources', 'app', 'apm', 'node_modules', '.bin', 'apm'), apmBinDestinationPath) } console.log(`Changing permissions to 755 for "${installationDirPath}"`) fs.chmodSync(installationDirPath, '755') } return Promise.resolve() }