semantic-release/test/integration.test.js
2018-01-01 20:20:19 -05:00

837 lines
32 KiB
JavaScript

import test from 'ava';
import {writeJson, readJson} from 'fs-extra';
import {stub} from 'sinon';
import execa from 'execa';
import {gitRepo, gitCommits, gitHead, gitTagVersion, gitPackRefs, gitAmmendCommit} from './helpers/git-utils';
import mockServer from './helpers/mockserver';
import npmRegistry from './helpers/npm-registry';
import semanticRelease from '..';
/* eslint camelcase: ["error", {properties: "never"}] */
// Environment variables used with semantic-release cli (similar to what a user would setup)
const env = {
GH_TOKEN: 'github_token',
GITHUB_URL: mockServer.url,
NPM_EMAIL: 'integration@test.com',
NPM_USERNAME: 'integration',
NPM_PASSWORD: 'suchsecure',
};
// Environment variables used only for the local npm command used to do verification
const testEnv = Object.assign({}, process.env, {
npm_config_registry: npmRegistry.url,
NPM_EMAIL: 'integration@test.com',
LEGACY_TOKEN: Buffer.from(`${process.env.NPM_USERNAME}:${process.env.NPM_PASSWORD}`, 'utf8').toString('base64'),
});
// Save the current process.env
const envBackup = Object.assign({}, process.env);
const cli = require.resolve('../bin/semantic-release');
const pluginError = require.resolve('./fixtures/plugin-error');
const pluginInheritedError = require.resolve('./fixtures/plugin-error-inherited');
// Save the current working diretory
const cwd = process.cwd();
// Disable logs during tests
stub(process.stdout, 'write');
stub(process.stderr, 'write');
test.before(async () => {
// Start the local NPM registry
await npmRegistry.start();
// Start Mock Server
await mockServer.start();
});
test.beforeEach(() => {
// 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.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.GH_URL;
delete process.env.GITHUB_URL;
delete process.env.GH_PREFIX;
delete process.env.GITHUB_PREFIX;
process.env.TRAVIS = 'true';
process.env.CI = 'true';
process.env.TRAVIS_BRANCH = 'master';
process.env.TRAVIS_PULL_REQUEST = 'false';
// 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_config')) {
delete process.env[keys[i]];
}
}
});
test.afterEach.always(() => {
// Restore process.env
process.env = envBackup;
// Restore the current working directory
process.chdir(cwd);
});
test.after.always(async () => {
// Stop the local NPM registry
await npmRegistry.stop();
// Stop Mock Server
await mockServer.stop();
});
test.serial('Release patch, minor and major versions', async t => {
const packageName = 'test-release';
const owner = 'test-owner';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: npmRegistry.url},
});
// Create a npm-shrinkwrap.json file
await execa('npm', ['shrinkwrap'], {env: testEnv});
/* No release */
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
t.log('Commit a chore');
await gitCommits(['chore: Init repository']);
t.log('$ semantic-release');
let {stdout, code} = await execa(cli, [], {env});
t.regex(stdout, /There are no relevant changes, so no new version is released/);
t.is(code, 0);
/* Initial release */
let version = '1.0.0';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson('./package.json')).version, version);
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/* Patch release */
version = '1.0.1';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a fix');
await gitCommits(['fix: bar']);
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson('./package.json')).version, version);
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/* Minor release */
version = '1.1.0';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a feature');
await gitCommits(['feat: baz']);
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson('./package.json')).version, version);
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
/* Major release */
version = '2.0.0';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a breaking change');
await gitCommits(['feat: foo\n\n BREAKING CHANGE: bar']);
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and npm-shrinkwrap.json have been updated
t.is((await readJson('./package.json')).version, version);
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Release versions from a packed git repository, using tags to determine last release gitHead', async t => {
const packageName = 'test-git-packed';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git@github.com:${owner}/${packageName}.git`},
publishConfig: {registry: npmRegistry.url},
});
/* Initial release */
let version = '1.0.0';
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ git pack-refs --all');
await gitPackRefs();
t.log('$ semantic-release');
let {stdout, code} = await execa(cli, [], {env});
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version
let releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
// Create a tag version so the tag can be used later to determine the commit associated with the version
await gitTagVersion(`v${version}`);
t.log(`Create git tag v${version}`);
/* Patch release */
version = '1.0.1';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a fix');
await gitCommits(['fix: bar']);
t.log('$ semantic-release');
({stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Exit with 1 if a plugin is not found', async t => {
const packageName = 'test-plugin-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {analyzeCommits: 'non-existing-path'},
});
const {code, stderr} = await t.throws(execa(cli, [], {env}));
t.is(code, 1);
t.regex(stderr, /Cannot find module/);
});
test.serial('Exit with 1 if a shareable config is not found', async t => {
const packageName = 'test-config-not-found';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {extends: 'non-existing-path'},
});
const {code, stderr} = await t.throws(execa(cli, [], {env}));
t.is(code, 1);
t.regex(stderr, /Cannot find module/);
});
test.serial('Exit with 1 if a shareable config reference a not found plugin', async t => {
const packageName = 'test-config-ref-not-found';
const owner = 'test-repo';
const shareable = {getLastRelease: 'non-existing-path'};
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {extends: './shareable.json'},
});
await writeJson('./shareable.json', shareable);
const {code, stderr} = await t.throws(execa(cli, [], {env}));
t.is(code, 1);
t.regex(stderr, /Cannot find module/);
});
test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', async t => {
const packageName = 'test-recovery';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: npmRegistry.url},
});
/* Initial release */
let version = '1.0.0';
let verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
let getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
let createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
let createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
let {stderr, stdout, code} = await execa(cli, [], {env});
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
const head = await gitHead();
t.is(releasedGitHead, head);
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
// Create a tag version so the tag can be used later to determine the commit associated with the version
await gitTagVersion(`v${version}`);
t.log(`Create git tag v${version}`);
/* Rewrite sha of commit used for release */
t.log('Amend release commit');
const {hash} = await gitAmmendCommit('feat: Initial commit');
/* Patch release */
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
t.log('Commit a fix');
await gitCommits(['fix: bar']);
t.log('$ semantic-release');
({stderr, stdout, code} = await execa(cli, [], {env, reject: false}));
t.log('Log "ENOTINHISTORY" message');
t.is(code, 1);
t.regex(
stderr,
new RegExp(
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "${version}" on the commit corresponding to this release`
)
);
/* Create a tag to recover and redo release */
t.log(`Create git tag v${version} to recover`);
await gitTagVersion(`v${version}`, hash);
version = '1.0.1';
verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {ref: `refs/tags/${version}`}}
);
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
t.log('$ semantic-release');
({stderr, stdout, code} = await execa(cli, [], {env}));
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
t.is(releasedVersion, version);
t.log(`+ released ${releasedVersion}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Dry-run', async t => {
const packageName = 'test-dry-run';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: npmRegistry.url},
});
/* Initial release */
const verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
const version = '1.0.0';
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release -d');
const {stdout, code} = await execa(cli, ['-d'], {env});
t.regex(stdout, new RegExp(`There is no previous release, the next release version is ${version}`));
t.regex(stdout, new RegExp(`Release note for version ${version}`));
t.regex(stdout, /Initial commit/);
t.is(code, 0);
// Verify package.json and has not been modified
t.is((await readJson('./package.json')).version, '0.0.0-dev');
await mockServer.verify(verifyMock);
});
test.serial('Pass options via CLI arguments', async t => {
const packageName = 'test-cli';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: npmRegistry.url},
});
/* Initial release */
const version = '1.0.0';
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
const {stdout, code} = await execa(
cli,
['--verify-conditions', '@semantic-release/npm', '--publish', '@semantic-release/npm', '--debug'],
{env}
);
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
t.is(code, 0);
// Verify package.json and has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
});
test.serial('Run via JS API', async t => {
const packageName = 'test-js-api';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
publishConfig: {registry: npmRegistry.url},
});
/* Initial release */
const version = '1.0.0';
const verifyMock = await mockServer.mock(
`/repos/${owner}/${packageName}`,
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
{body: {permissions: {push: true}}, method: 'GET'}
);
const getRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs/tags/v${version}`,
{},
{body: {}, statusCode: 404, method: 'GET'}
);
const createRefMock = await mockServer.mock(
`/repos/${owner}/${packageName}/git/refs`,
{
body: {ref: `refs/tags/v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {ref: `refs/tags/${version}`}}
);
const createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}}
);
process.env = Object.assign(process.env, env);
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ Call semantic-release via API');
await semanticRelease();
// Verify package.json and has been updated
t.is((await readJson('./package.json')).version, version);
// Retrieve the published package from the registry and check version and gitHead
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
);
t.is(releasedVersion, version);
t.is(releasedGitHead, await gitHead());
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
await mockServer.verify(verifyMock);
await mockServer.verify(getRefMock);
await mockServer.verify(createRefMock);
await mockServer.verify(createReleaseMock);
});
test.serial('Log unexpected errors from plugins and exit with 1', async t => {
const packageName = 'test-unexpected-error';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: pluginError},
});
/* Initial release */
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
const {stderr, code} = await execa(cli, [], {env, reject: false});
// Verify the type and message are logged
t.regex(stderr, /Error: a/);
// Verify the the stacktrace is logged
t.regex(stderr, new RegExp(pluginError));
// Verify the Error properties are logged
t.regex(stderr, /errorProperty: 'errorProperty'/);
t.is(code, 1);
});
test.serial('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
const packageName = 'test-inherited-error';
const owner = 'test-repo';
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository and package.json');
await gitRepo();
// Create package.json in repository root
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: `git+https://github.com/${owner}/${packageName}`},
release: {verifyConditions: pluginInheritedError},
});
/* Initial release */
t.log('Commit a feature');
await gitCommits(['feat: Initial commit']);
t.log('$ semantic-release');
const {stdout, code} = await execa(cli, [], {env, reject: false});
// Verify the type and message are logged
t.regex(stdout, /EINHERITED Inherited error/);
t.is(code, 1);
});
test.serial('CLI returns error code and prints help if called with a command', async t => {
t.log('$ semantic-release pre');
const {stdout, code} = await execa(cli, ['pre'], {env, reject: false});
t.regex(stdout, /Usage: semantic-release/);
t.is(code, 1);
});
test.serial('CLI prints help if called with --help', async t => {
t.log('$ semantic-release --help');
const {stdout, code} = await execa(cli, ['--help'], {env});
t.regex(stdout, /Usage: semantic-release/);
t.is(code, 0);
});
test.serial('CLI returns error code with invalid option', async t => {
t.log('$ semantic-release --unknown-option');
const {stderr, code} = await execa(cli, ['--unknown-option'], {env, reject: false});
t.regex(stderr, /unknown option/);
t.is(code, 1);
});