Skip to content

Instantly share code, notes, and snippets.

@chancancode
Last active July 3, 2017 06:32
Show Gist options
  • Save chancancode/6d0671b79c23b699838709cfc5521bed to your computer and use it in GitHub Desktop.
Save chancancode/6d0671b79c23b699838709cfc5521bed to your computer and use it in GitHub Desktop.
New Twiddle
import Ember from 'ember';
import { task } from 'ember-concurrency';
const { inject: { service } } = Ember;
export default Ember.Controller.extend({
github: service()
});
import Ember from 'ember';
import { task } from 'ember-concurrency';
import { E_BAD_CREDENTIALS, E_BAD_OTP } from '../services/github';
const { computed: { and, equal, not }, inject: { service } } = Ember;
export default Ember.Controller.extend({
github: service(),
username: null,
password: null,
otp: null,
hasSignIn: and('username', 'password'),
disableSignIn: not('hasSignIn'),
disableVerify: not('otp'),
step: 'credentials',
isCredentials: equal('step', 'credentials'),
isOTP: equal('step', 'otp'),
submit: task(function * () {
let {
github, username, password, otp
} = this.getProperties(
'github', 'username', 'password', 'otp'
);
try {
let result = yield github.get('login').perform(username, password, otp);
this.setProperties({
username: null,
password: null,
otp: null,
step: 'credentials'
});
this.transitionToRoute('index');
} catch(error) {
if (error.message === E_BAD_CREDENTIALS) {
this.setProperties({
step: 'credentials',
password: null,
otp: null
});
throw new Error('Incorrect username or password.');
} else if (error.message === E_BAD_OTP) {
this.setProperties({
step: 'otp',
otp: null
});
if (otp) {
throw new Error('Two-factor authentication failed.');
} else {
return;
}
}
throw error;
}
}).restartable()
});
import Ember from 'ember';
import { task } from 'ember-concurrency';
const { computed, computed: { reads }, inject: { service } } = Ember;
export default Ember.Controller.extend({
github: service(),
majorVersion: computed('model.finalVersion', function () {
let version = this.get('model.finalVersion');
let match = version.match(/^([0-9])\./);
if (match) {
let [_, major] = match;
return parseInt(major, 10);
} else {
return 3;
}
}),
minorVersion: computed('model.finalVersion', function () {
let version = this.get('model.finalVersion');
let match = version.match(/^([0-9])\.([0-9]+)\./);
if (match) {
let [_, major, minor] = match;
return parseInt(minor, 10);
} else {
return 0;
}
}),
version: computed('majorVersion', 'minorVersion', function () {
let { majorVersion, minorVersion } = this.getProperties('majorVersion', 'minorVersion');
return `${majorVersion}.${minorVersion}`;
}),
betaVersion: computed('majorVersion', 'minorVersion', function () {
let { majorVersion, minorVersion } = this.getProperties('majorVersion', 'minorVersion');
return `${majorVersion}.${minorVersion+1}`;
}),
previousVersion: computed('majorVersion', 'minorVersion', function () {
let { majorVersion, minorVersion } = this.getProperties('majorVersion', 'minorVersion');
return `${majorVersion}.${minorVersion-1}`;
}),
releaseDate: reads('model.cycleEstimatedFinishDate'),
title: computed('version', 'betaVersion', function() {
let { version, betaVersion } = this.getProperties('version', 'betaVersion');
return `Ember ${version} and ${betaVersion} Beta Released`;
}),
authors: reads('github.user.name'),
pullRequestTitle: computed('version', function() {
let version = this.get('version');
return `Ember ${version} Release Blog Post`;
}),
branch: computed('version', function() {
let version = this.get('version');
let name = `${version}-release-blog-post`;
return name.toLowerCase().replace(/[^a-z0-9]/g, '-');
}),
path: computed('version', function() {
let { version, releaseDate } = this.getProperties('version', 'releaseDate');
let filename = `${releaseDate}-ember-${version}-released`;
filename = filename.toLowerCase().replace(/[^a-z0-9]/g, '-');
return `source/blog/${filename}.md`;
}),
emberjsSignOff: '@chancancode',
emberDataSignOff: '@bmac',
emberCLISignOff: '@rwjblue',
websiteSignOff: '@locks, @mixonic',
submit: task(function * () {
let {
github, branch, path, title, authors, version, betaVersion, previousVersion, pullRequestTitle
} = this.getProperties(
'github', 'branch', 'path', 'title', 'authors', 'version', 'betaVersion', 'previousVersion', 'pullRequestTitle'
);
try {
yield github.get('createBranch').perform('emberjs', 'website', branch);
} catch(error) {
throw new Error(`Cannot create branch "${path}": ${error.message}`);
}
try {
let content = `---
title: ${title}
author: ${authors}
tags: Releases
---
Today the Ember project is releasing version ${version}.0 of Ember.js, Ember Data, and Ember CLI.
This release kicks off the ${betaVersion} beta cycle for all sub-projects. We encourage our
community (especially addon authors) to help test these beta builds and report
any bugs before they are published as a final release in six weeks' time. The
[ember-try](https://github.com/ember-cli/ember-try) addon is a great way to
continuously test your projects against the latest Ember releases.
You can read more about our general release process here:
- [Release Dashboard](http://emberjs.com/builds/)
- [The Ember Release Cycle](http://emberjs.com/blog/2013/09/06/new-ember-release-process.html)
- [The Ember Project](http://emberjs.com/blog/2015/06/16/ember-project-at-2-0.html)
- [Ember LTS Releases](http://emberjs.com/blog/2016/02/25/announcing-embers-first-lts.html)
---
## Ember.js
Ember.js is the core framework for building ambitious web applications.
### Changes in Ember.js ${version}
Ember.js ${version} is an incremental, backwards compatible release of Ember with
bugfixes, performance improvements, and minor deprecations.
#### Deprecations in Ember ${version}
Deprecations are added to Ember.js when an API will be removed at a later date.
Each deprecation has an entry in the deprecation guide describing the migration
path to more stable API. Deprecated public APIs are not removed until a major
release of the framework.
Consider using the
[ember-cli-deprecation-workflow](https://github.com/mixonic/ember-cli-deprecation-workflow)
addon if you would like to upgrade your application without immediately addressing
deprecations.
Two new deprecations are introduces in Ember.js ${version}:
* TODO
* TODO
For more details on changes in Ember.js ${version}, please review the
[Ember.js ${version}.0 release page](https://github.com/emberjs/ember.js/releases/tag/v${version}.0).
### Upcoming Changes in Ember.js ${betaVersion}
Ember.js ${betaVersion} will introduce two new features:
* TODO
* TODO
#### Deprecations in Ember.js ${betaVersion}
Two new deprecations are introduces in Ember.js ${betaVersion}:
* TODO
* TODO
For more details on the upcoming changes in Ember.js ${betaVersion}, please review the
[Ember.js ${betaVersion}.0-beta.1 release page](https://github.com/emberjs/ember.js/releases/tag/v${betaVersion}.0-beta.1).
---
## Ember Data
Ember Data is the official data persistence library for Ember.js applications.
### Changes in Ember Data ${version}
#### Deprecations in Ember Data ${version}
Two new deprecations are introduces in Ember Data ${version}:
* TODO
* TODO
For more details on changes in Ember Data ${version}, please review the
[Ember Data ${version}.0 release page](https://github.com/emberjs/data/releases/tag/v${version}.0).
### Upcoming changes in Ember Data ${betaVersion}
#### Deprecations in Ember Data ${betaVersion}
For more details on the upcoming changes in Ember Data ${betaVersion}, please review the
[Ember Data ${betaVersion}.0-beta.1 release page](https://github.com/emberjs/data/releases/tag/v${betaVersion}.0-beta.1).
---
## Ember CLI
Ember CLI is the command line interface for managing and packaging Ember.js
applications.
### Upgrading Ember CLI
You may upgrade Ember CLI separately from Ember.js and Ember Data! To upgrade
your projects using \`yarn\` run:
\`\`\`
yarn upgrade ember-cli
\`\`\`
To upgrade your projects using \`npm\` run:
\`\`\`
npm install --save-dev ember-cli
\`\`\`
After running the
upgrade command run \`ember init\` inside of the project directory to apply the
blueprint changes. You can preview those changes for [applications](https://github.com/ember-cli/ember-new-output/compare/v${previousVersion}.0...v${version}.0)
and [addons](https://github.com/ember-cli/ember-addon-output/compare/v${previousVersion}.0...v${version}.0).
### Changes in Ember CLI ${version}
#### Deprecations in Ember Data ${version}
Two new deprecations are introduces in Ember Data ${version}:
* TODO
* TODO
For more details on the changes in Ember CLI ${version} and detailed upgrade
instructions, please review the [Ember CLI ${version}.0 release page](https://github.com/ember-cli/ember-cli/releases/tag/v${version}.0).
### Upcoming Changes in Ember CLI ${betaVersion}
#### Deprecations in Ember CLI ${betaVersion}
For more details on the changes in Ember CLI ${betaVersion}.0-beta.1 and detailed upgrade
instructions, please review the [Ember CLI ${betaVersion}.0-beta.1 release page](https://github.com/ember-cli/ember-cli/releases/tag/v${betaVersion}.0-beta.1).
## Thank You!
As a community-driven open-source project with an ambitious scope, each of
these releases serve as a reminder that the Ember project would not have been
possible without your continued support. We are extremely grateful to our
contributors for their efforts.`;
yield github.get('createFile').perform('emberjs', 'website', path, pullRequestTitle, content, { branch });
} catch(error) {
throw new Error(`Cannot create file "${path}": ${error.message}`);
}
try {
let {
emberjsSignOff, emberDataSignOff, emberCLISignOff, websiteSignOff
} = this.getProperties(
'emberjsSignOff', 'emberDataSignOff', 'emberCLISignOff', 'websiteSignOff'
);
let content = `
- [Rendered](https://github.com/emberjs/website/blob/${branch}/${path})
- Preview
## Sign-offs
- [ ] Ember.js (${emberjsSignOff})
- [ ] Ember Data (${emberDataSignOff})
- [ ] Ember CLI (${emberCLISignOff})
- [ ] Website (${websiteSignOff})
## Checklist
- Ember
- [ ] Blog
- [ ] v${version}.0
- [ ] v${betaVersion}.0-beta.1
- Ember Data
- [ ] Blog
- [ ] v${version}.0
- [ ] v${betaVersion}.0-beta.1
- Ember CLI
- [ ] Blog
- [ ] v${version}.0
- [ ] v${betaVersion}.0-beta.1
See https://github.com/emberjs/website/pull/2824/files for inspiration.`;
let url = yield github.get('createPullRequest').perform('emberjs', 'website', pullRequestTitle, branch, 'master', content);
return url;
} catch(error) {
throw new Error(`Cannot create pull request "${title}": ${error.message}`);
}
}).restartable()
});
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
this.route('login');
this.route('release-blog-post');
});
export default Router;
import Ember from 'ember';
const { inject: { service } } = Ember;
export default Ember.Route.extend({
github: service(),
async beforeModel() {
try {
await this.get('github.validateToken').perform();
} catch(error) {
this.replaceWith('login');
}
}
});
import Ember from 'ember';
const { inject: { service } } = Ember;
export default Ember.Route.extend({
github: service(),
async model() {
let content = await this.get('github.readFile').perform('ember-learn', 'builds', 'app/fixtures/ember/beta.js');
content = content.replace('export default', 'return');
return eval(`(function() {${content}})()`);
}
});
import Ember from 'ember';
import { isUnauthorizedError } from 'ember-ajax/errors';
import { task } from 'ember-concurrency';
const { inject: { service } } = Ember;
const TOKEN_NAME = 'Ember Core Dashboard';
const GITHUB_HOST = 'https://api.github.com';
const GITHUB_HEADERS = Object.freeze({
'Content-Type': 'application/vnd.github.v3+json'
});
export const E_BAD_CREDENTIALS = 'BAD CREDENTIALS';
export const E_BAD_OTP = 'BAD OTP';
export const E_BAD_TOKEN = 'BAD TOKEN';
export default Ember.Service.extend({
ajax: service(),
token: '739daa7d3f65f4753582d4a5d063396fd82c933a', // Hardcode a token here to make development easier
user: null,
login: task(function * (username, password, otp) {
this.setProperties({
token: null,
user: null
});
let ajax = this.get('ajax');
try {
let url = `${GITHUB_HOST}/authorizations`;
let auth = btoa(`${username}:${password}`);
let headers = {
'Authorization': `Basic ${auth}`,
'X-GitHub-OTP': otp,
...GITHUB_HEADERS
};
while (true) {
let response = yield ajax.raw(url, { headers });
let token = response.payload.find(t => t.app.name === TOKEN_NAME);
if (token) {
url = `${GITHUB_HOST}/authorizations/${token.id}`;
yield ajax.raw(url, {
method: 'DELETE',
headers
});
break;
}
let match = response.jqXHR.getResponseHeader("Link").match(/<(.+)>; rel="next"/);
if (match) {
url = match[1];
} else {
break;
}
}
url = `${GITHUB_HOST}/authorizations`;
let response = yield ajax.raw(url, {
method: 'POST',
headers,
data: JSON.stringify({
scopes: ['repo'],
note: 'Ember Core Dashboard'
})
});
this.set('token', response.payload.token);
return this.get('validateToken').perform();
} catch(error) {
if (isUnauthorizedError(error.response)) {
if (error.jqXHR.getResponseHeader('X-GitHub-OTP')) {
throw new Error(E_BAD_OTP);
} else {
throw new Error(E_BAD_CREDENTIALS);
}
}
throw error;
}
}).restartable(),
validateToken: task(function * () {
let { ajax, token } = this.getProperties('ajax', 'token');
try {
if (!token) {
throw new Error('Missing token');
}
let url = `${GITHUB_HOST}/user`;
let headers = {
'Authorization': `token ${token}`,
...GITHUB_HEADERS
};
let user = yield ajax.request(url, { headers });
this.set('user', user);
return true;
} catch(error) {
this.set('token', null);
throw new Error(E_BAD_TOKEN);
}
}).restartable(),
createBranch: task(function * (owner, repo, branch, { base = 'master' } = {}) {
let { ajax, token } = this.getProperties('ajax', 'token');
let url = `${GITHUB_HOST}/repos/${owner}/${repo}/git/refs/heads/${base}`;
let headers = {
'Authorization': `token ${token}`,
...GITHUB_HEADERS
};
let { object: { sha } } = yield ajax.request(url, { headers });
url = `${GITHUB_HOST}/repos/${owner}/${repo}/git/refs`;
return ajax.request(url, {
method: 'POST',
headers,
data: JSON.stringify({
ref: `refs/heads/${branch}`,
sha
})
});
}),
readFile: task(function * (owner, repo, path) {
let { ajax, token } = this.getProperties('ajax', 'token');
let url = `${GITHUB_HOST}/repos/${owner}/${repo}/contents/${path}`;
let headers = {
'Authorization': `token ${token}`,
...GITHUB_HEADERS
};
let response = yield ajax.request(url, { headers });
return atob(response.content);
}),
createFile: task(function * (owner, repo, path, message, content, { branch } = {}) {
let { ajax, token } = this.getProperties('ajax', 'token');
let url = `${GITHUB_HOST}/repos/${owner}/${repo}/contents/${path}`;
let headers = {
'Authorization': `token ${token}`,
...GITHUB_HEADERS
};
return ajax.request(url, {
method: 'PUT',
headers,
data: JSON.stringify({
path,
message,
branch,
content: btoa(content)
})
});
}),
createPullRequest: task(function * (owner, repo, title, head, base, body) {
let { ajax, token } = this.getProperties('ajax', 'token');
let url = `${GITHUB_HOST}/repos/${owner}/${repo}/pulls`;
let headers = {
'Authorization': `token ${token}`,
...GITHUB_HEADERS
};
return ajax.request(url, {
method: 'POST',
headers,
data: JSON.stringify({
title,
head,
base,
body
})
});
}),
});
* {
box-sizing: border-box;
}
body {
margin: 12px 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 12pt;
color: #333;
background: #f9f9f9;
}
h1 {
margin: 32px 0 24px;
color: #999;
font-weight: lighter;
font-size: 12px;
text-align: center;
}
h2 {
margin: 0.67em 0;
font-weight: 300;
font-size: 24px;
text-align: center;
}
a {
color: #0366d6;
text-decoration: none;
}
a:hover, a:active {
text-decoration: underline;
outline-width: 0;
}
.narrow {
width: 340px;
margin: auto;
}
.success, .error {
padding: 15px 20px;
margin: 0 auto;
margin-bottom: 10px;
font-size: 13px;
border-style: solid;
border-width: 1px;
border-radius: 5px;
}
.success {
color: #165c26;
background-color: #dcffe4;
border-color: rgba(27,31,35,0.15);
}
.error {
color: #86181d;
background-color: #ffdce0;
border: 1px solid rgba(27,31,35,0.15);
}
.box {
background-color: #fff;
border: 1px solid #d8dee2;
border-radius: 5px;
padding: 20px;
font-size: 14px;
}
label {
display: block;
font-weight: 600;
}
input {
display: block;
width: 100%;
margin-top: 7px;
margin-bottom: 15px;
padding: 6px 8px;
font-size: 14px;
line-height: 20px;
color: #24292e;
vertical-align: middle;
border: 1px solid #d1d5da;
border-radius: 3px;
outline: none;
box-shadow: inset 0 1px 2px rgba(27,31,35,0.075);
}
input:focus {
border-color: #2188ff;
outline: none;
box-shadow: inset 0 1px 2px rgba(27,31,35,0.075), 0 0 0 0.2em rgba(3,102,214,0.3);
}
button {
display: block;
width: 100%;
text-align: center;
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-repeat: repeat-x;
background-position: -1px -1px;
background-size: 110% 110%;
border: 1px solid rgba(27,31,35,0.2);
border-radius: 0.25em;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 6px 0px;
color: #24292e;
background-color: #eff3f6;
background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%);
}
button:focus {
box-shadow: 0 0 0 0.2em rgba(3,102,214,0.3);
}
button:disabled {
color: rgba(36,41,46,0.4);
background-color: #eff3f6;
background-image: none;
border-color: rgba(27,31,35,0.2);
box-shadow: none;
cursor: default;
}
button.primary {
margin-top: 20px;
color: #fff;
background-color: #28a745;
background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%);
}
button.primary:focus {
box-shadow: 0 0 0 0.2em rgba(52,208,88,0.3);
}
button.primary:disabled {
color: rgba(255,255,255,0.75);
background-color: #94d3a2;
background-image: none;
border-color: rgba(27,31,35,0.2);
box-shadow: none;
}
button.danger {
color: #cb2431;
background-color: #fafbfc;
}
<h1>Ember Core Dashboard</h1>
{{outlet}}
<div class="narrow">
<h2>Workflows</h2>
<div class="box">
{{link-to "Release Blog Post" "release-blog-post" tagName="button"}}
<button>Something Else</button>
<button>One More Thing™</button>
</div>
</div>
<div class="narrow">
{{#if isCredentials}}
<h2>Sign in to GitHub</h2>
{{else if isOTP}}
<h2>Two-factor authentication</h2>
{{/if}}
{{#if submit.last.isError}}
<p class="error">{{submit.last.error.message}}</p>
{{/if}}
<div class="box">
<form>
{{#if isCredentials}}
<label>
Username or email address
{{input value=username}}
</label>
<label>
Password
{{input type="password" value=password}}
</label>
{{#if submit.isIdle}}
<button class="primary" disabled={{disableSignIn}} {{action (perform submit)}}>Sign in</button>
{{else}}
<button class="primary" disabled>Signing in...</button>
{{/if}}
{{else if isOTP}}
<p>
<label>
Authentication code
{{input value=otp}}
</label>
</p>
<p>
{{#if submit.isIdle}}
<button class="primary" disabled={{disableVerify}} {{action (perform submit)}}>Verify</button>
{{else}}
<button class="primary" disabled>Verifying...</button>
{{/if}}
</p>
{{/if}}
</form>
</div>
</div>
<div class="narrow">
<h2>Release Blog Post</h2>
{{#if submit.last.isError}}
<p class="error">{{submit.last.error.message}}</p>
{{/if}}
{{#if submit.last.isSuccessful}}
{{#with submit.last.value as |pr|}}
<p class="success">
<strong>Success!</strong>
Pull request
<a href="{{pr.html_url}}" target="_blank">#{{pr.number}}</a>
created.
</p>
{{/with}}
{{else}}
<div class="box">
<form>
<label>
Version
{{input value=version}}
</label>
<label>
Beta Version
{{input value=betaVersion}}
</label>
<label>
Previous Version
{{input value=previousVersion}}
</label>
<label>
Release Date
{{input value=releaseDate}}
</label>
<label>
Blog Post Title
{{input value=title}}
</label>
<label>
Blog Post Authors
{{input value=authors}}
</label>
<label>
Pull Request Title
{{input value=pullRequestTitle}}
</label>
<label>
Branch name
{{input value=branch}}
</label>
<label>
Ember.js Sign Off
{{input value=emberjsSignOff}}
</label>
<label>
Ember Data Sign Off
{{input value=emberDataSignOff}}
</label>
<label>
Ember CLI Sign Off
{{input value=emberCLISignOff}}
</label>
<label>
Website Sign Off
{{input value=websiteSignOff}}
</label>
{{#if submit.isIdle}}
<button class="primary" disabled={{disableSignIn}} {{action (perform submit)}}>Submit</button>
{{else}}
<button class="primary" disabled>Submitting...</button>
{{/if}}
</form>
</div>
{{/if}}
</div>
{
"version": "0.12.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.12.0",
"ember-template-compiler": "2.12.0",
"ember-testing": "2.12.0"
},
"addons": {
"ember-ajax": "3.0.0",
"ember-concurrency": "0.8.7",
"ember-data": "2.12.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment