Skip to content

Commit 187b823

Browse files
pvdlggr2m
authored andcommitted
fix: Pass registry URL to npm CLI with --registry
The environment variable `npm_config_registry` takes precedence to the registry configured in `.npmrc` when running `npm` commands. Yarn set this variable to `https://registry.yarnpkg.com` creating an `EINVALIDNPMTOKEN` error when running `semantic-release` with Yarn. Passing `--registry` with the value retrieved from `.npmrc` or `package.json` override the `npm_config_registry` set by Yarn.
1 parent 0f654b1 commit 187b823

File tree

7 files changed

+82
-50
lines changed

7 files changed

+82
-50
lines changed

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async function publish(pluginConfig, {nextRelease: {version}, logger}) {
2828
await verifyNpm(pkg, logger);
2929
verified = true;
3030
}
31-
await publishNpm(version, logger);
31+
await publishNpm(pkg, version, logger);
3232
}
3333

3434
module.exports = {verifyConditions, getLastRelease, publish};

lib/publish.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
const execa = require('execa');
2+
const getRegistry = require('./get-registry');
23
const updatePackageVersion = require('./update-package-version');
34

4-
module.exports = async (version, logger) => {
5+
module.exports = async ({publishConfig, name}, version, logger) => {
6+
const registry = await getRegistry(publishConfig, name);
57
await updatePackageVersion(version, logger);
68

79
logger.log('Publishing version %s to npm registry', version);
8-
const shell = await execa('npm', ['publish']);
10+
const shell = await execa('npm', ['publish', '--registry', registry]);
911
process.stdout.write(shell.stdout);
1012
};

lib/set-npmrc-auth.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ const {appendFile} = require('fs-extra');
22
const getAuthToken = require('registry-auth-token');
33
const nerfDart = require('nerf-dart');
44
const SemanticReleaseError = require('@semantic-release/error');
5-
const getRegistry = require('./get-registry');
65

7-
module.exports = async ({publishConfig, name}, logger) => {
8-
const registry = await getRegistry(publishConfig, name);
6+
module.exports = async (registry, logger) => {
97
logger.log('Verify authentication for registry %s', registry);
108
const {NPM_TOKEN, NPM_USERNAME, NPM_PASSWORD, NPM_EMAIL} = process.env;
119

lib/verify.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const execa = require('execa');
22
const SemanticReleaseError = require('@semantic-release/error');
3+
const getRegistry = require('./get-registry');
34
const setNpmrcAuth = require('./set-npmrc-auth');
45

56
module.exports = async (pkg, logger) => {
6-
await setNpmrcAuth(pkg, logger);
7+
const registry = await getRegistry(pkg.publishConfig, pkg.name);
8+
await setNpmrcAuth(registry, logger);
79
try {
8-
await execa('npm', ['whoami']);
10+
await execa('npm', ['whoami', '--registry', registry]);
911
} catch (err) {
1012
throw new SemanticReleaseError('Invalid npm token.', 'EINVALIDNPMTOKEN');
1113
}

test/get-registry.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import test from 'ava';
2+
import {appendFile} from 'fs-extra';
3+
import tempy from 'tempy';
4+
import getRegistry from '../lib/get-registry';
5+
6+
test.beforeEach(t => {
7+
// Save the current process.env
8+
t.context.env = Object.assign({}, process.env);
9+
// Save the current working diretory
10+
t.context.cwd = process.cwd();
11+
// Change current working directory to a temp directory
12+
process.chdir(tempy.directory());
13+
});
14+
15+
test.afterEach.always(t => {
16+
// Restore the current working directory
17+
process.chdir(t.context.cwd);
18+
});
19+
20+
test.serial('Get default registry', async t => {
21+
const registry = await getRegistry({}, 'package-name');
22+
23+
t.is(registry, 'https://registry.npmjs.org/');
24+
});
25+
26+
test.serial('Get the registry configured in ".npmrc" and normalize trailing slash', async t => {
27+
await appendFile('./.npmrc', 'registry = https://custom1.registry.com');
28+
const registry = await getRegistry({}, 'package-name');
29+
30+
t.is(registry, 'https://custom1.registry.com/');
31+
});
32+
33+
test.serial('Get the registry configured from "publishConfig"', async t => {
34+
const registry = await getRegistry({registry: 'https://custom2.registry.com/'}, 'package-name');
35+
36+
t.is(registry, 'https://custom2.registry.com/');
37+
});
38+
39+
test.serial('Get the registry configured in ".npmrc" for scoped package', async t => {
40+
await appendFile('./.npmrc', '@scope:registry = https://custom3.registry.com');
41+
const registry = await getRegistry({}, '@scope/package-name');
42+
43+
t.is(registry, 'https://custom3.registry.com/');
44+
});

test/integration.test.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,24 @@ test.beforeEach(t => {
3939
});
4040

4141
test.afterEach.always(t => {
42-
// Clear `rc` from the npm cache as it cache the relative path of .npmrc files, preventing to load a new file after changing current working directory
43-
clearModule('rc');
4442
// Restore process.env
4543
process.env = Object.assign({}, t.context.env);
4644
// Restore the current working directory
4745
process.chdir(t.context.cwd);
4846
});
4947

5048
test.after.always(async () => {
51-
// Stop the local NPM registry
52-
await npmRegistry.stop();
5349
// Restore stdout and stderr
5450
processStderr.restore();
5551
processStdout.restore();
52+
// Stop the local NPM registry
53+
await npmRegistry.stop();
5654
});
5755

5856
test.serial('Throws error if NPM token is invalid', async t => {
5957
process.env.NPM_TOKEN = 'wrong_token';
6058
const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}};
6159
await writeJson('./package.json', pkg);
62-
await appendFile('./.npmrc', `\nregistry = ${npmRegistry.url}`);
6360
const error = await t.throws(t.context.m.verifyConditions({}, {logger: t.context.logger}));
6461

6562
t.true(error instanceof SemanticReleaseError);
@@ -81,6 +78,18 @@ test.serial('Verify npm auth and package', async t => {
8178
t.regex(npmrc, /email =/);
8279
});
8380

81+
test.serial('Verify npm auth and package with "npm_config_registry" env var set by yarn', async t => {
82+
Object.assign(process.env, npmRegistry.authEnv);
83+
process.env.npm_config_registry = 'https://registry.yarnpkg.com'; // eslint-disable-line camelcase
84+
const pkg = {name: 'valid-token', publishConfig: {registry: npmRegistry.url}};
85+
await writeJson('./package.json', pkg);
86+
await t.notThrows(t.context.m.verifyConditions({}, {logger: t.context.logger}));
87+
88+
const npmrc = (await readFile('.npmrc')).toString();
89+
t.regex(npmrc, /_auth =/);
90+
t.regex(npmrc, /email =/);
91+
});
92+
8493
test.serial('Return nothing if no version if published', async t => {
8594
Object.assign(process.env, npmRegistry.authEnv);
8695
const pkg = {name: 'not-published', publishConfig: {registry: npmRegistry.url}};

test/set-npmrc-auth.test.js

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import {readFile, appendFile} from 'fs-extra';
22
import test from 'ava';
33
import {stub} from 'sinon';
44
import tempy from 'tempy';
5-
import clearModule from 'clear-module';
6-
import SemanticReleaseError from '@semantic-release/error';
75
import setNpmrcAuth from '../lib/set-npmrc-auth';
86

97
test.beforeEach(t => {
@@ -18,35 +16,21 @@ test.beforeEach(t => {
1816
t.context.cwd = process.cwd();
1917
// Change current working directory to a temp directory
2018
process.chdir(tempy.directory());
21-
// Prevent to use `.npmrc` from the home directory in case there is a valid token set there
22-
process.env.HOME = process.cwd();
23-
process.env.USERPROFILE = process.cwd();
2419
// Stub the logger
2520
t.context.log = stub();
2621
t.context.logger = {log: t.context.log};
2722
});
2823

2924
test.afterEach.always(t => {
30-
// Clear `rc` from the npm cache as it cache the relative path of .npmrc files, preventing to load a new file after changing current working directory. See https://github.com/dominictarr/rc/issues/101
31-
clearModule('rc');
3225
// Restore process.env
3326
process.env = Object.assign({}, t.context.env);
3427
// Restore the current working directory
3528
process.chdir(t.context.cwd);
3629
});
3730

38-
test.serial('Set auth with "NPM_TOKEN" and default registry', async t => {
31+
test.serial('Set auth with "NPM_TOKEN"', async t => {
3932
process.env.NPM_TOKEN = 'npm_token';
40-
await setNpmrcAuth({name: 'package-name'}, t.context.logger);
41-
42-
const npmrc = (await readFile('.npmrc')).toString();
43-
t.regex(npmrc, /\/\/registry.npmjs.org\/:_authToken = \$\{NPM_TOKEN\}/);
44-
t.true(t.context.log.calledWith('Wrote NPM_TOKEN to .npmrc.'));
45-
});
46-
47-
test.serial('Set auth with "NPM_TOKEN" and custom registry', async t => {
48-
process.env.NPM_TOKEN = 'npm_token';
49-
await setNpmrcAuth({name: 'package-name', publishConfig: {registry: 'http://custom.registry.com'}}, t.context.logger);
33+
await setNpmrcAuth('http://custom.registry.com', t.context.logger);
5034

5135
const npmrc = (await readFile('.npmrc')).toString();
5236
t.regex(npmrc, /\/\/custom.registry.com\/:_authToken = \$\{NPM_TOKEN\}/);
@@ -58,7 +42,7 @@ test.serial('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', asyn
5842
process.env.NPM_PASSWORD = 'npm_pasword';
5943
process.env.NPM_EMAIL = 'npm_email';
6044

61-
await setNpmrcAuth({name: 'package-name'}, t.context.logger);
45+
await setNpmrcAuth('http://custom.registry.com', t.context.logger);
6246

6347
const npmrc = (await readFile('.npmrc')).toString();
6448
t.regex(
@@ -72,63 +56,56 @@ test.serial('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', asyn
7256
});
7357

7458
test.serial('Do not modify ".npmrc" if auth is already configured', async t => {
75-
await appendFile('./.npmrc', `//registry.npmjs.org/:_authToken = \${NPM_TOKEN}`);
76-
await setNpmrcAuth({name: 'package-name'}, t.context.logger);
77-
78-
t.true(t.context.log.calledOnce);
79-
});
80-
81-
test.serial('Do not modify ".npmrc" if auth is already configured with custom registry', async t => {
8259
await appendFile('./.npmrc', `//custom.registry.com/:_authToken = \${NPM_TOKEN}`);
83-
await setNpmrcAuth({name: 'package-name', publishConfig: {registry: 'http://custom.registry.com'}}, t.context.logger);
60+
await setNpmrcAuth('http://custom.registry.com', t.context.logger);
8461

8562
t.true(t.context.log.calledOnce);
8663
});
8764

88-
test.serial('Do not modify ".npmrc" is auth is already configured for a scoped package', async t => {
65+
test.serial('Do not modify ".npmrc" if auth is already configured for a scoped package', async t => {
8966
await appendFile(
9067
'./.npmrc',
9168
`@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`
9269
);
93-
await setNpmrcAuth({name: '@scope/package-name'}, t.context.logger);
70+
await setNpmrcAuth('http://custom.registry.com', t.context.logger);
9471

9572
t.true(t.context.log.calledOnce);
9673
});
9774

9875
test.serial('Throw error if "NPM_TOKEN" is missing', async t => {
99-
const error = await t.throws(setNpmrcAuth({name: 'package-name'}, t.context.logger));
76+
const error = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
10077

101-
t.true(error instanceof SemanticReleaseError);
78+
t.is(error.name, 'SemanticReleaseError');
10279
t.is(error.message, 'No npm token specified.');
10380
t.is(error.code, 'ENONPMTOKEN');
10481
});
10582

10683
test.serial('Throw error if "NPM_USERNAME" is missing', async t => {
10784
process.env.NPM_PASSWORD = 'npm_pasword';
10885
process.env.NPM_EMAIL = 'npm_email';
109-
const error = await t.throws(setNpmrcAuth({name: 'package-name'}, t.context.logger));
86+
const error = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
11087

111-
t.true(error instanceof SemanticReleaseError);
88+
t.is(error.name, 'SemanticReleaseError');
11289
t.is(error.message, 'No npm token specified.');
11390
t.is(error.code, 'ENONPMTOKEN');
11491
});
11592

11693
test.serial('Throw error if "NPM_PASSWORD" is missing', async t => {
11794
process.env.NPM_USERNAME = 'npm_username';
11895
process.env.NPM_EMAIL = 'npm_email';
119-
const error = await t.throws(setNpmrcAuth({name: 'package-name'}, t.context.logger));
96+
const error = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
12097

121-
t.true(error instanceof SemanticReleaseError);
98+
t.is(error.name, 'SemanticReleaseError');
12299
t.is(error.message, 'No npm token specified.');
123100
t.is(error.code, 'ENONPMTOKEN');
124101
});
125102

126103
test.serial('Throw error if "NPM_EMAIL" is missing', async t => {
127104
process.env.NPM_USERNAME = 'npm_username';
128105
process.env.NPM_PASSWORD = 'npm_password';
129-
const error = await t.throws(setNpmrcAuth({name: 'package-name'}, t.context.logger));
106+
const error = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
130107

131-
t.true(error instanceof SemanticReleaseError);
108+
t.is(error.name, 'SemanticReleaseError');
132109
t.is(error.message, 'No npm token specified.');
133110
t.is(error.code, 'ENONPMTOKEN');
134111
});

0 commit comments

Comments
 (0)