Skip to content

Commit 9977e26

Browse files
committed
feat(deploy): add github pages deploy
1 parent cc578db commit 9977e26

File tree

6 files changed

+248
-12
lines changed

6 files changed

+248
-12
lines changed

README.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ with two sub-routes. The file structure will be as follows:
122122
...
123123
```
124124

125-
By default the cli will add the import statements for HeroList and HeroDetail to
125+
By default the cli will add the import statements for HeroList and HeroDetail to
126126
`hero-root.component.ts`:
127127

128128
```
@@ -147,7 +147,7 @@ export const CliRouteConfig = [
147147
Visiting `http://localhost:4200/hero` will show the hero list.
148148

149149

150-
There is an optional flag for `skip-router-generation` which will not add the route to the `CliRouteConfig` for the application.
150+
There is an optional flag for `skip-router-generation` which will not add the route to the `CliRouteConfig` for the application.
151151

152152
### Creating a build
153153

@@ -165,7 +165,7 @@ ng test
165165

166166
Tests will execute after a build is executed via [Karma](http://karma-runner.github.io/0.13/index.html)
167167

168-
If run with the watch argument `--watch` (shorthand `-w`) builds will run when source files have changed
168+
If run with the watch argument `--watch` (shorthand `-w`) builds will run when source files have changed
169169
and tests will run after each successful build
170170

171171

@@ -182,16 +182,24 @@ End-to-end tests are ran via [Protractor](https://angular.github.io/protractor/)
182182

183183
### Deploying the app via GitHub Pages
184184

185-
The CLI currently comes bundled with [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages).
186-
187-
This means that you can deploy your apps quickly via:
185+
You can deploy your apps quickly via:
188186

189187
```
190-
git commit -a -m "final tweaks before deployment - what could go wrong?"
191-
ng github-pages:deploy
188+
git checkout master
189+
ng github-pages:deploy --message "Optional commit message"
192190
```
193191

194-
Checkout [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages) docs for more info.
192+
This will do the following:
193+
194+
- creates GitHub repo for the current project if one doesn't exist
195+
- rebuilds the app at the current `HEAD`
196+
- creates a local `gh-pages` branch if one doesn't exist
197+
- moves your app to the `gh-pages` branch and creates a commit
198+
- pushes the `gh-pages` branch to github
199+
- returns back to the original `HEAD`
200+
201+
This functionality relies on ssh authentication for all git operations that communicate with github.com.
202+
To simplify the authentication, be sure to [setup your ssh keys](https://help.github.com/articles/generating-ssh-keys/).
195203

196204
### Linting and formatting code
197205

addon/ng2/blueprints/ng2/files/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
},
2323
"devDependencies": {
2424
"angular-cli": "0.0.*",
25-
"angular-cli-github-pages": "^0.2.0",
2625
"clang-format": "^1.0.35",
2726
"codelyzer": "0.0.12",
2827
"ember-cli-inject-live-reload": "^1.4.0",
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import * as Command from 'ember-cli/lib/models/command';
2+
import * as SilentError from 'silent-error';
3+
import { exec } from 'child_process';
4+
import * as Promise from 'ember-cli/lib/ext/promise';
5+
import * as chalk from 'chalk';
6+
import * as fs from 'fs';
7+
import * as fse from 'fs-extra';
8+
import * as path from 'path';
9+
import * as BuildTask from 'ember-cli/lib/tasks/build';
10+
import * as win from 'ember-cli/lib/utilities/windows-admin';
11+
import * as CreateGithubRepo from '../tasks/create-github-repo'
12+
13+
// import * as https from 'https';
14+
// import * as inquire from 'inquirer';
15+
16+
module.exports = Command.extend({
17+
name: 'github-pages:deploy',
18+
aliases: ['gh-pages:deploy'],
19+
description: 'Build the test app for production, commit it into a git branch, setup GitHub repo and push to it',
20+
works: 'insideProject',
21+
22+
availableOptions: [
23+
{
24+
name: 'message',
25+
type: String,
26+
default: 'new gh-pages version',
27+
description: 'The commit message to include with the build, must be wrapped in quotes.'
28+
}, {
29+
name: 'environment',
30+
type: String,
31+
default: 'production',
32+
description: 'The Angular environment to create a build for'
33+
}, {
34+
name: 'branch',
35+
type: String,
36+
default: 'gh-pages',
37+
description: 'The git branch to push your pages to'
38+
}],
39+
40+
run: function(options, rawArgs) {
41+
var ui = this.ui;
42+
var root = this.project.root;
43+
var execOptions = {
44+
cwd: root
45+
};
46+
var projectName = this.project.pkg.name;
47+
48+
var fsReadFile = Promise.denodeify(fs.readFile);
49+
var fsWriteFile = Promise.denodeify(fs.writeFile);
50+
var fsReadDir = Promise.denodeify(fs.readdir);
51+
var fsCopy = Promise.denodeify(fse.copy);
52+
var execPromise = Promise.denodeify(exec);
53+
54+
let currentBranchName;
55+
56+
var buildTask = new BuildTask({
57+
ui: this.ui,
58+
analytics: this.analytics,
59+
project: this.project
60+
});
61+
62+
var buildOptions = {
63+
environment: options.environment,
64+
outputPath: 'dist/'
65+
};
66+
67+
var createGithubRepoTask = new CreateGithubRepo({
68+
ui: this.ui,
69+
analytics: this.analytics,
70+
project: this.project
71+
});
72+
73+
var createGithubRepoOptions = {
74+
projectName
75+
};
76+
77+
return checkForPendingChanges()
78+
.then(() => win.checkWindowsElevation(this.ui))
79+
.then(() => buildTask.run(buildOptions))
80+
.then(saveStartingBranchName)
81+
.then(createGitHubRepoIfNeeded)
82+
.then(checkoutGhPages)
83+
.then(copyFiles)
84+
.then(updateBaseHref)
85+
.then(addAndCommit)
86+
.then(returnStartingBranch)
87+
.then(pushToGitRepo)
88+
.then(printProjectUrl);
89+
90+
function checkForPendingChanges() {
91+
return execPromise('git status --porcelain')
92+
.then(stdout => {
93+
if (/\w+/m.test(stdout)) {
94+
let msg = 'Uncommitted file changes found! Please commit all changes before deploying.';
95+
return Promise.reject(new SilentError(msg));
96+
}
97+
});
98+
}
99+
100+
function saveStartingBranchName() {
101+
return execPromise('git rev-parse --abbrev-ref HEAD')
102+
.then((stdout) => currentBranchName = stdout);
103+
}
104+
105+
function createGitHubRepoIfNeeded() {
106+
return execPromise('git remote -v')
107+
.then(function(stdout) {
108+
if (!/origin\s+git@github\.com/m.test(stdout)) {
109+
return createGithubRepoTask.run(createGithubRepoOptions)
110+
});
111+
}
112+
113+
function checkoutGhPages() {
114+
return execPromise(`git checkout ${options.branch}`)
115+
.catch(createGhPagesBranch)
116+
}
117+
118+
function createGhPagesBranch() {
119+
return execPromise(`git checkout --orphan ${options.branch}`)
120+
.then(() => execPromise('git rm --cached -r .', execOptions))
121+
.then(() => execPromise('git add .gitignore', execOptions))
122+
.then(() => execPromise('git clean -f -d', execOptions))
123+
.then(() => execPromise('git commit -m \"initial gh-pages commit\"'));
124+
}
125+
126+
function copyFiles() {
127+
return fsReadDir('dist')
128+
.then((files) => Promise.all(files.map((file) => fsCopy(path.join('dist', file), path.join('.', file)))))
129+
}
130+
131+
function updateBaseHref() {
132+
let indexHtml = path.join(root, 'index.html');
133+
return fsReadFile(indexHtml, 'utf8')
134+
.then((data) => data.replace(/<base href="\/">/g, `<base href="/${projectName}/>"`))
135+
.then((data) => fsWriteFile(indexHtml, data, 'utf8'));
136+
}
137+
138+
function addAndCommit() {
139+
return execPromise('git add .', execOptions)
140+
.then(() => execPromise(`git commit -m "${options.message}"`))
141+
.catch(() => Promise.reject(new SilentError('No changes found. Deployment skipped.')));
142+
}
143+
144+
function returnStartingBranch() {
145+
return execPromise(`git checkout ${currentBranchName}`);
146+
}
147+
148+
function pushToGitRepo(committed) {
149+
return execPromise(`git push origin ${options.branch}`);
150+
}
151+
152+
function printProjectUrl() {
153+
return execPromise('git remote -v')
154+
.then((stdout) => {
155+
let userName = stdout.match(/origin\s+git@github\.com\:([^\/]+)/m)[1].toLowerCase();
156+
ui.writeLine(chalk.green(`Deployed! Visit https://${userName}.github.io/${projectName}/`));
157+
ui.writeLine('Github pages might take a few minutes to show the deployed site.');
158+
})
159+
}
160+
}
161+
});

addon/ng2/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* jshint node: true */
1+
/* jshint node: true */
22
'use strict';
33

44
module.exports = {
@@ -14,7 +14,8 @@ module.exports = {
1414
'format': require('./commands/format'),
1515
'version': require('./commands/version'),
1616
'completion': require('./commands/completion'),
17-
'doc': require('./commands/doc')
17+
'doc': require('./commands/doc'),
18+
'github-pages-deploy': require('./commands/github-pages-deploy')
1819
};
1920
}
2021
};

addon/ng2/tasks/create-github-repo.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as Promise from 'ember-cli/lib/ext/promise';
2+
import * as Task from 'ember-cli/lib/models/task';
3+
import { exec } from 'child_process';
4+
import * as https from 'https';
5+
import * as inquire from 'inquirer';
6+
7+
module.exports = Task.extend({
8+
run: function(commandOptions) {
9+
var ui = this.ui;
10+
var execPromise = Promise.denodeify(exec);
11+
12+
ui.writeLine("\nIn order to deploy this project via GitHub Pages, we must first create a repository for it.");
13+
ui.writeLine("It's safer to use a token than to use a password, so you will need to create one.\n");
14+
ui.writeLine("Go to the following page and click 'Generate new token'.");
15+
ui.writeLine("https://github.com/settings/tokens\n");
16+
ui.writeLine("Choose 'public_repo' as scope and then click 'Generate token'.\n");
17+
18+
return new Promise(function(resolve, reject) {
19+
inquire.prompt([
20+
{
21+
name: 'ghToken',
22+
type: 'input',
23+
message: 'Please enter GitHub token you just created (used only once to create the repo):',
24+
validate: function(token) {
25+
return /.+/.test(token);
26+
}
27+
}, {
28+
name: 'ghUserName',
29+
type: 'input',
30+
message: 'and your GitHub user name:',
31+
validate: function(userName) {
32+
return /\w+/.test(userName);
33+
}
34+
}], function(answers) {
35+
36+
var postData = JSON.stringify({
37+
'name': commandOptions.projectName
38+
});
39+
40+
var req = https.request({
41+
hostname: 'api.github.com',
42+
port: 443,
43+
path: '/user/repos',
44+
method: 'POST',
45+
headers: {
46+
'Authorization': `token ${answers.ghToken}`,
47+
'Content-Type': 'application/json',
48+
'Content-Length': postData.length,
49+
'User-Agent': 'angular-cli-github-pages'
50+
}
51+
});
52+
53+
req.on('response', function(response) {
54+
if (response.statusCode === 201) {
55+
resolve(execPromise(`git remote add origin [email protected]:${answers.ghUserName}/${commandOptions.projectName}.git`))
56+
} else {
57+
reject(`Failed to create GitHub repo. Error: ${response.statusCode} ${response.statusMessage}`);
58+
}
59+
});
60+
61+
req.write(postData);
62+
req.end();
63+
});
64+
});
65+
}
66+
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"ember-cli": "2.4.2",
4141
"exit": "^0.1.2",
4242
"fs-extra": "^0.26.6",
43+
"inquirer": "^0.10.1",
4344
"leek": "0.0.21",
4445
"lodash": "^4.6.1",
4546
"multidep": "^2.0.0",

0 commit comments

Comments
 (0)