Compare commits

..
36 Commits
Author SHA1 Message Date
Stephan Bönnemann 875bb038a1 chore(package): change version to yet unpublished version
one last time hopefully >:(
2015-02-05 01:35:53 +01:00
Stephan Bönnemann 89f1928769 test(publish): initial scenario 2015-02-05 01:33:50 +01:00
Christoph Witzko 3799ad0cb6 test(postpublish): initial scenario 2015-02-05 01:04:52 +01:00
Christoph Witzko 7380d586ee feat(release): configure github api endpoint via environment variables 2015-02-05 01:00:42 +01:00
Christoph Witzko eb470712d7 test: added github-release-fake-server 2015-02-05 01:00:42 +01:00
Christoph Witzko f2484b8e42 test: set npm registry inside .npmrc 2015-02-04 20:18:19 +01:00
Stephan Bönnemann 73b888fdc3 docs(readme): remove usage outside of npm scripts 2015-02-04 19:02:37 +01:00
Stephan Bönnemann f44cedbaa7 text: expose correct registry to environment 2015-02-04 18:58:37 +01:00
Stephan Bönnemann a540c402a9 fix(version): use $npm_config_registry variable to ensure the right registry
BREAKING CHANGE: This breaks the usage of the `semantic-release` CLI outside of npm scripts.
2015-02-04 18:56:31 +01:00
Stephan Bönnemann ffbd39e78a test(prepublish): initial scenario 2015-02-04 15:39:05 +01:00
Stephan Bönnemann 81df21b924 test(install): initial scenario 2015-02-04 15:23:30 +01:00
Stephan Bönnemann d22b98a4ee test(create-module): helper to create fresh repo/module 2015-02-04 15:23:21 +01:00
Stephan Bönnemann c936e8be4b test: setup private npm registry w/sinopia 2015-02-04 15:23:16 +01:00
Stephan Bönnemann 019aeeabe1 refator: use error-first-handler 2015-02-03 20:45:03 +01:00
Stephan Bönnemann 19fd15c1dc refactor(cli): parse npmArgv w/minimist 2015-02-03 14:21:42 +01:00
Christoph WitzkoandStephan Bönnemann 16cf9aee9b fix(cli): hooks work correctly w/abbrevd npm commands 2015-02-03 14:09:02 +01:00
Stephan Bönnemann f2c701ca1b feat(cli): use --no-cli to publish locally w/o prompt 2015-02-02 23:30:55 +01:00
Stephan Bönnemann b39fecbdc2 fix(restart): correctly prevent double npm publish run 2015-02-02 17:57:06 +01:00
Stephan Bönnemann f7faebb175 docs(readme): fix json syntax 2015-02-02 16:33:57 +01:00
Stephan Bönnemann cf36c2fe9b chore(package): change dummy version to yet unpublished version 2015-02-02 16:26:23 +01:00
Stephan Bönnemann a48b0c957d docs(readme): document npm scripts automation capabilities 2015-02-02 16:26:23 +01:00
Stephan Bönnemann a1c86fc0d0 fix(post): correctly link issues and commits in changelog 2015-02-02 16:26:23 +01:00
Stephan Bönnemann bd90936927 feat(semantic-release): let the user confirm if they run hooks outside of CI 2015-02-02 16:26:22 +01:00
Stephan Bönnemann c0aebd9873 chore(package): configure prepublish hook 2015-02-02 15:37:16 +01:00
Stephan Bönnemann 9191260cb0 feat(pre): make hook "prepublish" compatible 2015-02-02 15:37:16 +01:00
Stephan Bönnemann 7fa0754ff2 chore: remove prepublish script and migrate to travis hook 2015-02-02 04:52:30 +01:00
Stephan Bönnemann 7d21697d78 chore(travis): correctly disable email notifications 2015-02-02 04:49:23 +01:00
Stephan Bönnemann 07c5d08d35 chore(package): provide dummy version so publish even starts 2015-02-02 04:37:11 +01:00
Stephan Bönnemann a89e7e6b1e chore(travis): skip_cleanup to provide version 2015-02-02 04:34:15 +01:00
Stephan Bönnemann 1d9350597a fix(semantic-release): not running prepublish on install 2015-02-02 04:31:00 +01:00
Stephan Bönnemann fd11c85692 chore(scripts): use relative path for own cli 2015-02-02 04:16:29 +01:00
Stephan Bönnemann fe750f7b9e feat(semantic-release): add initial version of cli 2015-02-02 04:13:58 +01:00
Stephan Bönnemann 17ad82bfbc feat(post): add initial version of command 2015-02-02 04:13:35 +01:00
Stephan Bönnemann 0ee9cc31e9 feat(pre): add initial version of command 2015-02-02 04:13:21 +01:00
Stephan Bönnemann f31a5dfd04 chore: meta files 2015-02-02 04:12:57 +01:00
Stephan Bönnemann 012dce2641 chore: inital commit 2014-09-15 18:18:52 +02:00
54 changed files with 573 additions and 1585 deletions
-11
View File
@@ -1,11 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
+1 -7
View File
@@ -1,9 +1,3 @@
coverage
node_modules
test/registry/couch
test/registry/data
test/tmp
*.log
*.dump
.DS_Store
.nyc_output
.tmp
+13 -15
View File
@@ -1,22 +1,20 @@
sudo: false
language: node_js
node_js:
- '0.10'
sudo: false
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- 5
- 4
- iojs
- '0.12'
- '0.10'
before_install:
- npm i -g npm@^2.0.0
before_script:
- npm prune
after_success:
- npm run coverage:upload
- npm run semantic-release
services:
- couchdb
- npm i -g npm@^2.0.0
deploy:
provider: npm
email: stephan@boennemann.me
skip_cleanup: true
api_key:
secure: Lq1FZweaNr/Ah8M7h3oWQ59HTswzvcksTUfzWb/dpqNn5FECN9I+4gi79EQPD62ZbJDCuDHY0EEEDLdnaB1z8N7kOj1RtmvC1k0teTEXFsGBPS4evXakRj+AfoBAGsTaXHqBYBOvYBxJT2Md/UoOgPSQjtAdZSNsmoFb1xkngQ0=
on:
branch: master
repo: boennemann/semantic-release
+12 -282
View File
@@ -1,294 +1,24 @@
# :package::rocket: semantic-release
**fully automated package publishing**
> **Trust us, this will change your workflow for the better.**
> [egghead.io](https://egghead.io/lessons/javascript-how-to-write-a-javascript-library-automating-releases-with-semantic-release)
[![Join the chat at https://gitter.im/semantic-release/semantic-release](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/semantic-release/semantic-release?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![Dependency Status](https://david-dm.org/semantic-release/semantic-release/caribou.svg)](https://david-dm.org/semantic-release/semantic-release/caribou)
[![devDependency Status](https://david-dm.org/semantic-release/semantic-release/caribou/dev-status.svg)](https://david-dm.org/semantic-release/semantic-release/caribou#info=devDependencies)
[![Build Status](https://travis-ci.org/semantic-release/semantic-release.svg?branch=caribou)](https://travis-ci.org/semantic-release/semantic-release)
[![Coverage Status](https://coveralls.io/repos/semantic-release/semantic-release/badge.svg?branch=caribou&service=github)](https://coveralls.io/github/semantic-release/semantic-release?branch=caribou)
Out of the box this is just about _commit-messages_, but you can do so much more.
* **Detect breaking changes** using the test suite of your last release: [cracks](https://github.com/semantic-release/cracks)
* Detect breaking changes using your dependents test suites: [Help out! Implement the **dont-break** plugin](https://github.com/semantic-release/semantic-release/issues/65)
* Detect breaking changes diffing your JSDoc interface: [Help out! Implement the **india** plugin](https://github.com/semantic-release/semantic-release/issues/66)
* Abort releases with **insufficient test coverage**: [Help out! Implement the **istanbul** plugin](https://github.com/semantic-release/semantic-release/issues/68)
* Abort releases with **vulnerable dependencies** in the tree: [Help out! Implement the **nsp** plugin](https://github.com/semantic-release/semantic-release/issues/67)
* Everything you can imagine: [Build Plugins!](https://github.com/semantic-release/semantic-release#plugins)
  | Commands | Comment
--- | --- | ---
| **manual/before** | <pre><code><div>npm version major</div><div>git push origin master --tags</div><div>npm publish</div></code></pre> | You **manually decide** what the **next version** is. You have to remember what major, minor and patch means. You have to remember to push both commits and tags. You have to wait for the CI to pass. |
| **semantic-release/after** | <pre><code><div>git commit -m "fix: &lt;message&gt;"</div><div>git push</div></code></pre> | You **describe the changes** youve made. A new version is automatically published with the correct version number.
This removes the immediate connection between human emotions and version numbers, so strictly following the [SemVer](http://semver.org/) spec is not a problem anymore  and thats ultimately `semantic-release`s goal.
<table>
<tr>
<th colspan="2">
“How to Write a JavaScript Library - Automating Releases with semantic-release”  egghead.io
</th>
</tr>
<tr>
<td colspan="2">
<a href="https://egghead.io/lessons/javascript-how-to-write-a-javascript-library-automating-releases-with-semantic-release"><img src="https://cloud.githubusercontent.com/assets/908178/9730739/7b1da5d8-5610-11e5-88b6-5c75fdda7ee2.png" alt="egghead.io session"></a>
</td>
</tr>
<tr>
<td colspan="2">
A free egghead.io tutorial series on how to write an open source library featuring semantic-release.
</td>
</tr>
<tr>
<th>
“We fail to follow SemVer and why it neednt matter”
</th>
<th>
“semantic-release Q&A with Kent C. Dodds”
</th>
</tr>
<tr>
<td>
<a href="https://www.youtube.com/watch?v=tc2UgG5L7WM&amp;index=6&amp;list=PLFZ5NyC0xHDaaTy6tY9p0C0jd_rRRl5Zm"><img alt="JSConfBP Talk" src="https://cloud.githubusercontent.com/assets/908178/9428178/c337ed26-49a2-11e5-8ad0-a602500a65bf.png"></a>
</td>
<td>
<a href="https://www.youtube.com/watch?v=g6y3DnhkjrI"><img alt="Hangout on Air with Kent C. Dodds" src="https://cloud.githubusercontent.com/assets/908178/9428179/c3387318-49a2-11e5-9f5f-dccd3fbb0c1d.png"></a>
</td>
</tr>
<tr>
<td>
This talk gives you a complete introduction to the underlying concepts of this module. 38:30
</td>
<td>
A “Hangouts on Air” conversation with hands on questions and answers about how to use semantic-release. 53:52
</td>
</tr>
</table>
## How does it work?
Instead of writing [meaningless commit messages](http://whatthecommit.com/), we can take our time to think about the changes in the codebase and write them down. Following formalized conventions it is then possible to generate a helpful changelog and to derive the next semantic version number from them.
When `semantic-release` is setup it will do that after every successful continuous integration build of your master branch (or any other branch you specify) and publish the new version for you. This way no human is directly involved in the release process and your releases are guaranteed to be [unromantic and unsentimental](http://sentimentalversioning.org/).
If you fear the loss of control over timing and marketing implications of software releases you should know that `semantic-release` supports [release channels](https://github.com/npm/npm/issues/2718) using `npm`s [dist-tags](https://docs.npmjs.com/cli/dist-tag). This way you can keep control over what your users end up using by default, you can decide when to promote an automatically released version to the stable channel, and you can choose which versions to write blogposts and tweets about. You can use the same mechanism to [support older versions of your software](https://gist.github.com/boennemann/54042374e49c7ade8910), for example with important security fixes.
This is what happens in series:
| 1. `git push` | 2. `semantic-release pre` | 3. `npm publish` | 4. `semantic-release post` |
| :--- | :--- | :--- | :---- |
| New code is pushed and triggers a CI build. | Based on all commits that happened since the last release, the new version number gets written to the `package.json`. | The new version gets published to `npm`. | A changelog gets generated and a [release](https://help.github.com/articles/about-releases/) (including a git tag) on GitHub gets created. |
_Note:_ The current release/tag implementation is tied to GitHub, but could be opened up to Bitbucket, GitLab, et al. Feel free to send PRs for these services.
## Default Commit Message Format
This module ships with the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) and changelog generator, but you can [define your own](#plugins) style.
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
format that includes a **type**, a **scope** and a **subject** ([full explanation](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)):
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
You can simplify using this convention for yourself and contributors by using [commitizen](https://github.com/commitizen/cz-cli) and [validate-commit-msg](https://github.com/kentcdodds/validate-commit-msg).
### Patch Release
```
fix(pencil): stop graphite breaking when too much pressure applied
```
### ~~Minor~~ Feature Release
```
feat(pencil): add 'graphiteWidth' option
```
### ~~Major~~ Breaking Release
```
perf(pencil): remove graphiteWidth option
BREAKING CHANGE: The graphiteWidth option has been removed. The default graphite width of 10mm is always used for performance reason.
```
## Setup
# semantic-release
[![Build Status](https://travis-ci.org/boennemann/semantic-release.svg)](https://travis-ci.org/boennemann/semantic-release)
[![Dependency Status](https://david-dm.org/boennemann/semantic-release.svg)](https://david-dm.org/boennemann/semantic-release)
[![devDependency Status](https://david-dm.org/boennemann/semantic-release/dev-status.svg)](https://david-dm.org/boennemann/semantic-release#info=devDependencies)
[![NPM](https://nodei.co/npm/semantic-release.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/semantic-release/)
```bash
npm install -g semantic-release-cli
cd your-module
semantic-release-cli setup
npm i semantic-release
```
![dialogue](https://cloud.githubusercontent.com/assets/908178/9428123/3628dfec-499f-11e5-8bdd-8f3042dd95ed.png)
_[This is what happens under the hood.](https://github.com/semantic-release/cli#what-it-does)_
## Options
You can pass options either via command line (in [kebab-case](https://lodash.com/docs#kebabCase)) or in the `release` field of your `package.json` (in [camelCase](https://lodash.com/docs#camelCase)). The following two examples are the same, but CLI arguments take precedence.
| CLI | package.json |
| --- | --- |
| <pre><code>semantic-release pre --no-debug</code><pre> | <pre><code><div>//package.json</div><div>"release": {</div><div> "debug": false</div><div>}</div></code></pre><pre><code>semantic-release pre</code></pre> |
These options are currently available:
- `branch`: The branch on which releases should happen. Default: `'master'`
- `debug`: If true doesnt actually publish to npm or write things to file. Default: `!process.env.CI`
- `githubToken`: The token used to authenticate with GitHub. Default: `process.env.GH_TOKEN`
- `githubUrl`: Optional. Pass your GitHub Enterprise endpoint.
- `githubApiPathPrefix`: Optional. The path prefix for your GitHub Enterprise API.
_A few notes on `npm` config_:
1. The `npm` token can only be defined in the environment as `NPM_TOKEN`, because thats where `npm` itself is going to read it from.
2. In order to publish to a different `npm` registry you can specify that inside the `package.json`s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field.
3. If you want to use another dist-tag for your publishes than `'latest'` you can specify that inside the `package.json`s [`publishConfig`](https://docs.npmjs.com/files/package.json#publishconfig) field.
4. `semantic-release` generally tries to orientate itself towards `npm` it inherits the loglevel for example.
## Plugins
There are numerous steps where you can customize `semantic-release`s behaviour using plugins. A plugin is a regular [option](#options), but passed inside the `release` block of `package.json`:
You *need* to set it up inside your `package.json`:
```json
{
"release": {
"analyzeCommits": "npm-module-name",
"generateNotes": "./path/to/a/local/module",
"verifyConditions": {
"path": "./path/to/a/module",
"additional": "config"
}
"scripts": {
"prepublish": "semantic-release pre",
"postpublish": "semantic-release post"
}
```
```
semantic-release pre --analyze-commits="npm-module-name"
```
A plugin itself is an async function that always receives three arguments.
```js
module.exports = function (pluginConfig, config, callback) {}
```
- `pluginConfig`: If the user of your plugin specifies additional plugin config in the `package.json` (see the `verifyConditions` example above) then its this object.
- `config`: A config object containing a lot of information to act upon.
- `env`: All environment variables
- `npm`: Select npm configuration bits like `registry`, `tag` and `auth`
- `options`: `semantic-release` options like `debug`, or `branch`
- `pkg`: Parsed `package.json`
- For certain plugins the `config` object contains even more information. See below.
- `callback`: If an error occurs pass it as first argument. Otherwise pass your result as second argument.
### `analyzeCommits`
This plugin is responsible for determining the type of the next release. It additionally receives a `commits` array inside `config`. One commit is an object with a `message` and `hash` property. Call the callback with `'major'`, `'premajor'`, `'minor'`, `'preminor'`, `'patch'`, `'prepatch'`, `'prerelease'`, or `null` if nothing changed.
While it may be tempting to use `'prepatch'`, `'preminor'` & `'prerelease'` as part of a release process, this is strongly discouraged. A better approach is to use [dist-tags](https://docs.npmjs.com/cli/dist-tag) to create release channels (such as 'latest', 'next', 'stable') and to return only `'major'`, `'premajor'` and `'minor'` from the commit analyzer.
Have a look at the [default implementation](https://github.com/semantic-release/commit-analyzer/).
### `generateNotes`
This plugin is responsible for generating release notes. Call the callback with the notes as a string. Have a look at the [default implementation](https://github.com/semantic-release/release-notes-generator/).
### `verifyConditions`
This plugins is responsible for verifying that a release should happen in the first place. For example, the [default implementation](https://github.com/semantic-release/condition-travis/) verifies that the publish is happening on Travis, that its the right branch, and that all other build jobs succeeded. There are more use cases for this, e.g. verifying that test coverage is above a certain threshold or that there are no [vulnerabilities](https://nodesecurity.io/) in your dependencies. Be creative.
Passing an array of plugins will run them in series.
### `verifyRelease`
This plugin is responsible for verifying a release that was determined before and is about to be published. There is no default implementation. It additionally receives `nextRelease`, `lastRelease` and `commits` inside `config`. While `commits` is the same as with analyzeCommits, `nextRelease` contains a `type` (e.g. `'major'`) and the new version (e.g. `'1.0.0'`) and `lastRelease` contains the old `version`, the `gitHead` at the time of the release and the npm dist-`tag` (e.g. `'latest'`). Using this information you could [detect breaking changes](https://github.com/semantic-release/cracks) or hold back certain types of releases. Again: Be creative.
Passing an array of plugins will run them in series.
### `getLastRelease`
This plugin is responsible for determining a packages last release version. The [default implementation](https://github.com/semantic-release/last-release-npm) uses the last published version on a npm registry.
## ITYM*FAQ*LT
> I think you might frequently ask questions like these
### Why is the `package.json`s version not updated in my repository?
The `npm` docs even state:
> The most important things in your package.json are the name and version fields. Those are actually required, and your package wont install without them.
> [npm docs](https://docs.npmjs.com/files/package.json#version)
While this entirely true the version number doesnt have to be checked into source control. `semantic-release` takes care of the version field right before `npm publish` uses it and this is the only point where it _really_ is required.
### Is there a way to preview which version would currently get published?
If you run `npm run semantic-release` locally a dry run gets performed, which logs the version that would currently get published.
### Can I run this on my own machine rather than on a CI server?
Of course you can, but this doesnt 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=<your_token>` and `NPM_TOKEN=<your_other_token>`.
### Can I manually trigger the release of a specific version?
You can trigger a release by pushing to your GitHub repository. You deliberately cannot trigger a _specific_ version release, because this is the whole point of `semantic-release`. Start your packages with `1.0.0` and semver on.
### Is it _really_ a good idea to release on every push?
It is indeed a great idea because it _forces_ you to follow best practices. If you dont feel comfortable making every passing feature or fix on your master branch addressable via `npm` you might not treat your master right. Have a look at [branch workflows](https://guides.github.com/introduction/flow/index.html). If you still think you should have control over the exact point in time of your release, e.g. because you are following a release schedule, you can release only on the `production`/`deploy`/`release` branch and push your code there in certain intervals, or better yet use [dist-tags](https://docs.npmjs.com/cli/dist-tag).
### Why should I trust `semantic-release` with my releases?
`semantic-release` has a full unit- and integration-test-suite that tests _actual_ `npm` publishes against the [npm-registry-couchapp](https://github.com/npm/npm-registry-couchapp/) on all major node.js versions from `^0.10` on. A new version wont get published if it doesnt pass on all these engines.
## Badge
Use this in one of your projects? Include one of these badges in your README.md to let people know that your package is published using `semantic-release`.
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
```md
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
```
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=plastic)](https://github.com/semantic-release/semantic-release)
```md
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=plastic)](https://github.com/semantic-release/semantic-release)
```
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release)
```md
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](https://github.com/semantic-release/semantic-release)
```
## License
Note: `semantic-release` works around a limitation in npm's "prepublish" hook. Once a version is published it prints an error that you can *safely ignore*.
See [npm/npm#7118](https://github.com/npm/npm/issues/7118).
MIT License
2015 © Stephan Bönnemann and [contributors](https://github.com/semantic-release/semantic-release/graphs/contributors)
[![](https://cloud.githubusercontent.com/assets/908178/6091690/cc86f58c-aeb8-11e4-94cb-15f15f486cde.png)](https://twitter.com/trodrigues/status/509301317467373571)
2015 Stephan Bönnemann
Executable
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
cat .tmp/sinopia.pid | xargs kill
cat .tmp/ghrs.pid | xargs kill
cp .tmp/.npmrc ~/.npmrc
rm -rf .tmp
Executable
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
set -e
mkdir -p .tmp/modules
./node_modules/.bin/sinopia ./tests/config.yml & echo $! >> .tmp/sinopia.pid
./node_modules/.bin/github-release-fake-server -p 4343 & echo $! >> .tmp/ghrs.pid
sleep 0.5
cp ~/.npmrc .tmp/.npmrc
cp tests/.npmrc ~/.npmrc
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env node
'use strict'
var abbrev = require('abbrev')
var confirm = require('confirm-simple')
var minimist = require('minimist')
var efh = require('../lib/error').standard
var argv = minimist(process.argv.slice(2), {
alias: {
d: 'debug',
dry: 'debug',
t: 'token',
g: 'github-url'
},
default: {
token: process.env.GH_TOKEN || process.env.TOKEN || process.env.GITHUB_TOKEN,
'github-url': process.env.GH_URL
}
})
var npmArgv = process.env.npm_config_argv ?
minimist(JSON.parse(process.env.npm_config_argv).cooked) :
{_: []}
if (~argv._.indexOf('pre')) {
// see src/restart.js
if (npmArgv['semantic-release-rerun']) process.exit(0)
// the `prepublish` hook is also executed when the package is installed
// in this case we abort the command and do nothing.
if (isAbbrev(npmArgv, 'install')) process.exit(0)
return confirmCI(function () {
console.log('Determining new version')
var publish = false
if (isAbbrev(npmArgv, 'publish')) publish = true
require('../src/pre')(argv, efh(function (result) {
if (!result) {
console.log('Nothing changed. Not publishing.')
process.exit(1)
}
console.log('Publishing v' + result)
if (!publish) process.exit(0)
require('../src/restart')(efh(function () {
process.exit(1)
}))
}))
})
}
if (~argv._.indexOf('post')) {
return confirmCI(function () {
require('../src/post')(argv, efh(function () {
// see src/restart.js
if (npmArgv['semantic-release-rerun']) {
console.log('Everything is alright :) npm will now print an error message that you can safely ignore.')
}
}))
})
}
function confirmCI (cb) {
if (process.env.CI || argv.ci === false) return cb(null, true)
confirm('Not running in a CI enviroment. Are you sure you want to run this hook?', function (ok) {
if (!ok) process.exit(1)
cb(null, ok)
})
}
function isAbbrev (argv, command) {
return argv._.some(Object.prototype.hasOwnProperty.bind(abbrev(command)))
}
-165
View File
@@ -1,165 +0,0 @@
#!/usr/bin/env node
var fs = require('fs')
var path = require('path')
var url = require('url')
var _ = require('lodash')
var log = require('npmlog')
var nopt = require('nopt')
var npmconf = require('npmconf')
var normalizeData = require('normalize-package-data')
log.heading = 'semantic-release'
var env = process.env
var pkg = JSON.parse(fs.readFileSync('./package.json'))
var originalPkg = _.cloneDeep(pkg)
normalizeData(pkg)
var knownOptions = {
branch: String,
debug: Boolean,
'github-token': String,
'github-url': String,
'analyze-commits': [path, String],
'generate-notes': [path, String],
'verify-conditions': [path, String],
'verify-release': [path, String]
}
var options = _.defaults(
_.mapKeys(nopt(knownOptions), function (value, key) {
return _.camelCase(key)
}),
pkg.release,
{
branch: 'master',
fallbackTags: {
next: 'latest'
},
debug: !env.CI,
githubToken: env.GH_TOKEN || env.GITHUB_TOKEN,
githubUrl: env.GH_URL
}
)
var plugins = require('../src/lib/plugins')(options)
npmconf.load({}, function (err, conf) {
if (err) {
log.error('init', 'Failed to load npm config.', err)
process.exit(1)
}
var npm = {
auth: {
token: env.NPM_TOKEN
},
cafile: conf.get('cafile'),
loglevel: conf.get('loglevel'),
registry: require('../src/lib/get-registry')(pkg, conf),
tag: (pkg.publishConfig || {}).tag || conf.get('tag') || 'latest'
}
// normalize trailing slash
npm.registry = url.format(url.parse(npm.registry))
log.level = npm.loglevel
var config = {
env: env,
pkg: pkg,
options: options,
plugins: plugins,
npm: npm
}
var hide = {}
if (options.githubToken) hide.githubToken = '***'
log.verbose('init', 'options:', _.assign({}, options, hide))
log.verbose('init', 'Verifying config.')
var errors = require('../src/lib/verify')(config)
errors.forEach(function (err) {
log.error('init', err.message + ' ' + err.code)
})
if (errors.length) process.exit(1)
if (options.argv.remain[0] === 'pre') {
log.verbose('pre', 'Running pre-script.')
log.verbose('pre', 'Veriying conditions.')
plugins.verifyConditions(config, function (err) {
if (err) {
log[options.debug ? 'warn' : 'error']('pre', err.message)
if (!options.debug) process.exit(1)
}
var nerfDart = require('nerf-dart')(npm.registry)
var wroteNpmRc = false
if (env.NPM_OLD_TOKEN && env.NPM_EMAIL) {
// Using the old auth token format is not considered part of the public API
// This might go away anytime (i.e. once we have a better testing strategy)
conf.set('_auth', '${NPM_OLD_TOKEN}', 'project') // eslint-disable-line no-template-curly-in-string
conf.set('email', '${NPM_EMAIL}', 'project') // eslint-disable-line no-template-curly-in-string
wroteNpmRc = true
} else if (env.NPM_TOKEN) {
conf.set(nerfDart + ':_authToken', '${NPM_TOKEN}', 'project') // eslint-disable-line no-template-curly-in-string
wroteNpmRc = true
}
conf.save('project', function (err) {
if (err) return log.error('pre', 'Failed to save npm config.', err)
if (wroteNpmRc) log.verbose('pre', 'Wrote authToken to .npmrc.')
require('../src/pre')(config, function (err, release) {
if (err) {
log.error('pre', 'Failed to determine new version.')
var args = ['pre', (err.code ? err.code + ' ' : '') + err.message]
if (err.stack) args.push(err.stack)
log.error.apply(log, args)
process.exit(1)
}
var message = 'Determined version ' + release.version + ' as "' + npm.tag + '".'
log.verbose('pre', message)
if (options.debug) {
log.error('pre', message + ' Not publishing in debug mode.', release)
process.exit(1)
}
try {
var shrinkwrap = JSON.parse(fs.readFileSync('./npm-shrinkwrap.json'))
shrinkwrap.version = release.version
fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(shrinkwrap, null, 2))
log.verbose('pre', 'Wrote version ' + release.version + 'to npm-shrinkwrap.json.')
} catch (e) {
log.silly('pre', 'Couldn\'t find npm-shrinkwrap.json.')
}
fs.writeFileSync('./package.json', JSON.stringify(_.assign(originalPkg, {
version: release.version
}), null, 2))
log.verbose('pre', 'Wrote version ' + release.version + ' to package.json.')
})
})
})
} else if (options.argv.remain[0] === 'post') {
log.verbose('post', 'Running post-script.')
require('../src/post')(config, function (err, published, release) {
if (err) {
log.error('post', 'Failed to publish release notes.', err)
process.exit(1)
}
log.verbose('post', (published ? 'Published' : 'Generated') + ' release notes.', release)
})
} else {
log.error('post', 'Command "' + options.argv.remain[0] + '" not recognized. Use either "pre" or "post"')
}
})
+14
View File
@@ -0,0 +1,14 @@
'use strict'
var efh = require('error-first-handler')
module.exports = {
efh: efh,
standard: efh(function(err) {
console.log('Something went wrong:')
if (typeof err === 'string') return console.log(err)
if (err instanceof Error) return console.log(err.message)
if (err.message) return console.log(err.message)
console.log(err)
})
}
+20
View File
@@ -0,0 +1,20 @@
'use strict'
module.exports = function (commits) {
var type = null
commits.every(function (commit) {
if (commit.breaks.length) {
type = 'major'
return false
}
if (commit.type === 'feat') type = 'minor'
if (!type && commit.type === 'fix') type = 'patch'
return true
})
return type
}
+13
View File
@@ -0,0 +1,13 @@
'use strict'
var git = require('conventional-changelog/lib/git')
var efh = require('../error').efh
module.exports = function (cb) {
git.latestTag(efh(cb)(function (from) {
git.getCommits({from: from}, efh(cb)(function (commits) {
cb(null, commits)
}))
}))
}
+11
View File
@@ -0,0 +1,11 @@
'use strict'
var analyze = require('./analyze')
var commits = require('./commits')
var efh = require('../error').efh
module.exports = function (cb) {
commits(efh(cb)(function (commits) {
cb(null, analyze(commits))
}))
}
+17
View File
@@ -0,0 +1,17 @@
'use strict'
var exec = require('child_process').exec
var efh = require('./error').efh
module.exports = function (pkg, cb) {
if (!pkg.name) return cb(new Error('Package must have a name'))
exec('curl -s "' + process.env.npm_config_registry + pkg.name + '"', efh(cb)(function(stdout) {
var pkg = JSON.parse(stdout)
if (pkg.error) return cb(null, null, true)
cb(null, pkg['dist-tags'].latest)
}))
}
+36 -68
View File
@@ -1,90 +1,58 @@
{
"name": "semantic-release",
"description": "automated semver compliant package publishing",
"version": "0.0.0-placeholder",
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
"bin": {
"semantic-release": "bin/semantic-release.js"
},
"description": "semantic semver compliant package publishing",
"version": "0.0.6",
"author": "Stephan Bönnemann <stephan@boennemann.me>",
"bin": "./bin/semantic-release",
"bugs": {
"url": "https://github.com/semantic-release/semantic-release/issues"
},
"czConfig": {
"path": "node_modules/cz-conventional-changelog/"
"url": "https://github.com/boennemann/semantic-release/issues"
},
"dependencies": {
"@semantic-release/commit-analyzer": "^2.0.0",
"@semantic-release/condition-travis": "^5.0.2",
"@semantic-release/error": "^1.0.0",
"@semantic-release/last-release-npm": "^1.2.1",
"@semantic-release/release-notes-generator": "^2.0.0",
"git-head": "^1.2.1",
"github": "^7.0.0",
"lodash": "^4.0.0",
"nerf-dart": "^1.0.0",
"nopt": "^3.0.3",
"normalize-package-data": "^2.3.4",
"npmconf": "^2.1.2",
"npmlog": "^4.0.0",
"parse-github-repo-url": "^1.3.0",
"require-relative": "^0.8.7",
"run-auto": "^2.0.0",
"run-series": "^1.1.3",
"semver": "^5.2.0"
"abbrev": "^1.0.5",
"confirm-simple": "^1.0.3",
"conventional-changelog": "0.0.11",
"error-first-handler": "^1.0.1",
"github": "^0.2.3",
"github-url-from-git": "^1.4.0",
"minimist": "^1.1.0",
"parse-github-repo-url": "^1.0.0",
"semver": "^4.2.0"
},
"devDependencies": {
"coveralls": "^2.11.4",
"cz-conventional-changelog": "^1.1.4",
"mkdirp": "^0.5.1",
"mock-spawn": "^0.2.6",
"nixt": "^0.5.0",
"nock": "^9.0.2",
"npm-registry-couchapp": "^2.6.12",
"nyc": "^10.0.0",
"proxyquire": "^1.7.3",
"rimraf": "^2.5.0",
"standard": "^8.6.0",
"tap": "^8.0.1"
"github-release-fake-server": "^1.1.1",
"lodash.defaults": "^3.0.0",
"nano-uid": "^0.2.0",
"nixt": "^0.4.1",
"sinopia": "^1.0.0",
"standard": "^2.3.1",
"tap-spec": "^2.2.0",
"tape": "^3.5.0"
},
"engines": {
"node": ">=0.10",
"npm": ">=2"
"node": "^0.10.0",
"npm": "^2.0.0"
},
"files": [
"bin",
"src"
],
"homepage": "https://github.com/semantic-release/semantic-release/tree/next#readme",
"homepage": "https://github.com/boennemann/semantic-release",
"keywords": [
"author",
"automation",
"changelog",
"release",
"publish",
"module",
"package",
"publish",
"release",
"semver",
"version"
"version",
"changelog"
],
"license": "MIT",
"main": "bin/semantic-release.js",
"publishConfig": {
"tag": "next"
},
"release": {
"branch": "caribou"
},
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/semantic-release/semantic-release.git"
"url": "https://github.com/boennemann/semantic-release.git"
},
"scripts": {
"coverage": "nyc report",
"coverage:upload": "npm run coverage -s -- --reporter=text-lcov | coveralls",
"pretest": "standard",
"semantic-release": "./bin/semantic-release.js pre && npm publish && ./bin/semantic-release.js post",
"test": "npm run test:unit && npm run test:integration",
"test:integration": "tap --no-cov test/scenarios/*.js",
"test:unit": "nyc tap --no-cov test/specs/*.js"
"postpublish": "./bin/semantic-release post",
"posttest": "./bin/post-test",
"prepublish": "./bin/semantic-release pre",
"pretest": "./bin/pre-test",
"test": "standard && node tests | tap-spec"
}
}
-79
View File
@@ -1,79 +0,0 @@
var childProcess = require('child_process')
var log = require('npmlog')
var SemanticReleaseError = require('@semantic-release/error')
module.exports = function (config, cb) {
var lastRelease = config.lastRelease
var options = config.options
var branch = options.branch
var from = lastRelease.gitHead
var range = (from ? from + '..' : '') + 'HEAD'
if (!from) return extract()
childProcess.exec('git branch --no-color --contains ' + from, function (err, stdout) {
var inHistory = false
var branches
if (!err && stdout) {
branches = stdout.split('\n')
.map(function (result) {
if (branch === result.replace('*', '').trim()) {
inHistory = true
return null
}
return result.trim()
})
.filter(function (branch) {
return !!branch
})
}
if (!inHistory) {
log.error('commits',
'The commit the last release of this package was derived from is not in the direct history of the "' + branch + '" branch.\n' +
'This means semantic-release can not extract the commits between now and then.\n' +
'This is usually caused by force pushing, releasing from an unrelated branch, or using an already existing package name.\n' +
'You can recover from this error by publishing manually or restoring the commit "' + from + '".' + (branches && branches.length
? '\nHere is a list of branches that still contain the commit in question: \n * ' + branches.join('\n * ')
: ''
))
return cb(new SemanticReleaseError('Commit not in history', 'ENOTINHISTORY'))
}
extract()
})
function extract () {
var child = childProcess.spawn('git', ['log', '-E', '--format=%H==SPLIT==%B==END==', range])
var stdout = ''
var err = ''
child.stdout.on('data', function (data) {
stdout += data
})
child.stderr.on('data', function (data) {
err += data
})
child.on('close', function (code) {
if (err || code) return cb(err)
cb(null, String(stdout).split('==END==\n')
.filter(function (raw) {
return !!raw.trim()
})
.map(function (raw) {
var data = raw.split('==SPLIT==')
return {
hash: data[0],
message: data[1]
}
})
)
})
}
}
-12
View File
@@ -1,12 +0,0 @@
module.exports = function (pkg, conf) {
if (pkg.publishConfig && pkg.publishConfig.registry) return pkg.publishConfig.registry
if (pkg.name[0] !== '@') return conf.get('registry') || 'https://registry.npmjs.org/'
var scope = pkg.name.split('/')[0]
var scopedRegistry = conf.get(scope + '/registry')
if (scopedRegistry) return scopedRegistry
return conf.get('registry') || 'https://registry.npmjs.org/'
}
-4
View File
@@ -1,4 +0,0 @@
/* istanbul ignore next */
module.exports = function (config, options, cb) {
cb(null)
}
-42
View File
@@ -1,42 +0,0 @@
var relative = require('require-relative')
var series = require('run-series')
var exports = module.exports = function (options) {
var plugins = {
analyzeCommits: exports.normalize(options.analyzeCommits, '@semantic-release/commit-analyzer'),
generateNotes: exports.normalize(options.generateNotes, '@semantic-release/release-notes-generator'),
getLastRelease: exports.normalize(options.getLastRelease, '@semantic-release/last-release-npm')
}
;['verifyConditions', 'verifyRelease'].forEach(function (plugin) {
if (!Array.isArray(options[plugin])) {
plugins[plugin] = exports.normalize(
options[plugin],
plugin === 'verifyConditions'
? '@semantic-release/condition-travis'
: './plugin-noop'
)
return
}
plugins[plugin] = function (pluginOptions, cb) {
var tasks = options[plugin].map(function (step) {
return exports.normalize(step, './plugin-noop').bind(null, pluginOptions)
})
series(tasks, cb)
}
})
return plugins
}
exports.normalize = function (pluginConfig, fallback) {
if (typeof pluginConfig === 'string') return relative(pluginConfig).bind(null, {})
if (pluginConfig && (typeof pluginConfig.path === 'string')) {
return relative(pluginConfig.path).bind(null, pluginConfig)
}
return require(fallback).bind(null, pluginConfig)
}
-21
View File
@@ -1,21 +0,0 @@
var SemanticReleaseError = require('@semantic-release/error')
module.exports = function (config, cb) {
var plugins = config.plugins
var lastRelease = config.lastRelease
plugins.analyzeCommits(config, function (err, type) {
if (err) return cb(err)
if (!type) {
return cb(new SemanticReleaseError(
'There are no relevant changes, so no new version is released.',
'ENOCHANGE'
))
}
if (!lastRelease.version) return cb(null, 'initial')
cb(null, type)
})
}
-40
View File
@@ -1,40 +0,0 @@
var SemanticReleaseError = require('@semantic-release/error')
module.exports = function (config) {
var pkg = config.pkg
var options = config.options
var env = config.env
var errors = []
if (!pkg.name) {
errors.push(new SemanticReleaseError(
'No "name" found in package.json.',
'ENOPKGNAME'
))
}
if (!pkg.repository || !pkg.repository.url) {
errors.push(new SemanticReleaseError(
'No "repository" found in package.json.',
'ENOPKGREPO'
))
}
if (options.debug) return errors
if (!options.githubToken) {
errors.push(new SemanticReleaseError(
'No github token specified.',
'ENOGHTOKEN'
))
}
if (!(env.NPM_TOKEN || (env.NPM_OLD_TOKEN && env.NPM_EMAIL))) {
errors.push(new SemanticReleaseError(
'No npm token specified.',
'ENONPMTOKEN'
))
}
return errors
}
+34 -32
View File
@@ -1,53 +1,55 @@
'use strict'
var exec = require('child_process').exec
var readFile = require('fs').readFileSync
var url = require('url')
var gitHead = require('git-head')
var changelog = require('conventional-changelog')
var GitHubApi = require('github')
var parseSlug = require('parse-github-repo-url')
var parseUrl = require('github-url-from-git')
module.exports = function (config, cb) {
var pkg = config.pkg
var options = config.options
var plugins = config.plugins
var ghConfig = options.githubUrl ? url.parse(options.githubUrl) : {}
var efh = require('../lib/error').efh
module.exports = function (options, cb) {
var pkg = JSON.parse(readFile('./package.json'))
var repository = pkg.repository ? pkg.repository.url : null
if (!repository) return cb(new Error('Package must have a repository'))
var config = options['github-url'] ? url.parse(options['github-url']) : {}
var github = new GitHubApi({
port: ghConfig.port,
protocol: (ghConfig.protocol || '').split(':')[0] || null,
host: ghConfig.hostname,
pathPrefix: options.githubApiPathPrefix || null
version: '3.0.0',
port: config.port,
protocol: (config.protocol || '').split(':')[0] || null,
host: config.hostname
})
plugins.generateNotes(config, function (err, log) {
if (err) return cb(err)
gitHead(function (err, hash) {
if (err) return cb(err)
var ghRepo = parseSlug(pkg.repository.url)
changelog({
version: pkg.version,
repository: parseUrl(repository),
file: false
}, efh(cb)(function (log) {
exec('git rev-parse HEAD', efh(cb)(function (hash) {
var ghRepo = parseSlug(repository)
var release = {
owner: ghRepo[0],
repo: ghRepo[1],
name: 'v' + pkg.version,
tag_name: 'v' + pkg.version,
target_commitish: hash,
draft: !!options.debug,
draft: options.debug,
body: log
}
if (options.debug && !options.githubToken) {
return cb(null, false, release)
}
github.authenticate({
type: 'token',
token: options.githubToken
type: 'oauth',
token: options.token
})
github.repos.createRelease(release, function (err) {
if (err) return cb(err)
cb(null, true, release)
})
})
})
github.releases.createRelease(release, efh(cb)(function () {
cb(null, true)
}))
}))
}))
}
+18 -39
View File
@@ -1,45 +1,24 @@
var _ = require('lodash')
var auto = require('run-auto')
'use strict'
var fs = require('fs')
var semver = require('semver')
var getCommits = require('./lib/commits')
var getType = require('./lib/type')
var type = require('../lib/type')
var version = require('../lib/version')
var efh = require('../lib/error').efh
module.exports = function (config, cb) {
var plugins = config.plugins
module.exports = function (options, cb) {
type(efh(cb)(function (type) {
if (!type) return cb(null, null)
auto({
lastRelease: plugins.getLastRelease.bind(null, config),
commits: ['lastRelease', function (results, cb) {
getCommits(_.assign({
lastRelease: results.lastRelease
}, config),
cb)
}],
type: ['commits', 'lastRelease', function (results, cb) {
getType(_.assign({
commits: results.commits,
lastRelease: results.lastRelease
}, config),
cb)
}]
}, function (err, results) {
if (err) return cb(err)
var path = './package.json'
var pkg = JSON.parse(fs.readFileSync(path))
version(pkg, efh(cb)(function (version, unpublished) {
pkg.version = unpublished ? '1.0.0' : semver.inc(version, type)
if (!options.debug) fs.writeFileSync(path, JSON.stringify(pkg, null, 2))
var nextRelease = {
type: results.type,
version: results.type === 'initial'
? '1.0.0'
: semver.inc(results.lastRelease.version, results.type)
}
plugins.verifyRelease(_.assign({
commits: results.commits,
lastRelease: results.lastRelease,
nextRelease: nextRelease
}, config), function (err) {
if (err) return cb(err)
cb(null, nextRelease)
})
})
cb(null, pkg.version)
}))
}))
}
+30
View File
@@ -0,0 +1,30 @@
'use strict'
var spawn = require('child_process').spawn
var exports = module.exports = function (cb) {
// npm loads package.json data before running the `prepublish` hook
// changing the version on `prepublish` has no effect
// see https://github.com/npm/npm/issues/7118
// to circumvent this behavior we are calling `npm publish` inside `prepublish`
// the package.json is then loaded again and the correct version will be published
var child = spawn('npm', ['publish', '--semantic-release-rerun'])
var handler = exports.handleCloseAndExit.bind(null, cb)
child.stdout.pipe(process.stdout)
child.stderr.pipe(process.stderr)
child.on('close', handler)
child.on('exit', handler)
child.on('error', cb)
}
exports.handleCloseAndExit = function (cb, code, signal) {
if (code === 0) return cb(null)
cb({
code: code,
signal: signal,
message: 'npm publish failed'
})
}
-12
View File
@@ -1,12 +0,0 @@
var nixt = require('nixt')
module.exports = function (cwd, uri) {
return nixt()
.cwd(cwd)
.env('NPM_OLD_TOKEN', 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==')
.env('NPM_EMAIL', 'integration@test.com')
.env('GH_TOKEN', 'ghtoken')
.env('CI', 'true')
.env('npm_config_registry', uri)
.clone()
}
-36
View File
@@ -1,36 +0,0 @@
var exec = require('child_process').exec
var join = require('path').join
var writeFileSync = require('fs').writeFileSync
var mkdirp = require('mkdirp')
module.exports = function (name, registry, cb) {
var cwd = join(__dirname, '../tmp', name)
mkdirp.sync(cwd)
writeFileSync(join(cwd, 'package.json'), JSON.stringify({
name: name,
repository: {
url: 'git+https://github.com/semantic-release/test'
},
release: {
verifyConditions: '../../../src/lib/plugin-noop'
}
}, null, 2))
exec(
'git init && ' +
'git config user.email "integration@test" && ' +
'git config user.name "Integration Test" && ' +
'git add . && ' +
'git commit -m "chore: root"'
, {cwd: cwd}, function (err, stdout, stderr) {
if (err) {
console.log(stdout, stderr)
return cb(err)
}
cb(null, cwd)
})
}
-30
View File
@@ -1,30 +0,0 @@
var mockSpawn = require('mock-spawn')()
mockSpawn.setStrategy(function (command, args, opts) {
return function (cb) {
this.stdout.write(
/\.\.HEAD/.test(args.join(' '))
? rawCommits[0]
: rawCommits.join()
)
cb(0)
}
})
const rawCommits = [
'hash-one==SPLIT==commit-one==END==\n',
'hash-two==SPLIT==commit-two==END==\n'
]
module.exports = {
exec: function (command, options, cb) {
if (typeof cb === 'undefined' && typeof options === 'function') {
cb = options
}
if (/contains/.test(command)) {
if (/notinhistory/.test(command)) return cb(new Error())
return cb(null, 'whatever\nmaster\n')
}
},
spawn: mockSpawn,
'@noCallThru': true
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (cb) {
cb(null, 'bar')
}
-12
View File
@@ -1,12 +0,0 @@
module.exports = function () {
return {
authenticate: function () {
return true
},
repos: {
createRelease: function (release, cb) {
cb(null)
}
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (config, options, cb) {
cb(new Error('a'))
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (config, options, cb) {
cb(new Error('b'))
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (config, options, cb) {
cb(null, 'a')
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (config, options, cb) {
cb(null, 'b')
}
-28
View File
@@ -1,28 +0,0 @@
const nock = require('nock')
const availableModule = {
'dist-tags': {
latest: '1.33.7',
foo: '0.8.15'
},
versions: {
'0.8.15': {
gitHead: 'bar'
},
'1.33.7': {
gitHead: 'HEAD'
}
}
}
module.exports = nock('http://registry.npmjs.org')
.get('/available')
.reply(200, availableModule)
.get('/tagged')
.reply(200, availableModule)
.get('/untagged')
.reply(200, availableModule)
.get('/@scoped%2Favailable')
.reply(200, availableModule)
.get('/unavailable')
.reply(404, {})
-11
View File
@@ -1,11 +0,0 @@
var exec = require('child_process').exec
var opts = {
cwd: __dirname
}
module.exports = {
start: exec.bind(null, './start.sh', opts),
stop: exec.bind(null, './stop.sh', opts),
uri: 'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/'
}
-19
View File
@@ -1,19 +0,0 @@
[couchdb]
database_dir = data
view_index_dir = data
delayed_commits = false
[couch_httpd_auth]
public_fields = appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev
users_db_public = true
[httpd]
port = 15986
bind_address = 127.0.0.1
secure_rewrites = false
[log]
file = couch/couch.log
[admins]
admin = -pbkdf2-89582b49cd2e8443e29a841f5a76d367956a8e58,1afa2f1531a17ac97f2ac9e334293753,10
-41
View File
@@ -1,41 +0,0 @@
#!/bin/bash
# exit if an error occurs
set -e
cd $(dirname $0)
mkdir -p couch
if [[ $TRAVIS = true ]]
then
COUCH=http://admin:password@127.0.0.1:5984
curl -X PUT http://127.0.0.1:5984/_config/admins/admin -d '"password"'
curl -X PUT $COUCH/_config/couchdb/delayed_commits -d '"false"'
curl -X PUT $COUCH/_config/couch_httpd_auth/users_db_public -d '"true"'
curl -X PUT $COUCH/_config/couch_httpd_auth/public_fields -d '"appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev"'
curl -X PUT $COUCH/_config/httpd/secure_rewrites -d '"false"'
else
COUCH=http://admin:password@127.0.0.1:15986
couchdb -b -a local.ini -p couch/pid -o couch/stdout.log -e couch/stderr.log
# wait for couch to start
sleep 1
fi
# create "registry" database
curl -X PUT $COUCH/registry
# create sample npm user
curl -X PUT $COUCH/_users/org.couchdb.user:integration -H Content-Type:application/json --data-binary '{"_id": "org.couchdb.user:integration", "name": "integration", "roles": [], "type": "user", "password": "suchsecure", "email": "integration@test.com"}'
# npm-registry-couchpp needs this variable set to run
export DEPLOY_VERSION=nope
# setup npm-registry-couchapp
npm explore npm-registry-couchapp -- npm start --npm-registry-couchapp:couch=$COUCH/registry
npm explore npm-registry-couchapp -- npm run load --npm-registry-couchapp:couch=$COUCH/registry
export NO_PROMPT=yes
npm explore npm-registry-couchapp -- npm run copy --npm-registry-couchapp:couch=$COUCH/registry
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
# close couchdb background process
couchdb -d
# delete data and logs
cd $(dirname $0)
cat couch/{couch,stdout,stderr}.log
cat couch/pid | xargs kill
rm -rf couch
rm -rf data
-96
View File
@@ -1,96 +0,0 @@
var join = require('path').join
var tap = require('tap')
var rimraf = require('rimraf')
var registry = require('../registry')
var testModule = require('../lib/test-module')
var baseScenario = require('../lib/base-scenario')
var tearDown = tap.tearDown
var test = tap.test
test('change version', {bail: process.env.TRAVIS === 'true'}, function (t) {
t.plan(7)
registry.start(function (err, stdout, stderr) {
t.error(err, 'registry started')
if (err) return t.end()
testModule('change-version', registry.uri, function (err, cwd) {
t.error(err, 'test-module created')
if (err) return t.end()
t.test('no version', function (tt) {
tt.plan(1)
baseScenario(cwd, registry.uri)
.env('npm_config_loglevel', 'info')
.run('node ../../../bin/semantic-release.js pre')
.stderr(/ENOCHANGE/)
.code(1)
.end(tt.error)
})
t.test('initial version', function (tt) {
tt.plan(1)
baseScenario(cwd, registry.uri)
.exec('git commit -m "feat: initial" --allow-empty')
.exec('node ../../../bin/semantic-release.js pre')
.run('npm publish')
.stdout(/1\.0\.0/)
.code(0)
.end(tt.error)
})
t.test('patch version', function (tt) {
tt.plan(1)
baseScenario(cwd, registry.uri)
.exec('git commit -m "fix: foo" --allow-empty')
.exec('node ../../../bin/semantic-release.js pre')
.run('npm publish')
.stdout(/1\.0\.1/)
.code(0)
.end(tt.error)
})
t.test('feature version', function (tt) {
tt.plan(1)
baseScenario(cwd, registry.uri)
.exec('git commit -m "feat: foo" --allow-empty')
.exec('node ../../../bin/semantic-release.js pre')
.run('npm publish')
.code(0)
.stdout(/1\.1\.0/)
.end(tt.error)
})
t.test('breaking version', function (tt) {
tt.plan(1)
baseScenario(cwd, registry.uri)
.exec('git commit -m "feat: foo\n\n BREAKING CHANGE: bar" --allow-empty')
.exec('node ../../../bin/semantic-release.js pre')
.run('npm publish')
.code(0)
.stdout(/2\.0\.0/)
.end(tt.error)
})
})
})
})
tearDown(function () {
if (process.env.TRAVIS === 'true') return
function cb (err, stdout, stderr) {
if (err) console.log(err)
if (stderr) console.log(stderr)
}
rimraf(join(__dirname, '../tmp'), cb)
registry.stop(cb)
})
-43
View File
@@ -1,43 +0,0 @@
var test = require('tap').test
var proxyquire = require('proxyquire')
var commits = proxyquire('../../src/lib/commits', {
'npmlog': {
error: function () {}
},
'child_process': require('../mocks/child-process')
})
test('commits since last release', function (t) {
t.test('get all commits', function (tt) {
commits({lastRelease: {}, options: {branch: 'master'}}, function (err, commits) {
tt.error(err)
tt.is(commits.length, 2, 'all commits')
tt.is(commits[0].hash, 'hash-one', 'parsed hash')
tt.is(commits[1].message, 'commit-two', 'parsed message')
tt.end()
})
})
t.test('get commits since hash', function (tt) {
commits({lastRelease: {gitHead: 'hash'}, options: {branch: 'master'}}, function (err, commits) {
tt.error(err)
tt.is(commits.length, 1, 'specified commits')
tt.is(commits[0].hash, 'hash-one', 'parsed hash')
tt.is(commits[0].message, 'commit-one', 'parsed message')
tt.end()
})
})
t.test('get commits since hash', function (tt) {
commits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'notmaster'}}, function (err, commits) {
tt.ok(err)
tt.is(err.code, 'ENOTINHISTORY')
tt.end()
})
})
t.end()
})
-44
View File
@@ -1,44 +0,0 @@
const test = require('tap').test
const getRegistry = require('../../src/lib/get-registry')
test('get correct registry', function (t) {
t.is(getRegistry({
name: 'publish-config',
publishConfig: {
registry: 'a'
}},
{}), 'a')
t.is(getRegistry({name: 'normal'}, {
get: function () {
return 'b'
}
}), 'b')
t.is(getRegistry({name: 'normal'}, {
get: function () {
return null
}
}), 'https://registry.npmjs.org/')
t.is(getRegistry({name: '@scoped/foo'}, {
get: function (input) {
return input === '@scoped/registry' ? 'c' : 'd'
}
}), 'c')
t.is(getRegistry({name: '@scoped/bar'}, {
get: function () {
return 'e'
}
}), 'e')
t.is(getRegistry({name: '@scoped/baz'}, {
get: function () {
return null
}
}), 'https://registry.npmjs.org/')
t.end()
})
-97
View File
@@ -1,97 +0,0 @@
var test = require('tap').test
var plugins = require('../../src/lib/plugins')
test('export plugins', function (t) {
t.plan(5)
var defaultPlugins = plugins({})
t.is(typeof defaultPlugins.analyzeCommits, 'function')
t.is(typeof defaultPlugins.generateNotes, 'function')
t.is(typeof defaultPlugins.verifyConditions, 'function')
t.is(typeof defaultPlugins.verifyRelease, 'function')
t.is(typeof defaultPlugins.getLastRelease, 'function')
})
test('plugin pipelines', function (t) {
t.plan(3)
t.test('get all results', function (tt) {
var pipelinePlugins = plugins({
verifyRelease: [
'./src/lib/plugin-noop',
'./test/mocks/plugin-result-a',
'./test/mocks/plugin-result-b'
]
})
pipelinePlugins.verifyRelease({}, function (err, results) {
tt.error(err)
tt.same(results, [undefined, 'a', 'b'])
tt.end()
})
})
t.test('get first error', function (tt) {
var pipelinePlugins = plugins({
verifyConditions: [
'./src/lib/plugin-noop',
'./test/mocks/plugin-error-a',
'./test/mocks/plugin-error-b'
]
})
pipelinePlugins.verifyConditions({}, function (err) {
tt.is(err.message, 'a')
tt.end()
})
})
t.test('get error and only results before', function (tt) {
var pipelinePlugins = plugins({
verifyRelease: [
'./src/lib/plugin-noop',
'./test/mocks/plugin-result-a',
'./test/mocks/plugin-error-b',
'./test/mocks/plugin-result-b'
]
})
pipelinePlugins.verifyRelease({}, function (err, results) {
tt.is(err.message, 'b')
tt.same(results, [undefined, 'a', undefined])
tt.end()
})
})
})
test('normalize and load plugin', function (t) {
t.test('load from string', function (tt) {
var plugin = plugins.normalize('./src/lib/plugin-noop')
tt.is(typeof plugin, 'function')
tt.end()
})
t.test('load from object', function (tt) {
var plugin = plugins.normalize({
path: './src/lib/plugin-noop'
})
tt.is(typeof plugin, 'function')
tt.end()
})
t.test('load from fallback', function (tt) {
var plugin = plugins.normalize(null, '../../src/lib/plugin-noop')
tt.is(typeof plugin, 'function')
tt.end()
})
t.end()
})
-74
View File
@@ -1,74 +0,0 @@
var defaults = require('lodash').defaults
var test = require('tap').test
var proxyquire = require('proxyquire')
var post = proxyquire('../../src/post', {
'git-head': require('../mocks/git-head'),
github: require('../mocks/github')
})
var pkg = {
version: '1.0.0',
repository: {url: 'http://github.com/whats/up.git'}
}
var plugins = {
generateNotes: function (pkg, cb) {
cb(null, 'the log')
}
}
var defaultRelease = {
owner: 'whats',
repo: 'up',
name: 'v1.0.0',
tag_name: 'v1.0.0',
target_commitish: 'bar',
body: 'the log'
}
test('full post run', function (t) {
t.test('in debug mode w/o token', function (tt) {
post({
options: {debug: true},
pkg: pkg,
plugins: plugins
}, function (err, published, release) {
tt.error(err)
tt.is(published, false)
tt.match(release, defaults({draft: true}, defaultRelease))
tt.end()
})
})
t.test('in debug mode w/token', function (tt) {
post({
options: {debug: true, githubToken: 'yo'},
pkg: pkg,
plugins: plugins
}, function (err, published, release) {
tt.error(err)
tt.is(published, true)
tt.match(release, defaults({draft: true}, defaultRelease))
tt.end()
})
})
t.test('production', function (tt) {
post({
options: {githubToken: 'yo'},
pkg: pkg,
plugins: plugins
}, function (err, published, release) {
tt.error(err)
tt.is(published, true)
tt.match(release, defaultRelease)
tt.end()
})
})
t.end()
})
-64
View File
@@ -1,64 +0,0 @@
var test = require('tap').test
var proxyquire = require('proxyquire')
require('../mocks/registry')
var pre = proxyquire('../../src/pre', {
'./lib/commits': proxyquire('../../src/lib/commits', {
'child_process': require('../mocks/child-process')
})
})
var versions = {
available: '1.0.0'
}
var plugins = {
verifyRelease: function (release, cb) {
cb(null, release)
},
analyzeCommits: function (commits, cb) {
cb(null, 'major')
},
getLastRelease: function (config, cb) {
cb(null, {version: versions[config.pkg.name] || null, gitHead: 'HEAD'})
}
}
var npm = {
registry: 'http://registry.npmjs.org/',
tag: 'latest'
}
test('full pre run', function (t) {
t.test('increase version', function (tt) {
tt.plan(3)
pre({
options: {branch: 'master'},
npm: npm,
pkg: {name: 'available'},
plugins: plugins
}, function (err, release) {
tt.error(err)
tt.is(release.type, 'major')
tt.is(release.version, '2.0.0')
})
})
t.test('increase version', function (tt) {
tt.plan(3)
pre({
options: {branch: 'master'},
npm: npm,
pkg: {name: 'unavailable'},
plugins: plugins
}, function (err, release) {
tt.error(err)
tt.is(release.type, 'initial')
tt.is(release.version, '1.0.0')
})
})
t.end()
})
-60
View File
@@ -1,60 +0,0 @@
var test = require('tap').test
var type = require('../../src/lib/type')
test('get type from commits', function (t) {
t.test('get type from plugin', function (tt) {
tt.plan(2)
type({
commits: [{
hash: '0',
message: 'a'
}],
lastRelease: {version: '1.0.0'},
plugins: {
analyzeCommits: function (config, cb) {
cb(null, 'major')
}
}
}, function (err, type) {
tt.error(err)
tt.is(type, 'major')
})
})
t.test('error when no changes', function (tt) {
tt.plan(1)
type({
commits: [],
lastRelease: {},
plugins: {
analyzeCommits: function (config, cb) {
cb(null, null)
}
}
}, function (err) {
tt.is(err.code, 'ENOCHANGE')
})
})
t.test('initial version', function (tt) {
tt.plan(2)
type({
commits: [],
lastRelease: {},
plugins: {
analyzeCommits: function (config, cb) {
cb(null, 'major')
}
}
}, function (err, type) {
tt.error(err)
tt.is(type, 'initial')
})
})
t.end()
})
-73
View File
@@ -1,73 +0,0 @@
var test = require('tap').test
var verify = require('../../src/lib/verify')
test('verify pkg, options and env', function (t) {
t.test('dry run verification', function (tt) {
var noErrors = verify({
options: {debug: true},
pkg: {
name: 'package',
repository: {
url: 'http://github.com/whats/up.git'
}
}
})
tt.is(noErrors.length, 0)
var errors = verify({
options: {debug: true},
pkg: {}
})
tt.is(errors.length, 2)
tt.is(errors[0].code, 'ENOPKGNAME')
tt.is(errors[1].code, 'ENOPKGREPO')
tt.end()
})
t.test('dry run verification for gitlab repo', function (tt) {
var noErrors = verify({
options: {debug: true},
pkg: {
name: 'package',
repository: {
url: 'http://gitlab.corp.com/whats/up.git'
}
}
})
console.log(noErrors)
tt.is(noErrors.length, 0)
tt.end()
})
t.test('publish verification', function (tt) {
var noErrors = verify({
env: {NPM_TOKEN: 'yo'},
options: {githubToken: 'sup'},
pkg: {
name: 'package',
repository: {
url: 'http://github.com/whats/up.git'
}
}
})
tt.is(noErrors.length, 0)
var errors = verify({env: {}, options: {}, pkg: {}})
tt.is(errors.length, 4)
tt.is(errors[0].code, 'ENOPKGNAME')
tt.is(errors[1].code, 'ENOPKGREPO')
tt.is(errors[2].code, 'ENOGHTOKEN')
tt.is(errors[3].code, 'ENONPMTOKEN')
tt.end()
})
t.end()
})
+1
View File
@@ -0,0 +1 @@
test:{SHA}C+7Hteo/D9vJXQ3UfzxbwnXaijM=
+3
View File
@@ -0,0 +1,3 @@
_auth=dGVzdDpmb28=
email=test@example.com
registry=http://127.0.0.1:4873/
+17
View File
@@ -0,0 +1,17 @@
storage: ../.tmp/.storage
auth:
htpasswd:
file: ./.htpasswd
uplinks:
npmjs:
url: https://registry.npmjs.org/
logs:
- {type: stdout, format: pretty, level: silent}
packages:
'*':
allow_access: all
allow_publish: all
+10
View File
@@ -0,0 +1,10 @@
'use strict'
var test = require('tape')
var createModule = require('./lib/create-module')
require('./scenarios/install')(test, createModule)
require('./scenarios/prepublish')(test, createModule)
require('./scenarios/postpublish')(test, createModule)
require('./scenarios/publish')(test, createModule)
+45
View File
@@ -0,0 +1,45 @@
'use strict'
var exec = require('child_process').exec
var join = require('path').join
var efh = require('error-first-handler')
var defaults = require('lodash.defaults')
var uid = require('nano-uid')()
module.exports = function (input) {
var cb = Array.prototype.pop.call(arguments)
uid.generate(5, efh(cb)(function (id) {
var pkg = defaults((typeof input === 'object' ? input : {}), {
name: id,
version: '0.0.0',
devDependencies: {
'semantic-release': 'file:../../../'
},
scripts: {
prepublish: 'semantic-release pre',
postpublish: 'semantic-release post'
},
publishConfig: {
registry: 'http://localhost:4873/'
}
})
id = pkg.name
var cwd = join(__dirname, '../../.tmp/modules', id)
exec(
'mkdir ' + cwd + ' && ' +
'cd ' + cwd + ' && ' +
'git init && ' +
'echo \'' + JSON.stringify(pkg, null, 2) + '\' >> package.json && ' +
'git add . && ' +
'git config user.email "integration@test" && ' +
'git config user.name "Integration Test" && ' +
'git commit -m "initial" && ' +
'npm install'
, efh(cb)(function (stdout) {
cb(null, id, cwd)
}))
}))
}
+33
View File
@@ -0,0 +1,33 @@
'use strict'
var fs = require('fs')
var efh = require('error-first-handler')
var nixt = require('nixt')
module.exports = function (test, createModule) {
createModule(efh()(function (name, cwd) {
test('install', function (t) {
installTest(t, 'npm install', 'not doing anything when the module is installed')
installTest(t, 'npm i', 'not doing anything when the module is installed with abbrevd command')
})
function installTest (t, command, name, last) {
t.test(name, function (t) {
t.plan(2)
var pkg = fs.readFileSync(cwd + '/package.json')
nixt()
.cwd(cwd)
.run(command)
.code(0)
.stdout(/> semantic-release pre\n$/m)
.end(function(err) {
t.is(pkg + '', fs.readFileSync(cwd + '/package.json') + '', 'package')
t.error(err, 'nixt')
})
})
}
}))
}
+57
View File
@@ -0,0 +1,57 @@
'use strict'
var efh = require('error-first-handler')
var GitHubApi = require('github')
var nixt = require('nixt')
var github = new GitHubApi({
version: '3.0.0',
port: 4343,
protocol: 'http',
host: '127.0.0.1'
})
github.authenticate({
type: 'oauth',
token: '***'
})
module.exports = function (test, createModule) {
createModule({
version: '2.0.0',
repository: {
type: 'git',
url: 'http://github.com/user/repo'
}
}, efh()(function (name, cwd) {
test('postpublish', function (t) {
t.test('publish new version to github releases', function (t) {
t.plan(1)
nixt()
.cwd(cwd)
.env('CI', true)
.env('GH_URL', 'http://127.0.0.1:4343/')
.env('GH_TOKEN', '***')
.exec('git commit --allow-empty -m "feat(cool): the next big thing"')
.run('npm run postpublish')
.code(0)
.stdout(/> semantic-release post\n\nGenerating changelog from.*\nParsed/m)
.end(function(err) {
t.error(err, 'nixt')
})
})
t.test('correct data published', function (t) {
t.plan(4)
github.releases.getRelease({ owner: 'user', repo: 'repo', id: 1}, function (err, res) {
t.error(err, 'github')
t.is(res.tag_name, 'v2.0.0', 'version')
t.is(res.author.login, 'user', 'user')
t.ok(/\n\n\n#### Features\n\n\* \*\*cool:\*\* the next big thing/.test(res.body), 'body')
})
})
})
}))
}
+50
View File
@@ -0,0 +1,50 @@
'use strict'
var fs = require('fs')
var exec = require('child_process').exec
var efh = require('error-first-handler')
var nixt = require('nixt')
module.exports = function (test, createModule) {
createModule(efh()(function (name, cwd) {
test('prepublish', function (t) {
prepublishTest(t, 'refactor: change', '0.0.0', 1, 'abort publish w/o changes')
prepublishTest(t, 'fix: change', '1.0.0', 0, 'release 1.0.0 if unpublished')
prepublishTest(t, 'fix: change', '1.0.1', 0, 'bump patch for fix')
prepublishTest(t, 'feat: change', '1.1.0', 0, 'bump minor for feature')
prepublishTest(t, 'fix: BREAKING CHANGE: change', '2.0.0', 0, 'bump major for breaking change')
})
function prepublishTest (t, message, version, code, name) {
t.test(name, function (t) {
t.plan(3)
nixt()
.cwd(cwd)
.env('CI', true)
.env('npm_config_registry', 'http://127.0.0.1:4873/')
.exec('git commit --allow-empty -m "' + message + '"')
.run('npm run prepublish')
.code(code)
.stdout(/> semantic-release pre\n\nDetermining new version\n/m)
.end(function(err) {
t.error(err, 'nixt')
var pkg = JSON.parse(fs.readFileSync(cwd + '/package.json'))
t.is(pkg.version, version, 'version')
if (code === 1) {
return t.error(null, 'no publish')
}
exec('npm publish --ignore-scripts', {cwd: cwd}, function(err) {
setTimeout(function() {
t.error(err, 'publish')
}, 300)
})
})
})
}
}))
}
+37
View File
@@ -0,0 +1,37 @@
'use strict'
var efh = require('error-first-handler')
var nixt = require('nixt')
module.exports = function (test, createModule) {
test('publish', function (t) {
publishTest(t, 'npm publish', 'pre and post hooks work as a part of publish')
publishTest(t, 'npm pub', 'pre and post hooks work as a part of publish with abbrevd command')
function publishTest (t, command, testname, last) {
createModule({
repository: {
type: 'git',
url: 'http://github.com/user/repo'
}
}, efh()(function (name, cwd) {
t.test(testname, function (t) {
t.plan(1)
nixt()
.cwd(cwd)
.env('CI', true)
.env('GH_URL', 'http://127.0.0.1:4343/')
.env('GH_TOKEN', '***')
.exec('git commit --allow-empty -m "feat: super"')
.run(command)
.code(1)
.stdout(/Everything is alright/)
.end(function(err) {
t.error(err, 'nixt')
})
})
}))
}
})
}