├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json └── src └── release.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | bin/** 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-airbnb"], 3 | "env": { 4 | "node": true 5 | }, 6 | "parser": "babel-eslint", 7 | "rules": { 8 | "comma-dangle": 0, 9 | "func-names": 0, 10 | "guard-for-in": 0, 11 | "one-var": [2, { "initialized": "never" }], 12 | "max-len": 0, 13 | "no-console": 0, 14 | "no-eq-null": 0, 15 | "no-param-reassign": 0, 16 | "no-undef": 2, 17 | "no-use-before-define": 0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | npm-debug.log* 4 | node_modules 5 | bin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | cache: 6 | directories: 7 | - node_modules 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v1.0.2 - Sat, 16 Jan 2016 20:24:14 GMT 2 | -------------------------------------- 3 | 4 | - [3d9bbd3](../../commit/3d9bbd3) [fixed] Docs only release 5 | 6 | 7 | 8 | v1.0.1 - Wed, 13 Jan 2016 22:19:33 GMT 9 | -------------------------------------- 10 | 11 | - [6fa946f](../../commit/6fa946f) [changed] put back "defaultDryRun" option 12 | 13 | 14 | 15 | v1.0.0 - Wed, 13 Jan 2016 18:22:40 GMT 16 | -------------------------------------- 17 | 18 | - [cd55fb8](../../commit/cd55fb8) [changed] make "actual running" by default 19 | 20 | 21 | 22 | v0.5.8 - Wed, 06 Jan 2016 17:01:45 GMT 23 | -------------------------------------- 24 | 25 | - [2229dbd](../../commit/2229dbd) [added] automatic registration of package if bower.json is located in the repository 26 | 27 | 28 | 29 | v0.5.7 - Mon, 04 Jan 2016 12:10:23 GMT 30 | -------------------------------------- 31 | 32 | - [4862716](../../commit/4862716) [added] '--skip-version-bumping' option 33 | 34 | 35 | 36 | v0.5.6 - Fri, 18 Dec 2015 19:02:18 GMT 37 | -------------------------------------- 38 | 39 | - [3e7ac06](../../commit/3e7ac06) [added] '--skip-test(s)' flag 40 | 41 | 42 | 43 | v0.5.5 - Thu, 19 Nov 2015 07:34:10 GMT 44 | -------------------------------------- 45 | 46 | - [8a5c227](../../commit/8a5c227) [fixed] checking that a local repo is behind the remote 47 | 48 | 49 | 50 | v0.5.4 - Fri, 16 Oct 2015 22:20:44 GMT 51 | -------------------------------------- 52 | 53 | 54 | 55 | 56 | 57 | v0.5.3 - Tue, 22 Sep 2015 15:15:59 GMT 58 | -------------------------------------- 59 | 60 | - [aef3379](../../commit/aef3379) [fixed] do not publish documents with '--preid' option 61 | 62 | 63 | 64 | v0.5.2 - Thu, 17 Sep 2015 10:51:11 GMT 65 | -------------------------------------- 66 | 67 | - [136c511](../../commit/136c511) [added] "defaultDryRun": "false" option 68 | 69 | 70 | 71 | v0.5.1 - Thu, 17 Sep 2015 06:54:44 GMT 72 | -------------------------------------- 73 | 74 | 75 | 76 | 77 | 78 | v0.5.0 - Thu, 17 Sep 2015 06:11:16 GMT 79 | -------------------------------------- 80 | 81 | - [57b2159](../../commit/57b2159) [changed] Now 'dry run' is default mode. To prevent accidental mistakes. 82 | 83 | 84 | 85 | v0.4.0 - Wed, 16 Sep 2015 17:07:17 GMT 86 | -------------------------------------- 87 | 88 | - [2c52100](../../commit/2c52100) [added] 'CHANGELOG-alpha.md' is generating for pre-releases now 89 | - [73aa215](../../commit/73aa215) [changed] use 'npm run docs-build' instead of 'npm run build' for '--only-docs' 90 | - [602343a](../../commit/602343a) [added] 'skipBuildStep' special case option 91 | 92 | 93 | 94 | v0.3.1 - Wed, 16 Sep 2015 12:33:32 GMT 95 | -------------------------------------- 96 | 97 | - [e0d568d](../../commit/e0d568d) [added] '--only-docs' option 98 | - [4353c61](../../commit/4353c61) [added] document site releasing 99 | 100 | 101 | 102 | v0.3.0 - Tue, 15 Sep 2015 15:59:16 GMT 103 | -------------------------------------- 104 | 105 | - [a8aaa5e](../../commit/a8aaa5e) [added] npm tags for pre-release versions 106 | 107 | 108 | 109 | v0.2.8 - Tue, 15 Sep 2015 13:50:43 GMT 110 | -------------------------------------- 111 | 112 | - [125ba00](../../commit/125ba00) [added] special case for 'mt-changelog' package 113 | 114 | 115 | 116 | v0.2.7 - Mon, 14 Sep 2015 15:21:20 GMT 117 | -------------------------------------- 118 | 119 | - [a07b407](../../commit/a07b407) [added] alternative package root folder option 120 | 121 | 122 | 123 | v0.2.6 - Sun, 13 Sep 2015 18:20:38 GMT 124 | -------------------------------------- 125 | 126 | - [12b8af8](../../commit/12b8af8) [changed] Only push once for both the commit and tags 127 | 128 | 129 | 130 | v0.2.5 - Thu, 13 Aug 2015 14:10:46 GMT 131 | -------------------------------------- 132 | 133 | - [9071d8d](../../commit/9071d8d) [fixed] parsing of string 'repository' field 134 | 135 | 136 | 137 | v0.2.4 - Sun, 09 Aug 2015 22:10:58 GMT 138 | -------------------------------------- 139 | 140 | - [150378e](../../commit/150378e) [added] support for GitHub releases 141 | 142 | 143 | 144 | v0.2.3 - Sun, 09 Aug 2015 10:03:22 GMT 145 | -------------------------------------- 146 | 147 | - [7a83edd](../../commit/7a83edd) [added] support for private option 148 | 149 | 150 | 151 | v0.2.2 - Fri, 07 Aug 2015 14:42:09 GMT 152 | -------------------------------------- 153 | 154 | - [c4ac720](../../commit/c4ac720) [changed] the `build` step made optional 155 | 156 | 157 | 158 | v0.2.1 - Mon, 13 Jul 2015 15:03:31 GMT 159 | -------------------------------------- 160 | 161 | - [b4c6d9f](../../commit/b4c6d9f) [fixed] changelog usage with 'git tag command' 162 | 163 | 164 | 165 | v0.2.0 - Mon, 13 Jul 2015 14:51:28 GMT 166 | -------------------------------------- 167 | 168 | - [cf2b9df](../../commit/cf2b9df) [changed] the usage of '[rf|mt]-changelog' package is optional now 169 | - [6551bab](../../commit/6551bab) [added] options description. 170 | - [556ad50](../../commit/556ad50) [changed] all options has been moved under the common json node 171 | 172 | 173 | 174 | v0.1.2 - Sun, 12 Jul 2015 09:16:05 GMT 175 | -------------------------------------- 176 | 177 | - [94ed987](../../commit/94ed987) [fixed] removed unnecessary 'main' field from package.json 178 | 179 | 180 | 181 | v0.1.1 - Sat, 11 Jul 2015 09:37:15 GMT 182 | -------------------------------------- 183 | 184 | 185 | 186 | 187 | 188 | v0.1.0 - Fri, 10 Jul 2015 18:43:40 GMT 189 | -------------------------------------- 190 | 191 | - [c2dee37](../../commit/c2dee37) [added] instruction and introduction into readme 192 | 193 | 194 | 195 | v0.0.1 - Fri, 10 Jul 2015 17:36:20 GMT 196 | -------------------------------------- 197 | 198 | - [3ced747](../../commit/3ced747) [fixed] changelog generation es empty on tmp-bower-repo 199 | - [2fded01](../../commit/2fded01) [changed] transpile straight to bin/release 200 | - [e171a8d](../../commit/e171a8d) [added] all initial files. working version. 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexander Shemetovsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # release-script 2 | 3 | Release tool for npm and bower packages. 4 | 5 | [![Build Status](https://travis-ci.org/AlexKVal/release-script.svg)](https://travis-ci.org/AlexKVal/release-script) 6 | 7 | --- 8 | #### Description 9 | 10 | With this tool there is no need to keep (transpiled) `lib`, `build` 11 | or `distr` files in the git repo. 12 | 13 | Because `Bower` keeps its files in the github repo, 14 | this tools helps to deal with that too. 15 | 16 | Just create new additional github repo for `Bower` version of your project. 17 | This repo will contain only commits generated by this tool. 18 | 19 | Say the name of your project is 20 | `original-project-name` 21 | then name the `bower` github repo as 22 | `original-project-name-bower`. 23 | 24 | Add `'release-script'.bowerRepo` into your `package.json`: 25 | ```json 26 | "release-script": { 27 | "bowerRepo": "git@github.com:/original-project-name-bower.git" 28 | } 29 | ``` 30 | 31 | Then add additional step into your building process, 32 | which will create `bower` package files in the `amd` folder, 33 | (basically it is just a process of copying the `lib` files, README and LICENSE) 34 | and that's all. 35 | 36 | Now `release-script` will do all those steps (described next), 37 | including `bower` publishing, for you - automatically. 38 | 39 | 40 | _Initial idea is got from `React-Bootstrap` release tools `./tools/release`, 41 | that have been written by [Matt Smith @mtscout6](https://github.com/mtscout6)_ 42 | 43 | _Kind of "migration manual"_ https://github.com/AlexKVal/release-script/issues/23 44 | 45 | #### Documentation pages publishing 46 | 47 | If your project generates static documentation pages (as `React-Boostrap` does) 48 | and needs publishing them to a standalone repo (via `git push`), just create additional github repo for the documentation pages. 49 | 50 | E.g. [react-bootstrap.github.io.git](https://github.com/react-bootstrap/react-bootstrap.github.io) 51 | 52 | Add it as `'release-script'.docsRepo` into your `package.json`: 53 | ```json 54 | "release-script": { 55 | "docsRepo": "git@github.com:/original-project-name-github.io.git" 56 | } 57 | ``` 58 | Default folders for documentation pages are: 59 | - `"docsRoot": "docs-built"` folder where the files will be built to. (by your custom building script) 60 | - `"tmpDocsRepo": "tmp-docs-repo"` temporary folder. (for the release-script usage) 61 | 62 | It is advised to add them both into `.gitignore`. 63 | 64 | You can customize them as you need: 65 | ```json 66 | "release-script": { 67 | "docsRepo": "git@github.com:/original-project-name-github.io.git", 68 | "docsRoot": "docs-built", 69 | "tmpDocsRepo": "tmp-docs-repo" 70 | } 71 | ``` 72 | 73 | If you need to publish only documentation pages (say with some minor fixes), 74 | you can do it this way: 75 | ``` 76 | > release --only-docs 77 | ``` 78 | In this case the `package.json` version will be bumped with `--preid docs` as `0.10.0` => `0.10.0-docs.0`. 79 | 80 | If `npm run docs-build` script is present, then it will be used instead of `npm run build` script. 81 | 82 | *Note: Documentation pages are not published when you are releasing "pre-release" version, 83 | (i.e. when you run it with `--preid` option).* 84 | 85 | #### Pre-release versions publishing 86 | 87 | Say you need to publish pre-release `v0.25.100-pre.0` version 88 | with the `canary` npm tag name (instead of default one `latest`). 89 | 90 | You can do it this way: 91 | ``` 92 | > release 0.25.100 --preid pre --tag canary 93 | or 94 | > npm run release 0.25.100 -- --preid pre --tag canary 95 | ``` 96 | 97 | If your `preid` tag and npm tag are the same, then you can just: 98 | ``` 99 | > release 0.25.100 --preid beta 100 | ``` 101 | It will produce `v0.25.100-beta.0` and `npm publish --tag beta`. 102 | 103 | `changelog` generated output will go into `CHANGELOG-alpha.md` for pre-releases, 104 | and with the next release this file will be removed. 105 | 106 | #### Alternative npm package root folder 107 | 108 | Say you want to publish the content of your `lib` folder as an npm package's root folder. 109 | Then you can do it as simple as adding the following to your `package.json` 110 | ```json 111 | "release-script": { 112 | "altPkgRootFolder": "lib" 113 | } 114 | ``` 115 | and that's all. 116 | 117 | #### The special case. `build` step in `tests` step. 118 | 119 | If you run building scripts within your `npm run test` step, e.g. 120 | ```json 121 | "scripts": { 122 | "test": "npm run lint && npm run build && npm run tests-set", 123 | } 124 | ``` 125 | then you can prevent `npm run build` step from running 126 | by setting `'release-script'.skipBuildStep` option: 127 | ```json 128 | "release-script": { 129 | "skipBuildStep": "true" 130 | } 131 | ``` 132 | 133 | #### Options 134 | 135 | All options for this package are kept under `'release-script'` node in your project's `package.json` 136 | 137 | - `bowerRepo` - the full url to github project for the bower pkg files 138 | - `bowerRoot` - the folder name where your `npm run build` command will put/transpile files for bower pkg 139 | - `default` value: `'amd'` 140 | - `tmpBowerRepo` - the folder name for temporary files for bower pkg. 141 | - `default` value: `'tmp-bower-repo'` 142 | - `altPkgRootFolder` - the folder name for alternative npm package's root folder 143 | 144 | It is advised to add `bowerRoot` and `tmpBowerRepo` folders to your `.gitignore` file. 145 | 146 | Example: 147 | ```json 148 | "release-script": { 149 | "bowerRepo": "git@github.com:/-bower.git", 150 | "bowerRoot": "amd", 151 | "tmpBowerRepo": "tmp-bower-repo", 152 | "altPkgRootFolder": "lib" 153 | } 154 | ``` 155 | 156 | #### GitHub releases 157 | 158 | If you need this script to publish github releases, 159 | you can generate a github token at https://github.com/settings/tokens 160 | and put it to `env.GITHUB_TOKEN` this way: 161 | ```sh 162 | > GITHUB_TOKEN="xxxxxxxxxxxx" && release-script patch 163 | ``` 164 | or into your shell scripts 165 | ```sh 166 | export GITHUB_TOKEN="xxxxxxxxxxxx" 167 | ``` 168 | You can set a custom message for github release via `--notes` CLI option: 169 | ``` 170 | > release patch --notes "This is a small fix" 171 | ``` 172 | 173 | #### If you need `dry-run` mode by default 174 | 175 | You can setup the `release-script` to run in `dry-run` mode by default. 176 | 177 | It can be done by setting `"true"` the `defaultDryRun` option in your `package.json`: 178 | ```json 179 | "release-script": { 180 | "defaultDryRun": "true" 181 | } 182 | ``` 183 | Then to actually run your commands you will have to add `--run`. 184 | 185 | 186 | #### This script does the following steps: 187 | 188 | - ensures that git repo has no pending changes 189 | - ensures that the latest version is fetched 190 | - checks linting and tests by running `npm run test` command 191 | - does version bumping 192 | - builds all by running `npm run build` command 193 | - If there is no `build` script, then `release-script` just skips the `build` step. 194 | - if one of `[rf|mt]-changelog` is used in 'devDependencies', then changelog is to be generated 195 | - adds and commits `package.json` with changed version and `CHANGELOG.md` if it's used 196 | - adds git tag with new version and changelog message if it's used 197 | - pushes changes to github repo 198 | - if github token is present the script publishes the release to the GitHub 199 | - if `--preid` tag set then `npm publish --tag` command for npm publishing is used 200 | - with `--tag` option one can set `npm tag name` for a pre-release version (e.g. `alpha` or `canary`) 201 | - otherwise `--preid` value will be used 202 | - if `altPkgRootFolder` isn't set it will just `npm publish [--tag]` as usual. Otherwise: 203 | - the release-script will `npm publish [--tag]` from the inside `altPkgRootFolder` folder 204 | - `scripts` and `devDependencies` will be removed from `package.json` 205 | - if `bowerRepo` field is present in the `package.json` then bower package will be released. 206 | - the script will clone the bower repo to the `tmpBowerRepo` folder 207 | - clean up all files but `.git` in the `tmpBowerRepo` folder 208 | - copy all files from `bowerRoot` to `tmpBowerRepo` (they has to be generated by `npm run build`) 209 | - add all files to the temporary git repo with `git add -A .` 210 | - then it will commit, tag and push. The same as for the `npm` package. 211 | - and at the end it will remove the `tmpBowerRepo` folder 212 | - if `docsRepo` option is set then documentation pages are being pushed to their repo. 213 | It is done the same way as `bower` publishing process. 214 | 215 | If `--only-docs` command line option is set then `github`, `npm` and `bower` publishing steps will be skipped. 216 | 217 | ## Installation 218 | 219 | ```sh 220 | > npm install -D release-script 221 | ``` 222 | 223 | If you need `bower` releases too then add `'release-script'.bowerRepo` into your `package.json`: 224 | ```json 225 | "release-script": { 226 | "bowerRepo": "git@github.com:/-bower.git" 227 | } 228 | ``` 229 | 230 | If you have smth like that in your shell: 231 | ```sh 232 | # npm 233 | export PATH="./node_modules/.bin:$PATH" 234 | ``` 235 | or you had installed `release-script` globally via `npm install -g release-script`, 236 | then you can release your new versions this way: 237 | ```sh 238 | > release patch 239 | or 240 | > release minor --preid alpha 241 | ``` 242 | 243 | Otherwise you need to type in the commands this way: 244 | ```sh 245 | > ./node_modules/.bin/release minor --preid alpha 246 | ``` 247 | 248 | You can as well add some helpful `script` commands to your `package.json`: 249 | ```json 250 | "scripts": { 251 | ... 252 | "patch": "release patch", 253 | "minor": "release minor", 254 | "major": "release major" 255 | ``` 256 | 257 | And you can add it like this: 258 | ```json 259 | "scripts": { 260 | ... 261 | "release": "release", 262 | ``` 263 | This way it became possible to run it like that: 264 | ``` 265 | > npm run release minor -- --preid alpha 266 | > npm run release patch -- --notes "This is small fix" 267 | > npm run release major --dry-run // for dry run 268 | etc 269 | ``` 270 | _Notice: You have to add additional double slash `--` before any `--option`. This way additional options get through to `release-script`._ 271 | 272 | 273 | ## License 274 | `release-script` is licensed under the [MIT License](https://github.com/alexkval/release-script/blob/master/LICENSE). 275 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "release-script", 3 | "version": "1.0.2", 4 | "description": "Release tools for projects. From github repo to npm and bower packages", 5 | "bin": { 6 | "release": "./bin/release.js" 7 | }, 8 | "files": [ 9 | "LICENSE", 10 | "README.md", 11 | "bin" 12 | ], 13 | "scripts": { 14 | "build": "rimraf bin && babel src --out-dir bin", 15 | "lint": "eslint .", 16 | "test": "npm run lint", 17 | "patch": "node bin/release.js patch", 18 | "minor": "node bin/release.js minor", 19 | "major": "node bin/release.js major" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:alexkval/release-script.git" 24 | }, 25 | "keywords": [ 26 | "release-tools", 27 | "release", 28 | "script", 29 | "tools" 30 | ], 31 | "author": { 32 | "name": "Alexander Shemetovsky", 33 | "email": "alexkval@gmail.com" 34 | }, 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/AlexKVal/release-script/issues" 38 | }, 39 | "homepage": "https://github.com/AlexKVal/release-script#readme", 40 | "devDependencies": { 41 | "babel-cli": "^6.0.15", 42 | "babel-eslint": "^4.0.5", 43 | "babel-preset-es2015": "^6.0.15", 44 | "eslint": "^1.0.0", 45 | "eslint-config-airbnb": "^3.0.0", 46 | "eslint-plugin-react": "^3.3.2", 47 | "mt-changelog": "^0.6.1", 48 | "rimraf": "^2.5.0" 49 | }, 50 | "dependencies": { 51 | "colors": "^1.1.2", 52 | "github-url-to-object": "^2.1.0", 53 | "request": "^2.60.0", 54 | "semver": "^5.0.0", 55 | "shelljs": "^0.5.1", 56 | "yargs": "^3.15.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/release.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* globals cat, config, cp, ls, popd, pushd, pwd, rm, test, exec, exit, which */ 3 | /* eslint curly: 0 */ 4 | import 'colors'; 5 | import 'shelljs/global'; 6 | import path from 'path'; 7 | import semver from 'semver'; 8 | import yargs from 'yargs'; 9 | import request from 'request'; 10 | import gh from 'github-url-to-object'; 11 | 12 | // do not die on errors 13 | config.fatal = false; 14 | 15 | //------------------------------------------------------------------------------ 16 | // constants 17 | const repoRoot = pwd(); 18 | const packagePath = path.join(repoRoot, 'package.json'); 19 | const bowerjsonPath = path.join(repoRoot, 'bower.json'); 20 | 21 | const npmjson = JSON.parse(cat(packagePath)); 22 | const bowerjson = test('-f', bowerjsonPath) ? JSON.parse(cat(bowerjsonPath)) : null; 23 | const isPrivate = npmjson.private; 24 | const devDepsNode = npmjson.devDependencies; 25 | 26 | //------------------------------------------------------------------------------ 27 | // check if one of 'rf-changelog' or 'mt-changelog' is used by project 28 | let isCommitsChangelogUsed = devDepsNode && 29 | (devDepsNode['rf-changelog'] || devDepsNode['mt-changelog']); 30 | if (isCommitsChangelogUsed && !which('changelog')) { 31 | printErrorAndExit('The "[rf|mt]-changelog" package is present in "devDependencies", but it is not installed.'); 32 | } 33 | 34 | const isWithinMtChangelog = npmjson.name === 'mt-changelog'; 35 | isCommitsChangelogUsed = isCommitsChangelogUsed || isWithinMtChangelog; 36 | 37 | //------------------------------------------------------------------------------ 38 | // options 39 | const configOptions = npmjson['release-script'] || {}; 40 | const bowerRoot = path.join(repoRoot, (configOptions.bowerRoot || 'amd/')); 41 | const tmpBowerRepo = path.join(repoRoot, (configOptions.tmpBowerRepo || 'tmp-bower-repo')); 42 | const bowerRepo = configOptions.bowerRepo; // if it is not set, then there is no bower repo 43 | 44 | const docsRoot = path.join(repoRoot, (configOptions.docsRoot || 'docs-built/')); 45 | const tmpDocsRepo = path.join(repoRoot, (configOptions.tmpDocsRepo || 'tmp-docs-repo')); 46 | const docsRepo = configOptions.docsRepo; // if it is not set, then there is no docs/site repo 47 | const docsBuild = npmjson.scripts && npmjson.scripts['docs-build']; 48 | 49 | const githubToken = process.env.GITHUB_TOKEN; 50 | 51 | const altPkgRootFolder = configOptions.altPkgRootFolder; 52 | 53 | const skipBuildStep = configOptions.skipBuildStep; 54 | 55 | 56 | //------------------------------------------------------------------------------ 57 | // command line options 58 | const yargsConf = yargs 59 | .usage('Usage: $0 [--preid ]\nor\nUsage: $0 --only-docs') 60 | .example('$0 minor --preid beta', 'Release with a minor version bump and a pre-release tag. (npm tag `beta`)') 61 | .example('$0 major', 'Release with a major version bump') 62 | .example('$0 major --notes "a custom message text"', 'Add a custom message to the release') 63 | .example('$0 --preid alpha', 'Release the same version with a pre-release tag. (npm tag `alpha`)') 64 | .example('$0 0.101.0 --preid rc --tag canary', 'Release pre-release version `v0.101.0-rc.0` with npm tag `canary`') 65 | .example('$0 ... --skip-test(s)', 'Use this flag if you need to skip `npm run test` step.') 66 | .command('patch', 'Release patch') 67 | .command('minor', 'Release minor') 68 | .command('major', 'Release major') 69 | .command('', 'Release arbitrary version number') 70 | .option('preid', { 71 | describe: 'pre-release identifier', 72 | type: 'string' 73 | }) 74 | .option('tag', { 75 | describe: 'Npm tag name for pre-release version.\nIf it is not provided, then `preid` value is used', 76 | type: 'string' 77 | }) 78 | .option('only-docs', { 79 | alias: 'docs', 80 | describe: 'Publish only documents' 81 | }) 82 | .option('dry-run', { 83 | alias: 'n', 84 | describe: 'This option toggles "dry run" mode' 85 | }) 86 | .option('run', { 87 | describe: 'You need this when "defaultDryRun": "true"' 88 | }) 89 | .option('verbose', { 90 | describe: 'Increased debug output' 91 | }) 92 | .option('skip-tests', { 93 | alias: 'skip-test', 94 | describe: 'Skip `npm run test` step' 95 | }) 96 | .option('skip-version-bumping', { 97 | describe: 'Skip version bumping step' 98 | }) 99 | .option('notes', { 100 | describe: 'A custom message for the release.\nOverrides [rf|mt]changelog message' 101 | }); 102 | 103 | const argv = yargsConf.argv; 104 | 105 | config.silent = !argv.verbose; 106 | 107 | const versionBumpOptions = { 108 | type: argv._[0], 109 | preid: argv.onlyDocs ? 'docs' : argv.preid, 110 | npmTagName: argv.tag || argv.preid 111 | }; 112 | 113 | if (!argv.skipVersionBumping && versionBumpOptions.type === undefined && versionBumpOptions.preid === undefined) { 114 | console.log('Must provide either a version bump type, "preid" or "--only-docs"'.red); 115 | console.log(yargsConf.help()); 116 | exit(1); 117 | } 118 | 119 | let notesForRelease = argv.notes; 120 | 121 | const isDefaultDryRunOptionSetTrue = 122 | configOptions.defaultDryRun === true || 123 | configOptions.defaultDryRun === 'true'; 124 | 125 | let dryRunMode; 126 | if (argv.run) { 127 | dryRunMode = false; 128 | } else { 129 | dryRunMode = argv.dryRun || isDefaultDryRunOptionSetTrue; 130 | } 131 | 132 | if (dryRunMode) { 133 | console.log('DRY RUN'.magenta); 134 | 135 | if (isDefaultDryRunOptionSetTrue) { 136 | console.log('------------------------------------------------------'); 137 | console.log('To actually run your command please add "--run" option'.yellow); 138 | console.log('------------------------------------------------------'); 139 | } 140 | } 141 | 142 | if (argv.preid) console.log('"--preid" detected. Documents will not be published'.yellow); 143 | if (argv.onlyDocs && !argv.preid) console.log('Publish only documents'.yellow); 144 | 145 | 146 | //------------------------------------------------------------------------------ 147 | // functions 148 | function printErrorAndExit(error) { 149 | console.error(error.red); 150 | exit(1); 151 | } 152 | 153 | function run(command, skipError) { 154 | const { code, output } = exec(command); 155 | if (code !== 0 && !skipError) printErrorAndExit(output); 156 | return output; 157 | } 158 | 159 | function safeRun(command, skipError) { 160 | if (dryRunMode) { 161 | console.log(`[${command}]`.grey, 'DRY RUN'.magenta); 162 | } else { 163 | return run(command, skipError); 164 | } 165 | } 166 | 167 | function safeRm(...args) { 168 | if (dryRunMode) console.log(`[rm ${args.join(' ')}]`.grey, 'DRY RUN'.magenta); 169 | else rm(args); 170 | } 171 | 172 | /** 173 | * Npm's `package.json` 'repository.url' could be set to one of three forms: 174 | * git@github.com:/.git 175 | * git+https://github.com//.git 176 | * or just / 177 | * @returns [, ] array 178 | */ 179 | function getOwnerAndRepo(url) { 180 | let match = url.match(/^git@github\.com:(.*)\.git$/); 181 | match = match || url.match(/^git\+https:\/\/github\.com\/(.*)\.git$/); 182 | const gitUrlBase = match && match[1]; 183 | return (gitUrlBase || url).split('/'); 184 | } 185 | 186 | function runAndGitRevertOnError(cmd) { 187 | const res = exec(cmd); 188 | if (res.code !== 0) { 189 | // if error, then revert and exit 190 | console.log(`"${cmd}" command failed, reverting version bump`.red); 191 | run('git reset HEAD .'); 192 | run('git checkout package.json'); 193 | console.log('Version bump reverted'.red); 194 | printErrorAndExit(res.output); 195 | } 196 | } 197 | 198 | function releaseAdRepo(repo, srcFolder, tmpFolder, vVersion) { 199 | if (!repo || !srcFolder || !tmpFolder || !vVersion) { 200 | printErrorAndExit('Bug error. Create github issue: releaseAdRepo - One of parameters is not set.'); 201 | } 202 | 203 | rm('-rf', tmpFolder); 204 | run(`git clone ${repo} ${tmpFolder}`); 205 | pushd(tmpFolder); 206 | rm('-rf', ls(tmpFolder).filter(file => file !== '.git')); // delete all but `.git` dir 207 | cp('-R', srcFolder, tmpFolder); 208 | safeRun('git add -A .'); 209 | safeRun(`git commit -m "Release ${vVersion}"`); 210 | safeRun(`git tag -a --message=${vVersion} ${vVersion}`); 211 | safeRun('git push --follow-tags'); 212 | popd(); 213 | safeRm('-rf', tmpFolder); 214 | } 215 | 216 | function release({ type, preid, npmTagName }) { 217 | // ensure git repo has no pending changes 218 | if (exec('git diff-index --name-only HEAD --').output.length) { 219 | printErrorAndExit('Git repository must be clean'); 220 | } 221 | console.info('No pending changes'.cyan); 222 | 223 | // ensure git repo last version is fetched 224 | exec('git fetch'); 225 | if (/behind (.*)\]/.test(exec('git status -sb').output)) { 226 | printErrorAndExit(`Your repo is behind by ${RegExp.$1} commits`); 227 | } 228 | console.info('Current with latest changes from remote'.cyan); 229 | 230 | // version bumping 231 | const oldVersion = npmjson.version; 232 | let newVersion; 233 | 234 | if (argv.skipVersionBumping) { 235 | newVersion = oldVersion; 236 | console.log('The version remains the same '.yellow + oldVersion.green); 237 | } else { 238 | if (type === undefined) { 239 | newVersion = oldVersion; // --preid 240 | } else if (['major', 'minor', 'patch'].indexOf(type) >= 0) { 241 | newVersion = semver.inc(oldVersion, type); 242 | } else { 243 | newVersion = type; // '', 'Release specific version' 244 | } 245 | 246 | if (preid) { 247 | newVersion = semver.inc(newVersion, 'pre', preid); 248 | } 249 | 250 | npmjson.version = newVersion; 251 | `${JSON.stringify(npmjson, null, 2)}\n`.to(packagePath); 252 | 253 | console.log('The version changed from '.cyan + oldVersion.green + ' to '.cyan + newVersion.green); 254 | safeRun('git add package.json'); 255 | } 256 | 257 | if (npmjson.scripts) { // do not throw if there are no 'scripts' at all 258 | if (npmjson.scripts.test && !argv.skipTests) { 259 | // npm run test 260 | // this step is placed after version bumping 261 | // for the case when documents are been built in "npm run test" script 262 | console.log('Running: '.cyan + '"npm run test"'.green); 263 | config.silent = !skipBuildStep; 264 | runAndGitRevertOnError('npm run test'); 265 | config.silent = !argv.verbose; 266 | console.log('Completed: '.cyan + '"npm run test"'.green); 267 | } 268 | 269 | // npm run build 270 | if (argv.onlyDocs && docsBuild) { 271 | console.log('Running: '.cyan + 'docs-build'.green); 272 | runAndGitRevertOnError('npm run docs-build'); 273 | console.log('Completed: '.cyan + 'docs-build'.green); 274 | } else { 275 | if (npmjson.scripts.build && !skipBuildStep) { 276 | console.log('Running: '.cyan + 'build'.green); 277 | runAndGitRevertOnError('npm run build'); 278 | console.log('Completed: '.cyan + 'build'.green); 279 | } else { 280 | console.log('Skipping "npm run build" step.'.yellow); 281 | } 282 | } 283 | } // if (npmjson.scripts) 284 | 285 | const vVersion = `v${newVersion}`; 286 | const versionAndNotes = notesForRelease = notesForRelease ? `${vVersion} ${notesForRelease}` : vVersion; 287 | 288 | // generate changelog 289 | // within mt-changelog at this stage `./bin/changelog` is already built and tested 290 | const changelogCmd = isWithinMtChangelog ? './bin/changelog' : 'changelog'; 291 | 292 | const changelog = path.join(repoRoot, 'CHANGELOG.md'); 293 | const changelogAlpha = path.join(repoRoot, 'CHANGELOG-alpha.md'); 294 | let changelogOutput, changelogArgs; 295 | if (preid) { 296 | changelogOutput = changelogAlpha; 297 | changelogArgs = ''; 298 | } else { 299 | changelogOutput = changelog; 300 | changelogArgs = '--exclude-pre-releases'; 301 | } 302 | 303 | if (isCommitsChangelogUsed) { 304 | let changelogAlphaRemovedFlag = false; 305 | if (test('-e', changelogAlpha)) { 306 | rm('-rf', changelogAlpha); 307 | changelogAlphaRemovedFlag = true; 308 | } 309 | 310 | run(`${changelogCmd} --title="${versionAndNotes}" --out ${changelogOutput} ${changelogArgs}`); 311 | safeRun(`git add ${changelog}`); 312 | if (preid || changelogAlphaRemovedFlag) { 313 | safeRun(`git add -A ${changelogAlpha}`); 314 | } 315 | 316 | console.log('The changelog has been generated'.cyan); 317 | } 318 | 319 | safeRun(`git commit -m "Release ${vVersion}"`); 320 | 321 | // tag and release 322 | console.log('Tagging: '.cyan + vVersion.green); 323 | if (isCommitsChangelogUsed) { 324 | notesForRelease = run(`${changelogCmd} --title="${versionAndNotes}" -s`); 325 | safeRun(`changelog --title="${versionAndNotes}" ${changelogArgs} -s | git tag -a -F - ${vVersion}`); 326 | } else { 327 | safeRun(`git tag -a --message="${versionAndNotes}" ${vVersion}`); 328 | } 329 | safeRun('git push --follow-tags'); 330 | console.log('Tagged: '.cyan + vVersion.green); 331 | 332 | if (!argv.onlyDocs) { 333 | const repo = npmjson.repository.url || npmjson.repository; 334 | 335 | // publish to GitHub 336 | if (githubToken) { 337 | console.log(`GitHub token found ${githubToken}`.green); 338 | console.log('Publishing to GitHub: '.cyan + vVersion.green); 339 | 340 | if (dryRunMode) { 341 | console.log(`[publishing to GitHub]`.grey, 'DRY RUN'.magenta); 342 | } else { 343 | const [githubOwner, githubRepo] = getOwnerAndRepo(repo); 344 | 345 | request({ 346 | uri: `https://api.github.com/repos/${githubOwner}/${githubRepo}/releases`, 347 | method: 'POST', 348 | json: true, 349 | body: { 350 | tag_name: vVersion, // eslint-disable-line camelcase 351 | name: `${githubRepo} ${vVersion}`, 352 | body: notesForRelease, 353 | draft: false, 354 | prerelease: !!preid 355 | }, 356 | headers: { 357 | Authorization: `token ${githubToken}`, 358 | 'User-Agent': 'release-script (https://github.com/alexkval/release-script)' 359 | } 360 | }, function (err, res, body) { 361 | if (err) { 362 | console.log('API request to GitHub, error has occured:'.red); 363 | console.log(err); 364 | console.log('Skipping GitHub releasing'.yellow); 365 | } else if (res.statusMessage === 'Unauthorized') { 366 | console.log(`GitHub token ${githubToken} is wrong`.red); 367 | console.log('Skipping GitHub releasing'.yellow); 368 | } else { 369 | console.log(`Published at ${body.html_url}`.green); 370 | } 371 | }); 372 | } 373 | } 374 | 375 | // npm 376 | if (isPrivate) { 377 | console.log('Package is private, skipping npm release'.yellow); 378 | } else { 379 | console.log('Releasing: '.cyan + 'npm package'.green); 380 | 381 | const npmPublishCmd = preid ? `npm publish --tag ${npmTagName}` : 'npm publish'; 382 | 383 | // publishing just /altPkgRootFolder content 384 | if (altPkgRootFolder) { 385 | // prepare custom `package.json` without `scripts` and `devDependencies` 386 | // because it already has been saved, we safely can use the same object 387 | delete npmjson.files; // because otherwise it would be wrong 388 | delete npmjson.scripts; 389 | delete npmjson.devDependencies; 390 | delete npmjson['release-script']; // this also doesn't belong to output 391 | const regexp = new RegExp(altPkgRootFolder + '\\/?'); 392 | npmjson.main = npmjson.main.replace(regexp, ''); // remove folder part from path 393 | `${JSON.stringify(npmjson, null, 2)}\n`.to(path.join(altPkgRootFolder, 'package.json')); 394 | 395 | pushd(altPkgRootFolder); 396 | safeRun(npmPublishCmd); 397 | popd(); 398 | } else { 399 | safeRun(npmPublishCmd); 400 | } 401 | 402 | console.log('Released: '.cyan + 'npm package'.green); 403 | } 404 | // bower (separate repo) 405 | if (isPrivate) { 406 | console.log('Package is private, skipping bower release'.yellow); 407 | } else if (bowerRepo) { 408 | console.log('Releasing: '.cyan + 'bower package'.green); 409 | releaseAdRepo(bowerRepo, bowerRoot, tmpBowerRepo, vVersion); 410 | console.log('Released: '.cyan + 'bower package'.green); 411 | } else { 412 | console.log('The "bowerRepo" is not set in package.json. Skipping Bower package publishing.'.yellow); 413 | } 414 | // bower (register package if bower.json is located in this repo) 415 | if (bowerjson) { 416 | if (bowerjson.private) { 417 | console.log('Package is private, skipping bower registration'.yellow); 418 | } else if (!which('bower')) { 419 | console.log('Bower is not installed globally, skipping bower registration'.yellow); 420 | } else { 421 | console.log('Registering: '.cyan + 'bower package'.green); 422 | 423 | const output = safeRun(`bower register ${bowerjson.name} ${gh(repo).clone_url}`, true); 424 | 425 | if (output.indexOf('EDUPLICATE') > -1) { 426 | console.log('Package already registered'.yellow); 427 | } else if (output.indexOf('registered successfully') < 0) { 428 | console.log('Error registering package, details:'.red); 429 | console.log(output.red); 430 | } else { 431 | console.log('Registered: '.cyan + 'bower package'.green); 432 | } 433 | } 434 | } 435 | } 436 | 437 | // documents site 438 | if (!isPrivate && docsRepo && (!preid || argv.onlyDocs)) { 439 | console.log('Releasing: '.cyan + 'documents site'.green); 440 | releaseAdRepo(docsRepo, docsRoot, tmpDocsRepo, vVersion); 441 | console.log('Documents site has been released'.green); 442 | } 443 | 444 | console.log('Version '.cyan + `v${newVersion}`.green + ' released!'.cyan); 445 | } 446 | 447 | 448 | //------------------------------------------------------------------------------ 449 | // 450 | release(versionBumpOptions); 451 | --------------------------------------------------------------------------------