Repository: semantic-release/npm
Pull request #89 (2018-07-17 03:04:34)

feat: use `cwd` and `env` options passed by core
BREAKING CHANGE: require `semantic-release` >= `15.8.0`


Patch:
diff --git a/index.js b/index.js
index 15c907c..c5e64b6 100644
--- a/index.js
+++ b/index.js
@@ -24,11 +24,11 @@ async function verifyConditions(pluginConfig, context) {
   const errors = verifyNpmConfig(pluginConfig);
 
   try {
-    const pkg = await getPkg(pluginConfig.pkgRoot);
+    const pkg = await getPkg(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) {
-      setLegacyToken();
+      setLegacyToken(context);
       await verifyNpmAuth(pluginConfig, pkg, context);
     }
   } catch (err) {
@@ -46,9 +46,9 @@ async function prepare(pluginConfig, context) {
 
   try {
     // Reload package.json in case a previous external step updated it
-    pkg = await getPkg(pluginConfig.pkgRoot);
+    pkg = await getPkg(pluginConfig, context);
     if (!verified && pluginConfig.npmPublish !== false && pkg.private !== true) {
-      setLegacyToken();
+      setLegacyToken(context);
       await verifyNpmAuth(pluginConfig, pkg, context);
     }
   } catch (err) {
@@ -65,11 +65,11 @@ async function publish(pluginConfig, context) {
   let pkg;
   const errors = verified ? [] : verifyNpmConfig(pluginConfig);
 
-  setLegacyToken();
+  setLegacyToken(context);
 
   try {
     // Reload package.json in case a previous external step updated it
-    pkg = await getPkg(pluginConfig.pkgRoot);
+    pkg = await getPkg(pluginConfig, context);
     if (!verified && pluginConfig.npmPublish !== false && pkg.private !== true) {
       await verifyNpmAuth(pluginConfig, pkg, context);
     }
diff --git a/lib/get-pkg.js b/lib/get-pkg.js
index 9219122..d22a1c3 100644
--- a/lib/get-pkg.js
+++ b/lib/get-pkg.js
@@ -1,13 +1,14 @@
+const path = require('path');
 const readPkg = require('read-pkg');
 const AggregateError = require('aggregate-error');
 const getError = require('./get-error');
 
-module.exports = async pkgRoot => {
+module.exports = async ({pkgRoot}, {cwd}) => {
   const errors = [];
   let pkg;
 
   try {
-    pkg = await readPkg({cwd: pkgRoot ? String(pkgRoot) : process.cwd()});
+    pkg = await readPkg({cwd: pkgRoot ? path.resolve(cwd, String(pkgRoot)) : cwd});
 
     if (!pkg.name) {
       errors.push(getError('ENOPKGNAME'));
diff --git a/lib/get-registry.js b/lib/get-registry.js
index b06f18c..5a25e4e 100644
--- a/lib/get-registry.js
+++ b/lib/get-registry.js
@@ -1,3 +1,11 @@
+const path = require('path');
+const rc = require('rc');
 const getRegistryUrl = require('registry-auth-token/registry-url');
 
-module.exports = async ({registry} = {}, name) => (registry ? registry : getRegistryUrl(name.split('/')[0]));
+module.exports = ({publishConfig: {registry} = {}, name}, {cwd}) =>
+  registry
+    ? registry
+    : getRegistryUrl(
+        name.split('/')[0],
+        rc('npm', {registry: 'https://registry.npmjs.org/'}, {config: path.resolve(cwd, '.npmrc')})
+      );
diff --git a/lib/get-release-info.js b/lib/get-release-info.js
index 7fbf6aa..6955088 100644
--- a/lib/get-release-info.js
+++ b/lib/get-release-info.js
@@ -1,19 +1,18 @@
 const execa = require('execa');
 const normalizeUrl = require('normalize-url');
 
-const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/';
-
 module.exports = async (
-  name,
-  publishConfig,
-  registry,
-  defaultRegistry = process.env.DEFAULT_NPM_REGISTRY || DEFAULT_NPM_REGISTRY
+  {name, publishConfig: {tag} = {}},
+  {cwd, env: {DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/', ...env}},
+  registry
 ) => {
-  const distTag =
-    (publishConfig && publishConfig.tag) || (await execa.stdout('npm', ['config', 'get', 'tag'])) || 'latest';
+  const distTag = tag || (await execa.stdout('npm', ['config', 'get', 'tag'], {cwd, env})) || 'latest';
 
   return {
     name: `npm package (@${distTag} dist-tag)`,
-    url: normalizeUrl(registry) === normalizeUrl(defaultRegistry) ? `https://www.npmjs.com/package/${name}` : undefined,
+    url:
+      normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)
+        ? `https://www.npmjs.com/package/${name}`
+        : undefined,
   };
 };
diff --git a/lib/prepare.js b/lib/prepare.js
index 763ce86..c337dbe 100644
--- a/lib/prepare.js
+++ b/lib/prepare.js
@@ -3,13 +3,13 @@ const {move} = require('fs-extra');
 const execa = require('execa');
 const updatePackageVersion = require('./update-package-version');
 
-module.exports = async ({tarballDir, pkgRoot}, {nextRelease: {version}, logger}) => {
-  const basePath = pkgRoot || '.';
+module.exports = async ({tarballDir, pkgRoot}, {cwd, nextRelease: {version}, logger}) => {
+  const basePath = pkgRoot ? path.resolve(cwd, pkgRoot) : cwd;
   await updatePackageVersion(version, basePath, logger);
 
   if (tarballDir) {
     logger.log('Creating npm package version %s', version);
-    const tarball = (await execa.stdout('npm', ['pack', `./${basePath}`])).split('\n').pop();
-    await move(tarball, path.join(tarballDir.trim(), tarball));
+    const tarball = (await execa.stdout('npm', ['pack', basePath], {cwd})).split('\n').pop();
+    await move(path.resolve(cwd, tarball), path.resolve(cwd, tarballDir.trim(), tarball));
   }
 };
diff --git a/lib/publish.js b/lib/publish.js
index fe25e25..8d9ea41 100644
--- a/lib/publish.js
+++ b/lib/publish.js
@@ -1,21 +1,25 @@
+const path = require('path');
 const execa = require('execa');
 const getRegistry = require('./get-registry');
 const getReleaseInfo = require('./get-release-info');
 
-module.exports = async (
-  {npmPublish, pkgRoot},
-  {publishConfig, name, private: priv},
-  {nextRelease: {version}, logger}
-) => {
-  if (npmPublish !== false && priv !== true) {
-    const basePath = pkgRoot || '.';
-    const registry = await getRegistry(publishConfig, name);
+module.exports = async ({npmPublish, pkgRoot}, pkg, context) => {
+  const {
+    cwd,
+    env,
+    nextRelease: {version},
+    logger,
+  } = context;
+
+  if (npmPublish !== false && pkg.private !== true) {
+    const basePath = pkgRoot ? path.resolve(cwd, pkgRoot) : cwd;
+    const registry = getRegistry(pkg, context);
 
     logger.log('Publishing version %s to npm registry', version);
-    const shell = await execa('npm', ['publish', `./${basePath}`, '--registry', registry]);
+    const shell = await execa('npm', ['publish', basePath, '--registry', registry], {cwd, env});
 
-    process.stdout.write(shell.stdout);
-    return getReleaseInfo(name, publishConfig, registry);
+    logger.stdout(shell.stdout);
+    return getReleaseInfo(pkg, context, registry);
   }
   logger.log(
     'Skip publishing to npm registry as %s is %s',
diff --git a/lib/set-legacy-token.js b/lib/set-legacy-token.js
index e9ded7b..a31a673 100644
--- a/lib/set-legacy-token.js
+++ b/lib/set-legacy-token.js
@@ -1,8 +1,6 @@
-module.exports = () => {
+module.exports = ({env}) => {
   // Set the environment variable `LEGACY_TOKEN` when user use the legacy auth, so it can be resolved by npm CLI
-  if (process.env.NPM_USERNAME && process.env.NPM_PASSWORD && process.env.NPM_EMAIL) {
-    process.env.LEGACY_TOKEN = Buffer.from(`${process.env.NPM_USERNAME}:${process.env.NPM_PASSWORD}`, 'utf8').toString(
-      'base64'
-    );
+  if (env.NPM_USERNAME && env.NPM_PASSWORD && env.NPM_EMAIL) {
+    env.LEGACY_TOKEN = Buffer.from(`${env.NPM_USERNAME}:${env.NPM_PASSWORD}`, 'utf8').toString('base64');
   }
 };
diff --git a/lib/set-npmrc-auth.js b/lib/set-npmrc-auth.js
index dda54bd..f0bfe89 100644
--- a/lib/set-npmrc-auth.js
+++ b/lib/set-npmrc-auth.js
@@ -1,22 +1,23 @@
+const path = require('path');
+const rc = require('rc');
 const {appendFile} = require('fs-extra');
 const getAuthToken = require('registry-auth-token');
 const nerfDart = require('nerf-dart');
 const AggregateError = require('aggregate-error');
 const getError = require('./get-error');
 
-module.exports = async (registry, logger) => {
+module.exports = async (registry, {cwd, env: {NPM_TOKEN, NPM_USERNAME, NPM_PASSWORD, NPM_EMAIL}, logger}) => {
   logger.log('Verify authentication for registry %s', registry);
-  const {NPM_TOKEN, NPM_USERNAME, NPM_PASSWORD, NPM_EMAIL} = process.env;
-
-  if (getAuthToken(registry)) {
+  const config = path.resolve(cwd, '.npmrc');
+  if (getAuthToken(registry, {npmrc: rc('npm', {registry: 'https://registry.npmjs.org/'}, {config})})) {
     return;
   }
   if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) {
-    await appendFile('./.npmrc', `\n_auth = ${Buffer.from(`\${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`)}`);
-    logger.log('Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to .npmrc.');
+    await appendFile(config, `\n_auth = ${Buffer.from(`\${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`)}`);
+    logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${config}`);
   } else if (NPM_TOKEN) {
-    await appendFile('./.npmrc', `\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);
-    logger.log('Wrote NPM_TOKEN to .npmrc.');
+    await appendFile(config, `\n${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);
+    logger.log(`Wrote NPM_TOKEN to ${config}`);
   } else {
     throw new AggregateError([getError('ENONPMTOKEN', {registry})]);
   }
diff --git a/lib/update-package-version.js b/lib/update-package-version.js
index 1c6af56..26cc36f 100644
--- a/lib/update-package-version.js
+++ b/lib/update-package-version.js
@@ -8,9 +8,9 @@ const DEFAULT_INDENT = 2;
 const DEFAULT_NEWLINE = '\n';
 
 module.exports = async (version, basePath, logger) => {
-  const packagePath = path.join(basePath, 'package.json');
-  const shrinkwrapPath = path.join(basePath, 'npm-shrinkwrap.json');
-  const packageLockPath = path.join(basePath, 'package-lock.json');
+  const packagePath = path.resolve(basePath, 'package.json');
+  const shrinkwrapPath = path.resolve(basePath, 'npm-shrinkwrap.json');
+  const packageLockPath = path.resolve(basePath, 'package-lock.json');
   const pkg = await readFile(packagePath, 'utf8');
 
   await writeJson(packagePath, {...parseJson(pkg), ...{version}}, getWriteOptions(pkg));
diff --git a/lib/verify-auth.js b/lib/verify-auth.js
index 13cb03a..f9e55d9 100644
--- a/lib/verify-auth.js
+++ b/lib/verify-auth.js
@@ -5,20 +5,18 @@ const getError = require('./get-error');
 const getRegistry = require('./get-registry');
 const setNpmrcAuth = require('./set-npmrc-auth');
 
-const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/';
+module.exports = async (pluginConfig, pkg, context) => {
+  const {
+    cwd,
+    env: {DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/', ...env},
+  } = context;
+  const registry = getRegistry(pkg, context);
 
-module.exports = async (
-  pluginConfig,
-  {publishConfig, name},
-  {logger},
-  defaultRegistry = process.env.DEFAULT_NPM_REGISTRY || DEFAULT_NPM_REGISTRY
-) => {
-  const registry = await getRegistry(publishConfig, name);
-  await setNpmrcAuth(registry, logger);
+  await setNpmrcAuth(registry, context);
 
-  if (normalizeUrl(registry) === normalizeUrl(defaultRegistry)) {
+  if (normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)) {
     try {
-      await execa('npm', ['whoami', '--registry', registry]);
+      await execa('npm', ['whoami', '--registry', registry], {cwd, env});
     } catch (err) {
       throw new AggregateError([getError('EINVALIDNPMTOKEN', {registry})]);
     }
diff --git a/package.json b/package.json
index a97e95d..ca7b4cc 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "nerf-dart": "^1.0.0",
     "normalize-url": "^3.0.0",
     "parse-json": "^4.0.0",
+    "rc": "^1.2.8",
     "read-pkg": "^4.0.0",
     "registry-auth-token": "^3.3.1"
   },
@@ -77,6 +78,9 @@
     ],
     "all": true
   },
+  "peerDependencies": {
+    "semantic-release": ">=15.8.0 <16.0.0"
+  },
   "prettier": {
     "printWidth": 120,
     "trailingComma": "es5"


Test patch:
diff --git a/test/get-pkg.test.js b/test/get-pkg.test.js
index d075c83..b8b53c9 100644
--- a/test/get-pkg.test.js
+++ b/test/get-pkg.test.js
@@ -1,59 +1,53 @@
+import path from 'path';
 import test from 'ava';
 import {outputJson, writeFile} from 'fs-extra';
 import tempy from 'tempy';
 import getPkg from '../lib/get-pkg';
 
-// Save the current working diretory
-const cwd = process.cwd();
-
-test.beforeEach(() => {
-  // Change current working directory to a temp directory
-  process.chdir(tempy.directory());
-});
-
-test.afterEach.always(() => {
-  // Restore the current working directory
-  process.chdir(cwd);
-});
-
-test.serial('Verify name and version then return parsed package.json', async t => {
+test('Verify name and version then return parsed package.json', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'package', version: '0.0.0'};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  const result = await getPkg();
+  const result = await getPkg({}, {cwd});
   t.is(pkg.name, result.name);
   t.is(pkg.version, result.version);
 });
 
-test.serial('Verify name and version then return parsed package.json from a sub-directory', async t => {
+test('Verify name and version then return parsed package.json from a sub-directory', async t => {
+  const cwd = tempy.directory();
+  const pkgRoot = 'dist';
   const pkg = {name: 'package', version: '0.0.0'};
-  await outputJson('./dist/package.json', pkg);
+  await outputJson(path.resolve(cwd, pkgRoot, 'package.json'), pkg);
 
-  const result = await getPkg('dist');
+  const result = await getPkg({pkgRoot}, {cwd});
   t.is(pkg.name, result.name);
   t.is(pkg.version, result.version);
 });
 
-test.serial('Throw error if missing package.json', async t => {
-  const [error] = await t.throws(getPkg());
+test('Throw error if missing package.json', async t => {
+  const cwd = tempy.directory();
+  const [error] = await t.throws(getPkg({}, {cwd}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.code, 'ENOPKG');
 });
 
-test.serial('Throw error if missing package name', async t => {
-  await outputJson('./package.json', {version: '0.0.0'});
+test('Throw error if missing package name', async t => {
+  const cwd = tempy.directory();
+  await outputJson(path.resolve(cwd, 'package.json'), {version: '0.0.0'});
 
-  const [error] = await t.throws(getPkg());
+  const [error] = await t.throws(getPkg({}, {cwd}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.code, 'ENOPKGNAME');
 });
 
-test.serial('Throw error if package.json is malformed', async t => {
-  await writeFile('./package.json', "{name: 'package',}");
+test('Throw error if package.json is malformed', async t => {
+  const cwd = tempy.directory();
+  await writeFile(path.resolve(cwd, 'package.json'), "{name: 'package',}");
 
-  const [error] = await t.throws(getPkg());
+  const [error] = await t.throws(getPkg({}, {cwd}));
 
   t.is(error.name, 'JSONError');
 });
diff --git a/test/get-registry.test.js b/test/get-registry.test.js
index d35c86e..fa542be 100644
--- a/test/get-registry.test.js
+++ b/test/get-registry.test.js
@@ -1,38 +1,35 @@
+import path from 'path';
 import test from 'ava';
 import {appendFile} from 'fs-extra';
 import tempy from 'tempy';
 import getRegistry from '../lib/get-registry';
 
-// Save the current working diretory
-const cwd = process.cwd();
-
-test.beforeEach(() => {
-  // Change current working directory to a temp directory
-  process.chdir(tempy.directory());
+test('Get default registry', t => {
+  const cwd = tempy.directory();
+  t.is(getRegistry({name: 'package-name'}, {cwd}), 'https://registry.npmjs.org/');
+  t.is(getRegistry({name: 'package-name', publishConfig: {}}, {cwd}), 'https://registry.npmjs.org/');
 });
 
-test.afterEach.always(() => {
-  // Restore the current working directory
-  process.chdir(cwd);
-});
+test('Get the registry configured in ".npmrc" and normalize trailing slash', async t => {
+  const cwd = tempy.directory();
+  await appendFile(path.resolve(cwd, '.npmrc'), 'registry = https://custom1.registry.com');
 
-test.serial('Get default registry', async t => {
-  t.is(await getRegistry(undefined, 'package-name'), 'https://registry.npmjs.org/');
-  t.is(await getRegistry({}, 'package-name'), 'https://registry.npmjs.org/');
+  t.is(getRegistry({name: 'package-name'}, {cwd}), 'https://custom1.registry.com/');
 });
 
-test.serial('Get the registry configured in ".npmrc" and normalize trailing slash', async t => {
-  await appendFile('./.npmrc', 'registry = https://custom1.registry.com');
-
-  t.is(await getRegistry({}, 'package-name'), 'https://custom1.registry.com/');
-});
+test('Get the registry configured from "publishConfig"', async t => {
+  const cwd = tempy.directory();
+  await appendFile(path.resolve(cwd, '.npmrc'), 'registry = https://custom2.registry.com');
 
-test.serial('Get the registry configured from "publishConfig"', async t => {
-  t.is(await getRegistry({registry: 'https://custom2.registry.com/'}, 'package-name'), 'https://custom2.registry.com/');
+  t.is(
+    getRegistry({name: 'package-name', publishConfig: {registry: 'https://custom3.registry.com/'}}, {cwd}),
+    'https://custom3.registry.com/'
+  );
 });
 
-test.serial('Get the registry configured in ".npmrc" for scoped package', async t => {
-  await appendFile('./.npmrc', '@scope:registry = https://custom3.registry.com');
+test('Get the registry configured in ".npmrc" for scoped package', async t => {
+  const cwd = tempy.directory();
+  await appendFile(path.resolve(cwd, '.npmrc'), '@scope:registry = https://custom3.registry.com');
 
-  t.is(await getRegistry({}, '@scope/package-name'), 'https://custom3.registry.com/');
+  t.is(getRegistry({name: '@scope/package-name'}, {cwd}), 'https://custom3.registry.com/');
 });
diff --git a/test/get-release-info.test.js b/test/get-release-info.test.js
index 7975613..575307a 100644
--- a/test/get-release-info.test.js
+++ b/test/get-release-info.test.js
@@ -1,16 +1,10 @@
-import {writeFile} from 'fs-extra';
+import path from 'path';
 import test from 'ava';
+import {writeFile} from 'fs-extra';
 import tempy from 'tempy';
 import getReleaseInfo from '../lib/get-release-info';
 
-// Save the current process.env
-const envBackup = Object.assign({}, process.env);
-// Save the current working diretory
-const cwd = process.cwd();
-
 test.beforeEach(() => {
-  // Change current working directory to a temp directory
-  process.chdir(tempy.directory());
   // Delete all `npm_config` environment variable set by CI as they take precedence over the `.npmrc` because the process that runs the tests is started before the `.npmrc` is created
   for (let i = 0, keys = Object.keys(process.env); i < keys.length; i++) {
     if (keys[i].startsWith('npm_')) {
@@ -19,54 +13,67 @@ test.beforeEach(() => {
   }
 });
 
-test.afterEach.always(() => {
-  // Restore process.env
-  process.env = envBackup;
-  // Restore the current working directory
-  process.chdir(cwd);
-});
+test('Default registry and tag', async t => {
+  const cwd = tempy.directory();
 
-test.serial('Default registry and tag', async t => {
-  t.deepEqual(await getReleaseInfo('module', null, 'https://registry.npmjs.org/'), {
+  t.deepEqual(await getReleaseInfo({name: 'module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
     name: 'npm package (@latest dist-tag)',
     url: 'https://www.npmjs.com/package/module',
   });
 });
 
-test.serial('Default registry, tag and scoped module', async t => {
-  t.deepEqual(await getReleaseInfo('@scope/module', null, 'https://registry.npmjs.org/'), {
+test('Default registry, tag and scoped module', async t => {
+  const cwd = tempy.directory();
+
+  t.deepEqual(await getReleaseInfo({name: '@scope/module'}, {cwd, env: {}}, 'https://registry.npmjs.org/'), {
     name: 'npm package (@latest dist-tag)',
     url: 'https://www.npmjs.com/package/@scope/module',
   });
 });
 
-test.serial('Custom registry, tag and scoped module', async t => {
-  t.deepEqual(await getReleaseInfo('@scope/module', null, 'https://custom.registry.org/'), {
+test('Custom registry, tag and scoped module', async t => {
+  const cwd = tempy.directory();
+
+  t.deepEqual(await getReleaseInfo({name: '@scope/module'}, {cwd, env: {}}, 'https://custom.registry.org/'), {
     name: 'npm package (@latest dist-tag)',
     url: undefined,
   });
 });
 
-test.serial('Default registry and tag from .npmrc', async t => {
-  await writeFile('./.npmrc', 'tag=npmrc');
-  t.deepEqual(await getReleaseInfo('module', {}, 'https://registry.npmjs.org/'), {
-    name: 'npm package (@npmrc dist-tag)',
-    url: 'https://www.npmjs.com/package/module',
-  });
+test('Default registry and tag from .npmrc', async t => {
+  const cwd = tempy.directory();
+  await writeFile(path.resolve(cwd, '.npmrc'), 'tag=npmrc');
+
+  t.deepEqual(
+    await getReleaseInfo({name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
+    {
+      name: 'npm package (@npmrc dist-tag)',
+      url: 'https://www.npmjs.com/package/module',
+    }
+  );
 });
 
-test.serial('Default registry and tag from package.json', async t => {
-  await writeFile('./.npmrc', 'tag=npmrc');
-  t.deepEqual(await getReleaseInfo('module', {tag: 'pkg'}, 'https://registry.npmjs.org/'), {
-    name: 'npm package (@pkg dist-tag)',
-    url: 'https://www.npmjs.com/package/module',
-  });
+test('Default registry and tag from package.json', async t => {
+  const cwd = tempy.directory();
+
+  await writeFile(path.resolve(cwd, '.npmrc'), 'tag=npmrc');
+
+  t.deepEqual(
+    await getReleaseInfo({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.serial('Default tag', async t => {
-  await writeFile('./.npmrc', 'tag=');
-  t.deepEqual(await getReleaseInfo('module', {}, 'https://registry.npmjs.org/'), {
-    name: 'npm package (@latest dist-tag)',
-    url: 'https://www.npmjs.com/package/module',
-  });
+test('Default tag', async t => {
+  const cwd = tempy.directory();
+
+  await writeFile(path.resolve(cwd, '.npmrc'), 'tag=');
+
+  t.deepEqual(
+    await getReleaseInfo({name: 'module', publishConfig: {}}, {cwd, env: {}}, 'https://registry.npmjs.org/'),
+    {
+      name: 'npm package (@latest dist-tag)',
+      url: 'https://www.npmjs.com/package/module',
+    }
+  );
 });
diff --git a/test/integration.test.js b/test/integration.test.js
index 11dd0cc..e4dfeed 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -1,18 +1,16 @@
+import path from 'path';
 import test from 'ava';
 import {outputJson, readJson, readFile, pathExists} from 'fs-extra';
 import execa from 'execa';
-import {stub} from 'sinon';
+import {spy} from 'sinon';
 import tempy from 'tempy';
 import clearModule from 'clear-module';
-import SemanticReleaseError from '@semantic-release/error';
 import npmRegistry from './helpers/npm-registry';
 
-// Save the current process.env
-const envBackup = Object.assign({}, process.env);
-// Save the current working diretory
-const cwd = process.cwd();
-// Disable logs during tests
-stub(process.stdout, 'write');
+const LEGACY_TOKEN = Buffer.from(
+  `${npmRegistry.authEnv.NPM_USERNAME}:${npmRegistry.authEnv.NPM_PASSWORD}`,
+  'utf8'
+).toString('base64');
 
 test.before(async () => {
   // Start the local NPM registry
@@ -20,27 +18,13 @@ test.before(async () => {
 });
 
 test.beforeEach(t => {
-  // Delete env paramaters that could have been set on the machine running the tests
-  delete process.env.NPM_TOKEN;
-  delete process.env.NPM_USERNAME;
-  delete process.env.NPM_PASSWORD;
-  delete process.env.NPM_EMAIL;
-  delete process.env.DEFAULT_NPM_REGISTRY;
-  // Change current working directory to a temporary directory
-  process.chdir(tempy.directory());
   // Clear npm cache to refresh the module state
   clearModule('..');
   t.context.m = require('..');
   // Stub the logger
-  t.context.log = stub();
-  t.context.logger = {log: t.context.log};
-});
-
-test.afterEach.always(() => {
-  // Restore process.env
-  process.env = envBackup;
-  // Restore the current working directory
-  process.chdir(cwd);
+  t.context.log = spy();
+  t.context.stdout = spy();
+  t.context.logger = {log: t.context.log, stdout: t.context.stdout};
 });
 
 test.after.always(async () => {
@@ -48,105 +32,120 @@ test.after.always(async () => {
   await npmRegistry.stop();
 });
 
-test.serial('Skip npm auth verification if "npmPublish" is false', async t => {
-  process.env.NPM_TOKEN = 'wrong_token';
+test('Skip npm auth verification if "npmPublish" is false', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_TOKEN: 'wrong_token'};
   const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({npmPublish: false}, {options: {}, logger: t.context.logger}));
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
+
+  await t.notThrows(
+    t.context.m.verifyConditions({npmPublish: false}, {cwd, env, options: {}, logger: t.context.logger})
+  );
 });
 
-test.serial('Skip npm auth verification if "package.private" is true', async t => {
-  process.env.NPM_TOKEN = 'wrong_token';
+test('Skip npm auth verification if "package.private" is true', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}, private: true};
-  await outputJson('./package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({}, {options: {}, logger: t.context.logger}));
-});
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-test.serial('Skip npm token verification if "npmPublish" is false', async t => {
-  delete process.env.NPM_TOKEN;
-  const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
   await t.notThrows(
     t.context.m.verifyConditions(
       {npmPublish: false},
-      {options: {publish: ['@semantic-release/npm']}, logger: t.context.logger}
+      {cwd, options: {publish: ['@semantic-release/npm']}, logger: t.context.logger}
     )
   );
 });
 
-test.serial('Skip npm token verification if "package.private" is true', async t => {
-  delete process.env.NPM_TOKEN;
+test('Skip npm token verification if "package.private" is true', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}, private: true};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
   await t.notThrows(
-    t.context.m.verifyConditions({}, {options: {publish: ['@semantic-release/npm']}, logger: t.context.logger})
+    t.context.m.verifyConditions({}, {cwd, options: {publish: ['@semantic-release/npm']}, logger: t.context.logger})
   );
 });
 
-test.serial('Throws error if NPM token is invalid', async t => {
-  process.env.NPM_TOKEN = 'wrong_token';
-  process.env.DEFAULT_NPM_REGISTRY = npmRegistry.url;
+test('Throws error if NPM token is invalid', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_TOKEN: 'wrong_token', DEFAULT_NPM_REGISTRY: npmRegistry.url};
   const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  const [error] = await t.throws(t.context.m.verifyConditions({}, {options: {}, logger: t.context.logger}));
+  const [error] = await t.throws(t.context.m.verifyConditions({}, {cwd, env, options: {}, logger: t.context.logger}));
 
-  t.true(error instanceof SemanticReleaseError);
+  t.is(error.name, 'SemanticReleaseError');
   t.is(error.code, 'EINVALIDNPMTOKEN');
   t.is(error.message, 'Invalid npm token.');
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /:_authToken/);
 });
 
-test.serial('Skip Token validation if the registry configured is not the default one', async t => {
-  process.env.NPM_TOKEN = 'wrong_token';
+test('Skip Token validation if the registry configured is not the default one', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_TOKEN: 'wrong_token'};
   const pkg = {name: 'published', version: '1.0.0', publishConfig: {registry: 'http://custom-registry.com/'}};
-  await outputJson('./package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({}, {options: {}, logger: t.context.logger}));
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
+  await t.notThrows(t.context.m.verifyConditions({}, {cwd, env, options: {}, logger: t.context.logger}));
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /:_authToken/);
 });
 
-test.serial('Verify npm auth and package', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
-
+test('Verify npm auth and package', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'valid-token', version: '0.0.0-dev', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({}, {options: {}, logger: t.context.logger}));
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
+  await t.notThrows(
+    t.context.m.verifyConditions({}, {cwd, env: npmRegistry.authEnv, options: {}, logger: t.context.logger})
+  );
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /_auth =/);
   t.regex(npmrc, /email =/);
 });
 
-test.serial('Verify npm auth and package from a sub-directory', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Verify npm auth and package from a sub-directory', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'valid-token', version: '0.0.0-dev', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./dist/package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({pkgRoot: 'dist'}, {options: {}, logger: t.context.logger}));
+  await outputJson(path.resolve(cwd, 'dist/package.json'), pkg);
+  await t.notThrows(
+    t.context.m.verifyConditions(
+      {pkgRoot: 'dist'},
+      {cwd, env: npmRegistry.authEnv, options: {}, logger: t.context.logger}
+    )
+  );
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /_auth =/);
   t.regex(npmrc, /email =/);
 });
 
-test.serial('Verify npm auth and package with "npm_config_registry" env var set by yarn', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
-  process.env.npm_config_registry = 'https://registry.yarnpkg.com'; // eslint-disable-line camelcase
+test('Verify npm auth and package with "npm_config_registry" env var set by yarn', async t => {
+  const cwd = tempy.directory();
   const pkg = {name: 'valid-token', version: '0.0.0-dev', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
-  await t.notThrows(t.context.m.verifyConditions({}, {options: {publish: []}, logger: t.context.logger}));
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
+  await t.notThrows(
+    t.context.m.verifyConditions(
+      {},
+      {
+        cwd,
+        env: {...npmRegistry.authEnv, npm_config_registry: 'https://registry.yarnpkg.com'}, // eslint-disable-line camelcase
+        options: {publish: []},
+        logger: t.context.logger,
+      }
+    )
+  );
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /_auth =/);
   t.regex(npmrc, /email =/);
 });
 
-test.serial('Throw SemanticReleaseError Array if config option are not valid in verifyConditions', async t => {
+test('Throw SemanticReleaseError Array if config option are not valid in verifyConditions', async t => {
+  const cwd = tempy.directory();
   const pkg = {publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
   const npmPublish = 42;
   const tarballDir = 42;
   const pkgRoot = 42;
@@ -155,6 +154,8 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
       t.context.m.verifyConditions(
         {},
         {
+          cwd,
+          env: {},
           options: {
             publish: ['@semantic-release/github', {path: '@semantic-release/npm', npmPublish, tarballDir, pkgRoot}],
           },
@@ -174,133 +175,143 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
   t.is(errors[3].code, 'ENOPKG');
 });
 
-test.serial('Publish the package', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Publish the package', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'publish', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  const result = await t.context.m.publish({}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  const result = await t.context.m.publish(
+    {},
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+  );
 
   t.deepEqual(result, {name: 'npm package (@latest dist-tag)', url: undefined});
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.false(await pathExists(`./${pkg.name}-1.0.0.tgz`));
-  t.is(await execa.stdout('npm', ['view', pkg.name, 'version']), '1.0.0');
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.false(await pathExists(path.resolve(cwd, `${pkg.name}-1.0.0.tgz`)));
+  t.is(
+    await execa.stdout('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}),
+    '1.0.0'
+  );
 });
 
-test.serial('Publish the package on a dist-tag', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
-  process.env.DEFAULT_NPM_REGISTRY = npmRegistry.url;
+test('Publish the package on a dist-tag', async t => {
+  const cwd = tempy.directory();
+  const env = {...npmRegistry.authEnv, DEFAULT_NPM_REGISTRY: npmRegistry.url};
   const pkg = {name: 'publish-tag', version: '0.0.0', publishConfig: {registry: npmRegistry.url, tag: 'next'}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  const result = await t.context.m.publish({}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  const result = await t.context.m.publish(
+    {},
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+  );
 
   t.deepEqual(result, {name: 'npm package (@next dist-tag)', url: 'https://www.npmjs.com/package/publish-tag'});
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.false(await pathExists(`./${pkg.name}-1.0.0.tgz`));
-  t.is(await execa.stdout('npm', ['view', pkg.name, 'version']), '1.0.0');
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.false(await pathExists(path.resolve(cwd, `${pkg.name}-1.0.0.tgz`)));
+  t.is(
+    await execa.stdout('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}),
+    '1.0.0'
+  );
 });
 
-test.serial('Publish the package from a sub-directory', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Publish the package from a sub-directory', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'publish-sub-dir', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./dist/package.json', pkg);
+  await outputJson(path.resolve(cwd, 'dist/package.json'), pkg);
 
   const result = await t.context.m.publish(
     {pkgRoot: 'dist'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
   t.deepEqual(result, {name: 'npm package (@latest dist-tag)', url: undefined});
-  t.is((await readJson('./dist/package.json')).version, '1.0.0');
-  t.false(await pathExists(`./${pkg.name}-1.0.0.tgz`));
-  t.is(await execa.stdout('npm', ['view', pkg.name, 'version']), '1.0.0');
+  t.is((await readJson(path.resolve(cwd, 'dist/package.json'))).version, '1.0.0');
+  t.false(await pathExists(path.resolve(cwd, `${pkg.name}-1.0.0.tgz`)));
+  t.is(
+    await execa.stdout('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}),
+    '1.0.0'
+  );
 });
 
-test.serial('Create the package and skip publish ("npmPublish" is false)', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
-  // Delete the authentication to make sure they are not required when skipping publish to registry
-  delete process.env.NPM_TOKEN;
-  delete process.env.NPM_USERNAME;
-  delete process.env.NPM_PASSWORD;
-  delete process.env.NPM_EMAIL;
-
+test('Create the package and skip publish ("npmPublish" is false)', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'skip-publish', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
   const result = await t.context.m.publish(
     {npmPublish: false, tarballDir: 'tarball'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
   t.falsy(result);
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.true(await pathExists(`./tarball/${pkg.name}-1.0.0.tgz`));
-  await t.throws(execa('npm', ['view', pkg.name, 'version']));
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.true(await pathExists(path.resolve(cwd, `tarball/${pkg.name}-1.0.0.tgz`)));
+  await t.throws(execa('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}));
 });
 
-test.serial('Create the package and skip publish ("package.private" is true)', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
-  // Delete the authentication to make sure they are not required when skipping publish to registry
-  delete process.env.NPM_TOKEN;
-  delete process.env.NPM_USERNAME;
-  delete process.env.NPM_PASSWORD;
-  delete process.env.NPM_EMAIL;
-
+test('Create the package and skip publish ("package.private" is true)', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'skip-publish', version: '0.0.0', publishConfig: {registry: npmRegistry.url}, private: true};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
   const result = await t.context.m.publish(
     {tarballDir: 'tarball'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
   t.falsy(result);
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.true(await pathExists(`./tarball/${pkg.name}-1.0.0.tgz`));
-  await t.throws(execa('npm', ['view', pkg.name, 'version']));
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.true(await pathExists(path.resolve(cwd, `tarball/${pkg.name}-1.0.0.tgz`)));
+  await t.throws(execa('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}));
 });
 
-test.serial('Create the package and skip publish from a sub-directory ("npmPublish" is false)', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Create the package and skip publish from a sub-directory ("npmPublish" is false)', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'skip-publish-sub-dir', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./dist/package.json', pkg);
+  await outputJson(path.resolve(cwd, 'dist/package.json'), pkg);
 
   const result = await t.context.m.publish(
     {npmPublish: false, tarballDir: './tarball', pkgRoot: './dist'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
   t.falsy(result);
-  t.is((await readJson('./dist/package.json')).version, '1.0.0');
-  t.true(await pathExists(`./tarball/${pkg.name}-1.0.0.tgz`));
-  await t.throws(execa('npm', ['view', pkg.name, 'version']));
+  t.is((await readJson(path.resolve(cwd, 'dist/package.json'))).version, '1.0.0');
+  t.true(await pathExists(path.resolve(cwd, `tarball/${pkg.name}-1.0.0.tgz`)));
+  await t.throws(execa('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}));
 });
 
-test.serial('Create the package and skip publish from a sub-directory ("package.private" is true)', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Create the package and skip publish from a sub-directory ("package.private" is true)', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {
-    name: 'skip-publish-sub-dir',
+    name: 'skip-publish-sub-dir-private',
     version: '0.0.0',
     publishConfig: {registry: npmRegistry.url},
     private: true,
   };
-  await outputJson('./dist/package.json', pkg);
+  await outputJson(path.resolve(cwd, 'dist/package.json'), pkg);
 
   const result = await t.context.m.publish(
     {tarballDir: './tarball', pkgRoot: './dist'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
   t.falsy(result);
-  t.is((await readJson('./dist/package.json')).version, '1.0.0');
-  t.true(await pathExists(`./tarball/${pkg.name}-1.0.0.tgz`));
-  await t.throws(execa('npm', ['view', pkg.name, 'version']));
+  t.is((await readJson(path.resolve(cwd, 'dist/package.json'))).version, '1.0.0');
+  t.true(await pathExists(path.resolve(cwd, `tarball/${pkg.name}-1.0.0.tgz`)));
+  await t.throws(execa('npm', ['view', pkg.name, 'version'], {cwd, env: {...npmRegistry.authEnv, LEGACY_TOKEN}}));
 });
 
-test.serial('Throw SemanticReleaseError Array if config option are not valid in publish', async t => {
+test('Throw SemanticReleaseError Array if config option are not valid in publish', async t => {
+  const cwd = tempy.directory();
   const pkg = {publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
   const npmPublish = 42;
   const tarballDir = 42;
   const pkgRoot = 42;
@@ -310,6 +321,8 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
       t.context.m.publish(
         {npmPublish, tarballDir, pkgRoot},
         {
+          cwd,
+          env: {},
           options: {publish: ['@semantic-release/github', '@semantic-release/npm']},
           nextRelease: {version: '1.0.0'},
           logger: t.context.logger,
@@ -328,44 +341,52 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
   t.is(errors[3].code, 'ENOPKG');
 });
 
-test.serial('Prepare the package', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Prepare the package', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'prepare', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  await t.context.m.prepare({}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  await t.context.m.prepare({}, {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}});
 
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.false(await pathExists(`./${pkg.name}-1.0.0.tgz`));
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.false(await pathExists(path.resolve(cwd, `${pkg.name}-1.0.0.tgz`)));
 });
 
-test.serial('Prepare the package from a sub-directory', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Prepare the package from a sub-directory', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'prepare-sub-dir', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./dist/package.json', pkg);
+  await outputJson(path.resolve(cwd, 'dist/package.json'), pkg);
 
-  await t.context.m.prepare({pkgRoot: 'dist'}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  await t.context.m.prepare(
+    {pkgRoot: 'dist'},
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+  );
 
-  t.is((await readJson('./dist/package.json')).version, '1.0.0');
-  t.false(await pathExists(`./${pkg.name}-1.0.0.tgz`));
+  t.is((await readJson(path.resolve(cwd, 'dist/package.json'))).version, '1.0.0');
+  t.false(await pathExists(path.resolve(cwd, `${pkg.name}-1.0.0.tgz`)));
 });
 
-test.serial('Create the package in prepare step', async t => {
+test('Create the package in prepare step', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'prepare-pkg', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
   await t.context.m.prepare(
     {npmPublish: false, tarballDir: 'tarball'},
-    {logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
   );
 
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.true(await pathExists(`./tarball/${pkg.name}-1.0.0.tgz`));
+  t.is((await readJson(path.resolve(cwd, 'package.json'))).version, '1.0.0');
+  t.true(await pathExists(path.resolve(cwd, `tarball/${pkg.name}-1.0.0.tgz`)));
 });
 
-test.serial('Throw SemanticReleaseError Array if config option are not valid in prepare', async t => {
+test('Throw SemanticReleaseError Array if config option are not valid in prepare', async t => {
+  const cwd = tempy.directory();
   const pkg = {publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
   const npmPublish = 42;
   const tarballDir = 42;
   const pkgRoot = 42;
@@ -375,6 +396,8 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
       t.context.m.prepare(
         {npmPublish, tarballDir, pkgRoot},
         {
+          cwd,
+          env: {},
           options: {publish: ['@semantic-release/github', '@semantic-release/npm']},
           nextRelease: {version: '1.0.0'},
           logger: t.context.logger,
@@ -393,14 +416,18 @@ test.serial('Throw SemanticReleaseError Array if config option are not valid in
   t.is(errors[3].code, 'ENOPKG');
 });
 
-test.serial('Verify token and set up auth only on the fist call, then prepare on prepare call only', async t => {
-  Object.assign(process.env, npmRegistry.authEnv);
+test('Verify token and set up auth only on the fist call, then prepare on prepare call only', async t => {
+  const cwd = tempy.directory();
+  const env = npmRegistry.authEnv;
   const pkg = {name: 'test-module', version: '0.0.0-dev', publishConfig: {registry: npmRegistry.url}};
-  await outputJson('./package.json', pkg);
+  await outputJson(path.resolve(cwd, 'package.json'), pkg);
 
-  await t.notThrows(t.context.m.verifyConditions({}, {options: {}, logger: t.context.logger}));
-  await t.context.m.prepare({}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  await t.notThrows(t.context.m.verifyConditions({}, {cwd, env, options: {}, logger: t.context.logger}));
+  await t.context.m.prepare({}, {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}});
 
-  const result = await t.context.m.publish({}, {logger: t.context.logger, nextRelease: {version: '1.0.0'}});
+  const result = await t.context.m.publish(
+    {},
+    {cwd, env, options: {}, logger: t.context.logger, nextRelease: {version: '1.0.0'}}
+  );
   t.deepEqual(result, {name: 'npm package (@latest dist-tag)', url: undefined});
 });
diff --git a/test/set-npmrc-auth.test.js b/test/set-npmrc-auth.test.js
index 428d072..c8d8570 100644
--- a/test/set-npmrc-auth.test.js
+++ b/test/set-npmrc-auth.test.js
@@ -1,104 +1,100 @@
-import {readFile, appendFile} from 'fs-extra';
+import path from 'path';
 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';
 
-// Save the current process.env
-const envBackup = Object.assign({}, process.env);
-// Save the current working diretory
-const cwd = process.cwd();
-
 test.beforeEach(t => {
-  // Delete env paramaters that could have been set on the machine running the tests
-  delete process.env.NPM_TOKEN;
-  delete process.env.NPM_USERNAME;
-  delete process.env.NPM_PASSWORD;
-  delete process.env.NPM_EMAIL;
-  // Change current working directory to a temp directory
-  process.chdir(tempy.directory());
   // Stub the logger
   t.context.log = stub();
   t.context.logger = {log: t.context.log};
 });
 
-test.afterEach.always(() => {
-  // Restore process.env
-  process.env = envBackup;
-  // Restore the current working directory
-  process.chdir(cwd);
-});
+test('Set auth with "NPM_TOKEN"', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_TOKEN: 'npm_token'};
 
-test.serial('Set auth with "NPM_TOKEN"', async t => {
-  process.env.NPM_TOKEN = 'npm_token';
-  await setNpmrcAuth('http://custom.registry.com', t.context.logger);
+  await setNpmrcAuth('http://custom.registry.com', {cwd, env, logger: t.context.logger});
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.regex(npmrc, /\/\/custom.registry.com\/:_authToken = \$\{NPM_TOKEN\}/);
-  t.deepEqual(t.context.log.args[1], ['Wrote NPM_TOKEN to .npmrc.']);
+  t.deepEqual(t.context.log.args[1], [`Wrote NPM_TOKEN to ${path.resolve(cwd, '.npmrc')}`]);
 });
 
-test.serial('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', async t => {
-  process.env.NPM_USERNAME = 'npm_username';
-  process.env.NPM_PASSWORD = 'npm_pasword';
-  process.env.NPM_EMAIL = 'npm_email';
+test('Set auth with "NPM_USERNAME", "NPM_PASSWORD" and "NPM_EMAIL"', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_USERNAME: 'npm_username', NPM_PASSWORD: 'npm_pasword', NPM_EMAIL: 'npm_email'};
 
-  await setNpmrcAuth('http://custom.registry.com', t.context.logger);
+  await setNpmrcAuth('http://custom.registry.com', {cwd, env, logger: t.context.logger});
 
-  const npmrc = (await readFile('.npmrc')).toString();
+  const npmrc = (await readFile(path.resolve(cwd, '.npmrc'))).toString();
   t.is(npmrc, `\n_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`);
-  t.deepEqual(t.context.log.args[1], ['Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to .npmrc.']);
+  t.deepEqual(t.context.log.args[1], [
+    `Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${path.resolve(cwd, '.npmrc')}`,
+  ]);
 });
 
-test.serial('Do not modify ".npmrc" if auth is already configured', async t => {
-  await appendFile('./.npmrc', `//custom.registry.com/:_authToken = \${NPM_TOKEN}`);
-  await setNpmrcAuth('http://custom.registry.com', t.context.logger);
+test('Do not modify ".npmrc" if auth is already configured', async t => {
+  const cwd = tempy.directory();
+
+  await appendFile(path.resolve(cwd, '.npmrc'), `//custom.registry.com/:_authToken = \${NPM_TOKEN}`);
+
+  await setNpmrcAuth('http://custom.registry.com', {cwd, env: {}, logger: t.context.logger});
 
   t.is(t.context.log.callCount, 1);
 });
 
-test.serial('Do not modify ".npmrc" if auth is already configured for a scoped package', async t => {
+test('Do not modify ".npmrc" if auth is already configured for a scoped package', async t => {
+  const cwd = tempy.directory();
+
   await appendFile(
-    './.npmrc',
+    path.resolve(cwd, '.npmrc'),
     `@scope:registry=http://custom.registry.com\n//custom.registry.com/:_authToken = \${NPM_TOKEN}`
   );
-  await setNpmrcAuth('http://custom.registry.com', t.context.logger);
+
+  await setNpmrcAuth('http://custom.registry.com', {cwd, env: {}, logger: t.context.logger});
 
   t.is(t.context.log.callCount, 1);
 });
 
-test.serial('Throw error if "NPM_TOKEN" is missing', async t => {
-  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
+test('Throw error if "NPM_TOKEN" is missing', async t => {
+  const cwd = tempy.directory();
+
+  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', {cwd, env: {}, logger: t.context.logger}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.message, 'No npm token specified.');
   t.is(error.code, 'ENONPMTOKEN');
 });
 
-test.serial('Throw error if "NPM_USERNAME" is missing', async t => {
-  process.env.NPM_PASSWORD = 'npm_pasword';
-  process.env.NPM_EMAIL = 'npm_email';
-  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
+test('Throw error if "NPM_USERNAME" is missing', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_PASSWORD: 'npm_pasword', NPM_EMAIL: 'npm_email'};
+
+  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', {cwd, env, logger: t.context.logger}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.message, 'No npm token specified.');
   t.is(error.code, 'ENONPMTOKEN');
 });
 
-test.serial('Throw error if "NPM_PASSWORD" is missing', async t => {
-  process.env.NPM_USERNAME = 'npm_username';
-  process.env.NPM_EMAIL = 'npm_email';
-  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
+test('Throw error if "NPM_PASSWORD" is missing', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_USERNAME: 'npm_username', NPM_EMAIL: 'npm_email'};
+
+  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', {cwd, env, logger: t.context.logger}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.message, 'No npm token specified.');
   t.is(error.code, 'ENONPMTOKEN');
 });
 
-test.serial('Throw error if "NPM_EMAIL" is missing', async t => {
-  process.env.NPM_USERNAME = 'npm_username';
-  process.env.NPM_PASSWORD = 'npm_password';
-  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', t.context.logger));
+test('Throw error if "NPM_EMAIL" is missing', async t => {
+  const cwd = tempy.directory();
+  const env = {NPM_USERNAME: 'npm_username', NPM_PASSWORD: 'npm_password'};
+
+  const [error] = await t.throws(setNpmrcAuth('http://custom.registry.com', {cwd, env, logger: t.context.logger}));
 
   t.is(error.name, 'SemanticReleaseError');
   t.is(error.message, 'No npm token specified.');
diff --git a/test/update-package-version.test.js b/test/update-package-version.test.js
index 3ad868c..f8eb397 100644
--- a/test/update-package-version.test.js
+++ b/test/update-package-version.test.js
@@ -1,3 +1,4 @@
+import path from 'path';
 import test from 'ava';
 import {outputJson, readJson, outputFile, readFile} from 'fs-extra';
 import tempy from 'tempy';
@@ -5,109 +6,107 @@ import execa from 'execa';
 import {stub} from 'sinon';
 import updatePackageVersion from '../lib/update-package-version';
 
-// Save the current working diretory
-const cwd = process.cwd();
-
 test.beforeEach(t => {
-  process.chdir(tempy.directory());
-
   t.context.log = stub();
   t.context.logger = {log: t.context.log};
 });
 
-test.afterEach.always(() => {
-  // Restore the current working directory
-  process.chdir(cwd);
-});
-
-test.serial('Updade package.json', async t => {
-  // Create package.json in repository root
-  await outputJson('./package.json', {version: '0.0.0-dev'});
+test('Updade package.json', async t => {
+  const cwd = tempy.directory();
+  const packagePath = path.resolve(cwd, 'package.json');
+  await outputJson(packagePath, {version: '0.0.0-dev'});
 
-  await updatePackageVersion('1.0.0', '.', t.context.logger);
+  await updatePackageVersion('1.0.0', cwd, t.context.logger);
 
   // Verify package.json has been updated
-  t.is((await readJson('./package.json')).version, '1.0.0');
+  t.is((await readJson(packagePath)).version, '1.0.0');
 
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'package.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
 });
 
-test.serial('Updade package.json and npm-shrinkwrap.json', async t => {
-  // Create package.json in repository root
-  await outputJson('./package.json', {version: '0.0.0-dev'});
+test('Updade package.json and npm-shrinkwrap.json', async t => {
+  const cwd = tempy.directory();
+  const packagePath = path.resolve(cwd, 'package.json');
+  const shrinkwrapPath = path.resolve(cwd, 'npm-shrinkwrap.json');
+  await outputJson(packagePath, {version: '0.0.0-dev'});
   // Create a npm-shrinkwrap.json file
-  await execa('npm', ['shrinkwrap']);
+  await execa('npm', ['shrinkwrap'], {cwd});
 
-  await updatePackageVersion('1.0.0', '.', t.context.logger);
+  await updatePackageVersion('1.0.0', cwd, t.context.logger);
 
   // Verify package.json and npm-shrinkwrap.json have been updated
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.is((await readJson('./npm-shrinkwrap.json')).version, '1.0.0');
+  t.is((await readJson(packagePath)).version, '1.0.0');
+  t.is((await readJson(shrinkwrapPath)).version, '1.0.0');
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'package.json']);
-  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', 'npm-shrinkwrap.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
+  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', shrinkwrapPath]);
 });
 
-test.serial('Updade package.json and package-lock.json', async t => {
-  // Create package.json in repository root
-  await outputJson('./package.json', {version: '0.0.0-dev'});
+test('Updade package.json and package-lock.json', async t => {
+  const cwd = tempy.directory();
+  const packagePath = path.resolve(cwd, 'package.json');
+  const packageLockPath = path.resolve(cwd, 'package-lock.json');
+  await outputJson(packagePath, {version: '0.0.0-dev'});
   // Create a npm-shrinkwrap.json file
-  await execa('npm', ['install']);
+  await execa('npm', ['install'], {cwd});
 
-  await updatePackageVersion('1.0.0', '.', t.context.logger);
+  await updatePackageVersion('1.0.0', cwd, t.context.logger);
 
   // Verify package.json and npm-shrinkwrap.json have been updated
-  t.is((await readJson('./package.json')).version, '1.0.0');
-  t.is((await readJson('./package-lock.json')).version, '1.0.0');
+  t.is((await readJson(packagePath)).version, '1.0.0');
+  t.is((await readJson(packageLockPath)).version, '1.0.0');
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'package.json']);
-  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', 'package-lock.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
+  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', packageLockPath]);
 });
 
-test.serial('Updade package.json and npm-shrinkwrap.json in a sub-directory', async t => {
-  // Create package.json in repository root
-  await outputJson('./dist/package.json', {version: '0.0.0-dev'});
-  process.chdir('dist');
+test('Updade package.json and npm-shrinkwrap.json in a sub-directory', async t => {
+  const cwd = tempy.directory();
+  const pkgRoot = 'dist';
+  const packagePath = path.resolve(cwd, pkgRoot, 'package.json');
+  const shrinkwrapPath = path.resolve(cwd, pkgRoot, 'npm-shrinkwrap.json');
+  await outputJson(packagePath, {version: '0.0.0-dev'});
   // Create a npm-shrinkwrap.json file
-  await execa('npm', ['shrinkwrap']);
-  process.chdir('..');
+  await execa('npm', ['shrinkwrap'], {cwd: path.resolve(cwd, pkgRoot)});
 
-  await updatePackageVersion('1.0.0', 'dist', t.context.logger);
+  await updatePackageVersion('1.0.0', path.resolve(cwd, pkgRoot), t.context.logger);
 
   // Verify package.json and npm-shrinkwrap.json have been updated
-  t.is((await readJson('./dist/package.json')).version, '1.0.0');
-  t.is((await readJson('./dist/npm-shrinkwrap.json')).version, '1.0.0');
+  t.is((await readJson(packagePath)).version, '1.0.0');
+  t.is((await readJson(path.resolve(cwd, 'dist', 'npm-shrinkwrap.json'))).version, '1.0.0');
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'dist/package.json']);
-  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', 'dist/npm-shrinkwrap.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
+  t.deepEqual(t.context.log.args[1], ['Wrote version %s to %s', '1.0.0', shrinkwrapPath]);
 });
 
-test.serial('Preserve indentation and newline', async t => {
-  // Create package.json in repository root
-  await outputFile('./package.json', `{\r\n        "name": "package-name",\r\n        "version": "0.0.0-dev"\r\n}\r\n`);
+test('Preserve indentation and newline', async t => {
+  const cwd = tempy.directory();
+  const packagePath = path.resolve(cwd, 'package.json');
+  await outputFile(packagePath, `{\r\n        "name": "package-name",\r\n        "version": "0.0.0-dev"\r\n}\r\n`);
 
-  await updatePackageVersion('1.0.0', '.', t.context.logger);
+  await updatePackageVersion('1.0.0', cwd, t.context.logger);
 
   // Verify package.json has been updated
   t.is(
-    await readFile('./package.json', 'utf-8'),
+    await readFile(packagePath, 'utf-8'),
     `{\r\n        "name": "package-name",\r\n        "version": "1.0.0"\r\n}\r\n`
   );
 
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'package.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
 });
 
-test.serial('Use default indentation and newline if it cannot be detected', async t => {
-  // Create package.json in repository root
-  await outputFile('./package.json', `{"name": "package-name","version": "0.0.0-dev"}`);
+test('Use default indentation and newline if it cannot be detected', async t => {
+  const cwd = tempy.directory();
+  const packagePath = path.resolve(cwd, 'package.json');
+  await outputFile(packagePath, `{"name": "package-name","version": "0.0.0-dev"}`);
 
-  await updatePackageVersion('1.0.0', '.', t.context.logger);
+  await updatePackageVersion('1.0.0', cwd, t.context.logger);
 
   // Verify package.json has been updated
-  t.is(await readFile('./package.json', 'utf-8'), `{\n  "name": "package-name",\n  "version": "1.0.0"\n}\n`);
+  t.is(await readFile(packagePath, 'utf-8'), `{\n  "name": "package-name",\n  "version": "1.0.0"\n}\n`);
 
   // Verify the logger has been called with the version updated
-  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', 'package.json']);
+  t.deepEqual(t.context.log.args[0], ['Wrote version %s to %s', '1.0.0', packagePath]);
 });