├── .gitignore ├── .jshintrc ├── .travis.yml ├── Brocfile.js ├── LICENSE.md ├── README.md ├── index.js ├── lib └── commands │ └── deploy.js ├── package.json └── testem.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | sudo: false 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | before_install: 11 | - "npm config set spin false" 12 | - "npm install -g npm@^2" 13 | 14 | install: 15 | - npm install 16 | 17 | script: 18 | - npm test 19 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* global require, module */ 3 | 4 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | var app = new EmberAddon(); 7 | 8 | // Use `app.import` to add additional libraries to the generated 9 | // output files. 10 | // 11 | // If you need to use different assets in different 12 | // environments, specify an object as the first parameter. That 13 | // object's keys should be the environment name and the values 14 | // should be the asset to use in that environment. 15 | // 16 | // If the library that you are including contains AMD or ES6 17 | // modules that you would like to import into your application 18 | // please specify an object with the list of modules as keys 19 | // along with the exports of each module as its value. 20 | 21 | module.exports = app.toTree(); 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-cli-github-pages 2 | 3 | Angular CLI addon for deploying apps to GitHub pages. 4 | 5 | If you need to quickly deploy and redeploy a small Angular 2 app via GitHub pages this angular-cli 6 | addon is for you! 7 | 8 | This addon does the following: 9 | 10 | - creates GitHub repo for the current project if one doesn't exist 11 | - rebuilds the app at the current `HEAD` 12 | - creates a local `gh-pages` branch if one doesn't exist 13 | - moves your app to the `gh-pages` branch and creates a commit 14 | - pushes the `gh-pages` branch to github 15 | - returns back to the original `HEAD` 16 | 17 | 18 | ## Installation & Setup 19 | 20 | This addon has the following prerequisites: 21 | 22 | - Node.js 4.x 23 | - SSH keys setup for authentication with GitHub (see "Authentication" section below) 24 | - Angular project created via [angular-cli](https://github.com/angular/angular-cli) 25 | 26 | To install this addon all you need to do is install angular-cli-github-pages via npm: 27 | 28 | ```sh 29 | npm install --save-dev angular-cli-github-pages 30 | ``` 31 | 32 | ## Usage 33 | 34 | Once that's done, you can checkout the branch you want to create the gh-page 35 | from (likely master) and run the command to build and commit it. 36 | 37 | Then run `ng github-pages:deploy` in order to rebuild gh-pages branch and deploy it. 38 | 39 | ```sh 40 | git checkout master 41 | ng github-pages:deploy --message "Optional commit message" 42 | ``` 43 | 44 | ## Authentication 45 | 46 | This addon relies on ssh authentication for all git operations that communicate with github.com. 47 | To simplify the authentication, be sure to [setup your ssh keys](https://help.github.com/articles/generating-ssh-keys/). 48 | 49 | For repository creation, the addon needs to make a single https call to the GitHub api, for this 50 | user name and password are requested when the repo is being created. 51 | 52 | *Note: Two factor authentication is currently not supported by this addon.* 53 | 54 | 55 | ## Authors 56 | 57 | - [Igor Minar](http://twitter.com/IgorMinar) 58 | - Based on [ember-cli-github-pages](https://github.com/poetic/ember-cli-github-pages) by [Jake Craige](http://twitter.com/jakecraige) 59 | 60 | ## License 61 | 62 | [Licensed under the MIT license](http://www.opensource.org/licenses/mit-license.php) 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-cli-github-pages', 6 | 7 | includedCommands: function() { 8 | return { 9 | 'github-pages:deploy': require('./lib/commands/deploy') 10 | }; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /lib/commands/deploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var exec = require('child_process').exec; 4 | var RSVP = require('rsvp'); 5 | var https = require('https'); 6 | var inquire = require('inquirer'); 7 | var fs = require('fs'); 8 | var fse = require('fs-extra'); 9 | var path = require('path'); 10 | 11 | 12 | module.exports = { 13 | name: 'github-pages:deploy', 14 | aliases: ['gh-pages:deploy'], 15 | description: 'Build the test app for production, commit it into a git branch, setup GitHub repo and push to it', 16 | works: 'insideProject', 17 | 18 | availableOptions: [{ 19 | name: 'message', 20 | type: String, 21 | default: 'new gh-pages version', 22 | description: 'The commit message to include with the build, must be wrapped in quotes.' 23 | }, { 24 | name: 'environment', 25 | type: String, 26 | default: 'production', 27 | description: 'The Angular environment to create a build for' 28 | }, { 29 | name: 'branch', 30 | type: String, 31 | default: 'gh-pages', 32 | description: 'The git branch to push your pages to' 33 | }], 34 | 35 | run: function(options, rawArgs) { 36 | var ui = this.ui; 37 | var root = this.project.root; 38 | var execOptions = { cwd: root }; 39 | var projectName = this.project.pkg.name; 40 | var projectUrl = ''; 41 | var fsReadDir = RSVP.denodeify(fs.readdir); 42 | var fsCopy = RSVP.denodeify(fse.copy); 43 | 44 | function buildApp() { 45 | var env = options.environment; 46 | return runCommand('ng build --environment=' + env, execOptions); 47 | } 48 | 49 | function checkForPendingChanges() { 50 | return runCommand('git status --porcelain', execOptions).then(stdout => { 51 | if (/\w+/m.test(stdout)) { 52 | return RSVP.reject('Uncommitted file changes found! Please commit all changes before deploying.'); 53 | } 54 | }); 55 | } 56 | 57 | function checkoutGhPages() { 58 | return runCommand('git checkout ' + options.branch, execOptions); 59 | } 60 | 61 | function copy() { 62 | return fsReadDir('dist').then( 63 | (files) => RSVP.all(files.map((file) => fsCopy(path.join('dist', file), path.join('.', file))))); 64 | } 65 | 66 | function addAndCommit() { 67 | return runCommand('git add favicon.ico index.html app* vendor', execOptions). 68 | then(runCommandLater('git commit -m "' + options.message + '"', execOptions)). 69 | then(function() { return true; }, function() { return false; }); 70 | } 71 | 72 | function createGitHubRepoIfNeeded() { 73 | return runCommand('git remote -v'). 74 | then(function(stdout) { 75 | 76 | if (!/origin\s+git@github\.com/m.test(stdout)) { 77 | ui.write("In order to deploy this project via GitHub Pages, we must first create a repository for it...\n"); 78 | 79 | return new RSVP.Promise(function(resolve, reject) { 80 | inquire.prompt([{ 81 | name: 'ghUserName', 82 | type: 'input', 83 | message: 'Please enter your GitHub user name:', 84 | validate: function(userName) { return /\w+/.test(userName); } 85 | }, { 86 | name: 'ghPassword', 87 | type: 'password', 88 | message: 'and your GitHub password (used only once to create the repo):', 89 | validate: function(password) { return /.+/.test(password); } 90 | }], function(answers) { 91 | 92 | projectUrl = `https://${answers.ghUserName.toLowerCase()}.github.io/${projectName}/`; 93 | 94 | var postData = JSON.stringify({ 95 | 'name' : projectName 96 | }); 97 | 98 | var req = https.request({ 99 | hostname: 'api.github.com', 100 | port: 443, 101 | path: '/user/repos', 102 | method: 'POST', 103 | auth: answers.ghUserName + ':' + answers.ghPassword, 104 | headers: { 105 | 'Content-Type': 'application/json', 106 | 'Content-Length': postData.length, 107 | 'User-Agent': 'angular-cli-github-pages' 108 | } 109 | }); 110 | 111 | req.on('response', function(response) { 112 | if (response.statusCode === 201) { 113 | resolve( 114 | runCommand(`git remote add origin git@github.com:${answers.ghUserName}/${projectName}.git`). 115 | then(runCommandLater("git checkout --orphan gh-pages")). 116 | then(runCommandLater("git rm --cached -r . ")). 117 | then(runCommandLater("git add .gitignore")). 118 | then(runCommandLater("git clean -f -d")). 119 | then(runCommandLater("git commit -m \"initial gh-pages commit\"")). 120 | then(runCommandLater("git checkout master")) 121 | ) 122 | } else { 123 | reject(`Failed to create GitHub repo. Error: ${response.statusCode} ${response.statusMessage}`); 124 | } 125 | }); 126 | 127 | req.write(postData); 128 | req.end(); 129 | }); 130 | }); 131 | } else { 132 | var userName = stdout.match(/origin\s+git@github\.com\:([^\/]+)/m)[1].toLowerCase(); 133 | projectUrl = `https://${userName}.github.io/${projectName}/`; 134 | } 135 | }); 136 | } 137 | 138 | function pushToGitRepo(committed) { 139 | if (committed) { 140 | return runCommand("git push origin gh-pages", execOptions).then(function() { return committed; }); 141 | } 142 | return committed; 143 | } 144 | 145 | function returnToPreviousCheckout(committed) { 146 | return runCommand("git checkout -", execOptions).then(function() { return committed; }); 147 | } 148 | 149 | 150 | 151 | return buildApp(). 152 | then(checkForPendingChanges). 153 | then(createGitHubRepoIfNeeded). 154 | then(checkoutGhPages). 155 | then(copy). 156 | then(addAndCommit). 157 | then(returnToPreviousCheckout). 158 | then(pushToGitRepo). 159 | then(function(committed) { 160 | //var branch = options.branch; 161 | if (committed) { 162 | ui.write(`Deployed! Visit ${projectUrl}\n`); 163 | } else { 164 | ui.write('No changes found. Deployment skipped.\n'); 165 | } 166 | }); 167 | } 168 | }; 169 | 170 | function runCommandLater() { 171 | var args = arguments; 172 | return function() { 173 | return runCommand.apply(null, args); 174 | } 175 | } 176 | 177 | function runCommand(/* child_process.exec args */) { 178 | var args = Array.prototype.slice.call(arguments); 179 | 180 | var lastIndex = args.length - 1; 181 | var lastArg = args[lastIndex]; 182 | var logOutput = false; 183 | if (typeof lastArg === 'boolean') { 184 | logOutput = lastArg; 185 | args.splice(lastIndex); 186 | } 187 | 188 | return new RSVP.Promise(function(resolve, reject) { 189 | var cb = function(err, stdout, stderr) { 190 | if (stderr) { 191 | console.log(stderr); 192 | } 193 | 194 | if (logOutput && stdout) { 195 | console.log(stdout); 196 | } 197 | 198 | if (err) { 199 | return reject(err); 200 | } 201 | 202 | return resolve(stdout); 203 | }; 204 | 205 | args.push(cb); 206 | exec.apply(exec, args); 207 | }.bind(this)); 208 | } 209 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cli-github-pages", 3 | "description": "Easily publish your projects as gh-pages via angular-cli", 4 | "version": "0.2.0", 5 | "repository": "https://github.com/IgorMinar/angular-cli-github-pages", 6 | "engines": { 7 | "node": ">= 4.1.0" 8 | }, 9 | "author": "Igor Minar ", 10 | "license": "MIT", 11 | "devDependencies": { 12 | }, 13 | "keywords": [ 14 | "ember-addon" 15 | ], 16 | "ember-addon": { 17 | "configPath": "tests/dummy/config" 18 | }, 19 | "dependencies": { 20 | "fs-extra": "^0.24.0", 21 | "inquirer": "^0.10.1", 22 | "rsvp": "^3.0.14" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------