From a43fdde48d8a75ba3130f50078a857daf1ab3689 Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Mon, 28 Oct 2019 12:48:35 -0400 Subject: [PATCH] fix: preserve config in both home and local `.npmrc` --- lib/set-npmrc-auth.js | 26 ++++----- test/set-npmrc-auth.test.js | 107 +++++++++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 35 deletions(-) diff --git a/lib/set-npmrc-auth.js b/lib/set-npmrc-auth.js index 0ffde7ed..d2d1ba8f 100644 --- a/lib/set-npmrc-auth.js +++ b/lib/set-npmrc-auth.js @@ -1,36 +1,34 @@ const path = require('path'); const rc = require('rc'); -const {outputFile, copy, readFile} = require('fs-extra'); +const {outputFile, readFile} = require('fs-extra'); const getAuthToken = require('registry-auth-token'); const nerfDart = require('nerf-dart'); const AggregateError = require('aggregate-error'); const getError = require('./get-error'); -const readFileIfExists = async path => { - try { - return await readFile(path); - } catch (_) { - return ''; - } -}; - module.exports = async ( npmrc, registry, {cwd, env: {NPM_TOKEN, NPM_CONFIG_USERCONFIG, NPM_USERNAME, NPM_PASSWORD, NPM_EMAIL}, logger} ) => { logger.log('Verify authentication for registry %s', registry); - const config = NPM_CONFIG_USERCONFIG || path.resolve(cwd, '.npmrc'); - if (getAuthToken(registry, {npmrc: rc('npm', {registry: 'https://registry.npmjs.org/'}, {config})})) { - await copy(config, npmrc); + const {configs, ...rcConfig} = rc( + 'npm', + {registry: 'https://registry.npmjs.org/'}, + {config: NPM_CONFIG_USERCONFIG || path.resolve(cwd, '.npmrc')} + ); + const currentConfig = configs ? (await Promise.all(configs.map(config => readFile(config)))).join('\n') : ''; + + if (getAuthToken(registry, {npmrc: rcConfig})) { + await outputFile(npmrc, currentConfig); return; } if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) { - await outputFile(npmrc, `${await readFileIfExists(config)}\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`); + await outputFile(npmrc, `${currentConfig}\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`); logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${npmrc}`); } else if (NPM_TOKEN) { - await outputFile(npmrc, `${await readFileIfExists(config)}\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`); + await outputFile(npmrc, `${currentConfig}\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`); logger.log(`Wrote NPM_TOKEN to ${npmrc}`); } else { throw new AggregateError([getError('ENONPMTOKEN', {registry})]); diff --git a/test/set-npmrc-auth.test.js b/test/set-npmrc-auth.test.js index da1705e0..94700ae5 100644 --- a/test/set-npmrc-auth.test.js +++ b/test/set-npmrc-auth.test.js @@ -3,72 +3,129 @@ import test from 'ava'; import {readFile, appendFile} from 'fs-extra'; import {stub} from 'sinon'; import tempy from 'tempy'; -import setNpmrcAuth from '../lib/set-npmrc-auth'; +import clearModule from 'clear-module'; + +const {HOME} = process.env; +const cwd = process.cwd(); test.beforeEach(t => { // Stub the logger t.context.log = stub(); t.context.logger = {log: t.context.log}; + + clearModule('rc'); + clearModule('../lib/set-npmrc-auth'); +}); + +test.afterEach.always(() => { + process.env.HOME = HOME; + process.chdir(cwd); }); -test('Set auth with "NPM_TOKEN"', async t => { +test.serial('Set auth with "NPM_TOKEN"', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const env = {NPM_TOKEN: 'npm_token'}; - await setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); t.regex((await readFile(npmrc)).toString(), /\/\/custom.registry.com\/:_authToken = \$\{NPM_TOKEN\}/); t.deepEqual(t.context.log.args[1], [`Wrote NPM_TOKEN to ${npmrc}`]); }); -test('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', async t => { +test.serial('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const env = {NPM_USERNAME: 'npm_username', NPM_PASSWORD: 'npm_pasword', NPM_EMAIL: 'npm_email'}; - await setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); t.is((await readFile(npmrc)).toString(), `\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`); t.deepEqual(t.context.log.args[1], [`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${npmrc}`]); }); -test('Copy ".npmrc" if auth is already configured', async t => { +test.serial('Preserve home ".npmrc"', async t => { + process.env.HOME = tempy.directory(); + const cwd = tempy.directory(); + process.chdir(cwd); + const npmrc = tempy.file({name: '.npmrc'}); + const env = {NPM_TOKEN: 'npm_token'}; + + await appendFile(path.resolve(process.env.HOME, '.npmrc'), 'home_config = test'); + + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); + + t.is((await readFile(npmrc)).toString(), `home_config = test\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + t.deepEqual(t.context.log.args[1], [`Wrote NPM_TOKEN to ${npmrc}`]); +}); + +test.serial('Preserve home and local ".npmrc"', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); + const npmrc = tempy.file({name: '.npmrc'}); + const env = {NPM_TOKEN: 'npm_token'}; + + await appendFile(path.resolve(cwd, '.npmrc'), 'cwd_config = test'); + await appendFile(path.resolve(process.env.HOME, '.npmrc'), 'home_config = test'); + + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}); + + t.is( + (await readFile(npmrc)).toString(), + `home_config = test\ncwd_config = test\n//custom.registry.com/:_authToken = \${NPM_TOKEN}` + ); + t.deepEqual(t.context.log.args[1], [`Wrote NPM_TOKEN to ${npmrc}`]); +}); + +test.serial('Preserve all ".npmrc" if auth is already configured', async t => { + process.env.HOME = tempy.directory(); + const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); await appendFile(path.resolve(cwd, '.npmrc'), `//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + await appendFile(path.resolve(process.env.HOME, '.npmrc'), 'home_config = test'); - await setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}); + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}); - t.is((await readFile(npmrc)).toString(), `//custom.registry.com/:_authToken = \${NPM_TOKEN}`); + t.is((await readFile(npmrc)).toString(), `home_config = test\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`); t.is(t.context.log.callCount, 1); }); -test('Copy ".npmrc" if auth is already configured for a scoped package', async t => { +test.serial('Preserve ".npmrc" if auth is already configured for a scoped package', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); await appendFile( path.resolve(cwd, '.npmrc'), `@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}` ); + await appendFile(path.resolve(process.env.HOME, '.npmrc'), 'home_config = test'); - await setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}); + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}); t.is( (await readFile(npmrc)).toString(), - `@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}` + `home_config = test\n@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}` ); t.is(t.context.log.callCount, 1); }); -test('Throw error if "NPM_TOKEN" is missing', async t => { +test.serial('Throw error if "NPM_TOKEN" is missing', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const [error] = await t.throwsAsync( - setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}) + require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}) ); t.is(error.name, 'SemanticReleaseError'); @@ -76,13 +133,15 @@ test('Throw error if "NPM_TOKEN" is missing', async t => { t.is(error.code, 'ENONPMTOKEN'); }); -test('Emulate npm config resolution if "NPM_CONFIG_USERCONFIG" is set', async t => { +test.serial('Emulate npm config resolution if "NPM_CONFIG_USERCONFIG" is set', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); await appendFile(path.resolve(cwd, '.custom-npmrc'), `//custom.registry.com/:_authToken = \${NPM_TOKEN}`); - await setNpmrcAuth(npmrc, 'http://custom.registry.com', { + await require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', { cwd, env: {NPM_CONFIG_USERCONFIG: path.resolve(cwd, '.custom-npmrc')}, logger: t.context.logger, @@ -92,13 +151,15 @@ test('Emulate npm config resolution if "NPM_CONFIG_USERCONFIG" is set', async t t.is(t.context.log.callCount, 1); }); -test('Throw error if "NPM_USERNAME" is missing', async t => { +test.serial('Throw error if "NPM_USERNAME" is missing', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const env = {NPM_PASSWORD: 'npm_pasword', NPM_EMAIL: 'npm_email'}; const [error] = await t.throwsAsync( - setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) + require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) ); t.is(error.name, 'SemanticReleaseError'); @@ -106,13 +167,15 @@ test('Throw error if "NPM_USERNAME" is missing', async t => { t.is(error.code, 'ENONPMTOKEN'); }); -test('Throw error if "NPM_PASSWORD" is missing', async t => { +test.serial('Throw error if "NPM_PASSWORD" is missing', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const env = {NPM_USERNAME: 'npm_username', NPM_EMAIL: 'npm_email'}; const [error] = await t.throwsAsync( - setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) + require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) ); t.is(error.name, 'SemanticReleaseError'); @@ -120,13 +183,15 @@ test('Throw error if "NPM_PASSWORD" is missing', async t => { t.is(error.code, 'ENONPMTOKEN'); }); -test('Throw error if "NPM_EMAIL" is missing', async t => { +test.serial('Throw error if "NPM_EMAIL" is missing', async t => { + process.env.HOME = tempy.directory(); const cwd = tempy.directory(); + process.chdir(cwd); const npmrc = tempy.file({name: '.npmrc'}); const env = {NPM_USERNAME: 'npm_username', NPM_PASSWORD: 'npm_password'}; const [error] = await t.throwsAsync( - setNpmrcAuth(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) + require('../lib/set-npmrc-auth')(npmrc, 'http://custom.registry.com', {cwd, env, logger: t.context.logger}) ); t.is(error.name, 'SemanticReleaseError');