-
Notifications
You must be signed in to change notification settings - Fork 12k
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
+585
−20
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.'); | ||
}); | ||
} | ||
} | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); | ||
} | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.