Skip to content

Commit 331f0cf

Browse files
committed
feat(deploy): add github pages deploy (#366)
1 parent 4fe8687 commit 331f0cf

File tree

8 files changed

+585
-20
lines changed

8 files changed

+585
-20
lines changed

README.md

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

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

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

150150

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

153153
### Creating a build
154154

@@ -166,7 +166,7 @@ ng test
166166

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

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

172172

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

184184
### Deploying the app via GitHub Pages
185185

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

190188
```
191-
git commit -a -m "final tweaks before deployment - what could go wrong?"
192-
ng github-pages:deploy
189+
ng github-pages:deploy --message "Optional commit message"
193190
```
194191

195-
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+
- edit the base tag in index.html to support github pages
199+
- pushes the `gh-pages` branch to github
200+
- returns back to the original `HEAD`
201+
202+
Creating the repo requires a token from github, and the remaining functionality
203+
relies on ssh authentication for all git operations that communicate with github.com.
204+
To simplify the authentication, be sure to [setup your ssh keys](https://help.github.com/articles/generating-ssh-keys/).
196205

197206
### Linting and formatting code
198207

@@ -201,15 +210,6 @@ This will use the `lint`/`format` npm script that in generated projects uses `ts
201210

202211
You can modify the these scripts in `package.json` to run whatever tool you prefer.
203212

204-
205-
### Formatting code
206-
207-
You can format your app code by running `ng format`.
208-
This will use the `format` npm script that in generated projects uses `clang-format`.
209-
210-
You can modify the `format` script in `package.json` to run whatever formatting tool
211-
you prefer and `ng format` will still run it.
212-
213213
### Support for offline applications
214214

215215
By default a file `manifest.appcache` will be generated which lists all files included in

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: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
const fsReadFile = Promise.denodeify(fs.readFile);
14+
const fsWriteFile = Promise.denodeify(fs.writeFile);
15+
const fsReadDir = Promise.denodeify(fs.readdir);
16+
const fsCopy = Promise.denodeify(fse.copy);
17+
18+
module.exports = Command.extend({
19+
name: 'github-pages:deploy',
20+
aliases: ['gh-pages:deploy'],
21+
description: 'Build the test app for production, commit it into a git branch, setup GitHub repo and push to it',
22+
works: 'insideProject',
23+
24+
availableOptions: [
25+
{
26+
name: 'message',
27+
type: String,
28+
default: 'new gh-pages version',
29+
description: 'The commit message to include with the build, must be wrapped in quotes.'
30+
}, {
31+
name: 'environment',
32+
type: String,
33+
default: 'production',
34+
description: 'The Angular environment to create a build for'
35+
}, {
36+
name: 'branch',
37+
type: String,
38+
default: 'gh-pages',
39+
description: 'The git branch to push your pages to'
40+
}, {
41+
name: 'skip-build',
42+
type: Boolean,
43+
default: false,
44+
description: 'Skip building the project before deploying'
45+
}, {
46+
name: 'gh-token',
47+
type: String,
48+
default: '',
49+
description: 'Github token'
50+
}, {
51+
name: 'gh-username',
52+
type: String,
53+
default: '',
54+
description: 'Github username'
55+
}],
56+
57+
run: function(options, rawArgs) {
58+
var ui = this.ui;
59+
var root = this.project.root;
60+
var execOptions = {
61+
cwd: root
62+
};
63+
var projectName = this.project.pkg.name;
64+
65+
let initialBranch;
66+
67+
// declared here so that tests can stub exec
68+
const execPromise = Promise.denodeify(exec);
69+
70+
var buildTask = new BuildTask({
71+
ui: this.ui,
72+
analytics: this.analytics,
73+
project: this.project
74+
});
75+
76+
var buildOptions = {
77+
environment: options.environment,
78+
outputPath: 'dist/'
79+
};
80+
81+
var createGithubRepoTask = new CreateGithubRepo({
82+
ui: this.ui,
83+
analytics: this.analytics,
84+
project: this.project
85+
});
86+
87+
var createGithubRepoOptions = {
88+
projectName,
89+
ghUsername: options.ghUsername,
90+
ghToken: options.ghToken
91+
};
92+
93+
return checkForPendingChanges()
94+
.then(build)
95+
.then(saveStartingBranchName)
96+
.then(createGitHubRepoIfNeeded)
97+
.then(checkoutGhPages)
98+
.then(copyFiles)
99+
.then(updateBaseHref)
100+
.then(addAndCommit)
101+
.then(returnStartingBranch)
102+
.then(pushToGitRepo)
103+
.then(printProjectUrl);
104+
105+
function checkForPendingChanges() {
106+
return execPromise('git status --porcelain')
107+
.then(stdout => {
108+
if (/\w+/m.test(stdout)) {
109+
let msg = 'Uncommitted file changes found! Please commit all changes before deploying.';
110+
return Promise.reject(new SilentError(msg));
111+
}
112+
});
113+
}
114+
115+
function build() {
116+
if (options.skipBuild) return Promise.resolve();
117+
return win.checkWindowsElevation(ui)
118+
.then(() => buildTask.run(buildOptions));
119+
}
120+
121+
function saveStartingBranchName() {
122+
return execPromise('git rev-parse --abbrev-ref HEAD')
123+
.then((stdout) => initialBranch = stdout);
124+
}
125+
126+
function createGitHubRepoIfNeeded() {
127+
return execPromise('git remote -v')
128+
.then(function(stdout) {
129+
if (!/origin\s+(https:\/\/|git@)github\.com/m.test(stdout)) {
130+
return createGithubRepoTask.run(createGithubRepoOptions);
131+
}
132+
});
133+
}
134+
135+
function checkoutGhPages() {
136+
return execPromise(`git checkout ${options.branch}`)
137+
.catch(createGhPagesBranch)
138+
}
139+
140+
function createGhPagesBranch() {
141+
return execPromise(`git checkout --orphan ${options.branch}`)
142+
.then(() => execPromise('git rm --cached -r .', execOptions))
143+
.then(() => execPromise('git add .gitignore', execOptions))
144+
.then(() => execPromise('git clean -f -d', execOptions))
145+
.then(() => execPromise(`git commit -m \"initial ${options.branch} commit\"`));
146+
}
147+
148+
function copyFiles() {
149+
return fsReadDir('dist')
150+
.then((files) => Promise.all(files.map((file) => fsCopy(path.join('dist', file), path.join('.', file)))))
151+
}
152+
153+
function updateBaseHref() {
154+
let indexHtml = path.join(root, 'index.html');
155+
return fsReadFile(indexHtml, 'utf8')
156+
.then((data) => data.replace(/<base href="\/">/g, `<base href="/${projectName}/>"`))
157+
.then((data) => fsWriteFile(indexHtml, data, 'utf8'));
158+
}
159+
160+
function addAndCommit() {
161+
return execPromise('git add .', execOptions)
162+
.then(() => execPromise(`git commit -m "${options.message}"`))
163+
.catch(() => Promise.reject(new SilentError('No changes found. Deployment skipped.')));
164+
}
165+
166+
function returnStartingBranch() {
167+
return execPromise(`git checkout ${initialBranch}`);
168+
}
169+
170+
function pushToGitRepo(committed) {
171+
return execPromise(`git push origin ${options.branch}`);
172+
}
173+
174+
function printProjectUrl() {
175+
return execPromise('git remote -v')
176+
.then((stdout) => {
177+
178+
let userName = stdout.match(/origin\s+(?:https:\/\/|git@)github\.com(?:\:|\/)([^\/]+)/m)[1].toLowerCase();
179+
ui.writeLine(chalk.green(`Deployed! Visit https://${userName}.github.io/${projectName}/`));
180+
ui.writeLine('Github pages might take a few minutes to show the deployed site.');
181+
});
182+
}
183+
}
184+
});

addon/ng2/index.js

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"mocha": "^2.4.5",
6868
"object-assign": "^4.0.1",
6969
"rewire": "^2.5.1",
70+
"sinon": "^1.17.3",
7071
"through": "^2.3.8",
7172
"tslint": "^3.6.0",
7273
"walk-sync": "^0.2.6"

0 commit comments

Comments
 (0)