From b5d20caaa2e4055511aa1be3c1f923ecedcb688d Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Sat, 1 Dec 2018 14:58:45 -0500 Subject: [PATCH] feat: use new `plugins` option and `@semantic-release/apm` plugin - Use `@semantic-release/apm` rather than custom code - Use new semantic-release `plugins` option - Update documentation - Remove test as the shareable config is purely static BREAKING CHANGE: require semantic-release@15.10.0 or above --- .travis.yml | 18 --- README.md | 84 ++++-------- index.js | 30 +---- package.json | 39 +----- test/helpers/git-utils.js | 90 ------------- test/helpers/gitbox.js | 75 ----------- test/helpers/mockserver.js | 107 --------------- test/integration.test.js | 264 ------------------------------------- 8 files changed, 34 insertions(+), 673 deletions(-) delete mode 100644 test/helpers/git-utils.js delete mode 100644 test/helpers/gitbox.js delete mode 100644 test/helpers/mockserver.js delete mode 100644 test/integration.test.js diff --git a/.travis.yml b/.travis.yml index 15709cd..fed665f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,6 @@ node_js: - 10 - 8 -services: - - docker - -addons: - apt: - packages: - # See https://github.com/atom/ci - - libsecret-1-dev - # Trigger a push build on master and greenkeeper branches + PRs build on every branches # Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147) branches: @@ -23,19 +14,10 @@ branches: install: # Retry install on fail to avoid failing a build on network/disk/external errors - travis_retry npm install - # See https://github.com/atom/ci - - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh - - chmod u+x build-package.sh - - ./build-package.sh - # Add apm to the PATH - - export PATH=$PATH:~/atom/usr/bin script: - npm run test -after_success: - - npm run codecov - jobs: include: - stage: release diff --git a/README.md b/README.md index db24104..42033b2 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,50 @@ # @semantic-release/apm-config -[Semantic-release](https://github.com/semantic-release/semantic-release) shareable config for releasing atom packages with [apm](https://github.com/atom/apm). +[**semantic-release**](https://github.com/semantic-release/semantic-release) shareable config to publish [Atom](https://www.atom.io) packages with with [apm](https://github.com/atom/apm). [![Travis](https://img.shields.io/travis/semantic-release/apm-config.svg)](https://travis-ci.org/semantic-release/apm-config) -[![Codecov](https://img.shields.io/codecov/c/github/semantic-release/apm-config.svg)](https://codecov.io/gh/semantic-release/apm-config) [![Greenkeeper badge](https://badges.greenkeeper.io/semantic-release/apm-config.svg)](https://greenkeeper.io/) [![npm latest version](https://img.shields.io/npm/v/@semantic-release/apm-config/latest.svg)](https://www.npmjs.com/package/@semantic-release/apm-config) [![npm next version](https://img.shields.io/npm/v/@semantic-release/apm-config/next.svg)](https://www.npmjs.com/package/@semantic-release/apm-config) -## Usage +## Plugins + +This shareable configuration use the following plugins: +- [`@semantic-release/commit-analyzer`](https://github.com/semantic-release/commit-analyzer) +- [`@semantic-release/git`](https://github.com/semantic-release/git) +- [`@semantic-release/release-notes-generator`](https://github.com/semantic-release/release-notes-generator) +- [`@semantic-release/npm`](https://github.com/semantic-release/npm) +- [`@semantic-release/exec`](https://github.com/semantic-release/exec) +- [`@semantic-release/changelog`](https://github.com/semantic-release/changelog) +- [`@semantic-release/github`](https://github.com/semantic-release/github) -### Local installation +## Install ```bash $ npm install --save-dev semantic-release @semantic-release/apm-config ``` -In `package.json`: +## Usage + +The shareable config can be configured in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration): ```json { - "release": { - "extends": "@semantic-release/apm-config" - } + "extends": "@semantic-release/apm-config" } ``` -### Global installation - -```bash -$ npm install -g semantic-release @semantic-release/apm-config -$ semantic-release -e @semantic-release/apm-config -``` - ## Configuration -### Atom installation - -The `apm` command line has to be installed in your CI environment and available in the `PATH`. - -See the [Atom Package CI Scripts](https://github.com/atom/ci#atom-package-ci-scripts) documentation. - -_Note: If you are running multiple versions of Atom in CI (Stable + Beta), -ensure that the `semantic-release` command is run on a build using the Stable -channel of Atom as the Beta channel builds only provide `apm-beta`. If you are -using [travis-deploy-once](https://github.com/semantic-release/travis-deploy-once) -this can be achieved by setting the Stable channel build to be the last build -to run, or by using the -[`buildLeaderId`](https://github.com/semantic-release/travis-deploy-once#-b---buildleaderid) -option._ - -### Atom authentication +See each [plugin](#plugins) documentation for required installation and configuration steps. -The Atom authentication configuration is **required** and can be set via [environment variables](#environment-variables). +### Overwritten options -Visit your account page on [Atom.io](https://atom.io/account) to obtain your authentication token. The token has to be made available in your CI environment via the `ATOM_ACCESS_TOKEN` environment variable. +This following options are set by this shareable config: -### GitHub authentication +| Option | Value | +|--------------------------------------------------------------|---------------------------------------------------| +| [`message`](https://github.com/semantic-release/git#message) | chore(release): \${nextRelease.version} [skip ci] | -The GitHub authentication configuration is **required** and can be set via [environment variables](#environment-variables). - -See [GitHub authentication](https://github.com/semantic-release/github#github-authentication). - -### Environment variables - -| Variable | Description | -|------------------------------|----------------------------------------------------------------------| -| `GH_TOKEN` or `GITHUB_TOKEN` | **Required.** The token used to authenticate with GitHub repository. | -| `ATOM_ACCESS_TOKEN` | **Required.** The token used to authenticate with Atom registry. | - -### Additional options - -This shareable config uses the [`@semantic-release/git`](https://github.com/semantic-release/git), [`@semantic-release/npm`](https://github.com/semantic-release/npm), [`@semantic-release/exec`](https://github.com/semantic-release/exec), [`@semantic-release/changelog`](https://github.com/semantic-release/changelog) and [`@semantic-release/github`](https://github.com/semantic-release/github) plugins. See the documentation of each plugins for additional options. - -Options can be set in the Semantic-release configuration. - -For example to change the commit message: - -```json -{ - "release": { - "extends": "@semantic-release/apm-config", - "message": "chore: prepare %s release ${nextRelease.version} [skip ci]" - } -} -``` +Other options use their default values. See each [plugin](#plugins) documentation for available options. diff --git a/index.js b/index.js index c65ffbc..cd4a6d2 100644 --- a/index.js +++ b/index.js @@ -1,30 +1,10 @@ -const execa = require('execa'); -const SemanticReleaseError = require('@semantic-release/error'); - module.exports = { - verifyConditions: [ - async () => { - if ((await execa('apm', ['-v'], {reject: false})).code !== 0) { - throw new SemanticReleaseError('The apm CLI must be installed.', 'ENOAPMCLI'); - } - }, - () => { - if (!process.env.ATOM_ACCESS_TOKEN) { - throw new SemanticReleaseError('The environment variable ATOM_ACCESS_TOKEN is required.', 'ENOAPMTOKEN'); - } - }, - {path: '@semantic-release/npm', npmPublish: false}, - '@semantic-release/changelog', - '@semantic-release/git', + plugins: [ + '@semantic-release/commit-analyzer', + '@semantic-release/release-notes-generator', '@semantic-release/github', - ], - prepare: [ '@semantic-release/changelog', - {path: '@semantic-release/npm', npmPublish: false}, - {path: '@semantic-release/git', message: `chore(release): \${nextRelease.version} [skip ci]`}, - ], - publish: [ - {path: '@semantic-release/exec', cmd: `apm publish --tag \${nextRelease.gitTag} 1>&2`}, - '@semantic-release/github', + '@semantic-release/apm', + ['@semantic-release/git', {message: `chore(release): \${nextRelease.version} [skip ci]`}], ], }; diff --git a/package.json b/package.json index f34394b..ee612be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@semantic-release/apm-config", - "description": "Semantic-release shareable config for releasing atom packages with apm", + "description": "semantic-release shareable config to publish Atom packages with with apm", "version": "0.0.0-development", "author": "Pierre Vanduynslager (https://github.com/pvdlg)", "bugs": { @@ -16,32 +16,18 @@ "Gregor Martynus (https://twitter.com/gr2m)" ], "dependencies": { + "@semantic-release/apm": "^1.0.0", "@semantic-release/changelog": "^3.0.0", - "@semantic-release/error": "^2.1.0", + "@semantic-release/commit-analyzer": "^6.1.0", "@semantic-release/exec": "^3.0.1", "@semantic-release/git": "^7.0.0", "@semantic-release/github": "^5.2.1", - "@semantic-release/npm": "^5.0.1", - "execa": "^1.0.0" + "@semantic-release/release-notes-generator": "^7.1.4" }, "devDependencies": { - "ava": "^0.25.0", - "codecov": "^3.0.0", "commitizen": "^3.0.0", "cz-conventional-changelog": "^2.0.0", - "dockerode": "^2.5.3", - "fs-extra": "^7.0.0", - "get-stream": "^4.0.0", - "git-log-parser": "^1.2.0", - "got": "^9.0.0", - "mockserver-client": "^5.3.0", - "nyc": "^12.0.1", - "p-retry": "^2.0.0", "semantic-release": "^15.0.0", - "sinon": "^6.0.0", - "strip-ansi": "^5.0.0", - "tempy": "^0.2.1", - "which": "^1.3.0", "xo": "^0.23.0" }, "engines": { @@ -63,19 +49,8 @@ ], "license": "MIT", "main": "index.js", - "nyc": { - "include": [ - "index.js" - ], - "reporter": [ - "json", - "text", - "html" - ], - "all": true - }, "peerDependencies": { - "semantic-release": ">=15.9.0 <16.0.0" + "semantic-release": ">=15.10.0 <16.0.0" }, "prettier": { "printWidth": 120, @@ -91,10 +66,8 @@ }, "scripts": { "cm": "git-cz", - "codecov": "codecov -f coverage/coverage-final.json", - "lint": "xo", "semantic-release": "semantic-release", - "test": "nyc ava -v" + "test": "xo" }, "xo": { "prettier": true, diff --git a/test/helpers/git-utils.js b/test/helpers/git-utils.js deleted file mode 100644 index 3a286c0..0000000 --- a/test/helpers/git-utils.js +++ /dev/null @@ -1,90 +0,0 @@ -import tempy from 'tempy'; -import execa from 'execa'; -import gitLogParser from 'git-log-parser'; -import getStream from 'get-stream'; - -/** - * Initialize an existing bare repository: - * - Clone the repository - * - Change the current working directory to the clone root - * - Create a default branch - * - Create an initial commits - * - Push to origin - * - * @param {String} origin The URL of the bare repository. - * @param {String} [branch='master'] the branch to initialize. - */ -export async function initBareRepo(origin, branch = 'master') { - const clone = tempy.directory(); - await execa('git', ['clone', '--no-hardlinks', origin, clone]); - process.chdir(clone); - await gitCheckout(branch); - await gitCommit('Initial commit'); - await execa('git', ['push', origin, branch]); -} - -/** - * Checkout a branch on the current git repository. - * - * @param {String} branch Branch name. - * @param {boolean} create `true` to create the branche ans switch, `false` to only switch. - */ -export async function gitCheckout(branch, create = true) { - await execa('git', create ? ['checkout', '-b', branch] : ['checkout', branch]); -} - -/** - * Create commit on the current git repository. - * - * @param {String} message commit message. - * - * @returns {Commit} The created commits. - */ -export async function gitCommit(message) { - const {stdout} = await execa('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign']); - const [, branch, hash] = /^\[(\w+)\(?.*?\)?(\w+)\] .+(?:\n|$)/.exec(stdout); - return {branch, hash, message}; -} - -/** - * Create a shallow clone of a git repository and change the current working directory to the cloned repository root. - * The shallow will contain a limited number of commit and no tags. - * - * @param {String} origin The path of the repository to clone. - * @param {Number} [depth=1] The number of commit to clone. - * @return {String} The path of the cloned repository. - */ -export async function gitShallowClone(origin, branch = 'master', depth = 1) { - const dir = tempy.directory(); - - process.chdir(dir); - await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, origin, dir]); - return dir; -} - -/** - * Get the list of parsed commits since a git reference. - * - * @param {String} [from] Git reference from which to seach commits. - * @return {Array} The list of parsed commits. - */ -export async function gitGetCommit(from) { - Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}}); - return (await getStream.array(gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}))).map(commit => { - commit.message = commit.message.trim(); - commit.gitTags = commit.gitTags.trim(); - return commit; - }); -} - -/** - * Get the list of files included in a commit. - * - * @param {String} [ref='HEAD'] The git reference for which to retrieve the files. - * @return {Array} The list of files path included in the commit. - */ -export async function gitCommitedFiles(ref = 'HEAD') { - return (await execa.stdout('git', ['diff-tree', '-r', '--name-only', '--no-commit-id', '-r', ref])) - .split('\n') - .filter(file => Boolean(file)); -} diff --git a/test/helpers/gitbox.js b/test/helpers/gitbox.js deleted file mode 100644 index 9f09e86..0000000 --- a/test/helpers/gitbox.js +++ /dev/null @@ -1,75 +0,0 @@ -import Docker from 'dockerode'; -import getStream from 'get-stream'; -import pRetry from 'p-retry'; -import {initBareRepo, gitShallowClone} from './git-utils'; - -const IMAGE = 'pvdlg/docker-gitbox'; -const SERVER_PORT = 80; -const HOST_PORT = 2080; -const SERVER_HOST = 'localhost'; -const GIT_USERNAME = 'integration'; -const GIT_PASSWORD = 'suchsecure'; -const docker = new Docker(); -let container; - -const gitCredential = `${GIT_USERNAME}:${GIT_PASSWORD}`; - -/** - * Download the `gitbox` Docker image, create a new container and start it. - * - * @return {Promise} Promise that resolves when the container is started. - */ -async function start() { - await getStream(await docker.pull(IMAGE)); - - container = await docker.createContainer({ - Tty: true, - Image: IMAGE, - PortBindings: {[`${SERVER_PORT}/tcp`]: [{HostPort: `${HOST_PORT}`}]}, - }); - await container.start(); - - const exec = await container.exec({ - Cmd: ['ng-auth', '-u', GIT_USERNAME, '-p', GIT_PASSWORD], - AttachStdout: true, - AttachStderr: true, - }); - await exec.start(); -} - -/** - * Stop and remote the `mockserver` Docker container. - * - * @return {Promise} Promise that resolves when the container is stopped. - */ -async function stop() { - await container.stop(); - await container.remove(); -} - -/** - * Initialize a remote repository and creates a shallow clone. - * - * @param {String} name The remote repository name. - * @param {String} [branch='master'] The branch to initialize. - * @param {String} [description=`Repository ${name}`] The repository description. - * @return {Object} The `repositoryUrl` (URL without auth) and `authUrl` (URL with auth). - */ -async function createRepo(name, branch = 'master', description = `Repository ${name}`) { - const exec = await container.exec({ - Cmd: ['repo-admin', '-n', name, '-d', description], - AttachStdout: true, - AttachStderr: true, - }); - await exec.start(); - - const repositoryUrl = `http://${SERVER_HOST}:${HOST_PORT}/git/${name}.git`; - const authUrl = `http://${gitCredential}@${SERVER_HOST}:${HOST_PORT}/git/${name}.git`; - - // Retry as the server might take a few ms to make the repo available push - await pRetry(() => initBareRepo(authUrl, branch), {retries: 3, minTimeout: 500, factor: 2}); - await gitShallowClone(authUrl); - return {repositoryUrl, authUrl}; -} - -export default {start, stop, gitCredential, createRepo}; diff --git a/test/helpers/mockserver.js b/test/helpers/mockserver.js deleted file mode 100644 index 4e8f0bf..0000000 --- a/test/helpers/mockserver.js +++ /dev/null @@ -1,107 +0,0 @@ -import Docker from 'dockerode'; -import getStream from 'get-stream'; -import got from 'got'; -import pRetry from 'p-retry'; -import {mockServerClient} from 'mockserver-client'; - -const IMAGE = 'jamesdbloom/mockserver:latest'; -const MOCK_SERVER_PORT = 1080; -const MOCK_SERVER_HOST = 'localhost'; -const docker = new Docker(); -let container; - -/** - * Download the `mockserver` Docker image, create a new container and start it. - * - * @return {Promise} Promise that resolves when the container is started. - */ -async function start() { - await getStream(await docker.pull(IMAGE)); - - container = await docker.createContainer({ - Tty: true, - Image: IMAGE, - PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]}, - }); - await container.start(); - - try { - // Wait for the mock server to be ready - await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, {cache: false}), { - retries: 7, - minTimeout: 1000, - factor: 2, - }); - } catch (err) { - throw new Error(`Couldn't start mock-server after 2 min`); - } -} - -/** - * Stop and remote the `mockserver` Docker container. - * - * @return {Promise} Promise that resolves when the container is stopped. - */ -async function stop() { - await container.stop(); - await container.remove(); -} - -/** - * @type {Object} A `mockserver` client configured to connect to the current instance. - */ -const client = mockServerClient(MOCK_SERVER_HOST, MOCK_SERVER_PORT); -/** - * @type {string} the url of the `mockserver` instance - */ -const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`; - -/** - * Set up the `mockserver` instance response for a specific request. - * - * @param {string} path URI for which to respond. - * @param {Object} request Request expectation. The http request made on `path` has to match those criteria in order to be valid. - * @param {Object} request.body The JSON body the expected request must match. - * @param {Object} request.headers The headers the expected request must match. - * @param {Object} response The http response to return when receiving a request on `path`. - * @param {String} [response.method='POST'] The http method for which to respond. - * @param {number} [response.statusCode=200] The status code to respond. - * @param {Object} response.body The JSON object to respond in the response body. - * @return {Object} An object representation the expectation. Pass to the `verify` function to validate the `mockserver` has been called with a `request` matching the expectations. - */ -async function mock( - path, - {body: requestBody, headers: requestHeaders}, - {method = 'POST', statusCode = 200, body: responseBody} -) { - await client.mockAnyResponse({ - httpRequest: {path, method}, - httpResponse: { - statusCode, - headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}], - body: JSON.stringify(responseBody), - }, - times: {remainingTimes: 1, unlimited: false}, - }); - - return { - method, - path, - headers: requestHeaders, - body: requestBody - ? {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'} - : undefined, - }; -} - -/** - * Verify the `mockserver` has been called with a requestion matching expectations. The `expectation` is created with the `mock` function. - * - * @param {Object} expectation The expectation created with `mock` function. - * @return {Promise} A Promise that resolves if the expectation is met or reject otherwise. - */ -async function verify(expectation) { - return client.verify(expectation); -} - -export default {start, stop, mock, verify, url}; diff --git a/test/integration.test.js b/test/integration.test.js deleted file mode 100644 index 4ba2d38..0000000 --- a/test/integration.test.js +++ /dev/null @@ -1,264 +0,0 @@ -import path from 'path'; -import test from 'ava'; -import {writeJson, readJson, ensureSymlink} from 'fs-extra'; -import {stub} from 'sinon'; -import execa from 'execa'; -import tempy from 'tempy'; -import which from 'which'; -import stripAnsi from 'strip-ansi'; -import semanticRelease from 'semantic-release'; -import {gitCommitedFiles, gitGetCommit} from './helpers/git-utils'; -import mockServer from './helpers/mockserver'; -import gitbox from './helpers/gitbox'; - -/* eslint camelcase: ["error", {properties: "never"}] */ - -// Save the current process.env -const envBackup = Object.assign({}, process.env); -// Save the current working diretory -const cwd = process.cwd(); - -const apmConfig = require.resolve('..'); - -test.before(async () => { - // Start the local Git server - await gitbox.start(); - // Start Mock Server - await mockServer.start(); -}); - -test.beforeEach(t => { - t.context.logs = ''; - t.context.stdout = stub(process.stdout, 'write').callsFake(val => { - t.context.logs += stripAnsi(val.toString()); - }); - t.context.sdterr = stub(process.stderr, 'write').callsFake(val => { - t.context.error += stripAnsi(val.toString()); - }); - - process.env.TRAVIS = 'true'; - process.env.CI = 'true'; - process.env.TRAVIS_BRANCH = 'master'; - process.env.TRAVIS_PULL_REQUEST = 'false'; -}); - -test.afterEach.always(t => { - // Restore process.env - process.env = envBackup; - // Restore the current working directory - process.chdir(cwd); - t.context.stdout.restore(); - t.context.sdterr.restore(); -}); - -test.after.always(async () => { - // Stop the local Git server - await gitbox.stop(); - // Stop Mock Server - await mockServer.stop(); -}); - -test.serial('Initial and minor releases', async t => { - const packageName = 'test-release'; - const owner = 'git'; - const branch = 'master'; - const failTitle = 'The automated release is failing 🚨'; - // Create a remote repo, initialize it, create a local shallow clone and set the cwd to the clone - const {repositoryUrl} = await gitbox.createRepo(packageName, branch); - process.env.GH_TOKEN = gitbox.gitCredential; - process.env.GITHUB_URL = mockServer.url; - process.env.ATOM_ACCESS_TOKEN = 'ATOM_TOKEN'; - process.env.ATOM_HOME = tempy.directory(); - process.env.ATOM_API_URL = mockServer.url; - process.env.ATOM_RESOURCE_PATH = tempy.directory(); - await writeJson('./package.json', { - name: packageName, - version: '0.0.0-dev', - repository: {url: repositoryUrl}, - release: {failTitle}, - }); - - /* Initial release */ - let version = '1.0.0'; - let verifyGitHubMock = await mockServer.mock( - `/repos/${owner}/${packageName}`, - {}, - {body: {permissions: {push: true}}, method: 'GET'} - ); - let getRepoMock = await mockServer.mock( - `/repos/${owner}/${packageName}`, - {}, - {body: {full_name: `${owner}/${packageName}`}, method: 'GET'} - ); - let verifyApmMock = await mockServer.mock('/packages', {}, {body: {}, method: 'POST', statusCode: 201}); - let getApmVersionMock = await mockServer.mock( - `/packages/${packageName}/versions`, - {}, - {body: {}, method: 'POST', statusCode: 201} - ); - let createReleaseMock = await mockServer.mock( - `/repos/${owner}/${packageName}/releases`, - {body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`}}, - {body: {html_url: `release-url/${version}`}} - ); - let searchPRsMock = await mockServer.mock( - '/search/issues', - {queryStringParameters: {q: `${escape(`repo:${owner}/${packageName}`)}+${escape('type:pr')}`}}, - {body: {items: []}, method: 'GET'} - ); - let searchIssuesMock = await mockServer.mock( - '/search/issues', - { - queryStringParameters: { - q: `${escape('in:title')}+${escape(`repo:${owner}/${packageName}`)}+${escape('type:issue')}+${escape( - 'state:open' - )}+${escape(failTitle)}`, - }, - }, - {body: {items: []}, method: 'GET'} - ); - - t.log('Commit a feature'); - await execa('git', ['commit', '-m', 'feat: new feature', '--allow-empty', '--no-gpg-sign']); - - t.log('Initial release'); - await semanticRelease({extends: apmConfig}); - - await mockServer.verify(verifyGitHubMock); - await mockServer.verify(getRepoMock); - await mockServer.verify(verifyApmMock); - await mockServer.verify(getApmVersionMock); - await mockServer.verify(createReleaseMock); - await mockServer.verify(searchIssuesMock); - t.regex(t.context.error, /Registering test-release/); - t.regex(t.context.error, new RegExp(`Publishing test-release@v${version}`)); - t.regex(t.context.logs, new RegExp(`Published GitHub release: release-url/${version}`)); - t.is((await readJson('./package.json')).version, version); - t.deepEqual(await gitCommitedFiles(), ['CHANGELOG.md', 'package.json']); - let [commit] = await gitGetCommit(); - t.is(commit.subject, `chore(release): ${version} [skip ci]`); - t.is(commit.gitTags, `(HEAD -> master, tag: v${version})`); - - /* Minor release */ - t.log('Commit a feature'); - await execa('git', ['commit', '-m', 'feat: other feature', '--allow-empty', '--no-gpg-sign']); - [commit] = await gitGetCommit(); - version = '1.1.0'; - verifyGitHubMock = await mockServer.mock( - `/repos/${owner}/${packageName}`, - {}, - {body: {permissions: {push: true}}, method: 'GET'} - ); - getRepoMock = await mockServer.mock( - `/repos/${owner}/${packageName}`, - {}, - {body: {full_name: `${owner}/${packageName}`}, method: 'GET'} - ); - verifyApmMock = await mockServer.mock('/packages', {}, {body: {}, method: 'POST', statusCode: 201}); - getApmVersionMock = await mockServer.mock( - `/packages/${packageName}/versions`, - {}, - {body: {}, method: 'POST', statusCode: 201} - ); - createReleaseMock = await mockServer.mock( - `/repos/${owner}/${packageName}/releases`, - {body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`}}, - {body: {html_url: `release-url/${version}`}} - ); - searchPRsMock = await mockServer.mock( - '/search/issues', - {queryStringParameters: {q: `${escape(`repo:${owner}/${packageName}`)}+${escape('type:pr')}+${commit.hash}`}}, - {body: {items: [{number: 1, pull_request: {}, state: 'closed'}]}, method: 'GET'} - ); - const getPRsMock = await mockServer.mock( - `/repos/${owner}/${packageName}/pulls/1/commits`, - {}, - {body: [{sha: commit.hash}], method: 'GET'} - ); - const addCommentMock = await mockServer.mock( - `/repos/${owner}/${packageName}/issues/1/comments`, - {}, - {body: {items: [{number: 1, pull_request: {}}]}} - ); - searchIssuesMock = await mockServer.mock( - '/search/issues', - { - queryStringParameters: { - q: `${escape('in:title')}+${escape(`repo:${owner}/${packageName}`)}+${escape('type:issue')}+${escape( - 'state:open' - )}+${escape(failTitle)}`, - }, - }, - {body: {items: []}, method: 'GET'} - ); - - t.log('Minor release'); - await semanticRelease({extends: apmConfig}); - - await mockServer.verify(verifyGitHubMock); - await mockServer.verify(verifyApmMock); - await mockServer.verify(getRepoMock); - await mockServer.verify(getApmVersionMock); - await mockServer.verify(createReleaseMock); - await mockServer.verify(searchPRsMock); - await mockServer.verify(getPRsMock); - await mockServer.verify(addCommentMock); - await mockServer.verify(searchIssuesMock); - - t.regex(t.context.error, /Registering test-release/); - t.regex(t.context.error, new RegExp(`Publishing test-release@v${version}`)); - t.regex(t.context.logs, new RegExp(`Published GitHub release: release-url/${version}`)); - t.is((await readJson('./package.json')).version, version); - t.deepEqual(await gitCommitedFiles(), ['CHANGELOG.md', 'package.json']); - [commit] = await gitGetCommit(); - t.is(commit.subject, `chore(release): ${version} [skip ci]`); - t.is(commit.gitTags, `(HEAD -> master, tag: v${version})`); -}); - -test.serial('Throw error if "ATOM_ACCESS_TOKEN" is not set', async t => { - const packageName = 'missing-token'; - const branch = 'master'; - const {repositoryUrl} = await gitbox.createRepo(packageName, branch); - process.env.GH_TOKEN = gitbox.gitCredential; - process.env.GITHUB_URL = mockServer.url; - process.env.ATOM_HOME = tempy.directory(); - process.env.ATOM_API_URL = mockServer.url; - process.env.ATOM_RESOURCE_PATH = tempy.directory(); - process.env.GIT_TERMINAL_PROMPT = 0; - delete process.env.ATOM_ACCESS_TOKEN; - await writeJson('./package.json', { - name: packageName, - version: '0.0.0-dev', - repository: {url: repositoryUrl}, - }); - - const error = await t.throws(semanticRelease({extends: apmConfig})); - - t.regex(error.message.trim(), /The environment variable ATOM_ACCESS_TOKEN is required./); -}); - -test.serial('Throw error if "apm" is not installed', async t => { - const packageName = 'missing-apm'; - const branch = 'master'; - const {repositoryUrl} = await gitbox.createRepo(packageName, branch); - process.env.GH_TOKEN = gitbox.gitCredential; - process.env.GITHUB_URL = mockServer.url; - process.env.ATOM_HOME = tempy.directory(); - process.env.ATOM_API_URL = mockServer.url; - process.env.ATOM_RESOURCE_PATH = tempy.directory(); - process.env.GIT_TERMINAL_PROMPT = 0; - // Fake PATH with only git available to make sure apm is not in the PATH - const PATH = tempy.directory(); - await ensureSymlink(which.sync('git'), path.join(PATH, 'git')); - process.env.PATH = PATH; - delete process.env.ATOM_ACCESS_TOKEN; - await writeJson('./package.json', { - name: packageName, - version: '0.0.0-dev', - repository: {url: repositoryUrl}, - }); - - const error = await t.throws(semanticRelease({extends: apmConfig})); - - t.regex(error.message.trim(), /The apm CLI must be installed./); -});