Skip to content

Commit b4e42e3

Browse files
darthtrevinoSebastian McKenzie
authored andcommitted
Allow token replacement of .npmrc configuration with env vars (#1207)
* test IGNORE ME * Update npm-registry to inject env vars into npm configuration the same way npm does * Add type annotation to config object iteration * Update envReplace function * Move env-replace function to a separate util module. Add unit tests * Correct the env-replace tests * Updates per @kittens' comments
1 parent 42dc14e commit b4e42e3

File tree

4 files changed

+55
-0
lines changed

4 files changed

+55
-0
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ end_of_line = lf
99
[*.{js,json}]
1010
indent_style = space
1111
indent_size = 2
12+

__tests__/util/env-replace.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* @flow */
2+
import envReplace from '../../src/util/env-replace';
3+
import assert from 'assert';
4+
5+
describe('environment variable replacement', () => {
6+
it('will replace a token that exists in the environment', () => {
7+
let result = envReplace('test ${a} replacement', {a: 'token'});
8+
assert(result === 'test token replacement', `result: ${result}`);
9+
10+
result = envReplace('${a} replacement', {a: 'token'});
11+
assert(result === 'token replacement', `result: ${result}`);
12+
13+
result = envReplace('${a}', {a: 'token'});
14+
assert(result === 'token', `result: ${result}`);
15+
});
16+
17+
it('will not replace a token that does not exist in the environment', () => {
18+
let thrown = false;
19+
try {
20+
envReplace('${a} replacement', {b: 'token'});
21+
} catch (err) {
22+
thrown = true;
23+
assert(err.message === 'Failed to replace env in config: ${a}', `error message: ${err.message}`);
24+
}
25+
assert(thrown);
26+
});
27+
28+
it('will not replace a token when a the token-replacement mechanism is prefixed a backslash literal', () => {
29+
const result = envReplace('\\${a} replacement', {a: 'token'});
30+
assert(result === '\\${a} replacement', `result: ${result}`);
31+
});
32+
});

src/registries/npm-registry.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type Config from '../config.js';
66
import type {ConfigRegistries} from './index.js';
77
import * as fs from '../util/fs.js';
88
import NpmResolver from '../resolvers/registries/npm-resolver.js';
9+
import envReplace from '../util/env-replace.js';
910
import Registry from './base-registry.js';
1011
import {addSuffix, removePrefix} from '../util/misc';
1112

@@ -121,6 +122,9 @@ export default class NpmRegistry extends Registry {
121122

122123
for (const [, loc, file] of await this.getPossibleConfigLocations('.npmrc')) {
123124
const config = Registry.normalizeConfig(ini.parse(file));
125+
for (const key: string in config) {
126+
config[key] = envReplace(config[key]);
127+
}
124128

125129
// normalize offline mirror path relative to the current npmrc
126130
const offlineLoc = config['yarn-offline-mirror'];

src/util/env-replace.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* @flow */
2+
const ENV_EXPR = /(\\*)\$\{([^}]+)\}/g;
3+
4+
export default function envReplace(value: string, env: {[key: string]: ?string} = process.env): string {
5+
if (typeof value !== 'string' || !value) {
6+
return value;
7+
}
8+
9+
return value.replace(ENV_EXPR, (match: string, esc: string, envVarName: string) => {
10+
if (esc.length && esc.length % 2) {
11+
return match;
12+
}
13+
if (undefined === env[envVarName]) {
14+
throw new Error('Failed to replace env in config: ' + match);
15+
}
16+
return env[envVarName] || '';
17+
});
18+
}

0 commit comments

Comments
 (0)