diff --git a/.gitignore b/.gitignore index 65493af99aa9eb3a0663afc1acdadfd53f485092..6a4aa6a96b586e04392cd6c064b7a7f48f7f780e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ .cache /*.log /.vscode +/.idea diff --git a/lib/config/definitions.js b/lib/config/definitions.js index deadf948b8ffa2eb4b9bb2670a7b6774d5e3a403..10a65d0233af14b2f537cf15c2e06f2a27b752a3 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -128,6 +128,15 @@ const options = [ stage: 'branch', type: 'boolean', }, + // Bot administration + { + name: 'exposeEnv', + description: + 'Enable this to expose bot process.env to repositories for npmrc substitution and package installation', + stage: 'global', + type: 'boolean', + default: false, + }, { name: 'platform', description: 'Platform type of repository', diff --git a/lib/manager/npm/registry.js b/lib/manager/npm/registry.js index c18f6d1dd9618f14a04b582b2cf431f863c3a0e0..1a5aeca088753ff740c34856fe5f89ec011d4b41 100644 --- a/lib/manager/npm/registry.js +++ b/lib/manager/npm/registry.js @@ -30,14 +30,35 @@ function resetCache() { resetMemCache(); } -function setNpmrc(input) { +function setNpmrc(input, exposeEnv = false) { + logger.debug('setNpmrc()'); if (input) { npmrc = ini.parse(input); + if (!exposeEnv) { + return; + } + for (const key in npmrc) { + if (Object.prototype.hasOwnProperty.call(npmrc, key)) { + npmrc[key] = envReplace(npmrc[key]); + } + } } else { npmrc = null; } } +function envReplace(value, env = process.env) { + const ENV_EXPR = /(\\*)\$\{([^}]+)\}/g; + + return value.replace(ENV_EXPR, (match, esc, envVarName) => { + if (env[envVarName] === undefined) { + logger.warn('Failed to replace env in config: ' + match); + throw new Error('env-replace'); + } + return env[envVarName]; + }); +} + async function getDependency(name) { logger.trace(`getDependency(${name})`); if (memcache[name]) { diff --git a/lib/workers/branch/lock-files.js b/lib/workers/branch/lock-files.js index d913b4ba95592d4123c866ee527e785335aa45bf..3bc2377563917e6b50140d2535b3a51ddd95cc1d 100644 --- a/lib/workers/branch/lock-files.js +++ b/lib/workers/branch/lock-files.js @@ -299,11 +299,18 @@ async function getUpdatedLockFiles(config) { await module.exports.writeExistingFiles(config); await module.exports.writeUpdatedPackageFiles(config); + const env = + config.global && config.global.exposeEnv + ? process.env + : { PATH: process.env.PATH }; + env.NODE_ENV = 'dev'; + for (const lockFileDir of dirs.packageLockFileDirs) { logger.debug(`Generating package-lock.json for ${lockFileDir}`); const lockFileName = upath.join(lockFileDir, 'package-lock.json'); const res = await npm.generateLockFile( - upath.join(config.tmpDir.path, lockFileDir) + upath.join(config.tmpDir.path, lockFileDir), + env ); if (res.error) { lockFileErrors.push({ @@ -331,7 +338,8 @@ async function getUpdatedLockFiles(config) { logger.debug(`Generating yarn.lock for ${lockFileDir}`); const lockFileName = upath.join(lockFileDir, 'yarn.lock'); const res = await yarn.generateLockFile( - upath.join(config.tmpDir.path, lockFileDir) + upath.join(config.tmpDir.path, lockFileDir), + env ); if (res.error) { lockFileErrors.push({ @@ -359,7 +367,8 @@ async function getUpdatedLockFiles(config) { logger.debug(`Generating shrinkwrap.yaml for ${lockFileDir}`); const lockFileName = upath.join(lockFileDir, 'shrinkwrap.yaml'); const res = await pnpm.generateLockFile( - upath.join(config.tmpDir.path, lockFileDir) + upath.join(config.tmpDir.path, lockFileDir), + env ); if (res.error) { lockFileErrors.push({ diff --git a/lib/workers/branch/npm.js b/lib/workers/branch/npm.js index 4c1e41bb67d18e8a1adf5c805dda2fd4ca9792c9..4f497cabfb80f576f6e24203e385dd3db602aff3 100644 --- a/lib/workers/branch/npm.js +++ b/lib/workers/branch/npm.js @@ -7,7 +7,7 @@ module.exports = { generateLockFile, }; -async function generateLockFile(tmpDir) { +async function generateLockFile(tmpDir, env) { logger.debug(`Spawning npm install to create ${tmpDir}/package-lock.json`); let lockFile = null; let stdout; @@ -58,7 +58,7 @@ async function generateLockFile(tmpDir) { ({ stdout, stderr } = await exec(cmd, { cwd: tmpDir, shell: true, - env: { NODE_ENV: 'dev', PATH: process.env.PATH }, + env, })); logger.debug(`npm stdout:\n${stdout}`); logger.debug(`npm stderr:\n${stderr}`); diff --git a/lib/workers/branch/pnpm.js b/lib/workers/branch/pnpm.js index 1032c10c286ab20c67bfb4b4d1c3281086c88a3c..f843220c359baf0ee89205b36c7a59a1b21fe973 100644 --- a/lib/workers/branch/pnpm.js +++ b/lib/workers/branch/pnpm.js @@ -7,7 +7,7 @@ module.exports = { generateLockFile, }; -async function generateLockFile(tmpDir) { +async function generateLockFile(tmpDir, env) { logger.debug(`Spawning pnpm install to create ${tmpDir}/shrinkwrap.yaml`); let lockFile = null; let stdout; @@ -61,7 +61,7 @@ async function generateLockFile(tmpDir) { ({ stdout, stderr } = await exec(cmd, { cwd: tmpDir, shell: true, - env: { NODE_ENV: 'dev', PATH: process.env.PATH }, + env, })); logger.debug(`pnpm stdout:\n${stdout}`); logger.debug(`pnpm stderr:\n${stderr}`); diff --git a/lib/workers/branch/yarn.js b/lib/workers/branch/yarn.js index e7b9f1ae804fd36c50ceb7e0105506e950b85703..cfa5d481c61b00359f07e1a2efe3afcfefb45680 100644 --- a/lib/workers/branch/yarn.js +++ b/lib/workers/branch/yarn.js @@ -7,7 +7,7 @@ module.exports = { generateLockFile, }; -async function generateLockFile(tmpDir) { +async function generateLockFile(tmpDir, env) { logger.debug(`Spawning yarn install to create ${tmpDir}/yarn.lock`); let lockFile = null; let stdout; @@ -60,7 +60,7 @@ async function generateLockFile(tmpDir) { ({ stdout, stderr } = await exec(cmd, { cwd: tmpDir, shell: true, - env: { NODE_ENV: 'dev', PATH: process.env.PATH }, + env, })); logger.debug(`yarn stdout:\n${stdout}`); logger.debug(`yarn stderr:\n${stderr}`); diff --git a/lib/workers/global/index.js b/lib/workers/global/index.js index 458cec4c3927952ea586c7adb6218a5e2cd8dfb6..51ff763792c1fcf25d13f3a5364db11aad6c544c 100644 --- a/lib/workers/global/index.js +++ b/lib/workers/global/index.js @@ -25,6 +25,13 @@ async function start() { 'No repositories found - did you want to run with flag --autodiscover?' ); } + // Move global variables that we need to use later + const importGlobals = ['exposeEnv']; + config.global = {}; + importGlobals.forEach(key => { + config.global[key] = config[key]; + delete config[key]; + }); // Iterate through repositories sequentially for (let index = 0; index < config.repositories.length; index += 1) { const repoConfig = module.exports.getRepositoryConfig(config, index); diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js index b26fb31397e03dadf062c77a8c3d4ebe08edc604..88423b02c9d5542d7d10c1bf267ac82c39701254 100644 --- a/lib/workers/package-file/index.js +++ b/lib/workers/package-file/index.js @@ -35,7 +35,10 @@ async function renovatePackageFile(packageFileConfig) { logger.debug('renovatePakageFile()'); if (config.npmrc) { logger.debug('Setting .npmrc'); - npmApi.setNpmrc(config.npmrc); + npmApi.setNpmrc( + config.npmrc, + config.global ? config.global.exposeEnv : false + ); } let upgrades = []; logger.info(`Processing package file`); diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js index 2d7f0b63199a9e0004a02d6cdbca6816b5b827ff..44ab838085baaa804a9c7e11a0a3f3f016718394 100644 --- a/lib/workers/repository/index.js +++ b/lib/workers/repository/index.js @@ -14,6 +14,7 @@ module.exports = { async function renovateRepository(repoConfig, token, loop = 1) { let config = { ...repoConfig, branchList: [] }; + config.global = config.global || {}; logger.setMeta({ repository: config.repository }); logger.info('Renovating repository'); logger.trace({ config, loop }, 'renovateRepository()'); diff --git a/lib/workers/repository/init/apis.js b/lib/workers/repository/init/apis.js index 3cf04ce5849d101e2af3da235e091370f0c1b0e4..13c74d289182941e5b89bba7e00770880470fad7 100644 --- a/lib/workers/repository/init/apis.js +++ b/lib/workers/repository/init/apis.js @@ -27,7 +27,10 @@ async function initApis(input, token) { config = await getPlatformConfig(config); config.npmrc = config.npmrc || (await platform.getFile('.npmrc')); npmApi.resetMemCache(); - npmApi.setNpmrc(config.npmrc); + npmApi.setNpmrc( + config.npmrc, + config.global ? config.global.exposeEnv : false + ); return config; } diff --git a/test/manager/npm/__snapshots__/registry.spec.js.snap b/test/manager/npm/__snapshots__/registry.spec.js.snap index 2220264b22e7844f6496bfd13912609dd4514a79..943cf847f4ddc384ada36ffc7136cb014dc33e97 100644 --- a/test/manager/npm/__snapshots__/registry.spec.js.snap +++ b/test/manager/npm/__snapshots__/registry.spec.js.snap @@ -34,6 +34,23 @@ Object { } `; +exports[`api/npm should replace any environment variable in npmrc 1`] = ` +Object { + "dist-tags": Object { + "latest": "0.0.1", + }, + "homepage": undefined, + "name": undefined, + "renovate-config": undefined, + "repositoryUrl": undefined, + "versions": Object { + "0.0.1": Object { + "time": "", + }, + }, +} +`; + exports[`api/npm should send an authorization header if provided 1`] = ` Object { "dist-tags": Object { diff --git a/test/manager/npm/registry.spec.js b/test/manager/npm/registry.spec.js index 498085728aa71a0749c488a6229bcc9fd3b600d7..a8f61c4ec8378295a8a6693de5e665dbe072cd46 100644 --- a/test/manager/npm/registry.spec.js +++ b/test/manager/npm/registry.spec.js @@ -162,4 +162,28 @@ describe('api/npm', () => { const res = await npm.getDependency('foobar'); expect(res).toMatchSnapshot(); }); + it('should replace any environment variable in npmrc', async () => { + nock('https://registry.from-env.com') + .get('/foobar') + .reply(200, npmResponse); + process.env.REGISTRY = 'https://registry.from-env.com'; + /* eslint-disable */ + npm.setNpmrc('registry=${REGISTRY}', true); + /* eslint-enable */ + const res = await npm.getDependency('foobar'); + expect(res).toMatchSnapshot(); + }); + it('should throw error if necessary env var is not present', () => { + let e; + try { + /* eslint-disable */ + npm.setNpmrc('registry=${REGISTRY_MISSING}', true); + /* eslint-enable */ + } catch (err) { + e = err; + } + /* eslint-disable */ + expect(e.message).toBe('env-replace'); + /* eslint-enable */ + }); });