Skip to content

Commit 7b39526

Browse files
authored
feat: allows yarn config set to override package.json config keys (#5518)
* feat: allows yarn config set to override package.json config keys feature in npm: https://docs.npmjs.com/files/package.json#config * test: makeEnv unit tests for setting environment variables for npm_* * Update execute-lifecycle-script.js
1 parent 6601f4b commit 7b39526

File tree

2 files changed

+136
-22
lines changed

2 files changed

+136
-22
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/* @flow */
2+
import path from 'path';
3+
import {makeEnv} from '../../src/util/execute-lifecycle-script';
4+
import Config from '../../src/config';
5+
import {NoopReporter} from '../../src/reporters';
6+
7+
const cwd = path.resolve(__dirname);
8+
9+
const initConfig = async cfg => {
10+
const reporter = new NoopReporter();
11+
const config = new Config(reporter);
12+
await config.init(cfg);
13+
return config;
14+
};
15+
16+
const withManifest = async (stage, value, keys = {}) => {
17+
const config = await initConfig({});
18+
config.maybeReadManifest = dir => {
19+
expect(dir).toEqual(cwd);
20+
return Promise.resolve({
21+
scripts: {[stage]: value},
22+
...keys,
23+
});
24+
};
25+
return config;
26+
};
27+
28+
describe('makeEnv', () => {
29+
it('assigns npm_lifecycle_event to env', async () => {
30+
const stage = 'my-script';
31+
const config = await initConfig({});
32+
const env = await makeEnv(stage, cwd, config);
33+
expect(env.npm_lifecycle_event).toEqual(stage);
34+
});
35+
36+
it('assigns NODE_ENV production', async () => {
37+
const config = await initConfig({production: true});
38+
const env = await makeEnv('test-script', cwd, config);
39+
expect(env.NODE_ENV).toEqual('production');
40+
});
41+
42+
describe('npm_package_*', () => {
43+
it('assigns npm_lifecycle_script if manifest has a matching script', async () => {
44+
const stage = 'test-script';
45+
const value = 'run this script';
46+
const config = await withManifest(stage, value);
47+
const env = await makeEnv(stage, cwd, config);
48+
expect(env.npm_lifecycle_script).toEqual(value);
49+
});
50+
51+
it('does not overwrite npm_lifecycle_script if manifest does not have a matching script', async () => {
52+
const stage = 'test-script';
53+
const config = await withManifest('wrong-stage', 'new value');
54+
const env = await makeEnv(stage, cwd, config);
55+
expect(env.npm_lifecycle_script).toEqual(process.env.npm_lifecycle_script);
56+
});
57+
58+
it('recursively adds keys separated by _', async () => {
59+
const config = await withManifest('', '', {
60+
top: 'first',
61+
recursive: {
62+
key: 'value',
63+
more: {another: 'what'},
64+
},
65+
});
66+
const env = await makeEnv('', cwd, config);
67+
expect(env['npm_package_top']).toEqual('first');
68+
expect(env['npm_package_recursive_key']).toEqual('value');
69+
expect(env['npm_package_recursive_more_another']).toEqual('what');
70+
});
71+
72+
it('replaces invalid chars with _', async () => {
73+
const config = await withManifest('', '', {'in^va!d_key': 'test'});
74+
const env = await makeEnv('', cwd, config);
75+
expect(env['npm_package_in_va_d_key']).toEqual('test');
76+
});
77+
});
78+
79+
describe('npm_package_config_*', () => {
80+
it('overwrites npm_package_config_* keys from yarn config or npm config', async () => {
81+
const name = 'manifest-name';
82+
const config = await withManifest('', '', {name, config: {key: 'value', keytwo: 'valuetwo'}});
83+
config.registries.yarn.config = {[`${name}:key`]: 'replaced'};
84+
config.registries.npm.config = {[`${name}:keytwo`]: 'also replaced'};
85+
const env = await makeEnv('', cwd, config);
86+
expect(env['npm_package_config_key']).toEqual('replaced');
87+
expect(env['npm_package_config_keytwo']).toEqual('also replaced');
88+
});
89+
90+
it('does not overwrite if the name does not match the manifest', async () => {
91+
const name = 'manifest-name';
92+
const config = await withManifest('', '', {name, config: {key: 'value', keytwo: 'valuetwo'}});
93+
config.registries.yarn.config = {'wrong-name:key': 'replaced'};
94+
config.registries.npm.config = {'another-name:keytwo': 'also replaced'};
95+
const env = await makeEnv('', cwd, config);
96+
expect(env['npm_package_config_key']).toEqual('value');
97+
expect(env['npm_package_config_keytwo']).toEqual('valuetwo');
98+
});
99+
});
100+
});

src/util/execute-lifecycle-script.js

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as child from './child.js';
88
import {exists} from './fs.js';
99
import {registries} from '../resolvers/index.js';
1010
import {fixCmdWinSlashes} from './fix-cmd-win-slashes.js';
11-
import {run as globalRun, getBinFolder as getGlobalBinFolder} from '../cli/commands/global.js';
11+
import {getBinFolder as getGlobalBinFolder, run as globalRun} from '../cli/commands/global.js';
1212

1313
const invariant = require('invariant');
1414
const path = require('path');
@@ -26,6 +26,8 @@ const IGNORE_MANIFEST_KEYS = ['readme'];
2626
// See https://github.com/yarnpkg/yarn/issues/2286.
2727
const IGNORE_CONFIG_KEYS = ['lastUpdateCheck'];
2828

29+
const INVALID_CHAR_REGEX = /[^a-zA-Z0-9_]/g;
30+
2931
export async function makeEnv(
3032
stage: string,
3133
cwd: string,
@@ -91,41 +93,53 @@ export async function makeEnv(
9193
}
9294

9395
//replacing invalid chars with underscore
94-
const cleanKey = key.replace(/[^a-zA-Z0-9_]/g, '_');
96+
const cleanKey = key.replace(INVALID_CHAR_REGEX, '_');
9597

9698
env[`npm_package_${cleanKey}`] = cleanVal;
9799
}
98100
}
99101
}
100102

101-
// add npm_config_*
103+
// add npm_config_* and npm_package_config_* from yarn config
102104
const keys: Set<string> = new Set([
103105
...Object.keys(config.registries.yarn.config),
104106
...Object.keys(config.registries.npm.config),
105107
]);
106-
for (const key of keys) {
107-
if (key.match(/:_/) || IGNORE_CONFIG_KEYS.indexOf(key) >= 0) {
108-
continue;
109-
}
110-
111-
let val = config.getOption(key);
112-
113-
if (!val) {
114-
val = '';
115-
} else if (typeof val === 'number') {
116-
val = '' + val;
117-
} else if (typeof val !== 'string') {
118-
val = JSON.stringify(val);
119-
}
120-
121-
if (val.indexOf('\n') >= 0) {
122-
val = JSON.stringify(val);
123-
}
108+
const cleaned = Array.from(keys)
109+
.filter(key => !key.match(/:_/) && IGNORE_CONFIG_KEYS.indexOf(key) === -1)
110+
.map(key => {
111+
let val = config.getOption(key);
112+
if (!val) {
113+
val = '';
114+
} else if (typeof val === 'number') {
115+
val = '' + val;
116+
} else if (typeof val !== 'string') {
117+
val = JSON.stringify(val);
118+
}
124119

120+
if (val.indexOf('\n') >= 0) {
121+
val = JSON.stringify(val);
122+
}
123+
return [key, val];
124+
});
125+
// add npm_config_*
126+
for (const [key, val] of cleaned) {
125127
const cleanKey = key.replace(/^_+/, '');
126-
const envKey = `npm_config_${cleanKey}`.replace(/[^a-zA-Z0-9_]/g, '_');
128+
const envKey = `npm_config_${cleanKey}`.replace(INVALID_CHAR_REGEX, '_');
127129
env[envKey] = val;
128130
}
131+
// add npm_package_config_*
132+
if (manifest && manifest.name) {
133+
const packageConfigPrefix = `${manifest.name}:`;
134+
for (const [key, val] of cleaned) {
135+
if (key.indexOf(packageConfigPrefix) !== 0) {
136+
continue;
137+
}
138+
const cleanKey = key.replace(/^_+/, '').replace(packageConfigPrefix, '');
139+
const envKey = `npm_package_config_${cleanKey}`.replace(INVALID_CHAR_REGEX, '_');
140+
env[envKey] = val;
141+
}
142+
}
129143

130144
// split up the path
131145
const envPath = env[constants.ENV_PATH_KEY];

0 commit comments

Comments
 (0)