Skip to content

feat: preserve local .npmrc file #198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const {defaultTo, castArray} = require('lodash');
const AggregateError = require('aggregate-error');
const tempy = require('tempy');
const setLegacyToken = require('./lib/set-legacy-token');
const getPkg = require('./lib/get-pkg');
const verifyNpmConfig = require('./lib/verify-config');
Expand All @@ -9,6 +10,7 @@ const publishNpm = require('./lib/publish');

let verified;
let prepared;
const npmrc = tempy.file({name: '.npmrc'});

async function verifyConditions(pluginConfig, context) {
// If the npm publish plugin is used and has `npmPublish`, `tarballDir` or `pkgRoot` configured, validate them now in order to prevent any release if the configuration is wrong
Expand All @@ -30,7 +32,7 @@ async function verifyConditions(pluginConfig, context) {

// Verify the npm authentication only if `npmPublish` is not false and `pkg.private` is not `true`
if (pluginConfig.npmPublish !== false && pkg.private !== true) {
await verifyNpmAuth(pluginConfig, pkg, context);
await verifyNpmAuth(npmrc, pkg, context);
}
} catch (error) {
errors.push(...error);
Expand All @@ -52,7 +54,7 @@ async function prepare(pluginConfig, context) {
// Reload package.json in case a previous external step updated it
const pkg = await getPkg(pluginConfig, context);
if (!verified && pluginConfig.npmPublish !== false && pkg.private !== true) {
await verifyNpmAuth(pluginConfig, pkg, context);
await verifyNpmAuth(npmrc, pkg, context);
}
} catch (error) {
errors.push(...error);
Expand All @@ -62,7 +64,7 @@ async function prepare(pluginConfig, context) {
throw new AggregateError(errors);
}

await prepareNpm(pluginConfig, context);
await prepareNpm(npmrc, pluginConfig, context);
prepared = true;
}

Expand All @@ -76,7 +78,7 @@ async function publish(pluginConfig, context) {
// Reload package.json in case a previous external step updated it
pkg = await getPkg(pluginConfig, context);
if (!verified && pluginConfig.npmPublish !== false && pkg.private !== true) {
await verifyNpmAuth(pluginConfig, pkg, context);
await verifyNpmAuth(npmrc, pkg, context);
}
} catch (error) {
errors.push(...error);
Expand All @@ -87,10 +89,10 @@ async function publish(pluginConfig, context) {
}

if (!prepared) {
await prepareNpm(pluginConfig, context);
await prepareNpm(npmrc, pluginConfig, context);
}

return publishNpm(pluginConfig, pkg, context);
return publishNpm(npmrc, pluginConfig, pkg, context);
}

module.exports = {verifyConditions, prepare, publish};
4 changes: 3 additions & 1 deletion lib/get-release-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ const execa = require('execa');
const normalizeUrl = require('normalize-url');

module.exports = async (
npmrc,
{name, publishConfig: {tag} = {}},
{cwd, env: {DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/', ...env}},
registry
) => {
const distTag = tag || (await execa('npm', ['config', 'get', 'tag'], {cwd, env})).stdout || 'latest';
const distTag =
tag || (await execa('npm', ['config', 'get', 'tag', '--userconfig', npmrc], {cwd, env})).stdout || 'latest';

return {
name: `npm package (@${distTag} dist-tag)`,
Expand Down
9 changes: 6 additions & 3 deletions lib/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ const path = require('path');
const {move} = require('fs-extra');
const execa = require('execa');

module.exports = async ({tarballDir, pkgRoot}, {cwd, env, stdout, stderr, nextRelease: {version}, logger}) => {
module.exports = async (npmrc, {tarballDir, pkgRoot}, {cwd, env, stdout, stderr, nextRelease: {version}, logger}) => {
const basePath = pkgRoot ? path.resolve(cwd, pkgRoot) : cwd;

logger.log('Write version %s to package.json in %s', version, basePath);

const versionResult = execa('npm', ['version', version, '--no-git-tag-version'], {cwd: basePath, env});
const versionResult = execa('npm', ['version', version, '--userconfig', npmrc, '--no-git-tag-version'], {
cwd: basePath,
env,
});
versionResult.stdout.pipe(
stdout,
{end: false}
Expand All @@ -21,7 +24,7 @@ module.exports = async ({tarballDir, pkgRoot}, {cwd, env, stdout, stderr, nextRe

if (tarballDir) {
logger.log('Creating npm package version %s', version);
const packResult = execa('npm', ['pack', basePath], {cwd, env});
const packResult = execa('npm', ['pack', basePath, '--userconfig', npmrc], {cwd, env});
packResult.stdout.pipe(
stdout,
{end: false}
Expand Down
6 changes: 3 additions & 3 deletions lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const execa = require('execa');
const getRegistry = require('./get-registry');
const getReleaseInfo = require('./get-release-info');

module.exports = async ({npmPublish, pkgRoot}, pkg, context) => {
module.exports = async (npmrc, {npmPublish, pkgRoot}, pkg, context) => {
const {
cwd,
env,
Expand All @@ -18,7 +18,7 @@ module.exports = async ({npmPublish, pkgRoot}, pkg, context) => {
const registry = getRegistry(pkg, context);

logger.log('Publishing version %s to npm registry', version);
const result = execa('npm', ['publish', basePath, '--registry', registry], {cwd, env});
const result = execa('npm', ['publish', basePath, '--userconfig', npmrc, '--registry', registry], {cwd, env});
result.stdout.pipe(
stdout,
{end: false}
Expand All @@ -30,7 +30,7 @@ module.exports = async ({npmPublish, pkgRoot}, pkg, context) => {
await result;

logger.log(`Published ${pkg.name}@${pkg.version} on ${registry}`);
return getReleaseInfo(pkg, context, registry);
return getReleaseInfo(npmrc, pkg, context, registry);
}

logger.log(
Expand Down
20 changes: 15 additions & 5 deletions lib/set-npmrc-auth.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
const path = require('path');
const rc = require('rc');
const {appendFile} = require('fs-extra');
const {outputFile, copy, 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);
return;
}

if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) {
await appendFile(config, `\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`);
logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${config}`);
await outputFile(npmrc, `${await readFileIfExists(config)}\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`);
logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${npmrc}`);
} else if (NPM_TOKEN) {
await appendFile(config, `\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);
logger.log(`Wrote NPM_TOKEN to ${config}`);
await outputFile(npmrc, `${await readFileIfExists(config)}\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);
logger.log(`Wrote NPM_TOKEN to ${npmrc}`);
} else {
throw new AggregateError([getError('ENONPMTOKEN', {registry})]);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/verify-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ const getError = require('./get-error');
const getRegistry = require('./get-registry');
const setNpmrcAuth = require('./set-npmrc-auth');

module.exports = async (pluginConfig, pkg, context) => {
module.exports = async (npmrc, pkg, context) => {
const {
cwd,
env: {DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/', ...env},
} = context;
const registry = getRegistry(pkg, context);

await setNpmrcAuth(registry, context);
await setNpmrcAuth(npmrc, registry, context);

if (normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)) {
try {
await execa('npm', ['whoami', '--registry', registry], {cwd, env});
await execa('npm', ['whoami', '--userconfig', npmrc, '--registry', registry], {cwd, env});
} catch (_) {
throw new AggregateError([getError('EINVALIDNPMTOKEN', {registry})]);
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"npm": "^6.10.3",
"rc": "^1.2.8",
"read-pkg": "^5.0.0",
"registry-auth-token": "^4.0.0"
"registry-auth-token": "^4.0.0",
"tempy": "^0.3.0"
},
"devDependencies": {
"ava": "^2.0.0",
Expand All @@ -44,7 +45,6 @@
"semantic-release": "^15.0.0",
"sinon": "^7.1.1",
"stream-buffers": "^3.0.2",
"tempy": "^0.3.0",
"xo": "^0.25.0"
},
"engines": {
Expand Down
30 changes: 20 additions & 10 deletions test/get-release-info.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from 'path';
import test from 'ava';
import {writeFile} from 'fs-extra';
import tempy from 'tempy';
Expand All @@ -15,37 +14,41 @@ test.beforeEach(() => {

test('Default registry and tag', async t => {
const cwd = tempy.directory();
const npmrc = tempy.file({name: '.npmrc'});

t.deepEqual(await getReleaseInfo({name: 'module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
t.deepEqual(await getReleaseInfo(npmrc, {name: 'module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
name: 'npm package (@latest dist-tag)',
url: 'https://www.npmjs.com/package/module',
});
});

test('Default registry, tag and scoped module', async t => {
const cwd = tempy.directory();
const npmrc = tempy.file({name: '.npmrc'});

t.deepEqual(await getReleaseInfo({name: '@scope/module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
t.deepEqual(await getReleaseInfo(npmrc, {name: '@scope/module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
name: 'npm package (@latest dist-tag)',
url: 'https://www.npmjs.com/package/@scope/module',
});
});

test('Custom registry, tag and scoped module', async t => {
const cwd = tempy.directory();
const npmrc = tempy.file({name: '.npmrc'});

t.deepEqual(await getReleaseInfo({name: '@scope/module'}, {cwd, env: {}}, 'https://custom.registry.org/'), {
t.deepEqual(await getReleaseInfo(npmrc, {name: '@scope/module'}, {cwd, env: {}}, 'https://custom.registry.org/'), {
name: 'npm package (@latest dist-tag)',
url: undefined,
});
});

test('Default registry and tag from .npmrc', async t => {
const cwd = tempy.directory();
await writeFile(path.resolve(cwd, '.npmrc'), 'tag=npmrc');
const npmrc = tempy.file({name: '.npmrc'});
await writeFile(npmrc, 'tag=npmrc');

t.deepEqual(
await getReleaseInfo({name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
await getReleaseInfo(npmrc, {name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
{
name: 'npm package (@npmrc dist-tag)',
url: 'https://www.npmjs.com/package/module',
Expand All @@ -55,22 +58,29 @@ test('Default registry and tag from .npmrc', async t => {

test('Default registry and tag from package.json', async t => {
const cwd = tempy.directory();
const npmrc = tempy.file({name: '.npmrc'});

await writeFile(path.resolve(cwd, '.npmrc'), 'tag=npmrc');
await writeFile(npmrc, 'tag=npmrc');

t.deepEqual(
await getReleaseInfo({name: 'module', publishConfig: {tag: 'pkg'}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
await getReleaseInfo(
npmrc,
{name: 'module', publishConfig: {tag: 'pkg'}},
{cwd, env: {}},
'https://registry.npmjs.org/'
),
{name: 'npm package (@pkg dist-tag)', url: 'https://www.npmjs.com/package/module'}
);
});

test('Default tag', async t => {
const cwd = tempy.directory();
const npmrc = tempy.file({name: '.npmrc'});

await writeFile(path.resolve(cwd, '.npmrc'), 'tag=');
await writeFile(npmrc, 'tag=');

t.deepEqual(
await getReleaseInfo({name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
await getReleaseInfo(npmrc, {name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
{
name: 'npm package (@latest dist-tag)',
url: 'https://www.npmjs.com/package/module',
Expand Down
20 changes: 1 addition & 19 deletions test/integration.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path';
import test from 'ava';
import {outputJson, readJson, readFile, pathExists} from 'fs-extra';
import {outputJson, readJson, pathExists} from 'fs-extra';
import execa from 'execa';
import {spy} from 'sinon';
import tempy from 'tempy';
Expand Down Expand Up @@ -110,9 +110,6 @@ test('Throws error if NPM token is invalid', async t => {
t.is(error.name, 'SemanticReleaseError');
t.is(error.code, 'EINVALIDNPMTOKEN');
t.is(error.message, 'Invalid npm token.');

const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
t.regex(npmrc, /:_authToken/);
});

test('Skip Token validation if the registry configured is not the default one', async t => {
Expand All @@ -126,9 +123,6 @@ test('Skip Token validation if the registry configured is not the default one',
{cwd, env, options: {}, stdout: t.context.stdout, stderr: t.context.stderr, logger: t.context.logger}
)
);

const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
t.regex(npmrc, /:_authToken/);
});

test('Verify npm auth and package', async t => {
Expand All @@ -148,10 +142,6 @@ test('Verify npm auth and package', async t => {
}
)
);

const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
t.regex(npmrc, /_auth =/);
t.regex(npmrc, /email =/);
});

test('Verify npm auth and package from a sub-directory', async t => {
Expand All @@ -171,10 +161,6 @@ test('Verify npm auth and package from a sub-directory', async t => {
}
)
);

const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
t.regex(npmrc, /_auth =/);
t.regex(npmrc, /email =/);
});

test('Verify npm auth and package with "npm_config_registry" env var set by yarn', async t => {
Expand All @@ -194,10 +180,6 @@ test('Verify npm auth and package with "npm_config_registry" env var set by yarn
}
)
);

const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
t.regex(npmrc, /_auth =/);
t.regex(npmrc, /email =/);
});

test('Throw SemanticReleaseError Array if config option are not valid in verifyConditions', async t => {
Expand Down
Loading