From 5bc46a08cfb9e7cdc50c47bd80e8376de2804368 Mon Sep 17 00:00:00 2001 From: Pierre Vanduynslager Date: Sun, 31 Dec 2017 20:46:07 -0500 Subject: [PATCH] feat: allow to release from local machine --- README.md | 4 +++- cli.js | 4 ++++ index.js | 6 ++--- test/index.test.js | 56 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a894689506..0484c09120 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ semantic-release These options are currently available: - `branch`: The branch on which releases should happen. Default: `'master'` - `repositoryUrl`: The git repository URL. Default: `repository` property in `package.json` or git origin url. Any valid git url format is supported (See [Git protocols](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols)). If the [Github plugin](https://github.com/semantic-release/github) is used the URL must be a valid Github URL that include the `owner`, the `repository` name and the `host`. The Github shorthand URL is not supported. +- `no-ci`: Skip Continuous Integration environment verifications, allowing to make releases from a local machine - `dry-run`: Dry-run mode, skip publishing, print next version and release notes - `extends`: Array of module or files path containing a shareable configuration. Options defined via CLI or in the `release` property will take precedence over the one defined in a shareable configuration. - `debug`: Output debugging information @@ -269,7 +270,8 @@ If you run `npm run semantic-release` locally a dry run gets performed, which lo ### Can I run this on my own machine rather than on a CI server? -Of course you can, but this doesn’t necessarily mean you should. Running your tests on an independent machine before releasing software is a crucial part of this workflow. Also it is a pain to set this up locally, with tokens lying around and everything. That said, you can run the scripts with `--debug=false` explicitly. You have to export `GH_TOKEN=` and `NPM_TOKEN=`. +Yes, you can by explicitly setting the [`--no-ci` CLI option](#options), but this doesn’t necessarily mean you should. Running your tests on an independent machine before releasing software is a crucial part of this workflow. +You will need to set all necessary authentication token (like `GH_TOKEN` and `NPM_TOKEN`) on your local machine. ### Can I manually trigger the release of a specific version? diff --git a/cli.js b/cli.js index 12d9743015..1f8e1365cc 100755 --- a/cli.js +++ b/cli.js @@ -27,6 +27,10 @@ module.exports = async () => { ) .option('--generate-notes ', 'Path or package name for the generateNotes plugin') .option('--publish ', 'Comma separated list of paths or packages name for the publish plugin(s)', list) + .option( + '--no-ci', + 'Skip Continuous Integration environment verifications, allowing to make releases from a local machine' + ) .option('--debug', 'Output debugging information') .option( '-d, --dry-run', diff --git a/index.js b/index.js index 964eba1faf..04bfbb4f13 100644 --- a/index.js +++ b/index.js @@ -10,13 +10,13 @@ const {gitHead: getGitHead, isGitRepo} = require('./lib/git'); module.exports = async opts => { const {isCi, branch, isPr} = envCi(); - if (!isCi && !opts.dryRun) { + if (!isCi && !opts.dryRun && !opts.noCi) { logger.log('This run was not triggered in a known CI environment, running in dry-run mode.'); opts.dryRun = true; } - if (isCi && isPr) { - logger.log('This run was triggered by a pull request and therefore a new version won’t be published.'); + if (isCi && isPr && !opts.noCi) { + logger.log("This run was triggered by a pull request and therefore a new version won't be published."); return; } diff --git a/test/index.test.js b/test/index.test.js index c256696cb7..65416225c4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -209,7 +209,7 @@ test.serial('Dry-run skips publish', async t => { t.is(publish.callCount, 0); }); -test.serial('Force a dry-run if not on a CI', async t => { +test.serial('Force a dry-run if not on a CI and ignore "noCi" is not explicitly set', async t => { // Create a git repository, set the current working directory at the root of the repo await gitRepo(); // Add commits to the master branch @@ -257,6 +257,58 @@ test.serial('Force a dry-run if not on a CI', async t => { t.is(publish.callCount, 0); }); +test.serial('Allow local releases with "noCi" option', async t => { + // Create a git repository, set the current working directory at the root of the repo + await gitRepo(); + // Add commits to the master branch + let commits = await gitCommits(['First']); + // Create the tag corresponding to version 1.0.0 + await gitTagVersion('v1.0.0'); + // Add new commits to the master branch + commits = (await gitCommits(['Second'])).concat(commits); + + const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'}; + const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'}; + const notes = 'Release notes'; + + const verifyConditions = stub().resolves(); + const getLastRelease = stub().resolves(lastRelease); + const analyzeCommits = stub().resolves(nextRelease.type); + const verifyRelease = stub().resolves(); + const generateNotes = stub().resolves(notes); + const publish = stub().resolves(); + + const options = { + noCi: true, + branch: 'master', + repositoryUrl: 'git@hostname.com:owner/module.git', + verifyConditions, + getLastRelease, + analyzeCommits, + verifyRelease, + generateNotes, + publish, + }; + + const semanticRelease = proxyquire('..', { + './lib/logger': t.context.logger, + 'env-ci': () => ({isCi: false, branch: 'master', isPr: true}), + }); + t.truthy(await semanticRelease(options)); + + t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.'); + t.not( + t.context.log.args[0][0], + "This run was triggered by a pull request and therefore a new version won't be published." + ); + t.is(verifyConditions.callCount, 1); + t.is(getLastRelease.callCount, 1); + t.is(analyzeCommits.callCount, 1); + t.is(verifyRelease.callCount, 1); + t.is(generateNotes.callCount, 1); + t.is(publish.callCount, 1); +}); + test.serial('Accept "undefined" values for the "getLastRelease" and "generateNotes" plugins', async t => { // Create a git repository, set the current working directory at the root of the repo await gitRepo(); @@ -333,7 +385,7 @@ test.serial('Returns falsy value if triggered by a PR', async t => { t.falsy(await semanticRelease({repositoryUrl: 'git@hostname.com:owner/module.git'})); t.is( t.context.log.args[0][0], - 'This run was triggered by a pull request and therefore a new version won’t be published.' + "This run was triggered by a pull request and therefore a new version won't be published." ); });