Compare commits

..
65 Commits
Author SHA1 Message Date
Stephan Bönnemann bf69c1ae93 fix(post): revert to execing git commands
`git-node` is nice in theory, but actually unstable and inconsistent.
2015-02-08 19:14:32 +01:00
Stephan Bönnemann 486ebed9f3 test(standard): ensure cli is tested by standard 2015-02-08 17:58:04 +01:00
Stephan Bönnemann 918e2c08a0 fix(cli): remove unused require call 2015-02-08 17:48:56 +01:00
Stephan Bönnemann a5e50e0d3d chore(travis): npm prune before running tests 2015-02-08 17:48:39 +01:00
Stephan Bönnemann 36f8643f3e docs(readme): add travis encrypt commands 2015-02-08 17:03:36 +01:00
Stephan Bönnemann c7862b8d0d docs(readme): update setup section 2015-02-08 17:03:36 +01:00
Stephan Bönnemann fee6d6baba feat(setup): setup package.json 2015-02-08 17:03:36 +01:00
Stephan Bönnemann 44eb633458 test(setup): setup package.json 2015-02-08 17:03:35 +01:00
Stephan Bönnemann e92bd610bb feat(verify): setup requirement verification 2015-02-08 17:03:35 +01:00
Stephan Bönnemann 5f96cd4589 test(verify): setup requirement verification 2015-02-08 14:19:22 +01:00
Stephan Bönnemann 6f7b382130 docs(readme): show only build status of master 2015-02-08 14:19:21 +01:00
Stephan Bönnemann ed39847510 docs(readme): add link to where you can get a github token 2015-02-08 14:19:21 +01:00
Stephan Bönnemann 7b170b1167 docs(readme): add synopsis of commit message conventions 2015-02-08 14:19:21 +01:00
Stephan Bönnemann c957308716 docs(readme): clarifications for ci and non-ci environments 2015-02-08 14:19:21 +01:00
Stephan Bönnemann 54fe414bab fix(cli): prepublish is executed on link; does nothing then 2015-02-08 14:19:21 +01:00
Stephan Bönnemann e0fc4840de fix(cli): automatically run in debug mode locally
BREAKING CHANGE: The `--no-ci` flag is gone. If you want to run `semantic-release` locally now you have to explicitly run `semantic-release pre --debug=false` && `semantic-release post --debug=false`
2015-02-08 14:19:21 +01:00
Stephan Bönnemann d94c6b76d3 chore(travis): actually require that all minions pass
I'm so good at bash, I'm using string concatenation for this.
2015-02-08 10:31:09 +01:00
Stephan Bönnemann be249fa0e0 chore(package): improve description and keywords 2015-02-08 10:30:35 +01:00
Stephan Bönnemann f5c6c9622a chore(package): clarify that the version is a placeholder 2015-02-08 00:40:01 +01:00
Stephan Bönnemann b309732953 docs(readme): add faq and clarify requirements 2015-02-08 00:35:55 +01:00
Stephan Bönnemann 6ef503001f docs(readme): typos, clarity and formating 2015-02-07 14:21:31 +01:00
Stephan Bönnemann 5610fafb25 chore(travis): also test on node 0.12 2015-02-07 12:26:39 +01:00
Stephan Bönnemann a4dc9c3ef9 docs(readme): verbose instructions and explanations 2015-02-07 12:21:53 +01:00
Stephan Bönnemann 466be0eb7e chore(travis): set up travis_after_all to deploy once 2015-02-05 23:49:00 +01:00
Stephan Bönnemann 7bf436fe8a chore(travis): add iojs to build matrix 2015-02-05 23:34:26 +01:00
Christoph Witzko 4ed5f73a04 chore: updated github-release-fake-server 2015-02-05 14:55:40 +01:00
Stephan Bönnemann 782c992755 Merge pull request #4 from boennemann/refactor-exec
Refactor exec
2015-02-05 13:14:42 +01:00
Christoph Witzko 5d8f6561a8 refactor: use git-node instead of exec git 2015-02-05 13:09:14 +01:00
Christoph Witzko 96fe49662a refactor: use request instead of exec curl 2015-02-05 12:39:20 +01:00
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
59 changed files with 916 additions and 1440 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
-11
View File
@@ -1,14 +1,3 @@
# common
coverage
node_modules
test/registry/couch
test/registry/data
*.log
*.dump
.DS_Store
.nyc_output
.test
.tmp
# build-artifacts
dist
-18
View File
@@ -1,18 +0,0 @@
# common
coverage
node_modules
test/registry/couch
test/registry/data
*.log
*.dump
.DS_Store
.nyc_output
.test
.tmp
# source/config
src
*.yml
.gitignore
.editorconfig
.npmrc
+23 -9
View File
@@ -1,22 +1,36 @@
sudo: false
language: node_js
node_js:
- iojs-v1
- '0.10'
- '0.12'
sudo: false
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- iojs-v2
- iojs-v1
- '0.12'
- '0.10'
before_install:
- npm i -g npm@^2.0.0
before_script:
- npm prune
- curl -Lo travis_after_all.py https://git.io/vLSON
- curl -Lo travis_after_all.py https://raw.github.com/dmakhno/travis_after_all/master/travis_after_all.py
after_success:
- python travis_after_all.py
- export $(cat .to_export_back)
- npm run coverage:upload
- npm run semantic-release
after_failure:
- python travis_after_all.py
- export $(cat .to_export_back)
before_deploy:
- rm -f travis_after_all.py .to_export_back
after_script:
- echo leader=$BUILD_LEADER status=$BUILD_AGGREGATE_STATUS
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
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
+152 -196
View File
@@ -1,251 +1,207 @@
# semantic-release
> **fully automated, semver-compliant package publishing**
[![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)
[![Dependency Status](https://david-dm.org/semantic-release/semantic-release/badger.svg)](https://david-dm.org/semantic-release/semantic-release/badger)
[![devDependency Status](https://david-dm.org/semantic-release/semantic-release/badger/dev-status.svg)](https://david-dm.org/semantic-release/semantic-release/badger#info=devDependencies)
[![Build Status](https://travis-ci.org/semantic-release/semantic-release.svg?branch=badger)](https://travis-ci.org/semantic-release/semantic-release)
[![Coverage Status](https://coveralls.io/repos/semantic-release/semantic-release/badge.svg?branch=badger&service=github)](https://coveralls.io/github/semantic-release/semantic-release?branch=badger)
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** | <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** | <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>
“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 this then possible to generate a helpful changelog and to derive the next semantic version number from them.
_Note: 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_.
> ### Commit Message Format
> 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**:
> ```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
> [Full explanation](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md)
When `semantic-release` got 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. That 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/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, 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.
## Setup
[![Build Status](https://travis-ci.org/boennemann/semantic-release.svg?branch=master)](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
## What is this thing even?
cd your-module
semantic-release-cli setup
`semantic-release` is a toolset to fully automate your package's releases. This will determine not only which version to release, but also when all without you having to care about it ever again.
This is fully integrated with the `npm` lifecycle, so all you have to do is to setup your CI to `npm publish`.
The goal of this package is to remove humans from version numbers and releases. The [SemVer](http://semver.org/) spec clearly and unambiguously defines when to increase the major, minor or patch part and still we tend to think we're clever when we ignore this, because marketing or something.
![https://twitter.com/trodrigues/status/509301317467373571](https://cloud.githubusercontent.com/assets/908178/6091690/cc86f58c-aeb8-11e4-94cb-15f15f486cde.png)
## How does this work?
Conventions, conventions, conventions. Instead of dumping [funny lols](http://whatthecommit.com/) into our commit messages, we can take some time to think about what we changed in the codebase and write it down. Following formalized conventions it this then possible to not only generate a meaningful changelog, but to determine the next semantic version to release. Currently the only supported style is the [AngularJS Commit Message Convention](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit) style, but feel free to formalize your own style, write a parser for it, and send a PR to this package.
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
```
![dialogue](https://cloud.githubusercontent.com/assets/908178/9428123/3628dfec-499f-11e5-8bdd-8f3042dd95ed.png)
Examples:
_[This is what happens under the hood.](https://github.com/semantic-release/cli#manual-setup)_
```
feat(ruler): add inches as well as centimeters
```
## Options
```
fix(protractor): fix 90 degrees counting as 91 degrees
```
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.
```
fix(pen): use blue ink instead of red ink
| 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> |
BREAKING CHANGE: Pen now uses blue ink instead of red.
To migrate, change your code from the following:
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.
`pen.draw('blue')`
_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.
To:
2. In order to publish to a different `npm` registry you can just configure that how you would usually do it and `semantic-release` will respect that setting.
`pen.draw('red')`
```
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.
[Synopsis](https://github.com/ajoslin/conventional-changelog/blob/master/CONVENTIONS.md)
4. `semantic-release` generally tries to orientate itself towards `npm` it inherits the loglevel for example.
The preferred configuration is the "try to release on every push" mode. What it does is that everytime a build passes `npm publish` is executed.
## Plugins
### The `prepublish` step
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`:
Before `npm` actually gets to publish a new version `semantic-release`'s `prepublish` step does the following:
- Analyze the commits since the last version was published
- Decide on the release type (`major`|`minor`|`patch`) or abort if nothing changed
- Get the last published version from the registry
- Increase the last version with the determined type
- Write the new version to the package
### The `publish` step
`npm` does its thing.
### The `postpublish` step
After `npm` published the new version the `postpublish` step does this:
- Generate a changelog
- Create a new [GitHub Release](https://help.github.com/articles/about-releases/) with the changelog as body
Note: The GitHub Release automatically creates a tag, too.
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).
## How do I set this up?
First of all you need to install `semantic-release` and save it as a `devDependency`.
### Installation
```bash
npm i -D semantic-release
```
### Package
```bash
./node_modules/.bin/semantic-release setup
```
What this does:
#### Scripts
The setup command configures `scripts` inside the `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"
Note: If you have already configured `scripts` for `prepublish` or `postpublish` they're just executed one after another. For example: `"npm run 6to5 && semantic-release pre"`.
#### Version
It would be preferable not to have a version field in the `package.json` at all, but due to an `npm` limitation it is required to have a _not yet published_ version in there [npm/npm#7118](https://github.com/npm/npm/issues/7118). Because of this the version gets changed to `"0.0.0-semantically-released"` until `npm` _hopefully_ removes its limitations.
#### 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.
### CI Server
Inside your `.travis.yml`:
```yml
language: node_js
node_js:
- iojs-v1
sudo: false
cache:
directories:
- node_modules
notifications:
email: false
env:
# Get your token here: https://github.com/settings/tokens/new
# You should encrypt this:
# `travis encrypt GH_TOKEN=<token> --add`
global: GH_TOKEN=<github-access-token-with-acceess-to-your-repo>
deploy:
provider: npm
email: <your-npm-mail@example.com>
# Very important. Don't forget this one.
skip_cleanup: true
# Travis currently only supports the old auth key format.
# Do `echo -n "<username>:<password>" | base64` to get it.
# You should encrypt this:
# `travis encrypt $(echo -n "<username>:<password>" | base64) --add deploy.api_key`
api_key: <npm-api-key>
on:
branch: master
repo: <user>/<repo>
```
A plugin itself is an async function that always receives three arguments.
Note: For once this isn't tied to a specific service, but example configuration is shown for [Travis CI](https://travis-ci.org/). Feel free to contribute configuration of other servers or services.
```js
module.exports = function (pluginConfig, config, callback) {}
```
Note: You should [encrypt](http://docs.travis-ci.com/user/environment-variables/#sts=Secure Variables) your api keys and tokens.
- `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.
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.
### `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. 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.
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).
## ITYM*FAQ*LT
> I think you might frequently ask questions like these
### Why is the `package.json`s version not updated in my repository?
### 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)
> The most important things in your package.json are the name and version fields. Those are actually required, and your package won't 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.
While this entirely true the version number doesn't 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 when 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.
If you're running `npm publish` locally `semantic-release` automatically performs a dry run. This does log the version that would currently get published, but only if you `git fetch --tags` before.
### 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>`.
Of course you can, but this doesn't 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 GitHub tokens lying around and everything. That said, you can either set the environment variable `CI=true`, or run the scripts with `--debug=false` explicitly. Don't forget to export `GH_TOKEN=your_token` as well.
### 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.
You can trigger a release by pushing to your repository. You deliberately can not trigger a _specific_ version release, because this is the whole point of `semantic-release`. Start your packages with `1.0.0` and semver on.
Note: pre-release flags are kind of an exeption here and a solution for them is being thought of. If you have one please open an issue. For the time being: Have a look at the next question.
### How do I get back to good ol' `npm publish`?
`npm` offers the `--no-scripts` flag. Doing `npm publish --no-scripts` doesn't execute the `prepublish` and `postpublish` scripts.
### 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).
It is indeed a great idea because it _forces_ you to follow best practices. If you don't 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, configure your CI server to release only on the `production`/`deploy`/`release` branch and push your code there in certain intervals.
### Why should I trust `semantic-release` with my releases?
### Why should I trust `semantic-release` with my releases? What if it breaks?
`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 node.js `^0.10`, `^0.12` and io.js `^1`, `^2`. A new version wont get published if it doesnt pass on all these engines.
Note: Currently integration-tests dont run on Travis CI. If you know stuff about npm/Travis/Couch: Please help!
## 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)
```
`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.
## License
MIT License
2015 © Stephan Bönnemann and [contributors](https://github.com/semantic-release/semantic-release/graphs/contributors)
![https://twitter.com/trodrigues/status/509301317467373571](https://cloud.githubusercontent.com/assets/908178/6091690/cc86f58c-aeb8-11e4-94cb-15f15f486cde.png)
MIT License
2015 © Stephan Bönnemann and [contributors](https://github.com/boennemann/semantic-release/graphs/contributors)
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
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
set -e
mkdir -p .tmp/modules
export DEBUG=ghrs:cli
./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
+75 -9
View File
@@ -1,13 +1,79 @@
#!/usr/bin/env node
'use strict'
/* istanbul ignore next */
try {
require('../dist')
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
require('babel/register')
require('../src')
} else {
console.log(err)
var abbrev = require('abbrev')
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'
},
booleans: ['debug'],
default: {
debug: !process.env.CI,
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') || isAbbrev(npmArgv, 'link')) process.exit(0)
if (argv.debug) console.log('This is a dry run')
console.log('Determining new version')
var publish = false
if (isAbbrev(npmArgv, 'publish')) publish = true
// require a correct setup during publish
if (publish && !argv.debug && !require('../src/verify')(argv)) process.exit(1)
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)
if (argv.debug) process.exit(1)
require('../src/restart')(efh(function () {
process.exit(1)
}))
}))
}
if (~argv._.indexOf('post')) {
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.')
}
}))
}
if (~argv._.indexOf('setup')) {
require('../src/setup')()
console.log('"package.json" is set up properly. Now configure your CI server.')
console.log('https://github.com/boennemann/semantic-release#ci-server')
}
function isAbbrev (argv, command) {
return argv._.some(Object.prototype.hasOwnProperty.bind(abbrev(command)))
}
+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 request = require('request')
var efh = require('./error').efh
module.exports = function (pkg, cb) {
if (!pkg.name) return cb(new Error('Package must have a name'))
request(process.env.npm_config_registry + pkg.name, efh(cb)(function (response, body) {
var pkg = JSON.parse(body)
if (response.statusCode === 404 || pkg.error) return cb(null, null, true)
cb(null, pkg['dist-tags'].latest)
}))
}
+37 -65
View File
@@ -1,90 +1,62 @@
{
"name": "semantic-release",
"description": "automated semver compliant package publishing",
"author": "Stephan Bönnemann <stephan@boennemann.me> (http://boennemann.me)",
"bin": {
"semantic-release": "./bin/semantic-release.js"
},
"version": "0.0.0-semantically-released",
"author": "Stephan Bönnemann <stephan@boennemann.me>",
"bin": "./bin/semantic-release.js",
"bugs": {
"url": "https://github.com/semantic-release/semantic-release/issues"
},
"engines": {
"node": ">=0.10",
"npm": ">=2"
},
"config": {
"nyc": {
"exclude": [
".test",
"node_modules"
]
}
"url": "https://github.com/boennemann/semantic-release/issues"
},
"dependencies": {
"@semantic-release/commit-analyzer": "^2.0.0",
"@semantic-release/condition-travis": "^4.1.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": "^0.2.4",
"lodash": "^3.9.3",
"nerf-dart": "^1.0.0",
"nopt": "^3.0.3",
"npmconf": "^2.1.2",
"npmlog": "^1.2.1",
"abbrev": "^1.0.5",
"conventional-changelog": "0.0.11",
"error-first-handler": "^1.0.1",
"github": "^0.2.3",
"github-url-from-git": "^1.4.0",
"ini": "^1.3.2",
"minimist": "^1.1.0",
"parse-github-repo-url": "^1.0.0",
"require-relative": "^0.8.7",
"run-auto": "^1.1.2",
"run-series": "^1.1.2",
"semver": "^5.0.1"
"request": "^2.53.0",
"semver": "^4.2.0"
},
"devDependencies": {
"babel": "^5.5.8",
"coveralls": "^2.11.2",
"mkdirp": "^0.5.1",
"github-release-fake-server": "^1.3.0",
"lodash.defaults": "^3.0.0",
"nano-uid": "^0.2.0",
"nixt": "^0.4.1",
"nock": "^2.5.0",
"npm-registry-couchapp": "^2.6.11",
"nyc": "^3.0.0",
"proxyquire": "^1.5.0",
"rimraf": "^2.4.0",
"standard": "^5.0.2",
"tap": "^1.3.1"
"sinopia": "^1.0.0",
"standard": "^2.3.1",
"tap-spec": "^2.2.0",
"tape": "^3.5.0"
},
"homepage": "https://github.com/semantic-release/semantic-release/tree/next#readme",
"engines": {
"iojs": "^1",
"node": "^0.10",
"npm": "^2"
},
"homepage": "https://github.com/boennemann/semantic-release",
"keywords": [
"author",
"automation",
"changelog",
"release",
"publish",
"module",
"package",
"publish",
"release",
"semver",
"version"
"version",
"changelog"
],
"license": "MIT",
"main": "dist/index.js",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/semantic-release/semantic-release.git"
},
"release": {
"branch": "badger"
"url": "https://github.com/boennemann/semantic-release.git"
},
"scripts": {
"build": "rimraf dist && mkdirp dist && babel src --out-dir dist",
"coverage": "nyc report",
"coverage:upload": "npm run coverage -- --reporter=lcovonly && coveralls < coverage/lcov.info",
"prepublish": "npm run build",
"pretest:integration": "npm run build && npm run test:build",
"pretest:unit": "npm run build && npm run test:build",
"semantic-release": "./bin/semantic-release.js pre && npm dedupe && npm publish && ./bin/semantic-release.js post",
"test": "npm run test:style && npm run test:unit",
"test:build": "rimraf .test && mkdirp .test && babel test --out-dir .test",
"test:integration": "tap --no-cov .test/scenarios/*.js",
"test:style": "standard",
"test:unit": "nyc tap --no-cov .test/specs/*.js"
"postpublish": "./bin/semantic-release.js post",
"posttest": "./bin/post-test",
"prepublish": "./bin/semantic-release.js pre",
"pretest": "./bin/pre-test",
"test": "standard && node tests | tap-spec"
}
}
-146
View File
@@ -1,146 +0,0 @@
const { readFileSync, writeFileSync } = require('fs')
const path = require('path')
const url = require('url')
const _ = require('lodash')
const log = require('npmlog')
const nopt = require('nopt')
const npmconf = require('npmconf')
log.heading = 'semantic-release'
const env = process.env
const pkg = JSON.parse(readFileSync('./package.json'))
const 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]
}
const options = _.defaults(
_.mapKeys(nopt(knownOptions), (value, key) => _.camelCase(key)),
pkg.release,
{
branch: 'master',
debug: !env.CI,
githubToken: env.GH_TOKEN || env.GITHUB_TOKEN,
githubUrl: env.GH_URL
}
)
const plugins = require('./lib/plugins')(options)
npmconf.load({}, (err, conf) => {
if (err) {
log.error('init', 'Failed to load npm config.', err)
process.exit(1)
}
let npm = {
auth: {
token: env.NPM_TOKEN
},
loglevel: conf.get('loglevel'),
registry: require('./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
const config = {env, pkg, options, plugins, npm}
let hide = {}
if (options.githubToken) hide.githubToken = '***'
log.verbose('init', 'options:', _.assign({}, options, hide))
log.verbose('init', 'Verifying config.')
const errors = require('./lib/verify')(config)
errors.forEach((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, (err) => {
if (err) {
log[options.debug ? 'warn' : 'error']('pre', err.message)
if (!options.debug) process.exit(1)
}
const nerfDart = require('nerf-dart')(npm.registry)
let wroteNpmRc = false
if (env.NPM_TOKEN) {
conf.set(`${nerfDart}:_authToken`, '${NPM_TOKEN}', 'project')
wroteNpmRc = true
} else 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')
conf.set('email', '${NPM_EMAIL}', 'project')
wroteNpmRc = true
}
conf.save('project', (err) => {
if (err) return log.error('pre', 'Failed to save npm config.', err)
if (wroteNpmRc) log.verbose('pre', 'Wrote authToken to .npmrc.')
require('./pre')(config, (err, release) => {
if (err) {
log.error('pre', 'Failed to determine new version.')
const args = ['pre', (err.code ? `${err.code} ` : '') + err.message]
if (err.stack) args.push(err.stack)
log.error(...args)
process.exit(1)
}
const 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 {
let shrinkwrap = JSON.parse(readFileSync('./npm-shrinkwrap.json'))
shrinkwrap.version = release.version
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.`)
}
writeFileSync('./package.json', JSON.stringify(_.assign(pkg, {
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('./post')(config, (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. User either "pre" or "post"`)
}
})
-65
View File
@@ -1,65 +0,0 @@
const { exec } = require('child_process')
const log = require('npmlog')
const SemanticReleaseError = require('@semantic-release/error')
module.exports = function ({lastRelease, options}, cb) {
const branch = options.branch
const from = lastRelease.gitHead
const range = (from ? from + '..' : '') + 'HEAD'
if (!from) return extract()
exec(`git branch --contains ${from}`, (err, stdout) => {
if (err) return cb(err)
let inHistory = false
const branches = stdout.split('\n')
.map((result) => {
if (branch === result.replace('*', '').trim()) {
inHistory = true
return null
}
return result.trim()
})
.filter(branch => !!branch)
if (!inHistory) {
log.error('commits',
`The commit the last release of this package was derived from is no longer
in the direct history of the "${branch}" branch.
This means semantic-release can not extract the commits between now and then.
This is usually caused by force pushing or releasing from an unrelated branch.
You can recover from this error by publishing manually or restoring
the commit "${from}".` + (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 () {
exec(
`git log -E --format=%H==SPLIT==%B==END== ${range}`,
(err, stdout) => {
if (err) return cb(err)
cb(null, String(stdout).split('==END==\n')
.filter((raw) => !!raw.trim())
.map((raw) => {
const 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/'
const scope = pkg.name.split('/')[0]
const 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 @@
const relative = require('require-relative')
const series = require('run-series')
let 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((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((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)
}
-20
View File
@@ -1,20 +0,0 @@
const SemanticReleaseError = require('@semantic-release/error')
module.exports = function (config, cb) {
const { plugins, lastRelease } = config
plugins.analyzeCommits(config, (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)
})
}
-44
View File
@@ -1,44 +0,0 @@
const parseSlug = require('parse-github-repo-url')
const SemanticReleaseError = require('@semantic-release/error')
module.exports = function ({pkg, options, env}) {
let 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'
))
} else if (!parseSlug(pkg.repository.url)) {
errors.push(new SemanticReleaseError(
'The "repository" field in the package.json is malformed.',
'EMALFORMEDPKGREPO'
))
}
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
}
+38 -34
View File
@@ -1,51 +1,55 @@
const url = require('url')
'use strict'
const gitHead = require('git-head')
const GitHubApi = require('github')
const parseSlug = require('parse-github-repo-url')
var exec = require('child_process').exec
var readFile = require('fs').readFileSync
var url = require('url')
module.exports = function (config, cb) {
const { pkg, options, plugins } = config
const ghConfig = options.githubUrl ? url.parse(options.githubUrl) : {}
var changelog = require('conventional-changelog')
var GitHubApi = require('github')
var parseSlug = require('parse-github-repo-url')
var parseUrl = require('github-url-from-git')
const github = new GitHubApi({
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({
version: '3.0.0',
port: ghConfig.port,
protocol: (ghConfig.protocol || '').split(':')[0] || null,
host: ghConfig.hostname
port: config.port,
protocol: (config.protocol || '').split(':')[0] || null,
host: config.hostname
})
plugins.generateNotes(config, (err, log) => {
if (err) return cb(err)
gitHead((err, hash) => {
if (err) return cb(err)
const ghRepo = parseSlug(pkg.repository.url)
const release = {
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}`,
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: 'oauth',
token: options.githubToken
token: options.token
})
github.releases.createRelease(release, (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 @@
const _ = require('lodash')
const auto = require('run-auto')
const semver = require('semver')
'use strict'
const getCommits = require('./lib/commits')
const getType = require('./lib/type')
var fs = require('fs')
module.exports = function (config, cb) {
const { plugins } = config
var semver = require('semver')
auto({
lastRelease: plugins.getLastRelease.bind(null, config),
commits: ['lastRelease', (cb, results) => {
getCommits(_.assign({
lastRelease: results.lastRelease
}, config),
cb)
}],
type: ['commits', 'lastRelease', (cb, results) => {
getType(_.assign({
commits: results.commits,
lastRelease: results.lastRelease
}, config),
cb)
}]
}, (err, results) => {
if (err) return cb(err)
var type = require('../lib/type')
var version = require('../lib/version')
var efh = require('../lib/error').efh
const nextRelease = {
type: results.type,
version: results.type === 'initial' ?
'1.0.0' :
semver.inc(results.lastRelease.version, results.type)
}
module.exports = function (options, cb) {
type(efh(cb)(function (type) {
if (!type) return cb(null, null)
plugins.verifyRelease(_.assign({
commits: results.commits,
lastRelease: results.lastRelease,
nextRelease
}, config), (err) => {
if (err) return cb(err)
cb(null, nextRelease)
})
})
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))
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'
})
}
+45
View File
@@ -0,0 +1,45 @@
'use strict'
var fs = require('fs')
var ini = require('ini')
var ghUrl = require('github-url-from-git')
module.exports = function () {
var pkg = JSON.parse(fs.readFileSync('./package.json') + '')
// ensure a yet unpublished version
pkg.version = '0.0.0-semantically-released'
// set up scripts
var pre = 'semantic-release pre'
var post = 'semantic-release post'
if (!pkg.scripts) pkg.scripts = {}
if (!pkg.scripts.prepublish) pkg.scripts.prepublish = pre
else if (!(new RegExp(pre).test(pkg.scripts.prepublish))) pkg.scripts.prepublish += ' && ' + pre
if (!pkg.scripts.postpublish) pkg.scripts.postpublish = post
else if (!(new RegExp(post).test(pkg.scripts.postpublish))) pkg.scripts.postpublish += ' && ' + post
// set up repository
if (!pkg.repository || !pkg.repository.url) {
var config = ini.decode(fs.readFileSync('./.git/config') + '')
var repo = config['remote "origin"'].url
if (repo) pkg.repository = {
type: 'git',
url: ghUrl(repo)
}
}
// set up devDependency
if (!pkg.devDependencies) pkg.devDependencies = {}
if (!pkg.devDependencies['semantic-release']) {
pkg.devDependencies['semantic-release'] = '^' + require('../package.json').version
}
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2))
}
+71
View File
@@ -0,0 +1,71 @@
'use strict'
var fs = require('fs')
var exports = module.exports = function (input) {
var options = exports.verifyOptions(input)
var pkg = exports.verifyPackage()
var travis = exports.verifyTravis()
return options && pkg && travis
}
exports.verifyTravis = function () {
try {
var travis = fs.readFileSync('.travis.yml') + ''
} catch (e) {
return true
}
var passed = true
if (!/\sdeploy:/m.test(travis)) {
console.error('You should configure deployments inside the ".travis.yml".')
passed = false
}
if (!/skip_cleanup:/m.test(travis)) {
console.error('You must set "skip_cleanup" to "true" inside the ".travis.yml".')
passed = false
}
return passed
}
exports.verifyPackage = function () {
var passed = true
try {
var pkg = fs.readFileSync('./package.json') + ''
} catch (e) {
console.error('You must have a "package.json" present.')
passed = false
pkg = '{}'
}
try {
pkg = JSON.parse(pkg)
} catch (e) {
console.error('You must have a "package.json" that is valid JSON.')
return false
}
if (!pkg.repository || !pkg.repository.url) {
console.error('You must define your GitHub "repository" inside the "package.json".')
passed = false
}
if (!pkg.scripts || !pkg.scripts.prepublish || !pkg.scripts.postpublish) {
console.error('You must define your "scripts" inside the "package.json".')
passed = false
}
return passed
}
exports.verifyOptions = function (options) {
if (!options) return true
if (options.token) return true
console.error('You must define a GitHub token.')
return false
}
-14
View File
@@ -1,14 +0,0 @@
const 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('TRAVIS', 'true')
.env('TRAVIS_BRANCH', 'master')
.env('npm_config_registry', uri)
.clone()
}
-45
View File
@@ -1,45 +0,0 @@
const { exec } = require('child_process')
const { join } = require('path')
const { writeFileSync } = require('fs')
const mkdirp = require('mkdirp')
module.exports = function (name, cb) {
const cwd = join(__dirname, '../tmp', name)
mkdirp.sync(cwd)
writeFileSync(join(cwd, '.npmrc'), `
//localhost:1337/registry/_design/app/_rewrite/:username=integration
//localhost:1337/registry/_design/app/_rewrite/:email=integration@test.com`, null, 2)
writeFileSync(join(cwd, 'package.json'), JSON.stringify({
name,
repository: {
url: 'git+https://github.com/semantic-release/test'
},
_npmUser: {
name: 'integration',
email: 'integration@test.com'
},
maintainers: [{
name: 'integration',
email: 'integration@test.com'
}]
}, 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}, (err, stdout, stderr) => {
if (err) {
console.log(stdout, stderr)
return cb(err)
}
cb(null, cwd)
})
}
-20
View File
@@ -1,20 +0,0 @@
const rawCommits = [
'hash-one==SPLIT==commit-one==END==\n',
'hash-two==SPLIT==commit-two==END==\n'
]
module.exports = {
exec: (command, cb) => {
if (/contains/.test(command)) {
return cb(null, `whatever\nmaster\n`)
}
cb(
null,
/\.\.HEAD/.test(command) ?
rawCommits[0] :
rawCommits.join()
)
},
'@noCallThru': true
}
-3
View File
@@ -1,3 +0,0 @@
module.exports = function (cb) {
cb(null, 'bar')
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = () => ({
authenticate: () => true,
releases: {
createRelease: (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, {})
-12
View File
@@ -1,12 +0,0 @@
const { exec } = require('child_process')
const { join } = require('path')
const opts = {
cwd: join(__dirname, '../../test/registry')
}
module.exports = {
start: exec.bind(null, './start.sh', opts),
stop: exec.bind(null, './stop.sh', opts),
uri: 'http://localhost: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
-36
View File
@@ -1,36 +0,0 @@
#!/bin/bash
# exit if an error occurs
set -e
cd $(dirname $0)
mkdir -p couch
# start couchdb as a background process, load local config, specify writable logfiles
if [[ $TRAVIS = true ]]
then
echo 'starting couch with sudo'
sudo couchdb -b -a local.ini -p couch/pid -o couch/stdout.log -e couch/stderr.log
else
couchdb -b -a local.ini -p couch/pid -o couch/stdout.log -e couch/stderr.log
fi
# wait for couch to start
sleep 5
COUCH=http://admin:password@127.0.0.1:15986
# 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
npm explore npm-registry-couchapp -- NO_PROMPT=yes npm run copy --npm-registry-couchapp:couch=$COUCH/registry
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
# close couchdb background process
couchdb -d
# delete data and logs
cd $(dirname $0)
cat couch/{couch,stdout,stderr}.log
rm -rf couch
rm -rf data
-98
View File
@@ -1,98 +0,0 @@
const { join } = require('path')
const { test, tearDown } = require('tap')
const rimraf = require('rimraf')
const registry = require('../registry')
const testModule = require('../lib/test-module')
const baseScenario = require('../lib/base-scenario')
test('change version', (t) => {
t.plan(7)
registry.start((err) => {
t.error(err, 'registry started')
if (err) {
t.end()
t.bailout('registry not started')
}
testModule('change-version', (err, cwd) => {
t.error(err, 'test-module created')
if (err) {
t.end()
t.bailout('test-module not created')
}
t.test('no version', (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', (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', (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', (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', (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 cb (err, stdout, stderr) {
if (err) console.log(err)
if (stdout) console.log(stdout)
if (stderr) console.log(stderr)
}
rimraf(join(__dirname, '../tmp'), cb)
registry.stop(cb)
})
-40
View File
@@ -1,40 +0,0 @@
const test = require('tap').test
const proxyquire = require('proxyquire')
const commits = proxyquire('../../dist/lib/commits', {
'child_process': require('../mocks/child-process')
})
test('commits since last release', (t) => {
t.test('get all commits', (tt) => {
commits({lastRelease: {}, options: {branch: 'master'}}, (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', (tt) => {
commits({lastRelease: {gitHead: 'hash'}, options: {branch: 'master'}}, (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', (tt) => {
commits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'notmaster'}}, (err, commits) => {
tt.ok(err)
tt.is(err.code, 'ENOTINHISTORY')
tt.end()
})
})
t.end()
})
-30
View File
@@ -1,30 +0,0 @@
const test = require('tap').test
const getRegistry = require('../../dist/lib/get-registry')
test('get correct registry', (t) => {
t.is(getRegistry({
name: 'publish-config',
publishConfig: {
registry: 'a'
}},
{}), 'a')
t.is(getRegistry({name: 'normal'}, {get: () => 'b'}), 'b')
t.is(getRegistry({name: 'normal'}, {get: () => null}), 'https://registry.npmjs.org/')
t.is(getRegistry({name: '@scoped/foo'}, {
get: (input) => input === '@scoped/registry' ? 'c' : 'd'
}), 'c')
t.is(getRegistry({name: '@scoped/bar'}, {
get: () => 'e'
}), 'e')
t.is(getRegistry({name: '@scoped/baz'}, {
get: () => null
}), 'https://registry.npmjs.org/')
t.end()
})
-96
View File
@@ -1,96 +0,0 @@
const test = require('tap').test
const plugins = require('../../dist/lib/plugins')
test('export plugins', (t) => {
t.plan(5)
const 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', (t) => {
t.plan(3)
t.test('get all results', (tt) => {
const pipelinePlugins = plugins({
verifyRelease: [
'./dist/lib/plugin-noop',
'./.test/mocks/plugin-result-a',
'./.test/mocks/plugin-result-b'
]
})
pipelinePlugins.verifyRelease({}, (err, results) => {
tt.error(err)
tt.same(results, [undefined, 'a', 'b'])
tt.end()
})
})
t.test('get first error', (tt) => {
const pipelinePlugins = plugins({
verifyConditions: [
'./dist/lib/plugin-noop',
'./.test/mocks/plugin-error-a',
'./.test/mocks/plugin-error-b'
]
})
pipelinePlugins.verifyConditions({}, (err) => {
tt.is(err.message, 'a')
tt.end()
})
})
t.test('get error and only results before', (tt) => {
const pipelinePlugins = plugins({
verifyRelease: [
'./dist/lib/plugin-noop',
'./.test/mocks/plugin-result-a',
'./.test/mocks/plugin-error-b',
'./.test/mocks/plugin-result-b'
]
})
pipelinePlugins.verifyRelease({}, (err, results) => {
tt.is(err.message, 'b')
tt.same(results, [undefined, 'a', undefined])
tt.end()
})
})
})
test('normalize and load plugin', (t) => {
t.test('load from string', (tt) => {
const plugin = plugins.normalize('./dist/lib/plugin-noop')
tt.is(typeof plugin, 'function')
tt.end()
})
t.test('load from object', (tt) => {
const plugin = plugins.normalize({
path: './dist/lib/plugin-noop'
})
tt.is(typeof plugin, 'function')
tt.end()
})
t.test('load from object', (tt) => {
const plugin = plugins.normalize(null, '../../dist/lib/plugin-noop')
tt.is(typeof plugin, 'function')
tt.end()
})
})
-68
View File
@@ -1,68 +0,0 @@
const { defaults } = require('lodash')
const test = require('tap').test
const proxyquire = require('proxyquire')
const post = proxyquire('../../dist/post', {
'git-head': require('../mocks/git-head'),
github: require('../mocks/github')
})
const pkg = {
version: '1.0.0',
repository: {url: 'http://github.com/whats/up.git'}
}
const plugins = {generateNotes: (pkg, cb) => cb(null, 'the log')}
const defaultRelease = {
owner: 'whats',
repo: 'up',
name: 'v1.0.0',
tag_name: 'v1.0.0',
target_commitish: 'bar',
body: 'the log'
}
test('full post run', (t) => {
t.test('in debug mode w/o token', (tt) => {
post({
options: {debug: true},
pkg,
plugins
}, (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', (tt) => {
post({
options: {debug: true, githubToken: 'yo'},
pkg,
plugins
}, (err, published, release) => {
tt.error(err)
tt.is(published, true)
tt.match(release, defaults({draft: true}, defaultRelease))
tt.end()
})
})
t.test('production', (tt) => {
post({
options: {githubToken: 'yo'},
pkg,
plugins
}, (err, published, release) => {
tt.error(err)
tt.is(published, true)
tt.match(release, defaultRelease)
tt.end()
})
})
})
-60
View File
@@ -1,60 +0,0 @@
const test = require('tap').test
const proxyquire = require('proxyquire')
require('../mocks/registry')
const pre = proxyquire('../../dist/pre', {
'./lib/commits': proxyquire('../../dist/lib/commits', {
'child_process': require('../mocks/child-process')
})
})
const versions = {
available: '1.0.0'
}
const plugins = {
verifyRelease: (release, cb) => cb(null, release),
analyzeCommits: (commits, cb) => cb(null, 'major'),
getLastRelease: ({ pkg }, cb) => {
cb(null, {version: versions[pkg.name] || null, gitHead: 'HEAD'})
}
}
const npm = {
registry: 'http://registry.npmjs.org/',
tag: 'latest'
}
test('full pre run', (t) => {
t.test('increase version', (tt) => {
tt.plan(3)
pre({
options: {branch: 'master'},
npm,
pkg: {name: 'available'},
plugins
}, (err, release) => {
tt.error(err)
tt.is(release.type, 'major')
tt.is(release.version, '2.0.0')
})
})
t.test('increase version', (tt) => {
tt.plan(3)
pre({
options: {branch: 'master'},
npm,
pkg: {name: 'unavailable'},
plugins
}, (err, release) => {
tt.error(err)
tt.is(release.type, 'initial')
tt.is(release.version, '1.0.0')
})
})
t.end()
})
-48
View File
@@ -1,48 +0,0 @@
const test = require('tap').test
const type = require('../../dist/lib/type')
test('get type from commits', (t) => {
t.test('get type from plugin', (tt) => {
tt.plan(2)
type({
commits: [{
hash: '0',
message: 'a'
}],
lastRelease: {version: '1.0.0'},
plugins: {analyzeCommits: (config, cb) => cb(null, 'major')}
}, (err, type) => {
tt.error(err)
tt.is(type, 'major')
})
})
t.test('error when no changes', (tt) => {
tt.plan(1)
type({
commits: [],
lastRelease: {},
plugins: {analyzeCommits: (config, cb) => cb(null, null)}
}, (err) => {
tt.is(err.code, 'ENOCHANGE')
})
})
t.test('initial version', (tt) => {
tt.plan(2)
type({
commits: [],
lastRelease: {},
plugins: {analyzeCommits: (config, cb) => cb(null, 'major')}
}, (err, type) => {
tt.error(err)
tt.is(type, 'initial')
})
})
t.end()
})
-68
View File
@@ -1,68 +0,0 @@
const test = require('tap').test
const verify = require('../../dist/lib/verify')
test('verify pkg, options and env', (t) => {
t.test('dry run verification', (tt) => {
const noErrors = verify({
options: {debug: true},
pkg: {
name: 'package',
repository: {
url: 'http://github.com/whats/up.git'
}
}
})
tt.is(noErrors.length, 0)
const errors = verify({
options: {debug: true},
pkg: {}
})
tt.is(errors.length, 2)
tt.is(errors[0].code, 'ENOPKGNAME')
tt.is(errors[1].code, 'ENOPKGREPO')
const errors2 = verify({
options: {debug: true},
pkg: {
name: 'package',
repository: {
url: 'lol'
}
}
})
tt.is(errors2.length, 1)
tt.is(errors2[0].code, 'EMALFORMEDPKGREPO')
tt.end()
})
t.test('publish verification', (tt) => {
const 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)
const 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()
})
})
+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
+12
View File
@@ -0,0 +1,12 @@
'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)
require('./scenarios/verify')(test, createModule)
require('./scenarios/setup')(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')
})
})
}))
}
})
}
+38
View File
@@ -0,0 +1,38 @@
'use strict'
var join = require('path').join
var readFile = require('fs').readFileSync
var efh = require('error-first-handler')
var nixt = require('nixt')
module.exports = function (test, createModule) {
createModule({
repository: {},
scripts: {
postpublish: 'npm run gh-pages'
}
}, efh()(function (name, cwd) {
test('setup', function (t) {
t.test('setup "package.json"', function (t) {
t.plan(5)
nixt()
.cwd(cwd)
.exec('git remote add origin git@github.com:user/repo.git')
.run('./node_modules/.bin/semantic-release setup')
.code(0)
.end(function(err) {
t.error(err, 'nixt')
var pkg = JSON.parse(readFile(join(cwd, 'package.json')))
t.is(pkg.version, '0.0.0-semantically-released', 'version')
t.is(pkg.repository.url, 'https://github.com/user/repo', 'repo')
t.is(pkg.scripts.prepublish, 'semantic-release pre', 'pre')
t.is(pkg.scripts.postpublish, 'npm run gh-pages && semantic-release post', 'post')
})
})
})
}))
}
+34
View File
@@ -0,0 +1,34 @@
'use strict'
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) {
t.test('verify package and options before publishing', function (t) {
t.plan(1)
nixt()
.cwd(cwd)
.env('CI', true)
.run('npm publish')
.stderr(new RegExp(
'You must define a GitHub token\.\n' +
'You must define your GitHub "repository" inside the "package.json"\.\n' +
'You must define your "scripts" inside the "package.json"\.'
, 'm'
))
.code(1)
.end(function(err) {
t.error(err, 'nixt')
})
})
})
}))
}