Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db531b511 | ||
|
|
db39c796a6 | ||
|
|
1591608f83 | ||
|
|
3250b9a1af | ||
|
|
96757ac645 | ||
|
|
25828960bc | ||
|
|
8244fe9f7d | ||
|
|
d0032de737 | ||
|
|
6c6562249b | ||
|
|
25cb545ea3 | ||
|
|
0930a6e33d | ||
|
|
890255692f | ||
|
|
fd4c4d1c81 | ||
|
|
7ba1cb956f | ||
|
|
1f70215ffe | ||
|
|
245f918201 | ||
|
|
f0f53f207b | ||
|
|
41c90124f6 | ||
|
|
44db677c4a | ||
|
|
127cb673bd | ||
|
|
a5dabed534 | ||
|
|
be9bb40dd2 | ||
|
|
4821aff69e | ||
|
|
773ecf693f | ||
|
|
f2dd66f614 | ||
|
|
3b921a63c5 | ||
|
|
785e7f7b3b | ||
|
|
b38f1e0687 |
@@ -1,5 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- iojs-v2
|
||||
- iojs-v1
|
||||
- '0.10'
|
||||
- '0.12'
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
|
||||
It fully automates your package's releases. It will determine not only which version to release, but also when – all without you wasting [cognitive resources](https://www.youtube.com/watch?v=FKTxC9pl-WM) on it.
|
||||
|
||||
[](https://www.youtube.com/watch?v=tc2UgG5L7WM&index=6&list=PLFZ5NyC0xHDaaTy6tY9p0C0jd_rRRl5Zm)
|
||||
|
||||
> This talk gives you a complete introduction to the underlying concepts plus a live demo at the end.
|
||||
|
||||
It is fully integrated into the `npm` lifecycle, so all you need to do is to configure your CI to regularly `npm publish` (i.e. for every commit).
|
||||
|
||||
Removing human descisions and emotions from version numbers and releases – strictly following the [SemVer](http://semver.org/) spec suddenly isn't a problem anymore.
|
||||
|
||||

|
||||
It removes human decisions and emotions from version numbers and releases – suddenly, strictly following the [SemVer](http://semver.org/) spec isn't a problem anymore.
|
||||
|
||||
## How does it work?
|
||||
|
||||
@@ -40,11 +42,11 @@ After `npm` published the new version the `postpublish` step does this:
|
||||
- Generate release notes
|
||||
- Create a new [GitHub Release](https://help.github.com/articles/about-releases/) (including a git tag) with the release notes
|
||||
|
||||
Note: The default release notes are a [changelog](https://github.com/ajoslin/conventional-changelog) that is generated from git metadata, using the AngularJS commit conventions. You can [specify your own release note generator](https://github.com/boennemann/semantic-release/wiki/release-note-generation).
|
||||
Note: The default release notes are a [changelog](https://github.com/ajoslin/conventional-changelog) that is generated from git metadata, using the AngularJS commit conventions. You can [specify your own release note generator](https://github.com/boennemann/semantic-release/wiki/release-note-generation).
|
||||
|
||||
Note: This is tied to GitHub, feel free to send PRs for other services.
|
||||
|
||||
Note: `semantic-release` works around a limitation in `npm`'s `prepublish` step. Once a version is published it prints an error that you can *safely ignore* [npm/npm#7118](https://github.com/npm/npm/issues/7118).
|
||||
Note: `semantic-release` works around a limitation in `npm`'s `prepublish` step. Once a version is published it prints a "Could not pack" error that you can *safely ignore* [npm/npm#7118](https://github.com/npm/npm/issues/7118).
|
||||
|
||||
## How you can set it up
|
||||
|
||||
@@ -85,28 +87,34 @@ It would be preferable not to have a version field in the `package.json` at all,
|
||||
|
||||
#### Repository
|
||||
|
||||
If you haven't defined your GitHub repository in the `package.json`s [repository field](https://docs.npmjs.com/files/package.json#repository) the remote `origin`'s repository is used.
|
||||
If you haven't defined your GitHub repository in the `package.json`s [repository field](https://docs.npmjs.com/files/package.json#repository) the remote `origin`'s repository is used.
|
||||
|
||||
### CI Server
|
||||
|
||||
Inside your `.travis.yml`:
|
||||
|
||||
```yml
|
||||
```yml
|
||||
language: node_js
|
||||
node_js:
|
||||
- iojs-v1
|
||||
# If you have a more sophisticated build with multiple jobs you should have a look at
|
||||
# https://github.com/dmakhno/travis_after_all which is also configured for this
|
||||
# package. (Check the .travis.yml)
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
notifications:
|
||||
email: false
|
||||
# See https://github.com/boennemann/semantic-release/issues/18
|
||||
before_deploy:
|
||||
- npm config set spin false --global
|
||||
env:
|
||||
# Get your token here: https://github.com/settings/tokens/new
|
||||
# Grant the token repo/public_repo scope (all others can be deselected)
|
||||
# You should encrypt this:
|
||||
# `travis encrypt GH_TOKEN=<token> --add`
|
||||
global: GH_TOKEN=<github-access-token-with-acceess-to-your-repo>
|
||||
global: GH_TOKEN=<github-access-token-with-access-to-your-repo>
|
||||
deploy:
|
||||
provider: npm
|
||||
email: <your-npm-mail@example.com>
|
||||
@@ -126,11 +134,9 @@ Note: This isn't tied to a specific service, but example configuration is provid
|
||||
|
||||
Note: You should [encrypt](http://docs.travis-ci.com/user/environment-variables/#sts=Secure Variables) your api keys and tokens.
|
||||
|
||||
Note: Your CI environment has to export `CI=true` in order for `semantic-release` not to automatically perform a dry run. Travis CI does this by default.
|
||||
Note: Your CI environment has to export `CI=true` in order for `semantic-release` not to automatically perform a dry run. Travis CI does this by default.
|
||||
|
||||
Note: It is crucial that your CI server also fetches all tags when checking out your repository. Travis CI does this by default.
|
||||
|
||||
Note: If you have a more sophisticated build with multiple jobs you should have a look at [travis-after-all](https://github.com/dmakhno/travis_after_all), which is also configured for this [package](.travis.yml).
|
||||
Note: It is crucial that your CI server also fetches all tags when checking out your repository. Travis CI does this by default.
|
||||
|
||||
## ITYM*FAQ*LT
|
||||
> I think you might frequently ask questions like these
|
||||
@@ -168,9 +174,11 @@ It is indeed a great idea because it _forces_ you to follow best practices. If y
|
||||
|
||||
### Why should I trust `semantic-release` with my releases? What if it breaks?
|
||||
|
||||
`semantic-release` has a full integration-test suite that tests _actual_ `npm` publishes and _actual_ GitHub Releases (with private registry/API) on node.js `^0.10`, `^0.12` and io.js `^1`. A new version won't get published if it doesn't pass on all these engines.
|
||||
`semantic-release` has a full integration-test suite that tests _actual_ `npm` publishes and _actual_ GitHub Releases (with private registry/API) on node.js `^0.10`, `^0.12` and io.js `^1`, `^2`. A new version won't get published if it doesn't pass on all these engines.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
MIT License
|
||||
2015 © Stephan Bönnemann and [contributors](https://github.com/boennemann/semantic-release/graphs/contributors)
|
||||
|
||||

|
||||
|
||||
@@ -31,7 +31,14 @@ var npmArgv = process.env.npm_config_argv ?
|
||||
|
||||
if (~argv._.indexOf('pre')) {
|
||||
// see src/restart.js
|
||||
if (npmArgv['semantic-release-rerun']) process.exit(0)
|
||||
if (npmArgv['semantic-release-rerun']) {
|
||||
if (!/semantically-released/.test(process.env.npm_package_version)) process.exit(0)
|
||||
|
||||
console.log('There is something wrong with your setup, as a placeholder version is about to be released.')
|
||||
console.log('Please verify that your setup is correct.')
|
||||
console.log('If you think this is a problem with semantic-release please open an issue.')
|
||||
process.exit(1)
|
||||
}
|
||||
// the `prepublish` hook is also executed when the package is installed
|
||||
// in this case we abort the command and do nothing.
|
||||
if (
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ var efh = require('./error').efh
|
||||
module.exports = function (from, cb) {
|
||||
var range = (from ? from + '..' : '') + 'HEAD'
|
||||
exec(
|
||||
'git log -E --format=%H==SPLIT==%s==END== ' + range,
|
||||
'git log -E --format=%H==SPLIT==%B==END== ' + range,
|
||||
efh(cb)(function (stdout) {
|
||||
cb(null, String(stdout).split('==END==\n')
|
||||
|
||||
|
||||
+38
-20
@@ -1,27 +1,45 @@
|
||||
'use strict'
|
||||
|
||||
var async = require('async')
|
||||
var npmconf = require('npmconf')
|
||||
var request = require('request')
|
||||
|
||||
var efh = require('./error').efh
|
||||
|
||||
module.exports = function (pkgName, cb) {
|
||||
request(process.env.npm_config_registry + pkgName, efh(cb)(function (response, body) {
|
||||
var res = {
|
||||
version: null,
|
||||
gitHead: null,
|
||||
pkg: null
|
||||
var registry = process.env.npm_config_registry
|
||||
async.waterfall([
|
||||
npmconf.load,
|
||||
function (conf, callback) {
|
||||
var cred = conf.getCredentialsByURI(registry)
|
||||
var reqopts = {
|
||||
url: registry + pkgName.replace(/\//g, '%2F'),
|
||||
headers: {}
|
||||
}
|
||||
if (cred.token) {
|
||||
reqopts.headers.Authorization = 'Bearer ' + cred.token
|
||||
} else if (cred.auth) {
|
||||
reqopts.headers.Authorization = 'Basic ' + cred.auth
|
||||
}
|
||||
callback(null, reqopts)
|
||||
},
|
||||
request,
|
||||
function (response, body, callback) {
|
||||
var res = {
|
||||
version: null,
|
||||
gitHead: null,
|
||||
pkg: null
|
||||
}
|
||||
|
||||
if (response.statusCode === 404 || !body) return callback(null, res)
|
||||
|
||||
var pkg = JSON.parse(body)
|
||||
|
||||
if (pkg.error) return callback(pkg.error)
|
||||
|
||||
res.version = pkg['dist-tags'].latest
|
||||
res.gitHead = pkg.versions[res.version].gitHead
|
||||
res.pkg = pkg
|
||||
|
||||
callback(null, res)
|
||||
}
|
||||
|
||||
if (response.statusCode === 404 || !body) return cb(null, res)
|
||||
|
||||
var pkg = JSON.parse(body)
|
||||
|
||||
if (pkg.error) return cb(pkg.error)
|
||||
|
||||
res.version = pkg['dist-tags'].latest
|
||||
res.gitHead = pkg.versions[res.version].gitHead
|
||||
res.pkg = pkg
|
||||
|
||||
cb(null, res)
|
||||
}))
|
||||
], cb)
|
||||
}
|
||||
|
||||
+8
-1
@@ -9,6 +9,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": "^1.0.5",
|
||||
"async": "^1.0.0",
|
||||
"conventional-changelog": "0.0.17",
|
||||
"error-first-handler": "^1.0.1",
|
||||
"git-head": "^1.2.1",
|
||||
@@ -16,17 +17,20 @@
|
||||
"github-url-from-git": "^1.4.0",
|
||||
"ini": "^1.3.2",
|
||||
"minimist": "^1.1.0",
|
||||
"npmconf": "^2.1.2",
|
||||
"parse-github-repo-url": "^1.0.0",
|
||||
"request": "^2.53.0",
|
||||
"semver": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cracks": "^2.0.1",
|
||||
"github-release-fake-server": "^1.3.0",
|
||||
"lodash.defaults": "^3.0.0",
|
||||
"nano-uid": "^0.2.0",
|
||||
"nixt": "^0.4.1",
|
||||
"nock": "^2.2.0",
|
||||
"sinopia": "^1.0.0",
|
||||
"standard": "^3.2.1",
|
||||
"standard": "^3.11.1",
|
||||
"tap-spec": "^3.0.0",
|
||||
"tape": "^4.0.0"
|
||||
},
|
||||
@@ -59,5 +63,8 @@
|
||||
"test:style": "standard",
|
||||
"test:integration": "node tests | tap-spec",
|
||||
"test": "./bin/test"
|
||||
},
|
||||
"release": {
|
||||
"verification": "cracks"
|
||||
}
|
||||
}
|
||||
|
||||
+38
-3
@@ -20,10 +20,45 @@ module.exports = function (options, plugins, cb) {
|
||||
var type = analyzer(commits)
|
||||
|
||||
if (!type) return cb(null, null)
|
||||
pkg.version = !res.version ? '1.0.0' : semver.inc(res.version, type)
|
||||
if (!options.debug) fs.writeFileSync(path, JSON.stringify(pkg, null, 2))
|
||||
|
||||
cb(null, pkg.version)
|
||||
if (res.version) {
|
||||
pkg.version = semver.inc(res.version, type)
|
||||
} else {
|
||||
type = 'major'
|
||||
pkg.version = '1.0.0'
|
||||
}
|
||||
|
||||
var writePkg = function () {
|
||||
if (!options.debug) fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n')
|
||||
cb(null, pkg.version)
|
||||
}
|
||||
|
||||
if (!plugins.verification) return writePkg()
|
||||
|
||||
var opts = {}
|
||||
|
||||
if (typeof plugins.verification === 'string') {
|
||||
opts.path = plugins.verification
|
||||
}
|
||||
if (typeof plugins.verification === 'object') {
|
||||
opts = plugins.verification
|
||||
opts.path = opts.path || opts.name
|
||||
}
|
||||
|
||||
opts.type = type
|
||||
opts.commits = commits
|
||||
opts.version = res.version
|
||||
opts.nextVersion = pkg.version
|
||||
|
||||
var verification = require(opts.path)
|
||||
|
||||
console.log('Running verification hook...')
|
||||
|
||||
verification(opts, function (error, ok) {
|
||||
if (!error && ok) return writePkg()
|
||||
console.log('Verification failed' + (error ? ': ' + error : ''))
|
||||
process.exit(1)
|
||||
})
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
+1
-1
@@ -38,5 +38,5 @@ module.exports = function () {
|
||||
pkg.devDependencies['semantic-release'] = '^' + require('../package.json').version
|
||||
}
|
||||
|
||||
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2))
|
||||
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n')
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
_auth=dGVzdDpmb28=
|
||||
email=test@example.com
|
||||
registry=http://127.0.0.1:4873/
|
||||
//registry.npmjs.org/:_authToken=testtoken
|
||||
|
||||
@@ -4,7 +4,9 @@ var test = require('tape')
|
||||
|
||||
var createModule = require('./lib/create-module')
|
||||
|
||||
require('./tap/npm-info')(test)
|
||||
require('./scenarios/custom-analyzer')(test, createModule)
|
||||
require('./scenarios/custom-verification')(test, createModule)
|
||||
require('./scenarios/ignore')(test, createModule)
|
||||
require('./scenarios/prepublish')(test, createModule)
|
||||
require('./scenarios/postpublish')(test, createModule)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (opts, cb) {
|
||||
cb(null, !(opts.commits.length % 2))
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use strict'
|
||||
|
||||
var path = require('path')
|
||||
|
||||
var efh = require('error-first-handler')
|
||||
var nixt = require('nixt')
|
||||
|
||||
module.exports = function (test, createModule) {
|
||||
createModule({
|
||||
release: {
|
||||
verification: path.join(__dirname, '../lib/custom-verification')
|
||||
}
|
||||
}, efh()(function (name, cwd) {
|
||||
test('custom-verification', function (t) {
|
||||
t.test('even commit count', function (t) {
|
||||
t.plan(1)
|
||||
nixt()
|
||||
.cwd(cwd)
|
||||
.env('CI', true)
|
||||
.env('npm_config_registry', 'http://127.0.0.1:4873/')
|
||||
.exec('git commit --allow-empty -m "feat: commit"')
|
||||
.run('npm run prepublish')
|
||||
.code(0)
|
||||
.end(function (err) {
|
||||
t.error(err, 'nixt')
|
||||
})
|
||||
})
|
||||
|
||||
t.test('odd commit count', function (t) {
|
||||
t.plan(1)
|
||||
nixt()
|
||||
.cwd(cwd)
|
||||
.env('CI', true)
|
||||
.env('npm_config_registry', 'http://127.0.0.1:4873/')
|
||||
.exec('git commit --allow-empty -m "feat: commit"')
|
||||
.run('npm run prepublish')
|
||||
.code(1)
|
||||
.stdout(/Verification failed/)
|
||||
.end(function (err) {
|
||||
t.error(err, 'nixt')
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
+26
-10
@@ -4,16 +4,15 @@ var efh = require('error-first-handler')
|
||||
var nixt = require('nixt')
|
||||
|
||||
module.exports = function (test, createModule) {
|
||||
createModule({
|
||||
repository: {},
|
||||
scripts: {
|
||||
prepublish: 'semantic-release pre --no-token'
|
||||
}
|
||||
}, efh()(function (name, cwd) {
|
||||
test('verify', function (t) {
|
||||
test('verify', function (t) {
|
||||
createModule({
|
||||
repository: {},
|
||||
scripts: {
|
||||
prepublish: 'semantic-release pre --no-token'
|
||||
}
|
||||
}, efh()(function (name, cwd) {
|
||||
t.test('verify package and options before publishing', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
nixt()
|
||||
.cwd(cwd)
|
||||
.env('CI', true)
|
||||
@@ -29,6 +28,23 @@ module.exports = function (test, createModule) {
|
||||
t.error(err, 'nixt')
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
}))
|
||||
|
||||
createModule({
|
||||
version: '1.0.0-semantically-released'
|
||||
}, efh()(function (name, cwd) {
|
||||
t.test('not publishing placeholder versions', function (t) {
|
||||
t.plan(1)
|
||||
|
||||
nixt()
|
||||
.cwd(cwd)
|
||||
.env('CI', true)
|
||||
.run('npm publish --semantic-release-rerun')
|
||||
.code(1)
|
||||
.end(function (err) {
|
||||
t.error(err, 'nixt')
|
||||
})
|
||||
})
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
var nock = require('nock')
|
||||
|
||||
var npmInfo = require('../../lib/npm-info.js')
|
||||
|
||||
var registry = 'http://registry.npmjs.org/'
|
||||
|
||||
var defaultModule = {
|
||||
'dist-tags': {
|
||||
latest: '1.0.0'
|
||||
},
|
||||
versions: {
|
||||
'1.0.0': {
|
||||
gitHead: 'HEAD'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.env.npm_config_registry = registry
|
||||
|
||||
module.exports = function (test) {
|
||||
test('npm-info', function (t) {
|
||||
var regMock = nock(registry, {
|
||||
reqheaders: {
|
||||
'authorization': 'Bearer testtoken'
|
||||
}
|
||||
})
|
||||
.get('/express')
|
||||
.reply(200, defaultModule)
|
||||
.get('/@user%2Fmodule')
|
||||
.reply(200, defaultModule)
|
||||
|
||||
t.test('get unscoped module', function (t) {
|
||||
t.plan(3)
|
||||
npmInfo('express', function (err, info) {
|
||||
t.error(err, 'error')
|
||||
t.is(info.version, '1.0.0', 'version')
|
||||
t.is(info.gitHead, 'HEAD', 'gitHead')
|
||||
})
|
||||
})
|
||||
t.test('get scoped module', function (t) {
|
||||
t.plan(3)
|
||||
npmInfo('@user/module', function (err, info) {
|
||||
t.error(err, 'error')
|
||||
t.is(info.version, '1.0.0', 'version')
|
||||
t.is(info.gitHead, 'HEAD', 'gitHead')
|
||||
regMock.done()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user