Skip to content

Commit 64b17ae

Browse files
Sebastian McKenziearcanis
authored andcommitted
Add cwd flag (#4174)
* Add cwd flag * Handle --cwd flag in .yarnrc * add test fixtures * Update rc.js * Update en.js
1 parent e92ac93 commit 64b17ae

File tree

10 files changed

+148
-79
lines changed

10 files changed

+148
-79
lines changed

__tests__/fixtures/rc/empty/.yarnrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--foo true

__tests__/fixtures/rc/inside/.yarnrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--cwd foo
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--foo true
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--add.foo true
2+
--install.bar true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--cwd pointer
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--bar true
2+
--cwd ".."

__tests__/rc.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* @flow */
2+
3+
import {getRcArgs} from '../src/rc.js';
4+
import * as path from 'path';
5+
6+
const fixturesLoc = path.join(__dirname, 'fixtures', 'rc');
7+
8+
test('resolve .yarnrc args and use --cwd if present', () => {
9+
const args = getRcArgs('install', ['--cwd', path.join(fixturesLoc, 'empty')]);
10+
expect(args.indexOf('--foo') !== -1).toBe(true);
11+
});
12+
13+
test('resolve .yarnrc args and use process.cwd() if no --cwd present', () => {
14+
const cwd = process.cwd();
15+
process.chdir(path.join(fixturesLoc, 'empty'));
16+
17+
try {
18+
const args = getRcArgs('install', []);
19+
expect(args.indexOf('--foo') !== -1).toBe(true);
20+
} finally {
21+
process.chdir(cwd);
22+
}
23+
});
24+
25+
test('resolve .yarnrc args and handle --cwd arg inside .yarnrc', () => {
26+
const args = getRcArgs('install', ['--cwd', path.join(fixturesLoc, 'inside')]);
27+
expect(args.indexOf('--foo') !== -1).toBe(true);
28+
});
29+
30+
test('resolve .yarnrc args and bail out of recursive --cwd args inside of .yarnrc', () => {
31+
expect(() => {
32+
getRcArgs('install', ['--cwd', path.join(fixturesLoc, 'recursive')]);
33+
}).toThrowError();
34+
});
35+
36+
test('resolve .yarnrc args and adds command name prefixed arguments', () => {
37+
const args = getRcArgs('add', ['--cwd', path.join(fixturesLoc, 'prefixed')]);
38+
expect(args.indexOf('--foo') !== -1).toBe(true);
39+
expect(args.indexOf('--bar') !== -1).toBe(false);
40+
});

src/cli/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export function main({
6464
commander.option('--mutex <type>[:specifier]', 'use a mutex to ensure only one yarn instance is executing');
6565
commander.option('--emoji [bool]', 'enable emoji in output', process.platform === 'darwin');
6666
commander.option('-s, --silent', 'skip Yarn console logs, other types of logs (script output) will be printed');
67+
commander.option('--cwd <cwd>', 'working directory to use', process.cwd());
6768
commander.option('--proxy <host>', '');
6869
commander.option('--https-proxy <host>', '');
6970
commander.option('--no-progress', 'disable progress bar');
@@ -114,7 +115,7 @@ export function main({
114115
// we use this for https://github.com/tj/commander.js/issues/346, otherwise
115116
// it will strip some args that match with any options
116117
'this-arg-will-get-stripped-later',
117-
...getRcArgs(commandName),
118+
...getRcArgs(commandName, args),
118119
...args,
119120
]);
120121
commander.args = commander.args.concat(endArgs);
@@ -339,6 +340,7 @@ export function main({
339340
networkTimeout: commander.networkTimeout,
340341
nonInteractive: commander.nonInteractive,
341342
scriptsPrependNodePath: commander.scriptsPrependNodePath,
343+
cwd: commander.cwd,
342344

343345
commandName: commandName === 'run' ? commander.args[0] : commandName,
344346
})

src/rc.js

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ import parse from './lockfile/parse.js';
55
import * as rcUtil from './util/rc.js';
66

77
// Keys that will get resolved relative to the path of the rc file they belong to
8-
const PATH_KEYS = ['cache-folder', 'global-folder', 'modules-folder'];
8+
const PATH_KEYS = ['cache-folder', 'global-folder', 'modules-folder', 'cwd'];
99

10-
let rcConfCache;
11-
let rcArgsCache;
12-
13-
const buildRcConf = () =>
14-
rcUtil.findRc('yarn', (fileText, filePath) => {
10+
// given a cwd, load all .yarnrc files relative to it
11+
function getRcConfigForCwd(cwd: string): {[key: string]: string} {
12+
return rcUtil.findRc('yarn', cwd, (fileText, filePath) => {
1513
const {object: values} = parse(fileText, 'yarnrc');
16-
const keys = Object.keys(values);
1714

18-
for (const key of keys) {
15+
// some keys reference directories so keep their relativity
16+
for (const key in values) {
1917
for (const pathKey of PATH_KEYS) {
2018
if (key.replace(/^(--)?([^.]+\.)*/, '') === pathKey) {
2119
values[key] = resolve(dirname(filePath), values[key]);
@@ -25,57 +23,78 @@ const buildRcConf = () =>
2523

2624
return values;
2725
});
28-
29-
export function getRcConf(): {[string]: Array<string>} {
30-
if (!rcConfCache) {
31-
rcConfCache = buildRcConf();
32-
}
33-
34-
return rcConfCache;
3526
}
3627

37-
const buildRcArgs = () =>
38-
Object.keys(getRcConf()).reduce((argLists, key) => {
39-
const miniparse = key.match(/^--(?:([^.]+)\.)?(.*)$/);
28+
// get the built of arguments of a .yarnrc chain of the passed cwd
29+
function buildRcArgs(cwd: string): Map<string, Array<string>> {
30+
const config = getRcConfigForCwd(cwd);
4031

41-
if (!miniparse) {
42-
return argLists;
32+
const argsForCommands: Map<string, Array<string>> = new Map();
33+
34+
for (const key in config) {
35+
// args can be prefixed with the command name they're meant for, eg.
36+
// `--install.check-files true`
37+
const keyMatch = key.match(/^--(?:([^.]+)\.)?(.*)$/);
38+
if (!keyMatch) {
39+
continue;
4340
}
4441

45-
const namespace = miniparse[1] || '*';
46-
const arg = miniparse[2];
47-
const value = getRcConf()[key];
42+
const commandName = keyMatch[1] || '*';
43+
const arg = keyMatch[2];
44+
const value = config[key];
4845

49-
if (!argLists[namespace]) {
50-
argLists[namespace] = [];
51-
}
46+
// create args for this command name if we didn't previously have them
47+
const args = argsForCommands.get(commandName) || [];
48+
argsForCommands.set(commandName, args);
5249

50+
// turn config value into appropriate cli flag
5351
if (typeof value === 'string') {
54-
argLists[namespace] = argLists[namespace].concat([`--${arg}`, value]);
52+
args.push(`--${arg}`, value);
5553
} else if (value === true) {
56-
argLists[namespace] = argLists[namespace].concat([`--${arg}`]);
54+
args.push(`--${arg}`);
5755
} else if (value === false) {
58-
argLists[namespace] = argLists[namespace].concat([`--no-${arg}`]);
56+
args.push(`--no-${arg}`);
5957
}
58+
}
6059

61-
return argLists;
62-
}, {});
60+
return argsForCommands;
61+
}
6362

64-
export function getRcArgs(command: string): Array<string> {
65-
if (!rcArgsCache) {
66-
rcArgsCache = buildRcArgs();
63+
// extract the value of a --cwd arg if present
64+
function extractCwdArg(args: Array<string>): ?string {
65+
for (let i = 0, I = args.length; i < I; ++i) {
66+
const arg = args[i];
67+
if (arg === '--') {
68+
return null;
69+
} else if (arg === '--cwd') {
70+
return args[i + 1];
71+
}
6772
}
73+
return null;
74+
}
6875

69-
let result = rcArgsCache['*'] || [];
76+
// get a list of arguments from .yarnrc that apply to this commandName
77+
export function getRcArgs(commandName: string, args: Array<string>, previousCwds?: Array<string> = []): Array<string> {
78+
// for the cwd, use the --cwd arg if it was passed or else use process.cwd()
79+
const origCwd = extractCwdArg(args) || process.cwd();
7080

71-
if (command !== '*' && Object.prototype.hasOwnProperty.call(rcArgsCache, command)) {
72-
result = result.concat(rcArgsCache[command] || []);
73-
}
81+
// get a map of command names and their arguments
82+
const argMap = buildRcArgs(origCwd);
7483

75-
return result;
76-
}
84+
// concat wildcard arguments and arguments meant for this specific command
85+
const newArgs = [].concat(argMap.get('*') || [], argMap.get(commandName) || []);
86+
87+
// check if the .yarnrc args specified a cwd
88+
const newCwd = extractCwdArg(newArgs);
89+
if (newCwd && newCwd !== origCwd) {
90+
// ensure that we don't enter into a loop
91+
if (previousCwds.indexOf(newCwd) !== -1) {
92+
throw new Error(`Recursive .yarnrc files specifying --cwd flags. Bailing out.`);
93+
}
94+
95+
// if we have a new cwd then let's refetch the .yarnrc args relative to it
96+
return getRcArgs(commandName, newArgs, previousCwds.concat(origCwd));
97+
}
7798

78-
export function clearRcCache() {
79-
rcConfCache = null;
80-
rcArgsCache = null;
99+
return newArgs;
81100
}

src/util/rc.js

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,17 @@
11
/* @flow */
22

33
import {readFileSync} from 'fs';
4-
import {basename, dirname, join} from 'path';
4+
import * as path from 'path';
55

66
const etc = '/etc';
77
const isWin = process.platform === 'win32';
88
const home = isWin ? process.env.USERPROFILE : process.env.HOME;
99

10-
export function findRc(name: string, parser: Function): Object {
11-
let configPaths = [];
10+
function getRcPaths(name: string, cwd: string): Array<string> {
11+
const configPaths = [];
1212

1313
function addConfigPath(...segments) {
14-
configPaths.push(join(...segments));
15-
}
16-
17-
function addRecursiveConfigPath(...segments) {
18-
const queue = [];
19-
20-
let oldPath;
21-
let path = join(...segments);
22-
23-
do {
24-
queue.unshift(path);
25-
26-
oldPath = path;
27-
path = join(dirname(dirname(path)), basename(path));
28-
} while (path !== oldPath);
29-
30-
configPaths = configPaths.concat(queue);
31-
}
32-
33-
function fetchConfigs(): Object {
34-
return Object.assign(
35-
{},
36-
...configPaths.map(path => {
37-
try {
38-
return parser(readFileSync(path).toString(), path);
39-
} catch (error) {
40-
return {};
41-
}
42-
}),
43-
);
14+
configPaths.push(path.join(...segments));
4415
}
4516

4617
if (!isWin) {
@@ -55,13 +26,42 @@ export function findRc(name: string, parser: Function): Object {
5526
addConfigPath(home, `.${name}rc`);
5627
}
5728

58-
addRecursiveConfigPath(process.cwd(), `.${name}rc`);
29+
// add .yarnrc locations relative to the cwd
30+
while (true) {
31+
configPaths.unshift(path.join(cwd, `.${name}rc`));
32+
33+
const upperCwd = path.dirname(cwd);
34+
if (upperCwd === cwd) {
35+
// we've reached the root
36+
break;
37+
} else {
38+
// continue since there's still more directories to search
39+
cwd = upperCwd;
40+
}
41+
}
5942

6043
const envVariable = `${name}_config`.toUpperCase();
6144

6245
if (process.env[envVariable]) {
6346
addConfigPath(process.env[envVariable]);
6447
}
6548

66-
return fetchConfigs();
49+
return configPaths;
50+
}
51+
52+
function parseRcPaths(paths: Array<string>, parser: Function): Object {
53+
return Object.assign(
54+
{},
55+
...paths.map(path => {
56+
try {
57+
return parser(readFileSync(path).toString(), path);
58+
} catch (error) {
59+
return {};
60+
}
61+
}),
62+
);
63+
}
64+
65+
export function findRc(name: string, cwd: string, parser: Function): Object {
66+
return parseRcPaths(getRcPaths(name, cwd), parser);
6767
}

0 commit comments

Comments
 (0)