Skip to content

Commit

Permalink
feat: add an otpProvider option to allow users to use a module to pro…
Browse files Browse the repository at this point in the history
…vide an OTP to semantic-release
  • Loading branch information
MarshallOfSound committed Dec 13, 2019
1 parent 45ddc5b commit 144fb63
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 8 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The npm authentication configuration is **required** and can be set via [environ

Both the [token](https://docs.npmjs.com/getting-started/working_with_tokens) and the legacy (`username`, `password` and `email`) authentication are supported. It is recommended to use the [token](https://docs.npmjs.com/getting-started/working_with_tokens) authentication. The legacy authentication is supported as the alternative npm registries [Artifactory](https://www.jfrog.com/open-source/#os-arti) and [npm-registry-couchapp](https://github.com/npm/npm-registry-couchapp) only supports that form of authentication.

**Note**: Only the `auth-only` [level of npm two-factor authentication](https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) is supported, **semantic-release** will not work with the default `auth-and-writes` level.
**Note**: Only the `auth-only` [level of npm two-factor authentication](https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) is supported, **semantic-release** will not work with the default `auth-and-writes` level by default. If you want to use `auth-and-writes` you will need to use the `otpProvider` option to somehow give semantic-release an OTP token to use.

### Environment variables

Expand All @@ -58,11 +58,12 @@ Use either `NPM_TOKEN` for token authentication or `NPM_USERNAME`, `NPM_PASSWORD

### Options

| Options | Description | Default |
|--------------|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `npmPublish` | Whether to publish the `npm` package to the registry. If `false` the `package.json` version will still be updated. | `false` if the `package.json` [private](https://docs.npmjs.com/files/package.json#private) property is `true`, `true` otherwise. |
| `pkgRoot` | Directory path to publish. | `.` |
| `tarballDir` | Directory path in which to write the the package tarball. If `false` the tarball is not be kept on the file system. | `false` |
| Options | Description | Default |
|---------------|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `npmPublish` | Whether to publish the `npm` package to the registry. If `false` the `package.json` version will still be updated. | `false` if the `package.json` [private](https://docs.npmjs.com/files/package.json#private) property is `true`, `true` otherwise. |
| `pkgRoot` | Directory path to publish. | `.` |
| `tarballDir` | Directory path in which to write the the package tarball. If `false` the tarball is not be kept on the file system. | `false` |
| `otpProvider` | Package name for an npm module that exports a single async function that resolves with an OTP code. | `undefined` |

**Note**: The `pkgRoot` directory must contains a `package.json`. The version will be updated only in the `package.json` and `npm-shrinkwrap.json` within the `pkgRoot` directory.

Expand Down
13 changes: 11 additions & 2 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 (npmrc, {npmPublish, pkgRoot}, pkg, context) => {
module.exports = async (npmrc, {npmPublish, otpProvider, pkgRoot}, pkg, context) => {
const {
cwd,
env,
Expand All @@ -18,7 +18,16 @@ module.exports = async (npmrc, {npmPublish, pkgRoot}, pkg, context) => {
const registry = getRegistry(pkg, context);

logger.log('Publishing version %s to npm registry', version);
const result = execa('npm', ['publish', basePath, '--userconfig', npmrc, '--registry', registry], {cwd, env});
const otpArgs = [];
if (otpProvider) {
const otp = await require(otpProvider)();
otpArgs.push('--otp', otp);
}

const result = execa('npm', ['publish', basePath, '--userconfig', npmrc, '--registry', registry, ...otpArgs], {
cwd,
env,
});
result.stdout.pipe(stdout, {end: false});
result.stderr.pipe(stderr, {end: false});
await result;
Expand Down
1 change: 1 addition & 0 deletions test/helpers/fake-otp-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = () => Promise.resolve('123');
27 changes: 27 additions & 0 deletions test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,33 @@ test('Publish the package on a dist-tag', async t => {
t.is((await execa('npm', ['view', pkg.name, 'version'], {cwd, env: testEnv})).stdout, '1.0.0');
});

test('Publish the package with OTP', async t => {
const cwd = tempy.directory();
const env = npmRegistry.authEnv;
const pkg = {name: 'publish-otp', version: '0.0.0', publishConfig: {registry: npmRegistry.url}};
await outputJson(path.resolve(cwd, 'package.json'), pkg);

const result = await t.context.m.publish(
{
otpProvider: path.resolve(__dirname, 'helpers', 'fake-otp-provider.js'),
},
{
cwd,
env,
options: {},
stdout: t.context.stdout,
stderr: t.context.stderr,
logger: t.context.logger,
nextRelease: {version: '1.0.0'},
}
);

t.deepEqual(result, {name: 'npm package (@latest dist-tag)', url: undefined});
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('npm', ['view', pkg.name, 'version'], {cwd, env: testEnv})).stdout, '1.0.0');
});

test('Publish the package from a sub-directory', async t => {
const cwd = tempy.directory();
const env = npmRegistry.authEnv;
Expand Down

0 comments on commit 144fb63

Please sign in to comment.