├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .travis.yml ├── Brocfile.js ├── CHANGELOG.md ├── README.md ├── bower.json ├── config └── environment.js ├── index.js ├── lib ├── commands │ ├── assets.js │ ├── base-command-factory.js │ ├── deploy-index.js │ ├── deploy.js │ ├── index.js │ └── setup.js └── tasks │ ├── assets.js │ ├── deploy-index.js │ └── setup.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── .jshintrc │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ ├── .gitkeep │ │ │ └── app.css │ │ ├── templates │ │ │ ├── .gitkeep │ │ │ ├── application.hbs │ │ │ └── components │ │ │ │ └── .gitkeep │ │ └── views │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── .gitkeep │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── resolver.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ └── .gitkeep └── vendor └── .gitkeep /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.md] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | sudo: false 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | install: 11 | - npm install -g bower 12 | - npm install 13 | - bower install 14 | 15 | script: 16 | - npm test 17 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | 3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | var app = new EmberAddon(); 6 | 7 | // Use `app.import` to add additional libraries to the generated 8 | // output files. 9 | // 10 | // If you need to use different assets in different 11 | // environments, specify an object as the first parameter. That 12 | // object's keys should be the environment name and the values 13 | // should be the asset to use in that environment. 14 | // 15 | // If the library that you are including contains AMD or ES6 16 | // modules that you would like to import into your application 17 | // please specify an object with the list of modules as keys 18 | // along with the exports of each module as its value. 19 | 20 | module.exports = app.toTree(); 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # What's new 2 | 3 | ### 0.1.0 (Feb 23 2015) 4 | 5 | * Asymmetrical keys used to verify builds. 6 | The client no longer uses an API key to deploy builds. Instead, builds 7 | are signed with an RSA key and then sent to the host server. 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-front-end-builds 2 | 3 | ### NOTE: This package is deprecated, in favor of [ember-cli-deploy-front-end-builds-pack](https://github.com/tedconf/ember-cli-deploy-front-end-builds-pack), a new addon for use with [Ember CLI Deploy](http://ember-cli.com/ember-cli-deploy/). 4 | 5 | Easily deploy your Ember CLI app to a [front_end_builds](https://github.com/tedconf/front_end_builds) 6 | Rails backend. 7 | 8 | The deploy process involves: 9 | 10 | 1. Creating a build of your ember-cli app 11 | 1. Uploading your assets to S3 12 | 1. Notifying your Rails backend with info about the new build 13 | 14 | ## Installation 15 | 16 | ``` 17 | npm install --save-dev ember-cli-front-end-builds 18 | ``` 19 | 20 | #### Backend 21 | 22 | Please make sure you have setup your Rails backend with the 23 | [front_end_builds](https://github.com/tedconf/front_end_builds) gem. 24 | 25 | You should also setup the admin area and add your application. 26 | 27 | #### Amazon S3 28 | 29 | You'll also need to setup a S3 bucket, and allow it to be accessed publicly. Add a policy such as: 30 | 31 | ```js 32 | { 33 | "Version": "2012-10-17", 34 | "Statement": [ 35 | { 36 | "Sid": "PublicReadForGetBucketObjects", 37 | "Effect": "Allow", 38 | "Principal": "*", 39 | "Action": "s3:GetObject", 40 | "Resource": "arn:aws:s3:::MY-BUCKET-NAME/*" 41 | } 42 | ] 43 | } 44 | ``` 45 | 46 | #### Ember App Setup 47 | 48 | In your App's `Brocfile.js`, you'll want to prepend your asset fingerprinting with your S3 Bucket URL: 49 | 50 | ```js 51 | var app = new EmberApp({ 52 | 'fingerprint': { 53 | prepend: "https://s3.amazonaws.com/MY-BUCKET-NAME/dist/" 54 | } 55 | }); 56 | ``` 57 | 58 | Please note that if you are serving assets off S3 and your bucket is 59 | not in the US Standard region your prepend string should be 60 | `https://MY-BUCKET-NAME.s3.amazonaws.com/dist/`. 61 | 62 | ## Setup 63 | 64 | To get started with deploy configuration simply run: 65 | 66 | ``` 67 | ember deploy:setup 68 | ``` 69 | 70 | This will ask you a series questions about your application and write a 71 | configuration file to ``config/deploy.js``. 72 | 73 | ### Explaining the configuration file. 74 | 75 | TODO 76 | 77 | ```json 78 | { 79 | "production": { 80 | "assets": { 81 | "accessKeyId": "[your-id]", 82 | "secretAccessKey": "[your-key]", 83 | "bucket": "[your-s3-bucket]", 84 | "prefix": "[optional, dir on S3 to dump all assets]", 85 | "distPrefix": "[optional, dir on S3 to put `dist` in e.g. dist-{{SHA}}]" 86 | }, 87 | "index": { 88 | "app": "[app name, e.g. ted-ed-lesson-creator]", 89 | "endpoints": [ 90 | "[endpoint to notify, e.g. http://local.ted.com:3000/ted-ed-lesson-creator]" 91 | ] 92 | }, 93 | "build": { 94 | "environment": "production" // optional, specify if you need an ember-cli build env different from your deploy environment (e.g. use `production` for my staging deploy) 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | ## Usage 101 | 102 | To deploy your application just run 103 | 104 | ``` 105 | ember deploy --environment=ENV 106 | ``` 107 | 108 | Where ENV is the name of the environment you wish to deploy to. 109 | 110 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-front-end-builds", 3 | "dependencies": { 4 | "handlebars": "~1.3.0", 5 | "jquery": "^1.11.1", 6 | "ember": "1.7.0", 7 | "ember-data": "1.0.0-beta.10", 8 | "ember-resolver": "~0.1.7", 9 | "loader.js": "stefanpenner/loader.js#1.0.1", 10 | "ember-cli-shims": "stefanpenner/ember-cli-shims#0.0.3", 11 | "ember-cli-test-loader": "rwjblue/ember-cli-test-loader#0.0.4", 12 | "ember-load-initializers": "stefanpenner/ember-load-initializers#0.0.2", 13 | "ember-qunit": "0.1.8", 14 | "ember-qunit-notifications": "0.0.4", 15 | "qunit": "~1.15.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var commands = require('./lib/commands'); 4 | 5 | module.exports = { 6 | name: 'ember-cli-front-end-builds', 7 | 8 | includedCommands: function() { 9 | return commands; 10 | } 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /lib/commands/assets.js: -------------------------------------------------------------------------------- 1 | var AssetsTask = require('../tasks/assets'); 2 | var BaseCommand = require('./base-command-factory')(); 3 | var _ = require('underscore'); 4 | 5 | module.exports = _.extend(BaseCommand, { 6 | name: 'deploy:assets', 7 | description: 'Deploys assets to s3 in a shared dir, and a build dir', 8 | works: 'insideProject', 9 | 10 | availableOptions: [ 11 | { name: 'environment', type: String, default: 'production' } 12 | ], 13 | 14 | run: function(commandOptions, rawArgs) { 15 | var environment = commandOptions.environment; 16 | var config = this.getConfigForEnvironment(environment); 17 | 18 | var assetsTask = new AssetsTask({ 19 | ui: this.ui, 20 | analytics: this.analytics, 21 | project: this.project 22 | }); 23 | 24 | return assetsTask.run(environment, config); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /lib/commands/base-command-factory.js: -------------------------------------------------------------------------------- 1 | var DeployIndexTask = require('../tasks/deploy-index'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var chalk = require('chalk'); 5 | 6 | // Tried just exporting an object, but it was getting 7 | // mutated by each function that was _.extend'ing it. 8 | // I'm sure there's a way to export a POJO and not have 9 | // it get mutated, but I don't know how. 10 | module.exports = function() { 11 | 12 | return { 13 | getConfigForEnvironment: function(environment) { 14 | var root = process.cwd(); 15 | var config = {}; 16 | var configFile = path.join(root, 'config', 'deploy.js'); 17 | 18 | if (fs.existsSync(configFile)) { 19 | config = require(configFile)[environment]; 20 | 21 | if (!config) { 22 | this.ui.writeLine(chalk.red('Config for "' + environment + '" environment not found.\n')); 23 | this.ui.writeLine("Change your environment with the --environment flag."); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | return config; 29 | } 30 | }; 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /lib/commands/deploy-index.js: -------------------------------------------------------------------------------- 1 | var DeployIndexTask = require('../tasks/deploy-index'); 2 | var path = require('path'); 3 | var chalk = require('chalk'); 4 | var BaseCommand = require('./base-command-factory')(); 5 | var _ = require('underscore'); 6 | 7 | module.exports = _.extend(BaseCommand, { 8 | name: 'deploy:index', 9 | description: 'Notifies back end of new index', 10 | works: 'insideProject', 11 | 12 | availableOptions: [ 13 | { name: 'environment', type: String, default: 'production' }, 14 | { name: 'verbose', type: Boolean, default: false } 15 | ], 16 | 17 | run: function(commandOptions) { 18 | var environment = commandOptions.environment; 19 | var verbose = commandOptions.verbose; 20 | var config = this.getConfigForEnvironment(environment); 21 | 22 | var deployIndexTask = new DeployIndexTask({ 23 | ui: this.ui, 24 | analytics: this.analytics, 25 | project: this.project 26 | }); 27 | 28 | return deployIndexTask.run(environment, config, verbose); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /lib/commands/deploy.js: -------------------------------------------------------------------------------- 1 | var BaseCommand = require('./base-command-factory')(); 2 | var _ = require('underscore'); 3 | 4 | module.exports = _.extend(BaseCommand, { 5 | name: 'deploy', 6 | description: 'Deploys an ember-cli app', 7 | works: 'insideProject', 8 | 9 | availableOptions: [ 10 | { name: 'environment', type: String, default: 'production' } 11 | ], 12 | 13 | run: function(commandOptions, rawArgs) { 14 | var environment = commandOptions.environment; 15 | var config = this.getConfigForEnvironment(environment); 16 | var buildEnvironment = commandOptions['build-environment'] || (config.build && config.build.environment) || environment; 17 | var DeployIndexTask = require('../tasks/deploy-index'); 18 | var AssetsTask = require('../tasks/assets'); 19 | var BuildTask = this.tasks.Build; 20 | 21 | var buildTask = new BuildTask({ 22 | ui: this.ui, 23 | analytics: this.analytics, 24 | project: this.project 25 | }); 26 | 27 | var buildOptions = { 28 | environment: buildEnvironment, 29 | outputPath: "dist/", 30 | watch: false, 31 | disableAnalytics: false 32 | }; 33 | 34 | var assetsTask = new AssetsTask({ 35 | ui: this.ui, 36 | analytics: this.analytics, 37 | project: this.project 38 | }); 39 | 40 | var deployIndexTask = new DeployIndexTask({ 41 | ui: this.ui, 42 | analytics: this.analytics, 43 | project: this.project 44 | }); 45 | 46 | return buildTask.run(buildOptions) 47 | .then(function() { 48 | return assetsTask.run(environment, config); 49 | }) 50 | .then(function() { 51 | return deployIndexTask.run(environment, config); 52 | }); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /lib/commands/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "deploy": require('./deploy'), 3 | "deploy:setup": require('./setup'), 4 | "deploy:assets": require('./assets'), 5 | "deploy:index": require('./deploy-index'), 6 | }; 7 | -------------------------------------------------------------------------------- /lib/commands/setup.js: -------------------------------------------------------------------------------- 1 | var SetupTask = require('../tasks/setup'); 2 | 3 | module.exports = { 4 | name: 'deploy:setup', 5 | description: 'Setup and configure front end builds', 6 | works: 'insideProject', 7 | 8 | availableOptions: [ 9 | { name: 'environment', type: String, default: 'staging' }, 10 | { name: 'verbose', type: Boolean, default: false } 11 | ], 12 | 13 | run: function(commandOptions) { 14 | var environment = commandOptions.environment; 15 | var verbose = commandOptions.verbose; 16 | 17 | var setupTask = new SetupTask({ 18 | ui: this.ui, 19 | analytics: this.analytics, 20 | project: this.project 21 | }); 22 | 23 | return setupTask.run(environment, verbose); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/tasks/assets.js: -------------------------------------------------------------------------------- 1 | var Task = require('ember-cli/lib/models/task'); 2 | var Promise = require('ember-cli/lib/ext/promise'); 3 | var SilentError = require('silent-error'); 4 | var s3 = require('s3'); 5 | var Mustache = require('mustache'); 6 | var path = require('path'); 7 | var chalk = require('chalk'); 8 | var green = chalk.green; 9 | var execSync12 = require('child_process').execSync; 10 | var execSync10 = require('sync-exec'); 11 | 12 | var sha; 13 | 14 | if (typeof execSync12 === 'function') { 15 | // node 0.12.x 16 | sha = execSync12('git rev-parse HEAD').toString().trim(); 17 | } else { 18 | // node 0.10.x 19 | sha = execSync10('git rev-parse HEAD').stdout.trim(); 20 | } 21 | 22 | module.exports = Task.extend({ 23 | getS3ClientParams: function(assetsConfig) { 24 | var params = { 25 | maxAsyncS3: 1, // concurrency is hard (and broken in node-s3) 26 | s3Options: { 27 | accessKeyId: assetsConfig.accessKeyId, 28 | secretAccessKey: assetsConfig.secretAccessKey, 29 | } 30 | }; 31 | 32 | return params; 33 | }, 34 | 35 | getUploadDirParams: function(bucket, remoteDir) { 36 | var params = { 37 | localDir: 'dist', 38 | s3Params: { 39 | Bucket: bucket, 40 | Prefix: remoteDir 41 | } 42 | }; 43 | 44 | return params; 45 | }, 46 | 47 | /* 48 | The consuming app's deploy.json can use Mustache for dynamic portions. 49 | 50 | e.g. distPrefix: "dist-{{SHA}}" 51 | */ 52 | getDistPrefix: function(assetsConfig) { 53 | var prefix = ''; 54 | 55 | if (assetsConfig && assetsConfig.distPrefix) { 56 | var str = assetsConfig.distPrefix; 57 | prefix = Mustache.render(str, { 58 | SHA: sha 59 | }) + '/'; 60 | } 61 | 62 | return prefix; 63 | }, 64 | 65 | uploadAssets: function(client, uploadParams) { 66 | var _this = this; 67 | var s3Params = uploadParams.s3Params; 68 | var message = 'Uploading assets to ' + s3Params.Bucket + '/' + s3Params.Prefix; 69 | 70 | this.ui.pleasantProgress.start(green(message), green('.')); 71 | 72 | return new Promise(function(resolve, reject){ 73 | var uploader = client.uploadDir(uploadParams); 74 | 75 | uploader.on('error', function(err) { 76 | var errorMessage = 'Unable to sync: ' + err.stack; 77 | return reject(new SilentError(errorMessage)); 78 | }); 79 | 80 | // uploader.on('fileUploadStart', function(fullPath, fullKey) { 81 | // _this.ui.writeLine('Uploading: ' + green(fullPath)); 82 | // }); 83 | 84 | uploader.on('end', function() { 85 | _this.ui.writeLine('Done uploading assets'); 86 | _this.ui.pleasantProgress.stop(); 87 | return resolve(); 88 | }); 89 | }); 90 | }, 91 | 92 | run: function(environment, config) { 93 | var assetsConfig = config.assets; 94 | 95 | var clientParams = this.getS3ClientParams(assetsConfig); 96 | var client = s3.createClient(clientParams); 97 | 98 | var sharedRemoteDir = config.assets.prefix || ''; 99 | var sharedUploadParams = this.getUploadDirParams(config.assets.bucket, sharedRemoteDir); 100 | 101 | var distRemoteDir = this.getDistPrefix(config.assets); 102 | var distUploadParams = this.getUploadDirParams(config.assets.bucket, distRemoteDir); 103 | 104 | return this.uploadAssets(client, sharedUploadParams) 105 | .then(function() { 106 | return this.uploadAssets(client, distUploadParams); 107 | }.bind(this)); 108 | } 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /lib/tasks/deploy-index.js: -------------------------------------------------------------------------------- 1 | var Task = require('ember-cli/lib/models/task'); 2 | var Promise = require('ember-cli/lib/ext/promise'); 3 | var SilentError = require('silent-error'); 4 | var Mustache = require('mustache'); 5 | var path = require('path'); 6 | var chalk = require('chalk'); 7 | var execSync12 = require('child_process').execSync; 8 | var execSync10 = require('sync-exec'); 9 | var qs = require('qs'); 10 | var request = require('request'); 11 | var _ = require('underscore'); 12 | var fs = require('fs'); 13 | var passwdUser = require('passwd-user'); 14 | var crypto = require('crypto'); 15 | 16 | var sha, branch; 17 | 18 | if (typeof execSync12 === 'function') { 19 | // node 0.12.x 20 | sha = execSync12('git rev-parse HEAD').toString().trim(); 21 | branch = execSync12('git rev-parse --abbrev-ref HEAD').toString().trim(); 22 | } else { 23 | // node 0.10.x 24 | sha = execSync10('git rev-parse HEAD').stdout.trim(); 25 | branch = execSync10('git rev-parse --abbrev-ref HEAD').stdout.trim(); 26 | } 27 | 28 | module.exports = Task.extend({ 29 | /* 30 | The consuming app's deploy.json can use Mustache for dynamic portions. 31 | 32 | e.g. distPrefix: "dist-{{SHA}}" 33 | */ 34 | getDistPrefix: function(assetsConfig) { 35 | var prefix = ''; 36 | 37 | if (assetsConfig && assetsConfig.distPrefix) { 38 | var str = assetsConfig.distPrefix; 39 | prefix = Mustache.render(str, { 40 | SHA: sha 41 | }) + '/'; 42 | } 43 | 44 | return prefix; 45 | }, 46 | 47 | getIndexEndpoint: function(config) { 48 | var distPrefix = this.getDistPrefix(config.assets), 49 | bucket = config.assets.bucket; 50 | 51 | return 'https://' + bucket + '.s3.amazonaws.com/' + distPrefix + 'index.html'; 52 | }, 53 | 54 | signDeploy: function(config, url) { 55 | var appName = config.index.app, 56 | algo = 'RSA-SHA256', 57 | homedir = passwdUser.sync(process.getuid()).homedir; 58 | keyFile = config.index.privateKey || homedir + '/.ssh/id_rsa'; 59 | 60 | return crypto 61 | .createSign(algo) 62 | .update(appName + "-" + url) 63 | .sign(fs.readFileSync(keyFile), 'base64'); 64 | }, 65 | 66 | notifyEndpoint: function(endpoint, config, verbose) { 67 | var indexEndpoint = this.getIndexEndpoint(config); 68 | var data = { 69 | app_name: config.index.app, 70 | branch: branch, 71 | sha: sha, 72 | endpoint: indexEndpoint, 73 | signature: this.signDeploy(config, indexEndpoint) 74 | }; 75 | var query = qs.stringify(data); 76 | 77 | return new Promise(function(resolve, reject) { 78 | var requestOptions = _.extend({ 79 | method: 'POST', 80 | uri: endpoint + '?' + query, 81 | }, config.index.requestOptions); 82 | 83 | return request(requestOptions, function(error, response, body) { 84 | if (error) { 85 | var errorMessage = 'Unable to reach endpoint ' + endpoint + ': ' + error.message; 86 | console.error(body); 87 | return reject(errorMessage); 88 | 89 | } else { 90 | var code = response.statusCode; 91 | 92 | if (code.toString().charAt(0) === '4') { 93 | return reject('Rejected with code ' + code + '\n' + body); 94 | } 95 | 96 | console.log(body); 97 | return resolve(body); 98 | } 99 | 100 | }); 101 | }); 102 | }, 103 | 104 | run: function(environment, config, verbose) { 105 | var _this = this; 106 | var promises = []; 107 | var endpoints = config.index.endpoints.map(function(endpoint) { 108 | return (endpoint.match(/\/front_end_builds\/builds$/) ? 109 | endpoint : 110 | endpoint + '/front_end_builds/builds'); 111 | }); 112 | 113 | endpoints.forEach(function(endpoint) { 114 | promises.push(_this.notifyEndpoint(endpoint, config, verbose)); 115 | }); 116 | 117 | this.ui.pleasantProgress.start(chalk.green('Notifying ' + endpoints.join(', ')), chalk.green('.')); 118 | 119 | return Promise.all(promises).then(function(posts) { 120 | _this.ui.writeLine('Endpoints successfully notified.'); 121 | _this.ui.pleasantProgress.stop(); 122 | }).catch(function(reason){ 123 | _this.ui.writeLine(chalk.red(reason)); 124 | }); 125 | } 126 | }); 127 | -------------------------------------------------------------------------------- /lib/tasks/setup.js: -------------------------------------------------------------------------------- 1 | var Task = require('ember-cli/lib/models/task'); 2 | var Promise = require('ember-cli/lib/ext/promise'); 3 | var fs = require('fs'); 4 | 5 | module.exports = Task.extend({ 6 | options: null, 7 | 8 | run: function(environment, verbose) { 9 | var task = this, 10 | show = function(f) { 11 | return function() { return task[f].call(task); }; 12 | }, 13 | ask = function(f) { 14 | return show("ask" + f.charAt(0).toUpperCase() + f.substr(1)); 15 | }, 16 | deploy = {}; 17 | 18 | return this.checkExisting() 19 | .then(show('welcome')) 20 | 21 | .then(ask('environment')) 22 | .then(function(answer) { 23 | deploy.environment = answer.environment; 24 | }) 25 | 26 | .then(ask('bucket')) 27 | .then(function(answer) { 28 | deploy.bucket = answer.bucket; 29 | }) 30 | 31 | .then(ask('frontEnd')) 32 | .then(function(answers) { 33 | var prefix = '', 34 | matchScheme = /^http(s?):\/\//; 35 | 36 | if (!matchScheme.test(answers.endpoint)) { 37 | prefix = 'http://'; 38 | } 39 | 40 | deploy.endpoint = prefix + answers.endpoint; 41 | deploy.appName = answers.appName; 42 | deploy.apiKey = answers.apiKey; 43 | }) 44 | 45 | .then(function() { 46 | return task.writeConfig(deploy); 47 | }) 48 | 49 | .then(show('allDone')) 50 | 51 | .then(null, function(error) { 52 | task.ui.writeLine('There was an error', 'ERROR'); 53 | console.log(error.stack); 54 | }); 55 | }, 56 | 57 | checkExisting: function() { 58 | // check for existing config, and warn 59 | return new Promise(function(resolve) { 60 | resolve(); 61 | }); 62 | }, 63 | 64 | welcome: function() { 65 | this.outputMessage([ 66 | "", 67 | "Welcome to front end builds!", 68 | "", 69 | "We're about to setup the configuration file, which", 70 | "will take about five minutse to complete. It assumes", 71 | "you have already setup your Rails application with", 72 | "the front_end_builds gem as well as an S3 bucket", 73 | "that will host assets. It's ok if you haven't done", 74 | "these yet, but be prepared to set them up during this", 75 | "installation process." 76 | ]); 77 | }, 78 | 79 | askEnvironment: function() { 80 | this.outputMessage([ 81 | "", 82 | "Environment", 83 | "", 84 | "What environment are we setting up right now? This", 85 | "is usually something like production or staging.", 86 | "" 87 | ]); 88 | 89 | return this.ui.prompt({ 90 | type: "input", 91 | name: "environment", 92 | message: "What environment are we setting up:", 93 | default: "staging" 94 | }); 95 | }, 96 | 97 | askBucket: function() { 98 | this.outputMessage([ 99 | "", 100 | "AWS S3 Bucket", 101 | "", 102 | "Please set up an S3 bucket with static website", 103 | "hosting enabled.", 104 | "" 105 | ]); 106 | 107 | return this.ui.prompt({ 108 | type: "input", 109 | name: "bucket", 110 | message: "What is the name of your S3 bucket:" 111 | }); 112 | }, 113 | 114 | askFrontEnd: function() { 115 | this.outputMessage([ 116 | "", 117 | "Front end application", 118 | "", 119 | "If you have not already, please go and setup your", 120 | "Rails app with the front_end_builds gem. Once you", 121 | "do this, use the admin interface to add a new front", 122 | "end application.", 123 | "" 124 | ]); 125 | 126 | return this.ui.prompt([{ 127 | type: "input", 128 | name: "appName", 129 | message: "What is the name of your front end application:" 130 | },{ 131 | type: "input", 132 | name: "endpoint", 133 | message: "What is the hostname where your Rails app lives:" 134 | }]); 135 | }, 136 | 137 | outputMessage: function(message, level) { 138 | level = level || 'INFO'; 139 | 140 | var task = this; 141 | 142 | message.forEach(function(line) { 143 | task.ui.writeLine(line, level); 144 | }); 145 | }, 146 | 147 | writeConfig: function(deploy) { 148 | var toWrite = {}, 149 | config = { 150 | assets: { 151 | accessKeyId: "Dont hard code, use something like process.env.AWS_ACCESS_KEY_ID", 152 | secretAccessKey: "Dont hard code, use something like process.env.AWS_SECRET_ACCESS_KEY", 153 | bucket: deploy.bucket, 154 | prefix: "dist", 155 | distPrefix: "dist-{{SHA}}" 156 | }, 157 | index: { 158 | app: deploy.appName, 159 | endpoints: [deploy.endpoint] 160 | } 161 | }; 162 | 163 | toWrite[deploy.environment] = config; 164 | 165 | return new Promise(function(resolve, reject) { 166 | fs.writeFile( 167 | "./config/deploy.js", 168 | "module.exports = " + JSON.stringify(toWrite, null, 2) + ";\n", 169 | function(err) { 170 | if (err) { 171 | reject(); 172 | } else { 173 | resolve(); 174 | } 175 | } 176 | ); 177 | }); 178 | }, 179 | 180 | allDone: function() { 181 | this.outputMessage([ 182 | "", 183 | "Almost there...", 184 | "", 185 | "We created a configuration file for you at:", 186 | "", 187 | " ./config/deploy.js", 188 | "", 189 | "Please review it and make sure all of the fields", 190 | "are correct. You'll then be able to deploy with:", 191 | "", 192 | " ember deploy --environment=ENV_NAME", 193 | "", 194 | "Thanks for using front end builds!" 195 | ]); 196 | } 197 | }); 198 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-front-end-builds", 3 | "version": "0.1.1", 4 | "directories": { 5 | "doc": "doc", 6 | "test": "tests" 7 | }, 8 | "scripts": { 9 | "start": "ember server", 10 | "build": "ember build", 11 | "test": "ember test" 12 | }, 13 | "repository": "https://github.com/tedconf/ember-cli-front-end-builds", 14 | "engines": { 15 | "node": ">= 0.10.0" 16 | }, 17 | "author": "", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "body-parser": "^1.2.0", 21 | "broccoli-asset-rev": "0.3.1", 22 | "broccoli-ember-hbs-template-compiler": "^1.6.1", 23 | "ember-cli": "0.1.2", 24 | "ember-cli-content-security-policy": "0.3.0", 25 | "ember-cli-ic-ajax": "0.1.1", 26 | "ember-cli-inject-live-reload": "^1.3.0", 27 | "ember-cli-qunit": "0.1.0", 28 | "ember-data": "1.0.0-beta.10", 29 | "ember-export-application-global": "^1.0.0", 30 | "express": "^4.8.5", 31 | "glob": "^4.0.5" 32 | }, 33 | "keywords": [ 34 | "ember-addon" 35 | ], 36 | "ember-addon": { 37 | "configPath": "tests/dummy/config" 38 | }, 39 | "dependencies": { 40 | "chalk": "^0.5.1", 41 | "mustache": "^2.3.0", 42 | "passwd-user": "^1.1.0", 43 | "qs": "^2.3.2", 44 | "request": "^2.49.0", 45 | "s3": "^4.3.1", 46 | "sync-exec": "^0.4.0", 47 | "underscore": "^1.7.0", 48 | "silent-error": "^1.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "QUnit", 10 | "define", 11 | "console", 12 | "equal", 13 | "notEqual", 14 | "notStrictEqual", 15 | "test", 16 | "asyncTest", 17 | "testBoth", 18 | "testWithDefault", 19 | "raises", 20 | "throws", 21 | "deepEqual", 22 | "start", 23 | "stop", 24 | "ok", 25 | "strictEqual", 26 | "module", 27 | "moduleFor", 28 | "moduleForComponent", 29 | "moduleForModel", 30 | "process", 31 | "expect", 32 | "visit", 33 | "exists", 34 | "fillIn", 35 | "click", 36 | "keyEvent", 37 | "triggerEvent", 38 | "find", 39 | "findWithAssert", 40 | "wait", 41 | "DS", 42 | "isolatedContainer", 43 | "startApp", 44 | "andThen", 45 | "currentURL", 46 | "currentPath", 47 | "currentRouteName" 48 | ], 49 | "node": false, 50 | "browser": false, 51 | "boss": true, 52 | "curly": false, 53 | "debug": false, 54 | "devel": false, 55 | "eqeqeq": true, 56 | "evil": true, 57 | "forin": false, 58 | "immed": false, 59 | "laxbreak": false, 60 | "newcap": true, 61 | "noarg": true, 62 | "noempty": false, 63 | "nonew": false, 64 | "nomen": false, 65 | "onevar": false, 66 | "plusplus": false, 67 | "regexp": false, 68 | "undef": true, 69 | "sub": true, 70 | "strict": false, 71 | "white": false, 72 | "eqnull": true, 73 | "esnext": true 74 | } 75 | -------------------------------------------------------------------------------- /tests/dummy/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser" : true, 8 | "boss" : true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | Ember.MODEL_FACTORY_INJECTIONS = true; 7 | 8 | var App = Ember.Application.extend({ 9 | modulePrefix: config.modulePrefix, 10 | podModulePrefix: config.podModulePrefix, 11 | Resolver: Resolver 12 | }); 13 | 14 | loadInitializers(App, config.modulePrefix); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tedconf/ember-cli-front-end-builds/686057a25dd411c57d58c397ad28343316bd563f/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tedconf/ember-cli-front-end-builds/686057a25dd411c57d58c397ad28343316bd563f/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tedconf/ember-cli-front-end-builds/686057a25dd411c57d58c397ad28343316bd563f/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |