├── CONTRIBUTING.md ├── other ├── src.eslintignore └── src.eslintrc ├── .eslintrc ├── .eslintignore ├── .gitignore ├── .npmignore ├── .travis.yml ├── .editorconfig ├── bin └── publish-latest ├── LICENSE ├── CODE_OF_CONDUCT.md ├── scripts └── publish-latest.sh ├── package.json ├── src ├── index.js └── index.test.js └── README.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /other/src.eslintignore: -------------------------------------------------------------------------------- 1 | *.test.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "kentcdodds/test" 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | local-examples 5 | other 6 | -------------------------------------------------------------------------------- /other/src.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "kentcdodds", 3 | "env": { 4 | "browser": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | *.iml 4 | *.DS_Store 5 | 6 | node_modules 7 | coverage 8 | dist 9 | nohup.out 10 | 11 | *.ignored.* 12 | *.ignored/ 13 | *.ignored 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | coverage 4 | local-examples 5 | demo 6 | .editorconfig 7 | .gitignore 8 | .travis.yml 9 | CONTRIBUTING.md 10 | karma.conf.js 11 | webpack.config.js 12 | other 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | branches: 7 | only: 8 | - master 9 | notifications: 10 | email: false 11 | node_js: 12 | - "node" 13 | before_install: 14 | - npm i -g npm@^3.0.0 15 | before_script: 16 | - npm prune 17 | script: 18 | - npm run eslint 19 | - npm t 20 | - npm run check-coverage 21 | after_success: 22 | - npm run report-coverage 23 | - npm run semantic-release 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # all files 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | max_line_length = 120 15 | 16 | [*.js] 17 | quote_type = single 18 | curly_bracket_next_line = false 19 | spaces_around_operators = true 20 | spaces_around_brackets = inside 21 | indent_brace_style = BSD KNF 22 | 23 | # HTML 24 | [*.html] 25 | quote_type = double 26 | -------------------------------------------------------------------------------- /bin/publish-latest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var programVersion = require('../package.json').version; 5 | var publishLatest = require('../dist/index'); 6 | 7 | program 8 | .version(programVersion) 9 | .option('-e, --user-email [email]', 'User email to use for the release commit (defaults to author/first contributor email)') 10 | .option('-n, --user-name [name]', 'User name to use for the release commit (defaults to author/first contributor name)') 11 | .option('-b, --branch [name]', 'The branch to push the latest to (defaults to `latest`)') 12 | .option('-u, --url [url]', 'The git URL to publish to (defaults to project git url)') 13 | .option('-r, --release-version [version]', 'Version to release (defaults to package.json version)') 14 | .option('-a, --add "[file1 dir1 file2]"', 'Files to add (defaults to `package.json dist`)') 15 | .option('-t, --temp-branch [name]', 'Temp branch used for preparing the release (defaults to tmp/travis)') 16 | .parse(process.argv); 17 | 18 | publishLatest(program); 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 12 | 13 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 14 | 15 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) 16 | -------------------------------------------------------------------------------- /scripts/publish-latest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # exit with non-zero exit code if there are failures 3 | 4 | RELEASE_VERSION=$1 5 | USER_EMAIL=$2 6 | USER_NAME=$3 7 | LATEST_BRANCH=$4 8 | FILES_TO_ADD=$5 9 | GIT_URL=$6 10 | TMP_BRANCH=$7 11 | 12 | _echo () { 13 | echo "> PL >>" ${@//https:\/\/*@/https:\/\/token-hidden@} 14 | } 15 | 16 | run_git () { 17 | if [ ! "$DRY_RUN" ]; then 18 | _echo "running git $*" 19 | git "$@" 20 | else 21 | _echo "DRY_RUN of git $*" 22 | fi 23 | } 24 | 25 | _echo "setting remote" 26 | run_git remote set-url origin $GIT_URL 27 | 28 | _echo "checking for $LATEST_BRANCH branch" 29 | if run_git ls-remote origin | grep -sw "$LATEST_BRANCH" 2>&1>/dev/null; then 30 | _echo "$LATEST_BRANCH exists on remote" 31 | else 32 | _echo "$LATEST_BRANCH does not exist on remote... creating it..." 33 | run_git checkout -b $LATEST_BRANCH 34 | run_git push origin $LATEST_BRANCH 35 | fi 36 | 37 | _echo "checking out temp branch $TMP_BRANCH" 38 | run_git checkout -b "$TMP_BRANCH" 39 | 40 | _echo "adding $FILES_TO_ADD" 41 | run_git add $FILES_TO_ADD -f 42 | 43 | _echo "committing with $RELEASE_VERSION as $USER_NAME with $USER_EMAIL" 44 | run_git commit -m v$RELEASE_VERSION --no-verify --author="$USER_NAME <$USER_EMAIL>" 45 | 46 | _echo "checking out $LATEST_BRANCH" 47 | run_git remote set-branches --add origin $LATEST_BRANCH # required because travis clones with --branch=master 48 | run_git fetch origin 49 | run_git checkout $LATEST_BRANCH 50 | 51 | _echo "merging built files" 52 | run_git merge $TMP_BRANCH -m v$RELEASE_VERSION -X theirs 53 | 54 | _echo "pushing" 55 | run_git push origin HEAD:$LATEST_BRANCH -f 56 | 57 | _echo "done!" 58 | 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "publish-latest", 3 | "description": "Script to publish generated files to a `latest` branch", 4 | "version": "0.0.0-semantically-released.0", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "npm run test:watch", 8 | "prebuild": "rm -rf dist && mkdir dist", 9 | "build": "cd src && babel index.js -d ../dist && cd ..", 10 | "commit": "git-cz", 11 | "eslint": "eslint src/ -c other/src.eslintrc --ignore-path other/src.eslintignore && eslint src/*.test.js", 12 | "check-coverage": "istanbul check-coverage --statements 80 --branches 58 --functions 90 --lines 80", 13 | "report-coverage": "cat ./coverage/lcov.info | codecov", 14 | "test:watch": "mocha src/*.test.js -w --compilers js:babel/register", 15 | "test": "istanbul cover -x *.test.js _mocha -- -R spec src/index.test.js --compilers js:babel/register", 16 | "prepublish": "npm run build", 17 | "postpublish": "bin/publish-latest", 18 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/kentcdodds/publish-latest.git" 23 | }, 24 | "keywords": [ 25 | "git", 26 | "ci", 27 | "release", 28 | "publish" 29 | ], 30 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/kentcdodds/publish-latest/issues" 34 | }, 35 | "bin": { 36 | "publish-latest": "bin/publish-latest" 37 | }, 38 | "homepage": "https://github.com/kentcdodds/publish-latest#readme", 39 | "devDependencies": { 40 | "babel": "5.8.23", 41 | "chai": "3.3.0", 42 | "chai-string": "1.1.3", 43 | "codecov.io": "0.1.6", 44 | "commitizen": "1.0.5", 45 | "cz-conventional-changelog": "1.1.2", 46 | "eslint": "1.5.1", 47 | "eslint-config-kentcdodds": "4.0.0", 48 | "eslint-plugin-mocha": "0.5.1", 49 | "ghooks": "0.3.2", 50 | "istanbul": "0.3.21", 51 | "lodash": "3.10.1", 52 | "mocha": "2.3.3", 53 | "semantic-release": "4.3.5", 54 | "validate-commit-msg": "1.0.0" 55 | }, 56 | "config": { 57 | "ghooks": { 58 | "commit-msg": "./node_modules/.bin/validate-commit-msg && npm run eslint && npm t && npm run check-coverage && echo 'pre-commit checks good 👍'" 59 | } 60 | }, 61 | "czConfig": { 62 | "path": "node_modules/cz-conventional-changelog/" 63 | }, 64 | "dependencies": { 65 | "commander": "2.8.1", 66 | "parse-author": "0.2.0", 67 | "path-here": "1.1.0", 68 | "repo-path-parse": "1.0.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const pathCwd = require('path-here'); 2 | const pathHere = pathCwd.dir(__dirname); 3 | const parseAuthor = require('parse-author'); 4 | const parseRepo = require('repo-path-parse'); 5 | 6 | const exec = require('child_process').exec; 7 | 8 | module.exports = publishLatest; 9 | 10 | function publishLatest(options, cb) { 11 | const script = pathHere('..', 'scripts', 'publish-latest.sh'); 12 | const args = getArgs(options); 13 | exec(`${script} ${args.join(' ')}`, {shell: '/bin/bash'}, (error, stdout, stderr) => { 14 | stdout = hideToken(stdout); 15 | stderr = hideToken(stderr); 16 | error = cleanError(error); 17 | const result = !!error ? null : {type: 'Success', stdout, stderr}; 18 | if (stderr) { 19 | console.error('stderr:', stderr); // eslint-disable-line no-console 20 | } 21 | if (stdout) { 22 | console.log('stdout:', stdout); // eslint-disable-line no-console 23 | } 24 | cb && cb(error, result); 25 | }); 26 | } 27 | 28 | function getArgs(options) { 29 | /* eslint complexity:[2, 8] */ 30 | const packageJson = require(pathCwd('package.json')); 31 | const author = parseAuthor(getAuthorData(packageJson)); 32 | const gitUrl = options.url || getGitUrl(packageJson, process.env.BOT_GH_TOKEN || process.env.GH_TOKEN); 33 | 34 | return [ 35 | options.releaseVersion || packageJson.version, // version 36 | options.userEmail || author.email, // email 37 | quote(options.userName || author.name), // name 38 | options.branch || 'latest', // latest branch 39 | quote(getFilesToAdd(options.add)), // files to add 40 | gitUrl, // git remote url 41 | options.tempBranch || 'travis/temp', // tmp branch 42 | ]; 43 | } 44 | 45 | function quote(string) { 46 | return `"${string}"`; 47 | } 48 | 49 | function getFilesToAdd(filesToAdd) { 50 | if (filesToAdd) { 51 | if (Array.isArray(filesToAdd)) { 52 | return filesToAdd.join(' '); 53 | } else if (typeof filesToAdd === 'string') { 54 | return filesToAdd; 55 | } 56 | } else { 57 | return 'dist package.json'; 58 | } 59 | } 60 | 61 | function getAuthorData(pkg) { 62 | if (pkg.author) { 63 | return pkg.author; 64 | } else if (pkg.contributors && pkg.contributors.length) { 65 | return pkg.contributors.length; 66 | } else { 67 | return ''; 68 | } 69 | } 70 | 71 | function getGitUrl(pkg, token) { 72 | let url = pkg.repository; 73 | if (url.url) { 74 | url = url.url; 75 | } 76 | const parsed = parseRepo(url); 77 | const owner = parsed.owner; 78 | const repo = parsed.repo; 79 | if (!owner || !repo) { 80 | throw new Error(`I couldn't parse your repository url: ${hideToken(url)}`); 81 | } 82 | const tokenInsertion = token ? token + '@' : ''; 83 | return `https://${tokenInsertion}github.com/${owner}/${repo}`; 84 | } 85 | 86 | function cleanError(error) { 87 | if (!error) { 88 | return error; 89 | } 90 | if (typeof error === 'string') { 91 | error = hideToken(error); 92 | } else { 93 | ['details', 'message', 'stack'].forEach(item => { 94 | if (error[item]) { 95 | error[item] = hideToken(error[item]); 96 | } 97 | }); 98 | } 99 | return error; 100 | } 101 | 102 | function hideToken(string) { 103 | return string.replace(/https\:\/\/.*?@/g, 'https://token-hidden@'); 104 | } 105 | 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # publish-latest (UNMAINTAINED) 2 | 3 | See the beginning edit of [this blogpost](https://medium.com/@kentcdodds/why-i-don-t-commit-generated-files-to-master-a4d76382564) for an explination of why this package is no longer necessary. 4 | 5 | Status: 6 | [![npm version](https://img.shields.io/npm/v/publish-latest.svg?style=flat-square)](https://www.npmjs.org/package/publish-latest) 7 | [![npm downloads](https://img.shields.io/npm/dm/publish-latest.svg?style=flat-square)](http://npm-stat.com/charts.html?package=publish-latest&from=2015-09-01) 8 | [![Build Status](https://img.shields.io/travis/kentcdodds/publish-latest.svg?style=flat-square)](https://travis-ci.org/kentcdodds/publish-latest) 9 | [![Code Coverage](https://img.shields.io/codecov/c/github/kentcdodds/publish-latest.svg?style=flat-square)](https://codecov.io/github/kentcdodds/publish-latest) 10 | 11 | A script to allow you to publish the generated built files of your project to a specific branch in your repository. 12 | 13 | I use this as part of my travis build with `semantic-release`. I run the build as a `prepublish` script, then as a 14 | `postpublish` script, I run this script which commits the built files and pushes them to my `latest` branch. 15 | Then `semantic-release post` will create a release on github with that commit. 16 | 17 | ## Usage 18 | 19 | ```javascript 20 | { 21 | "scripts": { 22 | "build": "echo 'building project'", 23 | "prepublish": "npm run build", 24 | "postpublish": "publish-latest", 25 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 26 | } 27 | } 28 | ``` 29 | 30 | ### CLI Options 31 | 32 | You can pass several options to `publish-latest` to override the defaults. Here's the output of `--help` 33 | 34 | ``` 35 | $ publish-latest --help 36 | 37 | Usage: publish-latest [options] 38 | 39 | Options: 40 | 41 | -h, --help output usage information 42 | -V, --version output the version number 43 | -e, --user-email [email] User email to use for the release commit (defaults to author/first contributor email) 44 | -n, --user-name [name] User name to use for the release commit (defaults to author/first contributor name) 45 | -b, --branch [name] The branch to push the latest to (defaults to `latest`) 46 | -u, --url [url] The git URL to publish to (defaults to project git url) 47 | -r, --release-version [version] Version to release (defaults to package.json version) 48 | -a, --add "[file1 dir1 file2]" Files to add (defaults to `package.json dist`) 49 | -t, --temp-branch [name] Temp branch used for preparing the release (defaults to tmp/travis) 50 | ``` 51 | 52 | ### ENV variables 53 | 54 | If you do not specify a `url` then the script will derive one from your `package.json` and then the script will add a 55 | token to the GitHub URL so the commit can be pushed. This token comes from either `BOT_GH_TOKEN` or `GH_TOKEN`. 56 | 57 | ## UNMAINTAINED 58 | 59 | I created this tool to solve issues mentioned in my blogpost [Why I don't commit generated files to master](https://medium.com/@kentcdodds/why-i-don-t-commit-generated-files-to-master-a4d76382564), however there is a service called [unpkg.com](https://unpkg.com) which mostly solves the problems I've mentioned in that blogpost. So I no longer need this tool and will not plan on maintaining it. If you're personally seriously interested in taking it on, please let me know! 60 | 61 | ## LICENSE 62 | 63 | MIT 64 | 65 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | process.env.DRY_RUN = true; 2 | process.env.BOT_GH_TOKEN = 'super-secret-token'; 3 | 4 | const _ = require('lodash'); 5 | const expect = require('chai').expect; 6 | const publishLatest = require('./index'); 7 | 8 | 9 | describe('publishLatest', () => { 10 | it('should run with good data without error', done => { 11 | const options = getTestOptions(); 12 | publishLatest(options, (error, result) => { 13 | expect(error, 'there should be no error').to.not.exist; 14 | expect(result.stderr, 'there should have been nothing logged to standard error').to.equal(''); 15 | expect(result.stdout, 'standard out should have the expected output').to.exist; 16 | 17 | const parts = [ 18 | [`git remote set-url origin ${options.url}`, 'setting remote'], 19 | [`git checkout -b ${options.tempBranch}`, 'checking out branch'], 20 | [`git add ${options.add} -f`, 'adding files'], 21 | [`git commit -m v${options.releaseVersion} --no-verify --author=${options.userName} <${options.userEmail}>`, 22 | 'committing files'], 23 | [`git remote set-branches --add origin ${options.branch}`, 'adding remote branch'], 24 | [`git fetch origin`, 'fetching origin'], 25 | [`git checkout ${options.branch}`, 'checking out remote branch'], 26 | [`git merge ${options.tempBranch} -m v${options.releaseVersion} -X theirs`, 'merging into branch'], 27 | [`git push origin HEAD:${options.branch} -f`, 'force pushing HEAD to origin'] 28 | ]; 29 | expectStandardOutToHaveParts(result.stdout, parts); 30 | done(error); 31 | }); 32 | }); 33 | 34 | it('should infer data from the repo in which it is run and have good defaults', done => { 35 | publishLatest({}, (error, result) => { 36 | expect(error, 'there should be no error').to.not.exist; 37 | expect(result.stderr, 'there should have been nothing logged to standard error').to.equal(''); 38 | expect(result.stdout, 'standard out should have the expected output').to.exist; 39 | const parts = [ 40 | [`git remote set-url origin https://token-hidden@github.com/kentcdodds/publish-latest`, 'setting remote'], 41 | [`git checkout -b travis/temp`, 'checking out branch'], 42 | [`git add dist package.json -f`, 'adding files'], 43 | [/git commit -m v.*? --no-verify --author=Kent C. Dodds /, 'committing files'], 44 | [`git remote set-branches --add origin latest`, 'adding remote branch'], 45 | [`git fetch origin`, 'fetching origin'], 46 | [`git checkout latest`, 'checking out remote branch'], 47 | [/git merge travis\/temp -m v.*? -X theirs/, 'merging into branch'], 48 | [`git push origin HEAD:latest -f`, 'force pushing HEAD to origin'] 49 | ]; 50 | expectStandardOutToHaveParts(result.stdout, parts); 51 | done(error); 52 | }); 53 | }); 54 | }); 55 | 56 | function expectStandardOutToHaveParts(out, parts) { 57 | let index = 0; 58 | parts.forEach(part => { 59 | let message; 60 | if (Array.isArray(part)) { 61 | message = `contains ${part[1]}`; 62 | part = part[0]; 63 | } 64 | if (typeof part === 'string') { 65 | expect(out.substring(index), message || 'standard out should have specific part').to.contain(part); 66 | index = out.indexOf(part); 67 | } else { 68 | expect(out.substring(index), message || 'standard out should match a specific part').to.match(part); 69 | index = part.exec(out).index; 70 | } 71 | }); 72 | } 73 | 74 | function getTestOptions(overrides) { 75 | return _.merge({ 76 | userName: 'Luke Skywalker', 77 | userEmail: 'luke@skywalkerfamily.com', 78 | branch: 'release/latest', 79 | url: 'https://github.com/skywalker/awesome-dads', 80 | releaseVersion: '1.2.3', 81 | add: 'dist some-other-file.js package.json', 82 | tempBranch: 'tmp/travis-test' 83 | }, overrides); 84 | } 85 | --------------------------------------------------------------------------------