Skip to content

feat(deploy): add github pages deploy #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ with two sub-routes. The file structure will be as follows:
...
```

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

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


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

### Creating a build

Expand All @@ -166,7 +166,7 @@ ng test

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

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


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

### Deploying the app via GitHub Pages

The CLI currently comes bundled with [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages).

This means that you can deploy your apps quickly via:
You can deploy your apps quickly via:

```
git commit -a -m "final tweaks before deployment - what could go wrong?"
ng github-pages:deploy
ng github-pages:deploy --message "Optional commit message"
```

Checkout [angular-cli-github-pages addon](https://github.com/IgorMinar/angular-cli-github-pages) docs for more info.
This will do the following:

- creates GitHub repo for the current project if one doesn't exist
- rebuilds the app at the current `HEAD`
- creates a local `gh-pages` branch if one doesn't exist
- moves your app to the `gh-pages` branch and creates a commit
- edit the base tag in index.html to support github pages
- pushes the `gh-pages` branch to github
- returns back to the original `HEAD`

Creating the repo requires a token from github, and the remaining functionality
relies on ssh authentication for all git operations that communicate with github.com.
To simplify the authentication, be sure to [setup your ssh keys](https://help.github.com/articles/generating-ssh-keys/).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda piggybacking on this PR tbh... but I noticed this is outdated as the previous section covers it. Can put it on a different PR if needed.

### Linting and formatting code

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

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


### Formatting code

You can format your app code by running `ng format`.
This will use the `format` npm script that in generated projects uses `clang-format`.

You can modify the `format` script in `package.json` to run whatever formatting tool
you prefer and `ng format` will still run it.

### Support for offline applications

By default a file `manifest.appcache` will be generated which lists all files included in
Expand Down
1 change: 0 additions & 1 deletion addon/ng2/blueprints/ng2/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
},
"devDependencies": {
"angular-cli": "0.0.*",
"angular-cli-github-pages": "^0.2.0",
"clang-format": "^1.0.35",
"codelyzer": "0.0.12",
"ember-cli-inject-live-reload": "^1.4.0",
Expand Down
184 changes: 184 additions & 0 deletions addon/ng2/commands/github-pages-deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import * as Command from 'ember-cli/lib/models/command';
import * as SilentError from 'silent-error';
import { exec } from 'child_process';
import * as Promise from 'ember-cli/lib/ext/promise';
import * as chalk from 'chalk';
import * as fs from 'fs';
import * as fse from 'fs-extra';
import * as path from 'path';
import * as BuildTask from 'ember-cli/lib/tasks/build';
import * as win from 'ember-cli/lib/utilities/windows-admin';
import * as CreateGithubRepo from '../tasks/create-github-repo';

const fsReadFile = Promise.denodeify(fs.readFile);
const fsWriteFile = Promise.denodeify(fs.writeFile);
const fsReadDir = Promise.denodeify(fs.readdir);
const fsCopy = Promise.denodeify(fse.copy);

module.exports = Command.extend({
name: 'github-pages:deploy',
aliases: ['gh-pages:deploy'],
description: 'Build the test app for production, commit it into a git branch, setup GitHub repo and push to it',
works: 'insideProject',

availableOptions: [
{
name: 'message',
type: String,
default: 'new gh-pages version',
description: 'The commit message to include with the build, must be wrapped in quotes.'
}, {
name: 'environment',
type: String,
default: 'production',
description: 'The Angular environment to create a build for'
}, {
name: 'branch',
type: String,
default: 'gh-pages',
description: 'The git branch to push your pages to'
}, {
name: 'skip-build',
type: Boolean,
default: false,
description: 'Skip building the project before deploying'
}, {
name: 'gh-token',
type: String,
default: '',
description: 'Github token'
}, {
name: 'gh-username',
type: String,
default: '',
description: 'Github username'
}],

run: function(options, rawArgs) {
var ui = this.ui;
var root = this.project.root;
var execOptions = {
cwd: root
};
var projectName = this.project.pkg.name;

let initialBranch;

// declared here so that tests can stub exec
const execPromise = Promise.denodeify(exec);

var buildTask = new BuildTask({
ui: this.ui,
analytics: this.analytics,
project: this.project
});

var buildOptions = {
environment: options.environment,
outputPath: 'dist/'
};

var createGithubRepoTask = new CreateGithubRepo({
ui: this.ui,
analytics: this.analytics,
project: this.project
});

var createGithubRepoOptions = {
projectName,
ghUsername: options.ghUsername,
ghToken: options.ghToken
};

return checkForPendingChanges()
.then(build)
.then(saveStartingBranchName)
.then(createGitHubRepoIfNeeded)
.then(checkoutGhPages)
.then(copyFiles)
.then(updateBaseHref)
.then(addAndCommit)
.then(returnStartingBranch)
.then(pushToGitRepo)
.then(printProjectUrl);

function checkForPendingChanges() {
return execPromise('git status --porcelain')
.then(stdout => {
if (/\w+/m.test(stdout)) {
let msg = 'Uncommitted file changes found! Please commit all changes before deploying.';
return Promise.reject(new SilentError(msg));
}
});
}

function build() {
if (options.skipBuild) return Promise.resolve();
return win.checkWindowsElevation(ui)
.then(() => buildTask.run(buildOptions));
}

function saveStartingBranchName() {
return execPromise('git rev-parse --abbrev-ref HEAD')
.then((stdout) => initialBranch = stdout);
}

function createGitHubRepoIfNeeded() {
return execPromise('git remote -v')
.then(function(stdout) {
if (!/origin\s+(https:\/\/|git@)github\.com/m.test(stdout)) {
return createGithubRepoTask.run(createGithubRepoOptions);
}
});
}

function checkoutGhPages() {
return execPromise(`git checkout ${options.branch}`)
.catch(createGhPagesBranch)
}

function createGhPagesBranch() {
return execPromise(`git checkout --orphan ${options.branch}`)
.then(() => execPromise('git rm --cached -r .', execOptions))
.then(() => execPromise('git add .gitignore', execOptions))
.then(() => execPromise('git clean -f -d', execOptions))
.then(() => execPromise(`git commit -m \"initial ${options.branch} commit\"`));
}

function copyFiles() {
return fsReadDir('dist')
.then((files) => Promise.all(files.map((file) => fsCopy(path.join('dist', file), path.join('.', file)))))
}

function updateBaseHref() {
let indexHtml = path.join(root, 'index.html');
return fsReadFile(indexHtml, 'utf8')
.then((data) => data.replace(/<base href="\/">/g, `<base href="/${projectName}/>"`))
.then((data) => fsWriteFile(indexHtml, data, 'utf8'));
}

function addAndCommit() {
return execPromise('git add .', execOptions)
.then(() => execPromise(`git commit -m "${options.message}"`))
.catch(() => Promise.reject(new SilentError('No changes found. Deployment skipped.')));
}

function returnStartingBranch() {
return execPromise(`git checkout ${initialBranch}`);
}

function pushToGitRepo(committed) {
return execPromise(`git push origin ${options.branch}`);
}

function printProjectUrl() {
return execPromise('git remote -v')
.then((stdout) => {

let userName = stdout.match(/origin\s+(?:https:\/\/|git@)github\.com(?:\:|\/)([^\/]+)/m)[1].toLowerCase();
ui.writeLine(chalk.green(`Deployed! Visit https://${userName}.github.io/${projectName}/`));
ui.writeLine('Github pages might take a few minutes to show the deployed site.');
});
}
}
});
3 changes: 2 additions & 1 deletion addon/ng2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ module.exports = {
'format': require('./commands/format'),
'version': require('./commands/version'),
'completion': require('./commands/completion'),
'doc': require('./commands/doc')
'doc': require('./commands/doc'),
'github-pages-deploy': require('./commands/github-pages-deploy')
};
}
};
77 changes: 77 additions & 0 deletions addon/ng2/tasks/create-github-repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as Promise from 'ember-cli/lib/ext/promise';
import * as Task from 'ember-cli/lib/models/task';
import * as SilentError from 'silent-error';
import { exec } from 'child_process';
import * as https from 'https';

module.exports = Task.extend({
run: function(commandOptions) {
var ui = this.ui;
let promise;

// declared here so that tests can stub exec
const execPromise = Promise.denodeify(exec);

if (/.+/.test(commandOptions.ghToken) && /\w+/.test(commandOptions.ghUsername)) {
promise = Promise.resolve({
ghToken: commandOptions.ghToken,
ghUsername: commandOptions.ghUsername
});
} else {
ui.writeLine("\nIn order to deploy this project via GitHub Pages, we must first create a repository for it.");
ui.writeLine("It's safer to use a token than to use a password, so you will need to create one.\n");
ui.writeLine("Go to the following page and click 'Generate new token'.");
ui.writeLine("https://github.com/settings/tokens\n");
ui.writeLine("Choose 'public_repo' as scope and then click 'Generate token'.\n");
promise = ui.prompt([
{
name: 'ghToken',
type: 'input',
message: 'Please enter GitHub token you just created (used only once to create the repo):',
validate: function(token) {
return /.+/.test(token);
}
}, {
name: 'ghUsername',
type: 'input',
message: 'and your GitHub user name:',
validate: function(userName) {
return /\w+/.test(userName);
}
}]);
}

return promise
.then((answers) => {
return new Promise(function(resolve, reject) {
var postData = JSON.stringify({
'name': commandOptions.projectName
});

var req = https.request({
hostname: 'api.github.com',
port: 443,
path: '/user/repos',
method: 'POST',
headers: {
'Authorization': `token ${answers.ghToken}`,
'Content-Type': 'application/json',
'Content-Length': postData.length,
'User-Agent': 'angular-cli-github-pages'
}
});

req.on('response', function(response) {
if (response.statusCode === 201) {
resolve(execPromise(`git remote add origin [email protected]:${answers.ghUsername}/${commandOptions.projectName}.git`))
} else {
reject(new SilentError(`Failed to create GitHub repo. Error: ${response.statusCode} ${response.statusMessage}`));
}
});

req.write(postData);
req.end();
});
});
}
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"mocha": "^2.4.5",
"object-assign": "^4.0.1",
"rewire": "^2.5.1",
"sinon": "^1.17.3",
"through": "^2.3.8",
"tslint": "^3.6.0",
"walk-sync": "^0.2.6"
Expand Down
Loading