diff --git a/__tests__/commands/global.js b/__tests__/commands/global.js index d867aefbbb..4147aacba9 100644 --- a/__tests__/commands/global.js +++ b/__tests__/commands/global.js @@ -24,11 +24,7 @@ const runGlobal = buildRun.bind( ); function getGlobalPath(prefix, name): string { - if (process.platform === 'win32') { - return path.join(prefix, name); - } else { - return path.join(prefix, 'bin', name); - } + return path.join(prefix, 'bin', name); } function getTempGlobalFolder(): string { diff --git a/src/cli/commands/create.js b/src/cli/commands/create.js index 26384e7059..19e8b6af08 100644 --- a/src/cli/commands/create.js +++ b/src/cli/commands/create.js @@ -26,7 +26,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg await runGlobal(config, reporter, {}, ['add', packageName]); - const binFolder = getBinFolder(config, {}); + const binFolder = await getBinFolder(config, {}); const command = path.resolve(binFolder, path.basename(commandName)); await child.spawn(command, [...rest], {stdio: `inherit`, shell: true}); diff --git a/src/cli/commands/global.js b/src/cli/commands/global.js index 441ced0f51..a7aadc516d 100644 --- a/src/cli/commands/global.js +++ b/src/cli/commands/global.js @@ -14,8 +14,11 @@ import {run as runRemove} from './remove.js'; import {run as runUpgrade} from './upgrade.js'; import {run as runUpgradeInteractive} from './upgrade-interactive.js'; import {linkBin} from '../../package-linker.js'; +import {POSIX_GLOBAL_PREFIX, FALLBACK_GLOBAL_PREFIX} from '../../constants.js'; import * as fs from '../../util/fs.js'; +const nativeFs = require('fs'); + class GlobalAdd extends Add { maybeOutputSaveTree(): Promise { for (const pattern of this.addedPatterns) { @@ -69,44 +72,44 @@ async function getBins(config: Config): Promise> { return paths; } -function getGlobalPrefix(config: Config, flags: Object): string { +async function getGlobalPrefix(config: Config, flags: Object): Promise { if (flags.prefix) { return flags.prefix; } else if (config.getOption('prefix')) { return String(config.getOption('prefix')); } else if (process.env.PREFIX) { return process.env.PREFIX; - } else if (process.platform === 'win32') { + } + + let prefix = FALLBACK_GLOBAL_PREFIX; + if (process.platform === 'win32') { + // %LOCALAPPDATA%\Yarn --> C:\Users\Alice\AppData\Local\Yarn if (process.env.LOCALAPPDATA) { - return path.join(process.env.LOCALAPPDATA, 'Yarn', 'bin'); + prefix = path.join(process.env.LOCALAPPDATA, 'Yarn'); } - // c:\node\node.exe --> prefix=c:\node\ - return path.dirname(process.execPath); } else { - // /usr/local/bin/node --> prefix=/usr/local - let prefix = path.dirname(path.dirname(process.execPath)); - - // destdir only is respected on Unix - if (process.env.DESTDIR) { - prefix = path.join(process.env.DESTDIR, prefix); + prefix = POSIX_GLOBAL_PREFIX; + } + try { + await fs.access(path.join(prefix, 'bin'), (nativeFs.constants || nativeFs).W_OK); + } catch (err) { + if (err.code === 'EACCES') { + prefix = FALLBACK_GLOBAL_PREFIX; + } else { + throw err; } - - return prefix; } + return prefix; } -export function getBinFolder(config: Config, flags: Object): string { - const prefix = getGlobalPrefix(config, flags); - if (process.platform === 'win32') { - return prefix; - } else { - return path.resolve(prefix, 'bin'); - } +export async function getBinFolder(config: Config, flags: Object): Promise { + const prefix = await getGlobalPrefix(config, flags); + return path.resolve(prefix, 'bin'); } async function initUpdateBins(config: Config, reporter: Reporter, flags: Object): Promise<() => Promise> { const beforeBins = await getBins(config); - const binFolder = getBinFolder(config, flags); + const binFolder = await getBinFolder(config, flags); function throwPermError(err: Error & {[code: string]: string}, dest: string) { if (err.code === 'EACCES') { @@ -205,8 +208,8 @@ const {run, setFlags: _setFlags} = buildSubCommands('global', { await updateBins(); }, - bin(config: Config, reporter: Reporter, flags: Object, args: Array) { - reporter.log(getBinFolder(config, flags)); + async bin(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { + reporter.log(await getBinFolder(config, flags)); }, async ls(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { diff --git a/src/cli/commands/link.js b/src/cli/commands/link.js index 1eb2537108..693f1f4b47 100644 --- a/src/cli/commands/link.js +++ b/src/cli/commands/link.js @@ -63,7 +63,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg // If there is a `bin` defined in the package.json, // link each bin to the global bin if (manifest.bin) { - const globalBinFolder = getGlobalBinFolder(config, flags); + const globalBinFolder = await getGlobalBinFolder(config, flags); for (const binName in manifest.bin) { const binSrc = manifest.bin[binName]; const binSrcLoc = path.join(linkLoc, binSrc); diff --git a/src/cli/commands/unlink.js b/src/cli/commands/unlink.js index b772cc0462..131704aded 100644 --- a/src/cli/commands/unlink.js +++ b/src/cli/commands/unlink.js @@ -40,7 +40,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg // If there is a `bin` defined in the package.json, // link each bin to the global bin if (manifest.bin) { - const globalBinFolder = getGlobalBinFolder(config, flags); + const globalBinFolder = await getGlobalBinFolder(config, flags); for (const binName in manifest.bin) { const binDestLoc = path.join(globalBinFolder, binName); if (await fs.exists(binDestLoc)) { diff --git a/src/constants.js b/src/constants.js index 2ab648dda7..1caf9bb7fc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -59,6 +59,9 @@ export const CONFIG_DIRECTORY = getDirectory('config'); export const LINK_REGISTRY_DIRECTORY = path.join(CONFIG_DIRECTORY, 'link'); export const GLOBAL_MODULE_DIRECTORY = path.join(CONFIG_DIRECTORY, 'global'); +export const POSIX_GLOBAL_PREFIX = '/usr/local'; +export const FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn'); + export const META_FOLDER = '.yarn-meta'; export const INTEGRITY_FILENAME = '.yarn-integrity'; export const LOCKFILE_FILENAME = 'yarn.lock'; diff --git a/src/util/execute-lifecycle-script.js b/src/util/execute-lifecycle-script.js index d5b58fb847..bdfe09dae9 100644 --- a/src/util/execute-lifecycle-script.js +++ b/src/util/execute-lifecycle-script.js @@ -144,7 +144,7 @@ export async function executeLifecycleScript( // Add global bin folder if it is not present already, as some packages depend // on a globally-installed version of node-gyp. - const globalBin = getGlobalBinFolder(config, {}); + const globalBin = await getGlobalBinFolder(config, {}); if (pathParts.indexOf(globalBin) === -1) { pathParts.unshift(globalBin); }