Skip to content

Commit 2134f5b

Browse files
KishanBagariaBYK
authored andcommitted
Fix: Fix yarn global prefix directory (#3721)
**Sumary** Refs #2064. Uses `%LOCALAPPDATA%\Yarn\bin` and `/usr/local/bin` on POSIX systems if it's writeable, falling back to `~/.yarn/bin` if it is not. **Test plan** N/A. Should add automated tests.
1 parent f7aa742 commit 2134f5b

File tree

7 files changed

+34
-32
lines changed

7 files changed

+34
-32
lines changed

__tests__/commands/global.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ const runGlobal = buildRun.bind(
2424
);
2525

2626
function getGlobalPath(prefix, name): string {
27-
if (process.platform === 'win32') {
28-
return path.join(prefix, name);
29-
} else {
30-
return path.join(prefix, 'bin', name);
31-
}
27+
return path.join(prefix, 'bin', name);
3228
}
3329

3430
function getTempGlobalFolder(): string {

src/cli/commands/create.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
2626

2727
await runGlobal(config, reporter, {}, ['add', packageName]);
2828

29-
const binFolder = getBinFolder(config, {});
29+
const binFolder = await getBinFolder(config, {});
3030
const command = path.resolve(binFolder, path.basename(commandName));
3131

3232
await child.spawn(command, [...rest], {stdio: `inherit`, shell: true});

src/cli/commands/global.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import {run as runRemove} from './remove.js';
1414
import {run as runUpgrade} from './upgrade.js';
1515
import {run as runUpgradeInteractive} from './upgrade-interactive.js';
1616
import {linkBin} from '../../package-linker.js';
17+
import {POSIX_GLOBAL_PREFIX, FALLBACK_GLOBAL_PREFIX} from '../../constants.js';
1718
import * as fs from '../../util/fs.js';
1819

20+
const nativeFs = require('fs');
21+
1922
class GlobalAdd extends Add {
2023
maybeOutputSaveTree(): Promise<void> {
2124
for (const pattern of this.addedPatterns) {
@@ -69,44 +72,44 @@ async function getBins(config: Config): Promise<Set<string>> {
6972
return paths;
7073
}
7174

72-
function getGlobalPrefix(config: Config, flags: Object): string {
75+
async function getGlobalPrefix(config: Config, flags: Object): Promise<string> {
7376
if (flags.prefix) {
7477
return flags.prefix;
7578
} else if (config.getOption('prefix')) {
7679
return String(config.getOption('prefix'));
7780
} else if (process.env.PREFIX) {
7881
return process.env.PREFIX;
79-
} else if (process.platform === 'win32') {
82+
}
83+
84+
let prefix = FALLBACK_GLOBAL_PREFIX;
85+
if (process.platform === 'win32') {
86+
// %LOCALAPPDATA%\Yarn --> C:\Users\Alice\AppData\Local\Yarn
8087
if (process.env.LOCALAPPDATA) {
81-
return path.join(process.env.LOCALAPPDATA, 'Yarn', 'bin');
88+
prefix = path.join(process.env.LOCALAPPDATA, 'Yarn');
8289
}
83-
// c:\node\node.exe --> prefix=c:\node\
84-
return path.dirname(process.execPath);
8590
} else {
86-
// /usr/local/bin/node --> prefix=/usr/local
87-
let prefix = path.dirname(path.dirname(process.execPath));
88-
89-
// destdir only is respected on Unix
90-
if (process.env.DESTDIR) {
91-
prefix = path.join(process.env.DESTDIR, prefix);
91+
prefix = POSIX_GLOBAL_PREFIX;
92+
}
93+
try {
94+
await fs.access(path.join(prefix, 'bin'), (nativeFs.constants || nativeFs).W_OK);
95+
} catch (err) {
96+
if (err.code === 'EACCES') {
97+
prefix = FALLBACK_GLOBAL_PREFIX;
98+
} else {
99+
throw err;
92100
}
93-
94-
return prefix;
95101
}
102+
return prefix;
96103
}
97104

98-
export function getBinFolder(config: Config, flags: Object): string {
99-
const prefix = getGlobalPrefix(config, flags);
100-
if (process.platform === 'win32') {
101-
return prefix;
102-
} else {
103-
return path.resolve(prefix, 'bin');
104-
}
105+
export async function getBinFolder(config: Config, flags: Object): Promise<string> {
106+
const prefix = await getGlobalPrefix(config, flags);
107+
return path.resolve(prefix, 'bin');
105108
}
106109

107110
async function initUpdateBins(config: Config, reporter: Reporter, flags: Object): Promise<() => Promise<void>> {
108111
const beforeBins = await getBins(config);
109-
const binFolder = getBinFolder(config, flags);
112+
const binFolder = await getBinFolder(config, flags);
110113

111114
function throwPermError(err: Error & {[code: string]: string}, dest: string) {
112115
if (err.code === 'EACCES') {
@@ -205,8 +208,8 @@ const {run, setFlags: _setFlags} = buildSubCommands('global', {
205208
await updateBins();
206209
},
207210

208-
bin(config: Config, reporter: Reporter, flags: Object, args: Array<string>) {
209-
reporter.log(getBinFolder(config, flags));
211+
async bin(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {
212+
reporter.log(await getBinFolder(config, flags));
210213
},
211214

212215
async ls(config: Config, reporter: Reporter, flags: Object, args: Array<string>): Promise<void> {

src/cli/commands/link.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
6363
// If there is a `bin` defined in the package.json,
6464
// link each bin to the global bin
6565
if (manifest.bin) {
66-
const globalBinFolder = getGlobalBinFolder(config, flags);
66+
const globalBinFolder = await getGlobalBinFolder(config, flags);
6767
for (const binName in manifest.bin) {
6868
const binSrc = manifest.bin[binName];
6969
const binSrcLoc = path.join(linkLoc, binSrc);

src/cli/commands/unlink.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg
4040
// If there is a `bin` defined in the package.json,
4141
// link each bin to the global bin
4242
if (manifest.bin) {
43-
const globalBinFolder = getGlobalBinFolder(config, flags);
43+
const globalBinFolder = await getGlobalBinFolder(config, flags);
4444
for (const binName in manifest.bin) {
4545
const binDestLoc = path.join(globalBinFolder, binName);
4646
if (await fs.exists(binDestLoc)) {

src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export const CONFIG_DIRECTORY = getDirectory('config');
5959
export const LINK_REGISTRY_DIRECTORY = path.join(CONFIG_DIRECTORY, 'link');
6060
export const GLOBAL_MODULE_DIRECTORY = path.join(CONFIG_DIRECTORY, 'global');
6161

62+
export const POSIX_GLOBAL_PREFIX = '/usr/local';
63+
export const FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn');
64+
6265
export const META_FOLDER = '.yarn-meta';
6366
export const INTEGRITY_FILENAME = '.yarn-integrity';
6467
export const LOCKFILE_FILENAME = 'yarn.lock';

src/util/execute-lifecycle-script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export async function executeLifecycleScript(
144144

145145
// Add global bin folder if it is not present already, as some packages depend
146146
// on a globally-installed version of node-gyp.
147-
const globalBin = getGlobalBinFolder(config, {});
147+
const globalBin = await getGlobalBinFolder(config, {});
148148
if (pathParts.indexOf(globalBin) === -1) {
149149
pathParts.unshift(globalBin);
150150
}

0 commit comments

Comments
 (0)