Skip to content

Commit

Permalink
feat: preserve local .npmrc file
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Oct 4, 2019
1 parent 0126704 commit 8726eff
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 71 deletions.
14 changes: 8 additions & 6 deletions index.js
@@ -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
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
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
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
@@ -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
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
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
@@ -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
@@ -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

0 comments on commit 8726eff

Please sign in to comment.