Skip to content
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

feat: add an otpProvider option to allow users to use a module to provide an OTP to semantic-release #234

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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